handler.py 16 KB

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