kapsikkum-unmanic – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | #!/usr/bin/env python3 |
2 | # -*- coding: utf-8 -*- |
||
3 | |||
4 | """ |
||
5 | unmanic.websocket.py |
||
6 | |||
7 | Written by: Josh.5 <jsunnex@gmail.com> |
||
8 | Date: 23 Jul 2021, (6:08 PM) |
||
9 | |||
10 | Copyright: |
||
11 | Copyright (C) Josh Sunnex - All Rights Reserved |
||
12 | |||
13 | Permission is hereby granted, free of charge, to any person obtaining a copy |
||
14 | of this software and associated documentation files (the "Software"), to deal |
||
15 | in the Software without restriction, including without limitation the rights |
||
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||
17 | copies of the Software, and to permit persons to whom the Software is |
||
18 | furnished to do so, subject to the following conditions: |
||
19 | |||
20 | The above copyright notice and this permission notice shall be included in all |
||
21 | copies or substantial portions of the Software. |
||
22 | |||
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||
26 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||
27 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
||
28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE |
||
29 | OR OTHER DEALINGS IN THE SOFTWARE. |
||
30 | |||
31 | """ |
||
32 | import json |
||
33 | import queue |
||
34 | import time |
||
35 | import uuid |
||
36 | |||
37 | import tornado.web |
||
38 | import tornado.locks |
||
39 | import tornado.ioloop |
||
40 | import tornado.websocket |
||
41 | from tornado import gen, log |
||
42 | |||
43 | from unmanic import config |
||
44 | from unmanic.libs import common, history, session |
||
45 | from unmanic.libs.uiserver import UnmanicDataQueues, UnmanicRunningTreads |
||
46 | from unmanic.webserver.helpers import completed_tasks, pending_tasks |
||
47 | |||
48 | |||
49 | class UnmanicWebsocketHandler(tornado.websocket.WebSocketHandler): |
||
50 | name = None |
||
51 | config = None |
||
52 | sending_frontend_message = False |
||
53 | sending_system_logs = False |
||
54 | sending_worker_info = False |
||
55 | sending_pending_tasks_info = False |
||
56 | sending_completed_tasks_info = False |
||
57 | close_event = False |
||
58 | |||
59 | def __init__(self, *args, **kwargs): |
||
60 | self.name = 'UnmanicWebsocketHandler' |
||
61 | self.config = config.Config() |
||
62 | self.server_id = str(uuid.uuid4()) |
||
63 | udq = UnmanicDataQueues() |
||
64 | urt = UnmanicRunningTreads() |
||
65 | self.data_queues = udq.get_unmanic_data_queues() |
||
66 | self.foreman = urt.get_unmanic_running_thread('foreman') |
||
67 | self.session = session.Session() |
||
68 | super(UnmanicWebsocketHandler, self).__init__(*args, **kwargs) |
||
69 | |||
70 | def open(self): |
||
71 | tornado.log.app_log.warning('WS Opened', exc_info=True) |
||
72 | self.close_event = tornado.locks.Event() |
||
73 | |||
74 | def on_message(self, message): |
||
75 | try: |
||
76 | message_data = json.loads(message) |
||
77 | if message_data.get('command'): |
||
78 | # Execute the function |
||
79 | getattr(self, message_data.get('command', 'default_failure_response'))(params=message_data.get('params', {})) |
||
80 | except json.decoder.JSONDecodeError: |
||
81 | tornado.log.app_log.error('Received incorrectly formatted message - {}'.format(message), exc_info=False) |
||
82 | |||
83 | def on_close(self): |
||
84 | tornado.log.app_log.warning('WS Closed', exc_info=True) |
||
85 | self.close_event.set() |
||
86 | self.stop_frontend_messages() |
||
87 | self.stop_workers_info() |
||
88 | self.stop_pending_tasks_info() |
||
89 | self.stop_completed_tasks_info() |
||
90 | |||
91 | def default_failure_response(self, params=None): |
||
92 | """ |
||
93 | WS Command - default_failure_response |
||
94 | Returns a failure response |
||
95 | |||
96 | :param params: |
||
97 | :type params: |
||
98 | :return: |
||
99 | :rtype: |
||
100 | """ |
||
101 | self.write_message({'success': False}) |
||
102 | |||
103 | def start_frontend_messages(self, params=None): |
||
104 | """ |
||
105 | WS Command - start_frontend_messages |
||
106 | Start sending messages from the application to the frontend. |
||
107 | |||
108 | :param params: |
||
109 | :type params: |
||
110 | :return: |
||
111 | :rtype: |
||
112 | """ |
||
113 | if not self.sending_frontend_message: |
||
114 | self.sending_frontend_message = True |
||
115 | tornado.ioloop.IOLoop.current().spawn_callback(self.async_frontend_message) |
||
116 | |||
117 | def stop_frontend_messages(self, params=None): |
||
118 | """ |
||
119 | WS Command - stop_frontend_messages |
||
120 | Stop sending messages from the application to the frontend. |
||
121 | |||
122 | :param params: |
||
123 | :type params: |
||
124 | :return: |
||
125 | :rtype: |
||
126 | """ |
||
127 | self.sending_frontend_message = False |
||
128 | |||
129 | def start_system_logs(self, params=None): |
||
130 | """ |
||
131 | WS Command - start_system_logs |
||
132 | Start sending system logs from the application to the frontend. |
||
133 | |||
134 | :param params: |
||
135 | :type params: |
||
136 | :return: |
||
137 | :rtype: |
||
138 | """ |
||
139 | if not self.sending_system_logs: |
||
140 | self.sending_system_logs = True |
||
141 | tornado.ioloop.IOLoop.current().spawn_callback(self.async_system_logs) |
||
142 | |||
143 | def stop_system_logs(self, params=None): |
||
144 | """ |
||
145 | WS Command - stop_system_logs |
||
146 | Stop sending system logs from the application to the frontend. |
||
147 | |||
148 | :param params: |
||
149 | :type params: |
||
150 | :return: |
||
151 | :rtype: |
||
152 | """ |
||
153 | self.sending_system_logs = False |
||
154 | |||
155 | def start_workers_info(self, params=None): |
||
156 | """ |
||
157 | WS Command - start_workers_info |
||
158 | Start sending information pertaining to the workers |
||
159 | |||
160 | :param params: |
||
161 | :type params: |
||
162 | :return: |
||
163 | :rtype: |
||
164 | """ |
||
165 | if not self.sending_worker_info: |
||
166 | self.sending_worker_info = True |
||
167 | tornado.ioloop.IOLoop.current().spawn_callback(self.async_workers_info) |
||
168 | |||
169 | def stop_workers_info(self, params=None): |
||
170 | """ |
||
171 | WS Command - stop_workers_info |
||
172 | Stop sending information pertaining to the workers |
||
173 | |||
174 | :param params: |
||
175 | :type params: |
||
176 | :return: |
||
177 | :rtype: |
||
178 | """ |
||
179 | self.sending_worker_info = False |
||
180 | |||
181 | def start_pending_tasks_info(self, params=None): |
||
182 | """ |
||
183 | WS Command - start_pending_tasks_info |
||
184 | Start sending information pertaining to the pending tasks list |
||
185 | |||
186 | :param params: |
||
187 | :type params: |
||
188 | :return: |
||
189 | :rtype: |
||
190 | """ |
||
191 | if not self.sending_pending_tasks_info: |
||
192 | self.sending_pending_tasks_info = True |
||
193 | tornado.ioloop.IOLoop.current().spawn_callback(self.async_pending_tasks_info) |
||
194 | |||
195 | def stop_pending_tasks_info(self, params=None): |
||
196 | """ |
||
197 | WS Command - stop_pending_tasks_info |
||
198 | Stop sending information pertaining to the pending tasks list |
||
199 | |||
200 | :param params: |
||
201 | :type params: |
||
202 | :return: |
||
203 | :rtype: |
||
204 | """ |
||
205 | self.sending_pending_tasks_info = False |
||
206 | |||
207 | def start_completed_tasks_info(self, params=None): |
||
208 | """ |
||
209 | WS Command - start_completed_tasks_info |
||
210 | Start sending information pertaining to the completed tasks list |
||
211 | |||
212 | :param params: |
||
213 | :type params: |
||
214 | :return: |
||
215 | :rtype: |
||
216 | """ |
||
217 | if not self.sending_completed_tasks_info: |
||
218 | self.sending_completed_tasks_info = True |
||
219 | tornado.ioloop.IOLoop.current().spawn_callback(self.async_completed_tasks_info) |
||
220 | |||
221 | def stop_completed_tasks_info(self, params=None): |
||
222 | """ |
||
223 | WS Command - stop_completed_tasks_info |
||
224 | Stop sending information pertaining to the completed tasks list |
||
225 | |||
226 | :param params: |
||
227 | :type params: |
||
228 | :return: |
||
229 | :rtype: |
||
230 | """ |
||
231 | self.sending_completed_tasks_info = False |
||
232 | |||
233 | def dismiss_message(self, params=None): |
||
234 | """ |
||
235 | WS Command - dismiss_message |
||
236 | Dismiss a specified message by id. |
||
237 | |||
238 | params: |
||
239 | - message_id - The ID of the message to be dismissed |
||
240 | |||
241 | :param params: |
||
242 | :type params: |
||
243 | :return: |
||
244 | :rtype: |
||
245 | """ |
||
246 | frontend_messages = self.data_queues.get('frontend_messages') |
||
247 | frontend_messages.remove_item(params.get('message_id', '')) |
||
248 | |||
249 | async def send(self, message): |
||
250 | if self.ws_connection: |
||
251 | await self.write_message(message) |
||
252 | |||
253 | async def async_frontend_message(self): |
||
254 | while self.sending_frontend_message: |
||
255 | frontend_messages = self.data_queues.get('frontend_messages') |
||
256 | frontend_message_items = frontend_messages.read_all_items() |
||
257 | # Send message to client |
||
258 | await self.send( |
||
259 | { |
||
260 | 'success': True, |
||
261 | 'server_id': self.server_id, |
||
262 | 'type': 'frontend_message', |
||
263 | 'data': frontend_message_items, |
||
264 | } |
||
265 | ) |
||
266 | |||
267 | # Sleep for X seconds |
||
268 | await gen.sleep(.2) |
||
269 | |||
270 | async def async_system_logs(self): |
||
271 | while self.sending_system_logs: |
||
272 | system_logs = self.config.read_system_logs(lines=35) |
||
273 | |||
274 | # Send message to client |
||
275 | await self.send( |
||
276 | { |
||
277 | 'success': True, |
||
278 | 'server_id': self.server_id, |
||
279 | 'type': 'system_logs', |
||
280 | 'data': { |
||
281 | "logs_path": self.config.get_log_path(), |
||
282 | 'system_logs': system_logs, |
||
283 | }, |
||
284 | } |
||
285 | ) |
||
286 | |||
287 | # Sleep for X seconds |
||
288 | await gen.sleep(1) |
||
289 | |||
290 | async def async_workers_info(self): |
||
291 | while self.sending_worker_info: |
||
292 | workers_info = self.foreman.get_all_worker_status() |
||
293 | |||
294 | # Send message to client |
||
295 | await self.send( |
||
296 | { |
||
297 | 'success': True, |
||
298 | 'server_id': self.server_id, |
||
299 | 'type': 'workers_info', |
||
300 | 'data': workers_info, |
||
301 | } |
||
302 | ) |
||
303 | |||
304 | # Sleep for X seconds |
||
305 | await gen.sleep(.2) |
||
306 | |||
307 | async def async_pending_tasks_info(self): |
||
308 | while self.sending_pending_tasks_info: |
||
309 | results = [] |
||
310 | params = { |
||
311 | 'start': '0', |
||
312 | 'length': '10', |
||
313 | 'search_value': '', |
||
314 | 'order': { |
||
315 | "column": 'priority', |
||
316 | "dir": 'desc', |
||
317 | } |
||
318 | } |
||
319 | task_list = pending_tasks.prepare_filtered_pending_tasks(params) |
||
320 | |||
321 | for task_result in task_list.get('results', []): |
||
322 | # Append the task to the results list |
||
323 | results.append( |
||
324 | { |
||
325 | 'id': task_result['id'], |
||
326 | 'label': task_result['abspath'], |
||
327 | 'priority': task_result['priority'], |
||
328 | 'status': task_result['status'], |
||
329 | } |
||
330 | ) |
||
331 | |||
332 | # Send message to client |
||
333 | await self.send( |
||
334 | { |
||
335 | 'success': True, |
||
336 | 'server_id': self.server_id, |
||
337 | 'type': 'pending_tasks', |
||
338 | 'data': { |
||
339 | 'results': results |
||
340 | }, |
||
341 | } |
||
342 | ) |
||
343 | |||
344 | # Sleep for X seconds |
||
345 | await gen.sleep(3) |
||
346 | |||
347 | async def async_completed_tasks_info(self): |
||
348 | while self.sending_completed_tasks_info: |
||
349 | results = [] |
||
350 | params = { |
||
351 | 'start': '0', |
||
352 | 'length': '10', |
||
353 | 'search_value': '', |
||
354 | 'order': { |
||
355 | "column": 'finish_time', |
||
356 | "dir": 'desc', |
||
357 | } |
||
358 | } |
||
359 | task_list = completed_tasks.prepare_filtered_completed_tasks(params) |
||
360 | |||
361 | for task_result in task_list.get('results', []): |
||
362 | # Set human readable time |
||
363 | if (int(task_result['finish_time']) + 60) > int(time.time()): |
||
364 | human_readable_time = 'Just Now' |
||
365 | else: |
||
366 | human_readable_time = common.make_timestamp_human_readable(int(task_result['finish_time'])) |
||
367 | |||
368 | # Append the task to the results list |
||
369 | results.append( |
||
370 | { |
||
371 | 'id': task_result['id'], |
||
372 | 'label': task_result['task_label'], |
||
373 | 'success': task_result['task_success'], |
||
374 | 'finish_time': task_result['finish_time'], |
||
375 | 'human_readable_time': human_readable_time, |
||
376 | } |
||
377 | ) |
||
378 | |||
379 | # Send message to client |
||
380 | await self.send( |
||
381 | { |
||
382 | 'success': True, |
||
383 | 'server_id': self.server_id, |
||
384 | 'type': 'completed_tasks', |
||
385 | 'data': { |
||
386 | 'results': results |
||
387 | }, |
||
388 | } |
||
389 | ) |
||
390 | |||
391 | # Sleep for X seconds |
||
392 | await gen.sleep(3) |