def __init__(self, monitor, handle_dir_delete, name_translations,
            platform_is_ignored, ignore_mask, ignore_config=None):
        super(GeneralINotifyProcessor, self).__init__()
        self.log = logging.getLogger('ubuntuone.SyncDaemon.'
            + 'filesystem_notifications.GeneralProcessor')
        self.log.setLevel(TRACE)
        self.invnames_log = logging.getLogger(
            'ubuntuone.SyncDaemon.InvalidNames')
        self.monitor = monitor
        self.handle_dir_delete = handle_dir_delete
        self.name_translations = name_translations
        self.platform_is_ignored = platform_is_ignored
        self.ignore_mask = ignore_mask
        self.frozen_path = None
        self.frozen_evts = False
        self._to_mute = MuteFilter()
        self.conflict_RE = re.compile(r"\.u1conflict(?:\.\d+)?$")

        if ignore_config is not None:
            self.log.info("Ignoring files: %s", ignore_config)
            # thanks Chipaca for the following "regex composing"
            complex = '|'.join('(?:' + r + ')' for r in ignore_config)
            self.ignore_RE = re.compile(complex)
        else:
            self.ignore_RE = None
class GeneralINotifyProcessor(object):
    """Processor that takes care of dealing with the events."""

    def __init__(self, monitor, handle_dir_delete, name_translations,
                 platform_is_ignored, ignore_mask, ignore_config=None):
        self.log = logging.getLogger(
            'ubuntuone.SyncDaemon.filesystem_notifications.GeneralProcessor')
        self.log.setLevel(TRACE)
        self.invnames_log = logging.getLogger(
            'ubuntuone.SyncDaemon.InvalidNames')
        self.monitor = monitor
        self.handle_dir_delete = handle_dir_delete
        self.name_translations = name_translations
        self.platform_is_ignored = platform_is_ignored
        self.ignore_mask = ignore_mask
        self.frozen_path = None
        self.frozen_evts = False
        self._to_mute = MuteFilter()
        self.conflict_RE = re.compile(r"\.u1conflict(?:\.\d+)?$")

        if ignore_config is not None:
            self.log.info("Ignoring files: %s", ignore_config)
            # thanks Chipaca for the following "regex composing"
            complex = '|'.join('(?:' + r + ')' for r in ignore_config)
            self.ignore_RE = re.compile(complex)
        else:
            self.ignore_RE = None

    def mute_filter(self, action, event, paths):
        """Really touches the mute filter."""
        # all events have one path except the MOVEs
        if event in ("FS_FILE_MOVE", "FS_DIR_MOVE"):
            f_path, t_path = paths['path_from'], paths['path_to']
            is_from_forreal = not self.is_ignored(f_path)
            is_to_forreal = not self.is_ignored(t_path)
            if is_from_forreal and is_to_forreal:
                action(event, **paths)
            elif is_to_forreal:
                action('FS_FILE_CREATE', path=t_path)
                action('FS_FILE_CLOSE_WRITE', path=t_path)
        else:
            path = paths['path']
            if not self.is_ignored(path):
                action(event, **paths)

    def rm_from_mute_filter(self, event, paths):
        self.mute_filter(self._to_mute.rm, event, paths)

    def add_to_mute_filter(self, event, paths):
        """Add an event and path(s) to the mute filter."""
        self.mute_filter(self._to_mute.add, event, paths)

    def get_path_share_id(self, path):
        """Return the id of the given path."""
        return self.monitor.fs.get_by_path(path).share_id

    def get_paths_starting_with(self, path, include_base=True):
        """Return all the paths that start with the given one."""
        return self.monitor.fs.get_paths_starting_with(
            path, include_base=False)

    def rm_watch(self, path):
        """Remove the watch for the given path."""
        self.monitor.rm_watch(path)

    def is_ignored(self, path):
        """should we ignore this path?"""
        # check first if the platform code knows hat to do with it
        if not self.platform_is_ignored(path):
            # check if we can read
            if path_exists(path) and not access(path):
                self.log.warning("Ignoring path as we don't have enough "
                                 "permissions to track it: %r", path)
                return True

            is_conflict = self.conflict_RE.search
            dirname, filename = os.path.split(path)
            # ignore conflicts
            if is_conflict(filename):
                return True
            # ignore partial downloads
            if filename == '.u1partial' or filename.startswith('.u1partial.'):
                return True

            # and ignore paths that are inside conflicts (why are we even
            # getting the event?)
            if any(part.endswith('.u1partial') or is_conflict(part)
                   for part in dirname.split(os.path.sep)):
                return True

            if self.ignore_RE is not None and self.ignore_RE.match(filename):
                return True

            return False
        return True

    def eq_push(self, event_name, **event_data):
        """Sends to EQ the event data, maybe filtering it."""
        if event_name == 'FS_DIR_DELETE':
            self.handle_dir_delete(event_data['path'])
        if not self._to_mute.pop(event_name, **event_data):
            self.monitor.eq.push(event_name, **event_data)

    def push_event(self, event):
        """Push the event to the EQ."""
        # ignore this trash
        if event.mask == self.ignore_mask:
            return

        # change the pattern IN_CREATE to FS_FILE_CREATE or FS_DIR_CREATE
        try:
            evt_name = self.name_translations[event.mask]
        except:
            self.log.error("Unhandled Event in INotify: %s", event)
            raise KeyError("Unhandled Event in INotify: %s" % event)
        # check if the path is not frozen
        if self.frozen_path is not None:
            if event.path == self.frozen_path:
                # this will at least store the last one, for debug
                # purposses
                self.frozen_evts = (evt_name, event.pathname)
                return

        if not self.is_ignored(event.pathname):
            self.eq_push(evt_name, path=event.pathname)

    def freeze_begin(self, path):
        """Puts in hold all the events for this path."""
        self.log.trace("Freeze begin: %r", path)
        self.frozen_path = path
        self.frozen_evts = False

    def freeze_rollback(self):
        """Unfreezes the frozen path, reseting to idle state."""
        self.log.debug("Freeze rollback: %r", self.frozen_path)
        self.frozen_path = None
        self.frozen_evts = False

    def freeze_commit(self, events):
        """Unfreezes the frozen path, sending received events if not dirty.

        If events for that path happened:
            - return True
        else:
            - push the here received events, return False
        """
        self.log.trace(
            "Freeze commit: %r (%d events)", self.frozen_path, len(events))
        if self.frozen_evts:
            # ouch! we're dirty!
            self.log.debug("Dirty by %s", self.frozen_evts)
            self.frozen_evts = False
            return True

        # push the received events
        for evt_name, path in events:
            if not self.is_ignored(path):
                self.eq_push(evt_name, path=path)

        self.frozen_path = None
        self.frozen_evts = False
        return False

    @property
    def filter(self):
        """Return the mute filter used by the processor."""
        return self._to_mute
 def setUp(self):
     self.mf = MuteFilter()
