-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLinger.py
More file actions
240 lines (197 loc) · 8.38 KB
/
Linger.py
File metadata and controls
240 lines (197 loc) · 8.38 KB
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
"""
Linger is a system that handles configurable triggers to actions
"""
import os
import sys
import time
import logging
import json
from collections import defaultdict
from apscheduler.schedulers.background import BackgroundScheduler
from future.utils import iteritems, itervalues
from LingerManagers.AdaptersManager import AdaptersManager
from LingerManagers.ActionsManager import ActionsManager
from LingerManagers.TriggersManager import TriggersManager
ABSPATH = os.path.abspath(__file__)
DIR_NAME = os.path.dirname(ABSPATH)
os.chdir(DIR_NAME)
LINGER_CONFIGURATION_DEFAULT_PATH = "Linger.config"
LOG_FILE_LOCATION = r"." + os.sep + r"Logs" + os.sep + time.strftime('%Y-%m-%d-%H-%M-%S') + ".log"
# LOG_LEVEL = logging.INFO
LOG_LEVEL = logging.DEBUG
logging.basicConfig(level=LOG_LEVEL,
format='%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
LOGGER = logging.getLogger('Linger')
FORMATTER = logging.Formatter('%(asctime)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
# Multiplexing log lines
FILE_HANDLER = logging.FileHandler(LOG_FILE_LOCATION, mode='w')
FILE_HANDLER.setFormatter(FORMATTER)
FILE_HANDLER.setLevel(LOG_LEVEL)
CONSOLE_HANDLER = logging.StreamHandler()
CONSOLE_HANDLER.setFormatter(FORMATTER)
CONSOLE_HANDLER.setLevel(LOG_LEVEL)
LOGGER.addHandler(FILE_HANDLER)
class Linger(object):
"""Linger monitors file changes, and acting according to a preset"""
def __init__(self, configuration_from_file):
self.logger = LOGGER
self.should_loop = True
self.should_restart = False
job_defaults = {
'misfire_grace_time': None,
'coalesce': True,
'max_instances': 1
}
self.scheduler = BackgroundScheduler(job_defaults=job_defaults)
# logging.getLogger('apscheduler').setLevel(logging.WARNING)
self.configuration = self.build_configuration(configuration_from_file)
self.keep_alive_job = None
# Pair trigger to it's respective action
self.actions_by_trigger = defaultdict(dict)
self.adapters_manager = AdaptersManager(self.configuration)
self.actions_manager = ActionsManager(self.configuration)
self.triggers_manager = TriggersManager(self.configuration)
for item in itervalues(self.configuration["Items"]):
if item["type"] == "Triggers":
self.triggers_manager.create_trigger(item)
elif item["type"] == "Actions":
self.actions_manager.create_action(item)
elif item["type"] == "Adapters":
self.adapters_manager.create_adapter(item)
# Register triggers
for trigger_id, trigger_action_item in iteritems(self.configuration["TriggerActions"]):
if trigger_action_item['enabled']:
self.triggers_manager.set_enabled(trigger_id)
for action_id in trigger_action_item['actions']:
self.actions_by_trigger[trigger_id][action_id] = self.actions_manager.get_action(action_id)
self.triggers_manager.register_action_to_trigger(trigger_id, self.actions_by_trigger[trigger_id][action_id])
def build_configuration(self, configuration_from_file):
"""
Building the configuration to be sent to the items
"""
configuration = {'shutdown': self.shutdown, 'set_should_restart': self.set_should_restart,
'get_adapter_by_uuid': self.get_adapter_by_uuid,
'get_trigger_labels_of_actions': self.get_trigger_labels_of_actions,
'scheduler': self.scheduler, 'trigger_callback': self.trigger_callback,
'trigger_specific_action_callback': self.trigger_specific_action_callback,
'dir_paths': configuration_from_file['dir_paths'], 'Items': configuration_from_file['Items'],
"counter_keep_alive": configuration_from_file["counter_keep_alive"],
"TriggerActions": configuration_from_file["TriggerActions"]}
return configuration
def trigger_callback(self, trigger_uuid, trigger_data):
"""
Activating an action according to trigger
"""
self.logger.debug("trigger calling back: %s", self.actions_by_trigger[trigger_uuid])
for action in itervalues(self.actions_by_trigger[trigger_uuid]):
action.act(trigger_data)
def trigger_specific_action_callback(self, trigger_uuid, action_uuid, trigger_data): # Want this method name! pylint: disable=C0103
"""
Activating an action according to trigger and the action label received by the trigger
"""
self.logger.debug("trigger calling back: %s", self.actions_by_trigger[trigger_uuid])
action = self.actions_by_trigger[trigger_uuid][action_uuid]
return action.act(trigger_data)
def start(self):
"""
Starting Linger activity
"""
self.scheduler.start()
self.keep_alive_job = self.scheduler.add_job(self.log_alive_task, 'interval', seconds=self.configuration['counter_keep_alive'])
self.adapters_manager.start()
self.triggers_manager.start()
def stop(self):
"""
Stopping Linger activity
"""
self.triggers_manager.stop()
self.adapters_manager.stop()
self.keep_alive_job.remove()
self.scheduler.shutdown(wait=True)
def shutdown(self):
"""
Setting Linger to stop loop and shutdown
"""
self.should_loop = False
def set_should_restart(self, flag):
"""
Setting should restart flag
"""
self.should_restart = flag
def get_adapter_by_uuid(self, uuid_of_adapter):
"""
Returns adapter instance by its ID
"""
# noinspection PyBroadException
try:
adapter = self.adapters_manager.get_adapter_by_uuid(uuid_of_adapter)
except Exception: # Don't want to crash pylint: disable=W0703
self.logger.error("Couldn't find adapter with ID:%s, Probably misconfiguration", uuid_of_adapter, exc_info=True)
adapter = None
return adapter
def get_trigger_labels_of_actions(self, uuid_of_trigger):
"""
Returns adapter instance by its ID
"""
# noinspection PyBroadException
try:
trigger_actions_labels = self.triggers_manager.get_trigger_labels_of_actions(uuid_of_trigger)
except Exception: # Don't want to crash pylint: disable=W0703
trigger_actions_labels = None
return trigger_actions_labels
@staticmethod
def log_alive_task():
"""
Writing keep alive to log
"""
LOGGER.info("Linger is still lingering")
def loop(self):
"""
Blocking loop function to keep running the main thread
"""
while self.should_loop:
time.sleep(1)
def clean(self):
"""
Cleaning up before shutting down
"""
self.adapters_manager.cleanup()
self.actions_manager.cleanup()
self.triggers_manager.cleanup()
if self.should_restart:
args = sys.argv[:]
self.logger.debug('Re-spawning %s', args)
args.insert(0, sys.executable)
if sys.platform == 'win32':
args = ['"%s"' % arg for arg in args]
os.execv(sys.executable, args)
def main():
"""
Linger main function, loads configuration and starting
"""
LOGGER.info("Linger has started, be afraid, be very afraid, Press Ctrl+c to quit")
linger_configuration_file_path = LINGER_CONFIGURATION_DEFAULT_PATH
if 2 >= len(sys.argv):
linger_configuration_file_path = sys.argv[1]
with open(linger_configuration_file_path, 'r') as configuration_file:
json_configuration = configuration_file.read()
configuration = json.loads(json_configuration)
configuration_file.close()
# Creating Linger with configuration
linger = Linger(configuration)
linger.start()
try:
linger.loop()
LOGGER.info("Linger has stopped working")
linger.stop()
LOGGER.info("Quitting Linger")
except KeyboardInterrupt:
linger.stop()
LOGGER.info("Quitting Linger")
finally:
linger.clean()
# Cleaning loggers
map(LOGGER.removeHandler, LOGGER.handlers)
if __name__ == '__main__':
main()