Passed
Push — all-web-control ( c78a90...de24a5 )
by Matt
03:02
created

ServerThread.__init__()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nop 6
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
"""
2
 *  PyDMXControl: A Python 3 module to control DMX using uDMX.
3
 *                Featuring fixture profiles, built-in effects and a web control panel.
4
 *  <https://github.com/MattIPv4/PyDMXControl/>
5
 *  Copyright (C) 2022 Matt Cowley (MattIPv4) ([email protected])
6
"""
7
8
import builtins  # Builtins for Jinja context
9
import logging  # Logging
10
from os import path  # OS Path
11
from threading import Thread  # Threading
12
from typing import Dict, Callable  # Typing
13
14
from flask import Flask  # Flask
15
from werkzeug.serving import make_server  # Flask server
16
17
from ._routes import routes  # Web Routes
18
from ..utils.timing import TimedEvents  # Timed Events
19
20
# Set error only logging
21
log = logging.getLogger('werkzeug')
22
log.setLevel(logging.ERROR)
23
24
25
class ServerThread(Thread):
26
27
    def __init__(self, host, port, app, *args, **kwargs):
28
        super().__init__(*args, **kwargs)
29
30
        self.srv = make_server(host, port, app)
31
        self.ctx = app.app_context()
32
        self.ctx.push()
33
34
    def run(self):
35
        print("Started web controller: http://{}:{}".format(self.srv.host, self.srv.port))
36
        self.srv.serve_forever()
37
38
    def shutdown(self):
39
        self.srv.shutdown()
40
41
42
# WebController
43
class WebController:
44
45
    def __init__(self, controller: 'Controller', *,
46
                 callbacks: Dict[str, Callable] = None,
47
                 timed_events: Dict[str, TimedEvents] = None,
48
                 host: str = "0.0.0.0", port: int = 8080):
49
50
        # Setup flask
51
        self.__thread = None
52
        self.__host = host
53
        self.__port = port
54
        self.__app = Flask("PyDMXControl Web Controller")
55
        self.__app.template_folder = path.dirname(__file__) + "/templates"
56
        self.__app.static_url_path = "/static"
57
        self.__app.static_folder = path.dirname(__file__) + "/static"
58
        self.__app.register_blueprint(routes)
59
        self.__app.parent = self
60
61
        # Setup controller
62
        self.controller = controller
63
64
        # Setup callbacks
65
        self.callbacks = {} if callbacks is None else callbacks
66
        self.__default_callbacks()
67
        self.__check_callbacks()
68
69
        # Setup timed events
70
        self.timed_events = {} if timed_events is None else timed_events
71
72
        # Setup template context
73
        @self.__app.context_processor
74
        def variables() -> dict:  # pylint: disable=unused-variable
75
            return dict({"controller": self.controller, "callbacks": self.callbacks, "timed_events": self.timed_events,
76
                         "web_resource": WebController.web_resource},
77
                        **dict(globals(), **builtins.__dict__))  # Dictionary stacking to concat
78
79
        # Setup thread
80
        self.__running = False
81
        self.run()
82
83
    @staticmethod
84
    def filemtime(file: str) -> int:
85
        try:
86
            return path.getmtime(file)
87
        except Exception:
88
            return 0
89
90
    @staticmethod
91
    def web_resource(file: str) -> str:
92
        return "{}?v={:.0f}".format(file, WebController.filemtime(path.dirname(__file__) + file))
93
94
    def __default_callbacks(self):
95
        # Some default callbacks
96
        if 'all_on' not in self.callbacks:
97
            self.callbacks['all_on'] = self.controller.all_on
98
        if 'all_off' not in self.callbacks:
99
            self.callbacks['all_off'] = self.controller.all_off
100
        if 'all_locate' not in self.callbacks:
101
            self.callbacks['all_locate'] = self.controller.all_locate
102
103
    def __check_callbacks(self):
104
        for key in self.callbacks.keys():
105
            if not self.callbacks[key] or not callable(self.callbacks[key]):
106
                del self.callbacks[key]
107
108
    def run(self):
109
        if not self.__running:
110
            self.__thread = ServerThread(self.__host, self.__port, self.__app, daemon=True)
111
            self.__thread.start()
112
113
    def stop(self):
114
        self.__thread.shutdown()
115
        self.__running = False
116