1 | #!/usr/bin/env python |
||
2 | # -*- coding: utf-8 -*- |
||
3 | |||
4 | import logging |
||
5 | logger = logging.getLogger(__name__) |
||
6 | logger.debug("%s loaded", __name__) |
||
7 | |||
8 | import threading |
||
9 | import time # used by: fire_event_synchron |
||
10 | from inspect import isfunction, ismethod # used by: register_action |
||
11 | import string, random # used by event_id |
||
12 | |||
13 | import sqlite3 |
||
14 | import os |
||
15 | |||
16 | from base import SingleAction |
||
17 | import doorpi |
||
18 | |||
19 | class EnumWaitSignalsClass(): |
||
20 | WaitToFinish = True |
||
21 | WaitToEnd = True |
||
22 | sync = True |
||
23 | syncron = True |
||
24 | |||
25 | DontWaitToFinish = False |
||
26 | DontWaitToEnd = False |
||
27 | async = False |
||
28 | asyncron = False |
||
29 | EnumWaitSignals = EnumWaitSignalsClass() |
||
30 | |||
31 | ONTIME = 'OnTime' |
||
32 | |||
33 | def id_generator(size = 6, chars = string.ascii_uppercase + string.digits): |
||
34 | return ''.join(random.choice(chars) for _ in range(size)) |
||
35 | |||
36 | class EventLog(object): |
||
37 | |||
38 | _db = False |
||
39 | |||
40 | #doorpi.DoorPi().conf.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db') |
||
41 | def __init__(self, file_name): |
||
42 | |||
43 | if not file_name: return |
||
44 | try: |
||
45 | if not os.path.exists(os.path.dirname(file_name)): |
||
46 | logger.info('Path %s does not exist - creating it now', os.path.dirname(file_name)) |
||
47 | os.makedirs(os.path.dirname(file_name)) |
||
48 | #https://docs.python.org/2/library/sqlite3.html#sqlite3.connect |
||
49 | self._db = sqlite3.connect( |
||
50 | database = file_name, |
||
51 | timeout = 1, |
||
52 | check_same_thread = False |
||
53 | ) |
||
54 | |||
55 | self.execute_sql(''' |
||
56 | CREATE TABLE IF NOT EXISTS event_log ( |
||
57 | event_id TEXT, |
||
58 | fired_by TEXT, |
||
59 | event_name TEXT, |
||
60 | start_time REAL, |
||
61 | additional_infos TEXT |
||
62 | );''' |
||
63 | ) |
||
64 | self.execute_sql(''' |
||
65 | CREATE TABLE IF NOT EXISTS action_log ( |
||
66 | event_id TEXT, |
||
67 | action_name TEXT, |
||
68 | start_time REAL, |
||
69 | action_result TEXT |
||
70 | );''' |
||
71 | ) |
||
72 | except: |
||
73 | logger.error('error to create event_db') |
||
74 | |||
75 | def get_event_log_entries_count(self, filter = ''): |
||
0 ignored issues
–
show
|
|||
76 | logger.debug('request event logs count with filter %s', filter) |
||
77 | try: |
||
78 | return self.execute_sql(''' |
||
79 | SELECT COUNT(*) |
||
80 | FROM event_log |
||
81 | WHERE event_id LIKE '%{filter}%' |
||
82 | OR fired_by LIKE '%{filter}%' |
||
83 | OR event_name LIKE '%{filter}%' |
||
84 | OR start_time LIKE '%{filter}%' |
||
85 | '''.format(filter = filter)).fetchone()[0] |
||
86 | except Exception as exp: |
||
87 | logger.exception(exp) |
||
88 | return -1 |
||
89 | |||
90 | def get_event_log_entries(self, max_count = 100, filter = ''): |
||
0 ignored issues
–
show
|
|||
91 | logger.debug('request last %s event logs with filter %s', max_count, filter) |
||
92 | return_object = [] |
||
93 | sql_statement = ''' |
||
94 | SELECT |
||
95 | event_id, |
||
96 | fired_by, |
||
97 | event_name, |
||
98 | start_time, |
||
99 | additional_infos |
||
100 | FROM event_log |
||
101 | WHERE event_id LIKE '%{filter}%' |
||
102 | OR fired_by LIKE '%{filter}%' |
||
103 | OR event_name LIKE '%{filter}%' |
||
104 | OR start_time LIKE '%{filter}%' |
||
105 | ORDER BY start_time DESC |
||
106 | LIMIT {max_count}'''.format(max_count = max_count, filter = filter) |
||
107 | |||
108 | for single_row in self.execute_sql(sql_statement): |
||
109 | return_object.append({ |
||
110 | 'event_id': single_row[0], |
||
111 | 'fired_by': single_row[1], |
||
112 | 'event_name': single_row[2], |
||
113 | 'start_time': single_row[3], |
||
114 | 'additional_infos': single_row[4] |
||
115 | }) |
||
116 | return return_object |
||
117 | |||
118 | def execute_sql(self, sql): |
||
119 | if not self._db: return |
||
120 | #logger.trace('fire sql: %s', sql) |
||
121 | return self._db.execute(sql) |
||
122 | |||
123 | def insert_event_log(self, event_id, fired_by, event_name, start_time, additional_infos): |
||
124 | sql_statement = ''' |
||
125 | INSERT INTO event_log VALUES ( |
||
126 | "{event_id}","{fired_by}","{event_name}",{start_time},"{additional_infos}" |
||
127 | ); |
||
128 | '''.format( |
||
129 | event_id = event_id, |
||
130 | fired_by = fired_by.replace('"', "'"), |
||
131 | event_name = event_name.replace('"', "'"), |
||
132 | start_time = start_time, |
||
133 | additional_infos = str(additional_infos).replace('"', "'") |
||
134 | ) |
||
135 | self.execute_sql(sql_statement) |
||
136 | |||
137 | def insert_action_log(self, event_id, action_name, start_time, action_result): |
||
138 | sql_statement = ''' |
||
139 | INSERT INTO action_log VALUES ( |
||
140 | "{event_id}","{action_name}",{start_time},"{action_result}" |
||
141 | ); |
||
142 | '''.format( |
||
143 | event_id = event_id, |
||
144 | action_name = action_name.replace('"', "'"), |
||
145 | start_time = start_time, |
||
146 | action_result = str(action_result).replace('"', "'") |
||
147 | ) |
||
148 | self.execute_sql(sql_statement) |
||
149 | |||
150 | def update_event_log(self): |
||
151 | pass |
||
152 | |||
153 | def destroy(self): |
||
154 | try: self._db.close() |
||
155 | except: pass |
||
156 | |||
157 | __del__ = destroy |
||
158 | |||
159 | class EventHandler: |
||
160 | |||
161 | __Sources = [] # Auflistung Sources |
||
162 | __Events = {} # Zuordnung Event zu Sources (1 : n) |
||
163 | __Actions = {} # Zuordnung Event zu Actions (1: n) |
||
164 | |||
165 | __additional_informations = {} |
||
166 | |||
167 | @property |
||
168 | def event_history(self): return self.db.get_event_log_entries() |
||
169 | |||
170 | @property |
||
171 | def sources(self): return self.__Sources |
||
172 | @property |
||
173 | def events(self): return self.__Events |
||
174 | @property |
||
175 | def events_by_source(self): |
||
176 | events_by_source = {} |
||
177 | for event in self.events: |
||
178 | for source in self.events[event]: |
||
179 | if source in events_by_source: |
||
180 | events_by_source[source].append(event) |
||
181 | else: |
||
182 | events_by_source[source] = [event] |
||
183 | return events_by_source |
||
184 | @property |
||
185 | def actions(self): return self.__Actions |
||
186 | @property |
||
187 | def threads(self): return threading.enumerate() |
||
188 | @property |
||
189 | def idle(self): return len(self.threads) - 1 is 0 |
||
190 | @property |
||
191 | def additional_informations(self): return self.__additional_informations |
||
192 | |||
193 | def __init__(self): |
||
194 | db_path = doorpi.DoorPi().config.get_string_parsed('DoorPi', 'eventlog', '!BASEPATH!/conf/eventlog.db') |
||
195 | self.db = EventLog(db_path) |
||
196 | |||
197 | __destroy = False |
||
198 | |||
199 | def destroy(self, force_destroy = False): |
||
0 ignored issues
–
show
|
|||
200 | self.__destroy = True |
||
201 | self.db.destroy() |
||
202 | |||
203 | def register_source(self, event_source): |
||
204 | if event_source not in self.__Sources: |
||
205 | self.__Sources.append(event_source) |
||
206 | logger.debug("event_source %s was added", event_source) |
||
207 | |||
208 | def register_event(self, event_name, event_source): |
||
209 | silent = ONTIME in event_name |
||
210 | if not silent: logger.trace("register Event %s from %s ", event_name, event_source) |
||
211 | self.register_source(event_source) |
||
212 | if event_name not in self.__Events: |
||
213 | self.__Events[event_name] = [event_source] |
||
214 | if not silent: logger.trace("added event_name %s and registered source %s", event_name, event_source) |
||
215 | elif event_source not in self.__Events[event_name]: |
||
216 | self.__Events[event_name].append(event_source) |
||
217 | if not silent: logger.trace("added event_source %s to existing event %s", event_source, event_name) |
||
218 | else: |
||
219 | if not silent: logger.trace("nothing to do - event %s from source %s is already known", event_name, event_source) |
||
220 | |||
221 | def fire_event(self, event_name, event_source, syncron = False, kwargs = None): |
||
222 | if syncron is False: return self.fire_event_asynchron(event_name, event_source, kwargs) |
||
223 | else: return self.fire_event_synchron(event_name, event_source, kwargs) |
||
224 | |||
225 | def fire_event_asynchron(self, event_name, event_source, kwargs = None): |
||
226 | silent = ONTIME in event_name |
||
227 | if self.__destroy and not silent: return False |
||
228 | if not silent: logger.trace("fire Event %s from %s asyncron", event_name, event_source) |
||
229 | return threading.Thread( |
||
230 | target = self.fire_event_synchron, |
||
231 | args = (event_name, event_source, kwargs), |
||
232 | name = "%s from %s" % (event_name, event_source) |
||
233 | ).start() |
||
234 | |||
235 | def fire_event_asynchron_daemon(self, event_name, event_source, kwargs = None): |
||
236 | logger.trace("fire Event %s from %s asyncron and as daemons", event_name, event_source) |
||
237 | t = threading.Thread( |
||
238 | target = self.fire_event_synchron, |
||
239 | args = (event_name, event_source, kwargs), |
||
240 | name = "daemon %s from %s" % (event_name, event_source) |
||
241 | ) |
||
242 | t.daemon = True |
||
243 | t.start() |
||
244 | |||
245 | def fire_event_synchron(self, event_name, event_source, kwargs = None): |
||
246 | silent = ONTIME in event_name |
||
247 | if self.__destroy and not silent: return False |
||
248 | |||
249 | event_fire_id = id_generator() |
||
250 | start_time = time.time() |
||
251 | if not silent: self.db.insert_event_log(event_fire_id, event_source, event_name, start_time, kwargs) |
||
252 | |||
253 | if event_source not in self.__Sources: |
||
254 | logger.warning('source %s unknown - skip fire_event %s', event_source, event_name) |
||
255 | return "source unknown" |
||
256 | if event_name not in self.__Events: |
||
257 | logger.warning('event %s unknown - skip fire_event %s from %s', event_name, event_name, event_source) |
||
258 | return "event unknown" |
||
259 | if event_source not in self.__Events[event_name]: |
||
260 | logger.warning('source %s unknown for this event - skip fire_event %s from %s', event_name, event_name, event_source) |
||
261 | return "source unknown for this event" |
||
262 | if event_name not in self.__Actions: |
||
263 | if not silent: logger.debug('no actions for event %s - skip fire_event %s from %s', event_name, event_name, event_source) |
||
264 | return "no actions for this event" |
||
265 | |||
266 | if kwargs is None: kwargs = {} |
||
267 | kwargs.update({ |
||
268 | 'last_fired': str(start_time), |
||
269 | 'last_fired_from': event_source, |
||
270 | 'event_fire_id': event_fire_id |
||
271 | }) |
||
272 | |||
273 | self.__additional_informations[event_name] = kwargs |
||
274 | if 'last_finished' not in self.__additional_informations[event_name]: |
||
275 | self.__additional_informations[event_name]['last_finished'] = None |
||
276 | |||
277 | if 'last_duration' not in self.__additional_informations[event_name]: |
||
278 | self.__additional_informations[event_name]['last_duration'] = None |
||
279 | |||
280 | if not silent: logger.debug("[%s] fire for event %s this actions %s ", event_fire_id, event_name, self.__Actions[event_name]) |
||
281 | for action in self.__Actions[event_name]: |
||
282 | if not silent: logger.trace("[%s] try to fire action %s", event_fire_id, action) |
||
283 | try: |
||
284 | result = action.run(silent) |
||
285 | if not silent: self.db.insert_action_log(event_fire_id, action.name, start_time, result) |
||
286 | if action.single_fire_action is True: del action |
||
287 | except SystemExit as exp: |
||
288 | logger.info('[%s] Detected SystemExit and shutdown DoorPi (Message: %s)', event_fire_id, exp) |
||
289 | doorpi.DoorPi().destroy() |
||
290 | except KeyboardInterrupt as exp: |
||
291 | logger.info("[%s] Detected KeyboardInterrupt and shutdown DoorPi (Message: %s)", event_fire_id, exp) |
||
292 | doorpi.DoorPi().destroy() |
||
293 | except: |
||
294 | logger.exception("[%s] error while fire action %s for event_name %s", event_fire_id, action, event_name) |
||
295 | if not silent: logger.trace("[%s] finished fire_event for event_name %s", event_fire_id, event_name) |
||
296 | self.__additional_informations[event_name]['last_finished'] = str(time.time()) |
||
297 | self.__additional_informations[event_name]['last_duration'] = str(time.time() - start_time) |
||
298 | return True |
||
299 | |||
300 | def unregister_event(self, event_name, event_source, delete_source_when_empty = True): |
||
301 | try: |
||
302 | logger.trace("unregister Event %s from %s ", event_name, event_source) |
||
303 | if event_name not in self.__Events: return "event unknown" |
||
304 | if event_source not in self.__Events[event_name]: return "source not know for this event" |
||
305 | self.__Events[event_name].remove(event_source) |
||
306 | if len(self.__Events[event_name]) is 0: |
||
307 | del self.__Events[event_name] |
||
308 | logger.debug("no more sources for event %s - remove event too", event_name) |
||
309 | if delete_source_when_empty: self.unregister_source(event_source) |
||
310 | logger.trace("event_source %s was removed for event %s", event_source, event_name) |
||
311 | return True |
||
312 | except Exception as exp: |
||
313 | logger.error('failed to unregister event %s with error message %s', event_name, exp) |
||
314 | return False |
||
315 | |||
316 | def unregister_source(self, event_source, force_unregister = False): |
||
317 | try: |
||
318 | logger.trace("unregister Eventsource %s and force_unregister is %s", event_source, force_unregister) |
||
319 | if event_source not in self.__Sources: return "event_source %s unknown" % (event_source) |
||
320 | for event_name in self.__Events.keys(): |
||
321 | if event_source in self.__Events[event_name] and force_unregister: |
||
322 | self.unregister_event(event_name, event_source, False) |
||
323 | elif event_source in self.__Events[event_name] and not force_unregister: |
||
324 | return "couldn't unregister event_source %s because it is used for event %s" % (event_source, event_name) |
||
325 | if event_source in self.__Sources: |
||
326 | # sollte nicht nötig sein, da es entfernt wird, wenn das letzte Event dafür gelöscht wird |
||
327 | self.__Sources.remove(event_source) |
||
328 | logger.trace("event_source %s was removed", event_source) |
||
329 | return True |
||
330 | except Exception as exp: |
||
331 | logger.exception('failed to unregister source %s with error message %s', event_source, exp) |
||
332 | return False |
||
333 | |||
334 | def register_action(self, event_name, action_object, *args, **kwargs): |
||
335 | if ismethod(action_object) and callable(action_object): |
||
336 | action_object = SingleAction(action_object, *args, **kwargs) |
||
337 | elif isfunction(action_object) and callable(action_object): |
||
338 | action_object = SingleAction(action_object, *args, **kwargs) |
||
339 | elif not isinstance(action_object, SingleAction): |
||
340 | action_object = SingleAction.from_string(action_object) |
||
341 | |||
342 | if action_object is None: |
||
343 | logger.error('action_object is None') |
||
344 | return False |
||
345 | |||
346 | if 'single_fire_action' in kwargs.keys() and kwargs['single_fire_action'] is True: |
||
347 | action_object.single_fire_action = True |
||
348 | del kwargs['single_fire_action'] |
||
349 | |||
350 | if event_name in self.__Actions: |
||
351 | self.__Actions[event_name].append(action_object) |
||
352 | logger.trace("action %s was added to event %s", action_object, event_name) |
||
353 | else: |
||
354 | self.__Actions[event_name] = [action_object] |
||
355 | logger.trace("action %s was added to new evententry %s", action_object, event_name) |
||
356 | |||
357 | return action_object |
||
358 | |||
359 | __call__ = fire_event_asynchron |
||
360 |
It is generally discouraged to redefine built-ins as this makes code very hard to read.