Passed
Push — master ( 207a08...b801a7 )
by Matt
02:53
created

ServerThread.shutdown()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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