Completed
Push — master ( 9c0775...44928b )
by Thomas
01:09
created

doorpi/action/handler.py (1 issue)

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
0 ignored issues
show
The name SingleAction does not seem to exist in module base.
Loading history...
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 = ''):
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 = ''):
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):
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