<commit_before>"""
fs.watch
========

Change notification support for FS.

This module defines a standard interface for FS subclasses that support change
notification callbacks.  It also offers some WrapFS subclasses that can 
simulate such an ability on top of an ordinary FS object.

An FS object that wants to be "watchable" must provide the following methods:

  * add_watcher(path,callback,events=None,recursive=True)

      Request that the given callback be executed in response to changes 
      to the given path.  A specific set of change events can be specified.
      This method returns a Watcher object.

  * del_watcher(watcher_or_callback)

      Remove the given watcher object, or any watchers associated with
      the given callback.


If you would prefer to read changes from a filesystem in a blocking fashion
rather than using callbacks, you can use the function 'iter_changes' to obtain
an iterator over the change events.


"""

import weakref
import threading 
import Queue

from fs.path import *
from fs.errors import *
from fs.wrapfs import WrapFS
from fs.base import FS


class EVENT(object):
    """Base class for change notification events."""
    def __init__(self,fs,path):
        self.fs = fs
        if path is not None:
            path = abspath(normpath(path))
        self.path = path

    def __str__(self):
        return unicode(self).encode("utf8")

    def __unicode__(self):
        return u"<fs.watch.%s object (path='%s') at %s>" % (self.__class__.__name__,self.path,hex(id(self)))

    def clone(self,fs=None,path=None):
        if fs is None:
            fs = self.fs
        if path is None:
            path = self.path
        return self.__class__(fs,path)


class ACCESSED(EVENT):
    """Event fired when a file's contents are accessed."""
    pass

class CREATED(EVENT):
    """Event fired when a new file or directory is created."""
    pass

class REMOVED(EVENT):
    """Event fired when a file or directory is removed."""
    pass

class MODIFIED(EVENT):
    """Event fired when a file or directory is modified."""
    def __init__(self,fs,path,data_changed=False):
        super(MODIFIED,self).__init__(fs,path)
        self.data_changed = data_changed

    def clone(self,fs=None,path=None,data_changed=None):
        evt = super(MODIFIED,self).clone(fs,path)
        if data_changed is None:
            data_changed = self.data_changed
        evt.data_changd = data_changed
        return evt

class MOVED_DST(EVENT):
    """Event fired when a file or directory is the target of a move."""
    def __init__(self,fs,path,source=None):
        super(MOVED_DST,self).__init__(fs,path)
        if source is not None:
            source = abspath(normpath(source))
        self.source = source

    def __unicode__(self):
        return u"<fs.watch.%s object (path=%r,src=%r) at %s>" % (self.__class__.__name__,self.path,self.source,hex(id(self)))

    def clone(self,fs=None,path=None,source=None):
        evt = super(MOVED_DST,self).clone(fs,path)
        if source is None:
            source = self.source
        evt.source = source
        return evt

class MOVED_SRC(EVENT):
    """Event fired when a file or directory is the source of a move."""
    def __init__(self,fs,path,destination=None):
        super(MOVED_SRC,self).__init__(fs,path)
        if destination is not None:
            destination = abspath(normpath(destination))
        self.destination = destination

    def __unicode__(self):
        return u"<fs.watch.%s object (path=%r,dst=%r) at %s>" % (self.__class__.__name__,self.path,self.destination,hex(id(self)))

    def clone(self,fs=None,path=None,destination=None):
        evt = super(MOVED_SRC,self).clone(fs,path)
        if destination is None:
            destination = self.destination
        evt.destination = destination
        return evt

class CLOSED(EVENT):
    """Event fired when the filesystem is closed."""
    pass

class ERROR(EVENT):
    """Event fired when some miscellaneous error occurs."""
    pass

class OVERFLOW(ERROR):
    """Event fired when some events could not be processed."""
    pass



class Watcher(object):
    """Object encapsulating filesystem watch info."""

    def __init__(self,fs,callback,path="/",events=None,recursive=True):
        if events is None:
            events = (EVENT,)
        else:
            events = tuple(events)
        # Since the FS probably holds a reference to the Watcher, keeping
        # a reference back to the FS would create a cycle containing a
        # __del__ method.  Use a weakref to avoid this.
        self._w_fs = weakref.ref(fs)
        self.callback = callback
        self.path = abspath(normpath(path))
        self.events = events
        self.recursive = recursive

    @property
    def fs(self):
        return self._w_fs()

    def delete(self):
        fs = self.fs
        if fs is not None:
            fs.del_watcher(self)

    def handle_event(self,event):
        if not isinstance(event,self.events):
            return
        if event.path is not None:
            if not isprefix(self.path,event.path):
                return
            if not self.recursive:
                if event.path != self.path:
                    if dirname(event.path) != self.path:
                        return
        self.callback(event)


