cc-4105: rename watched subdirs barely works

This commit is contained in:
Rudi Grinberg 2012-08-03 18:13:15 -04:00
parent 632f2ab470
commit 1e7de08bf3
4 changed files with 69 additions and 16 deletions

View File

@ -2,8 +2,10 @@
import os import os
import abc import abc
from media.monitor.pure import LazyProperty from media.monitor.pure import LazyProperty
import media.monitor.pure as mmp
from media.monitor.metadata import Metadata from media.monitor.metadata import Metadata
from media.monitor.log import Loggable from media.monitor.log import Loggable
from media.monitor.exceptions import BadSongFile
class PathChannel(object): class PathChannel(object):
"""a dumb struct; python has no record types""" """a dumb struct; python has no record types"""
@ -56,13 +58,26 @@ class BaseEvent(Loggable):
def __str__(self): def __str__(self):
return "Event. Path: %s" % self.__raw_event.pathname return "Event. Path: %s" % self.__raw_event.pathname
def is_dir_event(self):
return self._raw_event.dir
# nothing to see here, please move along # nothing to see here, please move along
def morph_into(self, evt): def morph_into(self, evt):
"""
'Morphing' should preserve the self.cookie invariant. I.e. either
None -> None or int -> int
"""
self.logger.info("Morphing '%s' into '%s'" % (self.__class__.__name__, self.logger.info("Morphing '%s' into '%s'" % (self.__class__.__name__,
evt.__class__.__name__)) evt.__class__.__name__))
self._raw_event = evt self._raw_event = evt
self.path = evt.path self.path = evt.path
self.__class__ = evt.__class__ self.__class__ = evt.__class__
return self
class FakePyinotify(object):
def __init__(self, path):
self.pathname = path
class OrganizeFile(BaseEvent, HasMetaData): class OrganizeFile(BaseEvent, HasMetaData):
def __init__(self, *args, **kwargs): super(OrganizeFile, self).__init__(*args, **kwargs) def __init__(self, *args, **kwargs): super(OrganizeFile, self).__init__(*args, **kwargs)
@ -78,7 +93,7 @@ class NewFile(BaseEvent, HasMetaData):
req_dict = self.metadata.extract() req_dict = self.metadata.extract()
req_dict['mode'] = u'create' req_dict['mode'] = u'create'
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
return req_dict return [req_dict]
class DeleteFile(BaseEvent): class DeleteFile(BaseEvent):
def __init__(self, *args, **kwargs): super(DeleteFile, self).__init__(*args, **kwargs) def __init__(self, *args, **kwargs): super(DeleteFile, self).__init__(*args, **kwargs)
@ -86,7 +101,7 @@ class DeleteFile(BaseEvent):
req_dict = {} req_dict = {}
req_dict['mode'] = u'delete' req_dict['mode'] = u'delete'
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
return req_dict return [req_dict]
class MoveFile(BaseEvent, HasMetaData): class MoveFile(BaseEvent, HasMetaData):
"""Path argument should be the new path of the file that was moved""" """Path argument should be the new path of the file that was moved"""
@ -96,15 +111,34 @@ class MoveFile(BaseEvent, HasMetaData):
req_dict['mode'] = u'moved' req_dict['mode'] = u'moved'
req_dict['MDATA_KEY_MD5'] = self.metadata.extract()['MDATA_KEY_MD5'] req_dict['MDATA_KEY_MD5'] = self.metadata.extract()['MDATA_KEY_MD5']
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
return req_dict return [req_dict]
def map_events(directory, constructor):
#return map(lambda f: constructor( FakePyinotify(f) ).pack(),
#mmp.walk_supported(directory.replace("-unknown-path",""),
#clean_empties=False))
for f in mmp.walk_supported(directory.replace("-unknown-path",""),
clean_empties=True):
try: yield constructor( FakePyinotify(f) ).pack()[0]
except BadSongFile as e: yield e
class DeleteDir(BaseEvent): class DeleteDir(BaseEvent):
def __init__(self, *args, **kwargs): super(DeleteDir, self).__init__(*args, **kwargs) def __init__(self, *args, **kwargs): super(DeleteDir, self).__init__(*args, **kwargs)
def pack(self):
return map_events( self.path, DeleteFile )
class MoveDir(BaseEvent):
def __init__(self, *args, **kwargs): super(MoveDir, self).__init__(*args, **kwargs)
def pack(self):
return map_events( self.path, MoveFile )
class DeleteDirWatch(BaseEvent):
def __init__(self, *args, **kwargs): super(DeleteDirWatch, self).__init__(*args, **kwargs)
def pack(self): def pack(self):
req_dict = {} req_dict = {}
req_dict['mode'] = u'delete_dir' req_dict['mode'] = u'delete_dir'
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
return req_dict return [req_dict]
class ModifyFile(BaseEvent, HasMetaData): class ModifyFile(BaseEvent, HasMetaData):
def __init__(self, *args, **kwargs): super(ModifyFile, self).__init__(*args, **kwargs) def __init__(self, *args, **kwargs): super(ModifyFile, self).__init__(*args, **kwargs)
@ -113,5 +147,5 @@ class ModifyFile(BaseEvent, HasMetaData):
req_dict['mode'] = u'modify' req_dict['mode'] = u'modify'
# path to directory that is to be removed # path to directory that is to be removed
req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path ) req_dict['MDATA_KEY_FILEPATH'] = unicode( self.path )
return req_dict return [req_dict]

