comparison env/lib/python3.9/site-packages/galaxy/util/watcher.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # TODO: this is largely copied from galaxy.tools.toolbox.galaxy and generalized, the tool-oriented watchers in that
2 # module should probably be updated to use this where possible
3
4
5 import logging
6 import os.path
7 import time
8
9
10 try:
11 from watchdog.events import FileSystemEventHandler
12 from watchdog.observers import Observer
13 from watchdog.observers.polling import PollingObserver
14 can_watch = True
15 except ImportError:
16 Observer = None
17 FileSystemEventHandler = object
18 PollingObserver = None
19 can_watch = False
20
21 from galaxy.util.hash_util import md5_hash_file
22
23 log = logging.getLogger(__name__)
24
25
26 def get_observer_class(config_name, config_value, default, monitor_what_str):
27 """
28 """
29 config_value = config_value or default
30 config_value = str(config_value).lower()
31 if config_value in ("true", "yes", "on", "auto"):
32 expect_observer = True
33 observer_class = Observer
34 elif config_value == "polling":
35 expect_observer = True
36 observer_class = PollingObserver
37 elif config_value in ('false', 'no', 'off'):
38 expect_observer = False
39 observer_class = None
40 else:
41 message = f"Unrecognized value for {config_name} config option: {config_value}"
42 raise Exception(message)
43
44 if expect_observer and observer_class is None:
45 message = "Watchdog library unavailable, cannot monitor %s." % monitor_what_str
46 if config_value == "auto":
47 log.info(message)
48 else:
49 raise Exception(message)
50
51 return observer_class
52
53
54 def get_watcher(config, config_name, default="False", monitor_what_str=None, watcher_class=None,
55 event_handler_class=None, **kwargs):
56 config_value = getattr(config, config_name, None)
57 observer_class = get_observer_class(config_name, config_value, default=default, monitor_what_str=monitor_what_str)
58 if observer_class is not None:
59 watcher_class = watcher_class or Watcher
60 event_handler_class = event_handler_class or EventHandler
61 return watcher_class(observer_class, event_handler_class, **kwargs)
62 else:
63 return NullWatcher()
64
65
66 class BaseWatcher:
67
68 def __init__(self, observer_class, even_handler_class, **kwargs):
69 self.observer = None
70 self.observer_class = observer_class
71 self.event_handler = even_handler_class(self)
72 self.monitored_dirs = {}
73
74 def start(self):
75 if self.observer is None:
76 self.observer = self.observer_class()
77 self.observer.start()
78 self.resume_watching()
79
80 def monitor(self, dir_path, recursive=False):
81 self.monitored_dirs[dir_path] = recursive
82 if self.observer is not None:
83 self.observer.schedule(self.event_handler, dir_path, recursive=recursive)
84
85 def resume_watching(self):
86 for dir_path, recursive in self.monitored_dirs.items():
87 self.monitor(dir_path, recursive)
88
89 def shutdown(self):
90 if self.observer is not None:
91 self.observer.stop()
92 self.observer.join()
93 self.observer = None
94
95
96 class Watcher(BaseWatcher):
97
98 def __init__(self, observer_class, event_handler_class, **kwargs):
99 super().__init__(observer_class, event_handler_class, **kwargs)
100 self.path_hash = {}
101 self.file_callbacks = {}
102 self.dir_callbacks = {}
103 self.ignore_extensions = {}
104 self.require_extensions = {}
105 self.event_handler = event_handler_class(self)
106
107 def watch_file(self, file_path, callback=None):
108 file_path = os.path.abspath(file_path)
109 dir_path = os.path.dirname(file_path)
110 if dir_path not in self.monitored_dirs:
111 if callback is not None:
112 self.file_callbacks[file_path] = callback
113 self.monitor(dir_path)
114 log.debug("Watching for changes to file: %s", file_path)
115
116 def watch_directory(self, dir_path, callback=None, recursive=False, ignore_extensions=None, require_extensions=None):
117 dir_path = os.path.abspath(dir_path)
118 if dir_path not in self.monitored_dirs:
119 if callback is not None:
120 self.dir_callbacks[dir_path] = callback
121 if ignore_extensions:
122 self.ignore_extensions[dir_path] = ignore_extensions
123 if require_extensions:
124 self.require_extensions[dir_path] = require_extensions
125 self.monitor(dir_path, recursive=recursive)
126 log.debug("Watching for changes in directory%s: %s", ' (recursively)' if recursive else '', dir_path)
127
128
129 class EventHandler(FileSystemEventHandler):
130
131 def __init__(self, watcher):
132 self.watcher = watcher
133
134 def on_any_event(self, event):
135 self._handle(event)
136
137 def _extension_check(self, key, path):
138 required_extensions = self.watcher.require_extensions.get(key)
139 if required_extensions:
140 return any(filter(path.endswith, required_extensions))
141 return not any(filter(path.endswith, self.watcher.ignore_extensions.get(key, [])))
142
143 def _handle(self, event):
144 # modified events will only have src path, move events will
145 # have dest_path and src_path but we only care about dest. So
146 # look at dest if it exists else use src.
147 path = getattr(event, 'dest_path', None) or event.src_path
148 path = os.path.abspath(path)
149 callback = self.watcher.file_callbacks.get(path)
150 if os.path.basename(path).startswith('.'):
151 return
152 if callback:
153 ext_ok = self._extension_check(path, path)
154 else:
155 # reversed sort for getting the most specific dir first
156 for key in reversed(sorted(self.watcher.dir_callbacks.keys())):
157 if os.path.commonprefix([path, key]) == key:
158 callback = self.watcher.dir_callbacks[key]
159 ext_ok = self._extension_check(key, path)
160 break
161 if not callback or not ext_ok:
162 return
163 cur_hash = md5_hash_file(path)
164 if cur_hash:
165 if self.watcher.path_hash.get(path) == cur_hash:
166 return
167 else:
168 time.sleep(0.5)
169 if cur_hash != md5_hash_file(path):
170 # We're still modifying the file, it'll be picked up later
171 return
172 self.watcher.path_hash[path] = cur_hash
173 callback(path=path)
174
175
176 class NullWatcher:
177
178 def start(self):
179 pass
180
181 def shutdown(self):
182 pass
183
184 def watch_file(self, *args, **kwargs):
185 pass
186
187 def watch_directory(self, *args, **kwargs):
188 pass