class WatchableFSMixin(FS):
    """Mixin class providing watcher management functions."""

    def __init__(self,*args,**kwds):
        self._watchers = PathMap()
        super(WatchableFSMixin,self).__init__(*args,**kwds)<commit_msg>catch and report exceptions in watcher callbacks<commit_after>"""
fs.watch
========

Change notification support for FS.

This module defines a standard interface for FS subclasses that support change
notification callbacks.  It also offers some WrapFS subclasses that can 
simulate such an ability on top of an ordinary FS object.

An FS object that wants to be "watchable" must provide the following methods:

  * add_watcher(path,callback,events=None,recursive=True)

      Request that the given callback be executed in response to changes 
      to the given path.  A specific set of change events can be specified.
      This method returns a Watcher object.

  * del_watcher(watcher_or_callback)

      Remove the given watcher object, or any watchers associated with
      the given callback.


If you would prefer to read changes from a filesystem in a blocking fashion
rather than using callbacks, you can use the function 'iter_changes' to obtain
an iterator over the change events.


"""

import weakref
import threading 
import Queue

from fs.path import *
from fs.errors import *
from fs.wrapfs import WrapFS
from fs.base import FS


class EVENT(object):
    """Base class for change notification events."""
    def __init__(self,fs,path):
        self.fs = fs
        if path is not None:
            path = abspath(normpath(path))
        self.path = path

    def __str__(self):
        return unicode(self).encode("utf8")

    def __unicode__(self):
        return u"<fs.watch.%s object (path='%s') at %s>" % (self.__class__.__name__,self.path,hex(id(self)))

    def clone(self,fs=None,path=None):
        if fs is None:
            fs = self.fs
        if path is None:
            path = self.path
        return self.__class__(fs,path)


class ACCESSED(EVENT):
    """Event fired when a file's contents are accessed."""
    pass

class CREATED(EVENT):
    """Event fired when a new file or directory is created."""
    pass

class REMOVED(EVENT):
    """Event fired when a file or directory is removed."""
    pass

class MODIFIED(EVENT):
    """Event fired when a file or directory is modified."""
    def __init__(self,fs,path,data_changed=False):
        super(MODIFIED,self).__init__(fs,path)
        self.data_changed = data_changed

    def clone(self,fs=None,path=None,data_changed=None):
        evt = super(MODIFIED,self).clone(fs,path)
        if data_changed is None:
            data_changed = self.data_changed
        evt.data_changd = data_changed
        return evt

class MOVED_DST(EVENT):
    """Event fired when a file or directory is the target of a move."""
    def __init__(self,fs,path,source=None):
        super(MOVED_DST,self).__init__(fs,path)
        if source is not None:
            source = abspath(normpath(source))
        self.source = source

    def __unicode__(self):
        return u"<fs.watch.%s object (path=%r,src=%r) at %s>" % (self.__class__.__name__,self.path,self.source,hex(id(self)))

    def clone(self,fs=None,path=None,source=None):
        evt = super(MOVED_DST,self).clone(fs,path)
        if source is None:
            source = self.source
        evt.source = source
        return evt

class MOVED_SRC(EVENT):
    """Event fired when a file or directory is the source of a move."""
    def __init__(self,fs,path,destination=None):
        super(MOVED_SRC,self).__init__(fs,path)
        if destination is not None:
            destination = abspath(normpath(destination))
        self.destination = destination

    def __unicode__(self):
        return u"<fs.watch.%s object (path=%r,dst=%r) at %s>" % (self.__class__.__name__,self.path,self.destination,hex(id(self)))

    def clone(self,fs=None,path=None,destination=None):
        evt = super(MOVED_SRC,self).clone(fs,path)
        if destination is None:
            destination = self.destination
        evt.destination = destination
        return evt

class CLOSED(EVENT):
    """Event fired when the filesystem is closed."""
    pass

class ERROR(EVENT):
    """Event fired when some miscellaneous error occurs."""
    pass

class OVERFLOW(ERROR):
    """Event fired when some events could not be processed."""
    pass



class Watcher(object):
    """Object encapsulating filesystem watch info."""

    def __init__(self,fs,callback,path="/",events=None,recursive=True):
        if events is None:
            events = (EVENT,)
        else:
            events = tuple(events)
        # Since the FS probably holds a reference to the Watcher, keeping
        # a reference back to the FS would create a cycle containing a
        # __del__ method.  Use a weakref to avoid this.
        self._w_fs = weakref.ref(fs)
        self.callback = callback
        self.path = abspath(normpath(path))
        self.events = events
        self.recursive = recursive

    @property
    def fs(self):
        return self._w_fs()

    def delete(self):
        fs = self.fs
        if fs is not None:
            fs.del_watcher(self)

    def handle_event(self,event):
        if not isinstance(event,self.events):
            return
        if event.path is not None:
            if not isprefix(self.path,event.path):
                return
            if not self.recursive:
                if event.path != self.path:
                    if dirname(event.path) != self.path:
                        return
        try:
            self.callback(event)
        except Exception:
            print >>sys.stderr, "error in FS watcher callback", self.callback
            traceback.print_exc()


class WatchableFSMixin(FS):
    """Mixin class providing watcher management functions."""

    def __init__(self,*args,**kwds):
        self._watchers = PathMap()
        super(WatchableFSMixin,self).__init__(*args,**kwds)
{'fraction_non_alphanumeric': 0.09847814546098035, 'fraction_numerical': 0.00017695983011856308, 'mean_word_length': 2.701047806155861, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6455320', 'n_tokens_mistral': 3293, 'n_tokens_neox': 3194, 'n_words': 1039}