View File

@ -5,7 +5,8 @@ from pydispatch import dispatcher
import media.monitor.pure as mmp import media.monitor.pure as mmp
from media.monitor.pure import IncludeOnly from media.monitor.pure import IncludeOnly
from media.monitor.events import OrganizeFile, NewFile, MoveFile, DeleteFile, \ from media.monitor.events import OrganizeFile, NewFile, MoveFile, DeleteFile, \
ModifyFile, DeleteDir, EventRegistry ModifyFile, DeleteDir, EventRegistry, MoveDir,\
DeleteDirWatch
from media.monitor.log import Loggable, get_logger from media.monitor.log import Loggable, get_logger
# We attempt to document a list of all special cases and hacks that the # We attempt to document a list of all special cases and hacks that the
@ -103,35 +104,47 @@ class OrganizeListener(BaseListener, pyinotify.ProcessEvent, Loggable):
dispatcher.send(signal=self.signal, sender=self, event=OrganizeFile(event)) dispatcher.send(signal=self.signal, sender=self, event=OrganizeFile(event))
class StoreWatchListener(BaseListener, Loggable, pyinotify.ProcessEvent): class StoreWatchListener(BaseListener, Loggable, pyinotify.ProcessEvent):
# TODO : must intercept DeleteDirWatch events somehow
def process_IN_CLOSE_WRITE(self, event): self.process_create(event) def process_IN_CLOSE_WRITE(self, event): self.process_create(event)
def process_IN_MOVED_TO(self, event): def process_IN_MOVED_TO(self, event):
if EventRegistry.registered(event): if EventRegistry.registered(event):
EventRegistry.matching(event).morph_into(MoveFile(event)) # We need this trick because we don't how to "expand" dir events
# into file events until we know for sure if we deleted or moved
morph = MoveDir(event) if event.dir else MoveFile(event)
EventRegistry.matching(event).morph_into(morph)
else: self.process_create(event) else: self.process_create(event)
def process_IN_MOVED_FROM(self, event): def process_IN_MOVED_FROM(self, event):
# Is either delete dir or delete file # Is either delete dir or delete file
evt = self.process_delete_dir(event) if event.dir else self.process_delete(event) evt = self.process_delete(event)
if hasattr(event,'cookie'): EventRegistry.register(evt) if hasattr(event,'cookie'): EventRegistry.register(evt)
def process_IN_DELETE(self,event): self.process_delete(event) def process_IN_DELETE(self,event): self.process_delete(event)
# Capturing modify events is too brittle and error prone # Capturing modify events is too brittle and error prone
# def process_IN_MODIFY(self,event): self.process_modify(event) # def process_IN_MODIFY(self,event): self.process_modify(event)
# TODO : Remove this code. Later decided we will ignore modify events
# since it's too difficult to tell which ones should be handled. Much
# easier to just intercept IN_CLOSE_WRITE and decide what to do on the php
# side
@mediate_ignored @mediate_ignored
@IncludeOnly(mmp.supported_extensions) @IncludeOnly(mmp.supported_extensions)
def process_modify(self, event): def process_modify(self, event):
FileMediator.skip_next('IN_MODIFY','IN_CLOSE_WRITE',key='maskname') FileMediator.skip_next('IN_MODIFY','IN_CLOSE_WRITE',key='maskname')
dispatcher.send(signal=self.signal, sender=self, event=ModifyFile(event)) evt = ModifyFile(event)
dispatcher.send(signal=self.signal, sender=self, event=evt)
@mediate_ignored @mediate_ignored
@IncludeOnly(mmp.supported_extensions) @IncludeOnly(mmp.supported_extensions)
def process_create(self, event): def process_create(self, event):
dispatcher.send(signal=self.signal, sender=self, event=NewFile(event)) evt = NewFile(event)
dispatcher.send(signal=self.signal, sender=self, event=evt)
return evt
@mediate_ignored @mediate_ignored
@IncludeOnly(mmp.supported_extensions) @IncludeOnly(mmp.supported_extensions)
def process_delete(self, event): def process_delete(self, event):
evt = DeleteFile(event) evt = None
if event.dir: evt = DeleteDir(event)
else: evt = DeleteFile(event)
dispatcher.send(signal=self.signal, sender=self, event=evt) dispatcher.send(signal=self.signal, sender=self, event=evt)
return evt return evt

