-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontroller.py
181 lines (142 loc) · 5.1 KB
/
controller.py
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# -*- coding: UTF-8 -*-
import json
from functools import partialmethod
from weakref import finalize
from tornado.web import Application, RequestHandler
from tornado.websocket import WebSocketHandler
import conf
from src import ui_modules, ui_methods, messages as msg
from src.boiler_ui_module import BoilerUIModule
from src.pub_sub import OwnerPubSub, NoMessageTypeError, \
NoActionForMsgTypeError, MsgIsNotDictError
_path = 'controller'
class GUIHandler(RequestHandler):
def get(self, _class):
if _class:
self.render('boxes.html',
classes={_class, 'system-panel'})
else:
self.render('boxes.html')
class MSGHandler(WebSocketHandler):
"""Serve the WebSocket clients.
An instance of this class is created every time a
client connects using WebSocket. The instances of this
class deliver messages to a group of objects
specialized in attending a group of messages.
.. automethod:: _finalize
"""
_path = msg.join_path(_path, 'MSGHandler')
ws_classes = []
clients = set()
client_count = 0 # Total clients that have connected
def initialize(self):
_path = msg.join_path(self._path, 'initialize')
msg.code_debug(
_path,
'New connection established! {0} '
'({0.request.remote_ip})'.format(self)
)
self.local_pub_sub = OwnerPubSub(
name='local_pub_sub')
self.ws_pub_sub = OwnerPubSub(
name='ws_pub_sub',
send_function=self.write_message
)
self.ws_objects = {
ws_class: ws_class(self)
for ws_class in self.ws_classes}
# Call ``self._finalize`` when this object is about
# to be destroyed.
self.finalize = finalize(self, self._finalize)
self.__class__.clients.add(self)
self.__class__.client_count += 1
@classmethod
def add_class(cls, wsclass):
cls.ws_classes.append(wsclass)
@classmethod
def broadcast(cls, message):
for client in cls.clients:
client.ws_pub_sub.send_message(message)
def on_message(self, message):
"""Process messages when they arrive.
:param str message:
The received message. This should be a valid
json document.
"""
try:
# Throws ValueError
message = json.loads(message)
self.ws_pub_sub.execute_actions(message)
except NoActionForMsgTypeError:
self.send_error(
'noActionForMsgType',
message,
"The client has sent a message for which "
"there is no associated action."
)
msg.no_action_for_msg_type(_path, message)
except (MsgIsNotDictError, NoMessageTypeError,
ValueError):
self.send_malformed_message_error(message)
msg.malformed_message(_path, message)
def send_error(self, critical_type, message,
description):
self.ws_pub_sub.send_message(
{'type': 'critical',
'critical_type': critical_type,
'message': message,
'description': description}
)
send_malformed_message_error = partialmethod(
send_error,
'malformedMessage',
description="The client has sent a message which "
"either isn't in JSON format, is not a "
"single JSON object, does not have a "
"'type' field or at least one "
"attribute is not consistent with the "
"others."
)
def _finalize(self):
"""Clean up the associated objects
This method calls the ``unregister`` method of all
objects in ``self.ws_objects`` and it removes
``self`` from ``self.__class__.clients``.
This method should not be called directly.
``self.finalize`` is setup to call this method when
the object is garbage collected, the WebSocket
connection closes or when the program ends.
To execute this method you should call
``self.finalize`` instead.
"""
_path = msg.join_path(self._path, '_finalize')
for ws_object in self.ws_objects.values():
ws_object.unregister()
self.__class__.clients.discard(self)
msg.code_debug(
_path,
'Connection closed! {0} '
'({0.request.remote_ip})'.format(self)
)
def on_close(self):
self.finalize()
app = Application(
[('/ws$', MSGHandler),
('/(.*)$', GUIHandler), ],
debug=conf.debug,
static_path='./static',
template_path='./templates',
ui_modules=[ui_modules],
ui_methods=[ui_methods],
)
app.listen(conf.port)
# Import the modules which register actions in the
# MSGHandler
import locking_panels
import notifications
import panels
# BoilerUIModules that aren't automatically loaded from
# a package, must add their handlers to the app.
for module in app.ui_modules.values():
if issubclass(module, BoilerUIModule):
module.add_handler(app)