cc-4105: added wrapper around pyinotify. added some stubs and todos

This commit is contained in:
Rudi Grinberg 2012-07-24 17:12:29 -04:00
parent 493f93c425
commit f044cd91e3
7 changed files with 119 additions and 6 deletions

View File

@ -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"])

View File

@ -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)

View File

@ -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))

View File

@ -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()

View File

@ -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

View File

@ -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))

View File

@ -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'])