~martijnbraam/pyatem

ref: a8d69070992bbf2806c4bff29e5f3b04f81c72e4 pyatem/openswitcher_proxy/frontend_httpapi.py -rw-r--r-- 5.1 KiB
a8d69070Martijn Braam openswitcher: improve reconnect logging 8 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import base64
import json
import threading
import logging
import http.server
from functools import partial
from urllib.parse import urlparse, parse_qsl

from openswitcher_proxy.frontend import AuthRequestHandler
from pyatem.field import FieldBase
import pyatem.command as commandmodule


class FieldEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, FieldBase):
            temp = obj.__dict__
            result = {}
            for key in temp:
                if key == 'raw':
                    continue
                result[key] = temp[key]
            return result
        elif isinstance(obj, bytes):
            return base64.b64encode(obj).decode()
        return obj


class ApiRequestHandler(AuthRequestHandler):
    def __init__(self, config, threadpool, *args, **kwargs):
        self.config = config
        self.threadpool = threadpool
        super().__init__(*args, **kwargs)

    def response(self, data, status=200):
        self.send_response(status)
        self.send_header('Content-type', 'application/json')
        self.end_headers()
        raw = json.dumps(data, cls=FieldEncoder, indent=2)
        self.wfile.write(raw.encode())

    def do_GET(self):
        if not self.verify_auth():
            return
        path = self.path
        allowed_hw = self.config['hardware'].split(',')
        if path == '/':
            hardware = []
            for hw in allowed_hw:
                row = {
                    'id': hw,
                }
                if hw not in self.threadpool['hardware']:
                    row['status'] = 'missing'
                else:
                    row['status'] = self.threadpool['hardware'][hw].get_status()
                hardware.append(row)
            self.response({'hardware': hardware})
            return
        parts = urlparse(path)
        path = parts.path[1:]
        part = path.split('/')
        args = parts.query
        if len(part) < 2:
            return self.response({}, 400)
        if part[0] not in allowed_hw:
            return self.response({'error': 'unknown device specified'}, 404)

        hw = part[0]
        fieldname = part[1]
        if fieldname in self.threadpool['hardware'][hw].switcher.mixerstate:
            field = self.threadpool['hardware'][hw].switcher.mixerstate[fieldname]
            return self.response(field)
        else:
            return self.response({'error': 'unknown field'}, 404)

    def do_POST(self):
        if not self.verify_auth():
            return
        path = self.path
        allowed_hw = self.config['hardware'].split(',')
        parts = urlparse(path)
        path = parts.path[1:]
        part = path.split('/')
        args = parts.query
        if len(part) < 2:
            return self.response({}, 400)
        if part[0] not in allowed_hw:
            return self.response({'error': 'unknown device specified'}, 404)

        hw = part[0]
        fieldname = part[1]
        classname = fieldname.title().replace('-', '') + "Command"
        if not hasattr(commandmodule, classname):
            return self.response({'error': 'unknown command'}, 404)

        rt = self.headers['Content-type']
        if rt is None:
            arguments = dict(parse_qsl(args))
        elif rt == 'application/x-www-form-urlencoded':
            length = int(self.headers['Content-length'])
            raw = self.rfile.read(length).decode()
            arguments = dict(parse_qsl(raw))
        elif rt == 'application/json':
            length = int(self.headers['Content-length'])
            raw = self.rfile.read(length).decode()
            arguments = json.loads(raw)
        else:
            return self.response({'error': 'unknown content-type'}, 400)

        for key in arguments:
            try:
                arguments[key] = int(arguments[key])
            except:
                pass
        if 'source' in arguments:
            inputs = self.threadpool['hardware'][hw].switcher.inputs
            if arguments['source'] in inputs:
                arguments['source'] = inputs[arguments['source']]

        try:
            cmd = getattr(commandmodule, classname)(**arguments)
            self.threadpool['hardware'][hw].switcher.send_commands([cmd])
        except Exception as e:
            return self.response({"error": str(e)}, 500)
        return self.response({"status": "ok"})


class HttpApiFrontendThread(threading.Thread):
    def __init__(self, config, threadlist):
        threading.Thread.__init__(self)
        self.name = 'http-api.' + str(config['bind'])
        self.config = config
        self.threadlist = threadlist
        self.stop = False
        self.server = None

    def run(self):
        logging.info('HTTP-Api frontend run')
        host, port = self.config['bind'].split(':')
        address = (host, int(port))
        logging.info(f'binding to {address}')

        handler = partial(ApiRequestHandler, self.config, self.threadlist)

        self.server = http.server.HTTPServer(address, handler)
        with self.server:
            self.server.serve_forever()

    def handler(self, *args):
        print(args)

    def get_status(self):
        return 'running'