From f044cd91e3ce34eca712ea97c5abf68fd57de784 Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Tue, 24 Jul 2012 17:12:29 -0400 Subject: [PATCH] cc-4105: added wrapper around pyinotify. added some stubs and todos --- python_apps/api_clients/api_client.py | 1 + .../media-monitor2/media/monitor/bootstrap.py | 6 +- .../media-monitor2/media/monitor/listeners.py | 6 ++ .../media/monitor/mm_manager.py | 90 +++++++++++++++++++ .../media-monitor2/media/monitor/organizer.py | 12 +++ .../media/monitor/watchersyncer.py | 9 +- python_apps/media-monitor2/mm2.py | 1 + 7 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 python_apps/media-monitor2/media/monitor/mm_manager.py diff --git a/python_apps/api_clients/api_client.py b/python_apps/api_clients/api_client.py index bc9185210..efe079a35 100644 --- a/python_apps/api_clients/api_client.py +++ b/python_apps/api_clients/api_client.py @@ -441,6 +441,7 @@ class AirtimeApiClient(): return [] def list_all_watched_dirs(self): + # Does this include the stor directory as well? logger = self.logger try: url = "http://%s:%s/%s/%s" % (self.config["base_url"], str(self.config["base_port"]), self.config["api_base"], self.config["list_all_watched_dirs"]) diff --git a/python_apps/media-monitor2/media/monitor/bootstrap.py b/python_apps/media-monitor2/media/monitor/bootstrap.py index ea2061c15..88baadc8a 100644 --- a/python_apps/media-monitor2/media/monitor/bootstrap.py +++ b/python_apps/media-monitor2/media/monitor/bootstrap.py @@ -61,7 +61,11 @@ class Bootstrapper(Loggable): # need the correct watch channel signal to call delete if watch_dir in signal_by_path: dispatcher.send(signal=signal_by_path[watch_dir], sender=self, event=DeleteFile(f)) - os.remove(to_delete) + # TODO : get rid of this, we should never delete files from + # the FS even if they are deleted in airtime. Instead we + # should put this file on a global ignore list until it's + # re-added or something + # os.remove(to_delete) deleted += 1 else: self.logger.error("Could not find the signal corresponding to path: '%s'" % watch_dir) diff --git a/python_apps/media-monitor2/media/monitor/listeners.py b/python_apps/media-monitor2/media/monitor/listeners.py index de767adaa..0d3ff5be6 100644 --- a/python_apps/media-monitor2/media/monitor/listeners.py +++ b/python_apps/media-monitor2/media/monitor/listeners.py @@ -38,6 +38,12 @@ class OrganizeListener(BaseListener, pyinotify.ProcessEvent): # got cookie def process_IN_MOVED_TO(self, event): self.process_to_organize(event) + def flush_events(self, path): + """organize the whole directory at path. (pretty much by doing what + handle does to every file""" + # TODO : implement me + pass + @IncludeOnly(mmp.supported_extensions) def process_to_organize(self, event): dispatcher.send(signal=self.signal, sender=self, event=OrganizeFile(event)) diff --git a/python_apps/media-monitor2/media/monitor/mm_manager.py b/python_apps/media-monitor2/media/monitor/mm_manager.py new file mode 100644 index 000000000..b50702cfd --- /dev/null +++ b/python_apps/media-monitor2/media/monitor/mm_manager.py @@ -0,0 +1,90 @@ +import pyinotify + +from media.monitor.log import Loggable +from media.monitor.listeners import StoreWatchListener, OrganizeListener +from media.monitor.organizer import Organizer + +class Manager(Loggable): + """ + An abstraction over media monitors core pyinotify functions. These include + adding watched,store, organize directories, etc. Basically composes over + WatchManager from pyinotify + """ + all_signals = set(['add_watch', 'remove_watch']) + # TODO : get rid of config object being passed? It's not actually being + # used + def __init__(self,config): + + self.wm = pyinotify.WatchManager() + # These two instance variables are assumed to be constant + self.watch_channel = 'watch' + self.organize_channel = 'organize' + + self.watch_listener = StoreWatchListener(self.watch_channel) + + self.organize = { + 'organize_path' : None, + 'store_path' : None, + # This guy doesn't need to be changed, always the same. + # Gets hooked by wm to different directories + 'organize_listener' : OrganizeListener(self.organize_channel), + # Also stays the same as long as its target, the directory + # which the "organized" files go to, isn't changed. + 'organizer' : None, + } + + self.watched_directories = set([]) + + def __remove_watch(self,path): + old_watch = self.wm.get_wd(path) + if old_watch: # only delete if dir is actually being watched + self.rm_watch(old_watch, rec=True) + + def __create_organizer(self, target_path): + return Organizer(channel=self.organize_channel,target_path=target_path) + + def set_organize_path(self, new_path): + # if we are already organizing a particular directory we remove the + # watch from it first before organizing another directory + self.__remove_watch(self.organize['organize_path']) + self.organize['organize_path'] = new_path + # the OrganizeListener instance will walk path and dispatch an organize + # event for every file in that directory + self.organize['organize_listener'].flush_events(new_path) + self.wm.add_watch(new_path, pyinotify.ALL_EVENTS, rec=True, auto_add=True, + proc_fun=self.organize['organize_listener']) + + def set_store_path(self,new_path): + """set the directory where organized files go to""" + self.__remove_watch(self.organize['store_path']) + self.organize['store_path'] = new_path + self.organize['organizer'] = self.__create_organizer(new_path) + # flush all the files in the new store_directory. this is done so that + # new files are added to the database. Note that we are not responsible + # for removing songs in the old store directory from the database + # we assume that this is already done for us. + self.watch_listener.flush_events(new_path) + self.wm.add_watch(new_path, pyinotify.ALL_EVENTS, rec=True, auto_add=True, + proc_fun=self.watch_listener) + + def add_watch_directory(self, new_dir): + if new_dir in self.watched_directories: + self.logger.info("Cannot add '%s' to watched directories. It's \ + already being watched" % new_dir) + else: + self.logger.info("Adding watched directory: '%s'" % new_dir) + self.watched_directories.add(new_dir) + self.wm.add_watch(new_dir, pyinotify.ALL_EVENTS, rec=True, auto_add=True, + proc_fun=self.watch_listener) + + def remove_watch_directory(self, watch_dir): + if watch_dir in self.watched_directories: + self.watched_directories.remove(watch_dir) + self.logger.info("Removing watched directory: '%s'", watch_dir) + self.__remove_watch(watch_dir) + else: + self.logger.info("'%s' is not being watched, henced cannot be removed" + % watch_dir) + + def loop(self): + pyinotify.Notifier(self.wm).loop() diff --git a/python_apps/media-monitor2/media/monitor/organizer.py b/python_apps/media-monitor2/media/monitor/organizer.py index fdf1da3c5..8be7d764b 100644 --- a/python_apps/media-monitor2/media/monitor/organizer.py +++ b/python_apps/media-monitor2/media/monitor/organizer.py @@ -6,6 +6,13 @@ from media.monitor.log import Loggable from media.monitor.exceptions import BadSongFile class Organizer(ReportHandler,Loggable): + """ + Organizer is responsible to to lisenting to OrganizeListener events and + committing the appropriate changes to the filesystem. It does not in any + interact with WatchSyncer's even when the the WatchSyncer is a "storage + directory". The "storage" directory picks up all of its events through + pyinotify. (These events are fed to it through StoreWatchListener) + """ def __init__(self, channel, target_path): self.channel = channel self.target_path = target_path @@ -22,4 +29,9 @@ class Organizer(ReportHandler,Loggable): # probably general error in mmp.magic.move... except Exception as e: self.report_problem_file(event=event, exception=e) + def flush_events(self, path): + """organize the whole directory at path. (pretty much by doing what + handle does to every file""" + # TODO : implement me + pass diff --git a/python_apps/media-monitor2/media/monitor/watchersyncer.py b/python_apps/media-monitor2/media/monitor/watchersyncer.py index eab6cc129..58c45ef60 100644 --- a/python_apps/media-monitor2/media/monitor/watchersyncer.py +++ b/python_apps/media-monitor2/media/monitor/watchersyncer.py @@ -47,7 +47,8 @@ class TimeoutWatcher(threading.Thread,Loggable): # Note that this isn't strictly necessary since RequestSync threads # already chain themselves if self.watcher.requests_in_queue(): - self.logger.info("We got %d requests waiting to be launched" % self.watcher.requests_left_count()) + self.logger.info("We got %d requests waiting to be launched" % + self.watcher.requests_left_count()) self.watcher.request_do() # Same for events, this behaviour is mandatory however. if self.watcher.events_in_queue(): @@ -73,14 +74,12 @@ class WatchSyncer(ReportHandler,Loggable): @property def target_path(self): return self.channel.path + @property def signal(self): return self.channel.signal def handle(self, sender, event): """We implement this abstract method from ReportHandler""" - # Using isinstance like this is usually considered to be bad style - # because you are supposed to use polymorphism instead however we would - # separate event handling itself from the events so there seems to be - # no better way to do this + # TODO : more types of events need to be handled here if isinstance(event, NewFile): try: self.logger.info("'%s' : New file added: '%s'" % (self.target_path, event.path)) diff --git a/python_apps/media-monitor2/mm2.py b/python_apps/media-monitor2/mm2.py index 01f2b0982..0a4d76296 100644 --- a/python_apps/media-monitor2/mm2.py +++ b/python_apps/media-monitor2/mm2.py @@ -94,6 +94,7 @@ for watch_dir in sdb.list_directories(): # The stor directory is the first directory in the watched directories list org = Organizer(channel=channels['org'],target_path=channels['watch'][0].path) +# TODO : this is wrong watches = [ WatchSyncer(channel=pc) for pc in channels['watch'] ] problem_files = ProblemFileHandler(channel=channels['badfile'])