View File

@ -42,7 +42,9 @@ class IncludeOnly(object):
def __call__(self, func): def __call__(self, func):
def _wrap(moi, event, *args, **kwargs): def _wrap(moi, event, *args, **kwargs):
ext = extension(event.pathname) ext = extension(event.pathname)
if ext in self.exts: return func(moi, event, *args, **kwargs) # Checking for emptiness b/c we don't want to skip direcotries
if (ext in self.exts) or event.dir:
return func(moi, event, *args, **kwargs)
return _wrap return _wrap

View File

@ -33,8 +33,12 @@ class RequestSync(threading.Thread,Loggable):
# A simplistic request would like: # A simplistic request would like:
# TODO : recorded shows aren't flagged right # TODO : recorded shows aren't flagged right
packed_requests = [] packed_requests = []
for request in self.requests: for request_event in self.requests:
try: packed_requests.append(request.pack()) try:
for request in request_event.pack():
if isinstance(request, BadSongFile):
self.logger.info("Bad song file: '%s'" % request.path)
else: packed_requests.append(request)
except BadSongFile as e: except BadSongFile as e:
self.logger.info("Bad song file: '%s'" % e.path) self.logger.info("Bad song file: '%s'" % e.path)
self.logger.info("TODO : put in ignore list") self.logger.info("TODO : put in ignore list")
@ -53,7 +57,7 @@ class RequestSync(threading.Thread,Loggable):
self.logger.info("Trying again after %f seconds" % self.request_wait) self.logger.info("Trying again after %f seconds" % self.request_wait)
time.sleep( self.request_wait ) time.sleep( self.request_wait )
except Exception as e: except Exception as e:
self.logger.unexpected_exception(e) self.unexpected_exception(e)
else: else:
self.logger.info("Request worked on the '%d' try" % (try_index + 1)) self.logger.info("Request worked on the '%d' try" % (try_index + 1))
break break