class MuteFilterTests(unittest.TestCase):
    """Tests the MuteFilter class."""

    def setUp(self):
        self.mf = MuteFilter()

    def test_empty(self):
        """Nothing there."""
        self.assertFalse(self.mf._cnt)

    def test_add_one_nodata(self):
        """Adds one element without data."""
        self.mf.add("foo")
        self.assertEqual(self.mf._cnt, dict(foo=[{}]))

    def test_add_one_withdata(self):
        """Adds one element with data."""
        self.mf.add("foo", bar=3)
        self.assertEqual(self.mf._cnt, dict(foo=[{'bar': 3}]))

    def test_add_two_event_different(self):
        """Adds two elements, different event."""
        self.mf.add("foo", a=1)
        self.mf.add("bar", a=1)
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 1}], bar=[{'a': 1}]))

    def test_add_two_data_different(self):
        """Adds two elements, different data."""
        self.mf.add("foo", a=1)
        self.mf.add("foo", b=1)
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 1}, {'b': 1}]))

    def test_add_two_equal(self):
        """Adds one element twice."""
        self.mf.add("foo")
        self.mf.add("foo")
        self.assertEqual(self.mf._cnt, dict(foo=[{}, {}]))

    def test_add_two_equal_and_third(self):
        """Adds one element."""
        self.mf.add("foo")
        self.mf.add("bar", b=3)
        self.mf.add("bar", b=3)
        self.assertEqual(
            self.mf._cnt, dict(foo=[{}], bar=[{'b': 3}, {'b': 3}]))

    def test_pop_simple(self):
        """Pops one element."""
        self.mf.add("foo")
        self.assertFalse(self.mf.pop("bar"))
        self.assertEqual(self.mf._cnt, dict(foo=[{}]))
        self.assertTrue(self.mf.pop("foo"))
        self.assertFalse(self.mf._cnt)

    def test_pop_complex(self):
        """Pops several elements."""
        # add several
        self.mf.add("foo", a=5)
        self.mf.add("bar")
        self.mf.add("bar")
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 5}], bar=[{}, {}]))

        # clean bar
        self.assertTrue(self.mf.pop("bar"))
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 5}], bar=[{}]))
        self.assertTrue(self.mf.pop("bar"))
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 5}]))
        self.assertFalse(self.mf.pop("bar"))
        self.assertEqual(self.mf._cnt, dict(foo=[{'a': 5}]))

        # clean foo
        self.assertTrue(self.mf.pop("foo", a=5))
        self.assertFalse(self.mf._cnt)
        self.assertFalse(self.mf.pop("foo"))
        self.assertFalse(self.mf._cnt)