[PATCH 1 of 3] util: add fswatcher class

Matt Mackall mpm at selenic.com
Thu May 26 15:38:54 CDT 2011


On Thu, 2011-05-26 at 13:06 +0200, Sune Foldager wrote:
> # HG changeset patch
> # User Sune Foldager <cryo at cyanite.org>
> # Date 1306407274 -7200
> # Node ID 991245e3b101e911c8fe6c3d75c33676613e5ef5
> # Parent  0969d91fad5cad68bcf60f85e2e3286acd11ab52
> util: add fswatcher class
> 
> Used to watch files and directories for modifications.

Not at all sure about this mtime/ctime comparison business you're doing.
Not only is its safety suspect, several filesystems don't have the
concept of a ctime.

But we know how to do this already: it's called dirstate. We track and
compare mtime and size. Since we're doing this in memory, we might as
well store the whole stat object and compare that so we can catch sneaky
things like inodes changing.

Other than that, I think the interface is fine.

> diff -r 0969d91fad5c -r 991245e3b101 mercurial/util.py
> --- a/mercurial/util.py	Thu May 26 11:11:34 2011 +0200
> +++ b/mercurial/util.py	Thu May 26 12:54:34 2011 +0200
> @@ -861,6 +861,49 @@
>              limit -= len(s)
>          yield s
>  
> +class fswatcher(object):
> +    """Watches files and directories for changes."""
> +
> +    def __init__(self, paths, reset=False):
> +        """create an fswatcher for the given paths.
> +
> +        reset - start in reset state; next check() call will show all
> +                files and directories that exist, modified.
> +        """
> +        self.paths = paths
> +        self.mtimes = {}
> +        if not reset:
> +            self.check()
> +
> +    def check(self):
> +        """return a list of changes since the last call."""
> +        changes = []
> +        oldmtimes = self.mtimes
> +        mtimes = {}
> +        ctime = int(time.time())
> +        for p in self.paths:
> +            try:
> +                st = os.stat(p)
> +                mtime = st.st_mtime
> +                if mtime == ctime:
> +                    mtimes[p] = mtime - 1
> +                else:
> +                    mtimes[p] = mtime
> +                if oldmtimes.get(p) != mtime:
> +                    changes.append(p)
> +            except OSError, inst:
> +                if inst.errno != errno.ENOENT:
> +                    raise
> +                if p in oldmtimes:
> +                    changes.append(p)
> +        self.mtimes = mtimes
> +        return changes
> +
> +    def reset(self):
> +        """reset the internal state, making all paths that exist appear
> +        changed on the next check() call."""
> +        self.mtimes = {}
> +
>  def makedate():
>      lt = time.localtime()
>      if lt[8] == 1 and time.daylight:
> diff -r 0969d91fad5c -r 991245e3b101 tests/test-fswatcher.py
> --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
> +++ b/tests/test-fswatcher.py	Thu May 26 12:54:34 2011 +0200
> @@ -0,0 +1,120 @@
> +import os, time, errno
> +from mercurial.util import fswatcher
> +
> +# mock code
> +
> +clock = 1
> +mtimes = {}
> +
> +class mockstatdata(object):
> +    def __init__(self, mtime):
> +        self.st_mtime = mtime
> +
> +def mockstat(path):
> +    if path not in mtimes:
> +        raise OSError(errno.ENOENT, 'file not found')
> +    return mockstatdata(mtimes[path])
> +
> +def mocktime():
> +    return clock
> +
> +
> +# support
> +
> +def tprint(str):
> +    print '[%3d] %s' % (clock, str)
> +
> +def timepasses():
> +    global clock
> +    clock += 1
> +
> +def fstouch(path):
> +    tprint('touching %s' % path)
> +    mtimes[path] = clock
> +
> +def fsdelete(path):
> +    tprint('deleting %s' % path)
> +    del mtimes[path]
> +
> +watcher = None
> +
> +def watch(paths, reset=False):
> +    global watcher
> +    tprint('now watching %s' % ', '.join(paths))
> +    watcher = fswatcher(paths, reset=reset)
> +
> +def check():
> +    tprint('check: [%s]' % ', '.join(watcher.check()))
> +
> +def reset():
> +    tprint('resetting watcher')
> +    watcher.reset()
> +
> +# test of fswatcher
> +
> +oldstat = os.stat
> +oldtime = time.time
> +os.stat = mockstat
> +time.time = mocktime
> +
> +try:
> +
> +    print '* basic touch/delete tests'
> +    fstouch('foo')
> +    timepasses()
> +    watch(('foo', 'bar'))
> +    check()
> +    timepasses()
> +    check()
> +    check()
> +    fstouch('foo')
> +    timepasses()
> +    check()
> +    timepasses()
> +    check()
> +    fsdelete('foo')
> +    timepasses()
> +    check()
> +    timepasses()
> +    check()
> +    check()
> +
> +    print
> +    print '* touch file once in same second as ctime'
> +    fstouch('foo')
> +    check()
> +    timepasses()
> +    check()
> +    timepasses()
> +    check()
> +
> +    print
> +    print '* touch file twice in same second as ctime'
> +    fstouch('foo')
> +    fstouch('bar')
> +    check()
> +    fstouch('foo')
> +    timepasses()
> +    check()
> +    timepasses()
> +    check()
> +
> +    print
> +    print '* reset the watcher'
> +    check()
> +    reset()
> +    timepasses()
> +    check()
> +    check()
> +
> +    print
> +    print '* start with a reset watcher'
> +    watch(('foo',), reset=True)
> +    check()
> +    timepasses()
> +    check()
> +    check()
> +
> +finally:
> +    os.stat = oldstat
> +    time.time = oldtime
> diff -r 0969d91fad5c -r 991245e3b101 tests/test-fswatcher.py.out
> --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
> +++ b/tests/test-fswatcher.py.out	Thu May 26 12:54:34 2011 +0200
> @@ -0,0 +1,39 @@
> +* basic touch/delete tests
> +[  1] touching foo
> +[  2] now watching foo, bar
> +[  2] check: []
> +[  3] check: []
> +[  3] check: []
> +[  3] touching foo
> +[  4] check: [foo]
> +[  5] check: []
> +[  5] deleting foo
> +[  6] check: [foo]
> +[  7] check: []
> +[  7] check: []
> +
> +* touch file once in same second as ctime
> +[  7] touching foo
> +[  7] check: [foo]
> +[  8] check: [foo]
> +[  9] check: []
> +
> +* touch file twice in same second as ctime
> +[  9] touching foo
> +[  9] touching bar
> +[  9] check: [foo, bar]
> +[  9] touching foo
> +[ 10] check: [foo, bar]
> +[ 11] check: []
> +
> +* reset the watcher
> +[ 11] check: []
> +[ 11] resetting watcher
> +[ 12] check: [foo, bar]
> +[ 12] check: []
> +
> +* start with a reset watcher
> +[ 12] now watching foo
> +[ 12] check: [foo]
> +[ 13] check: []
> +[ 13] check: []
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel


-- 
Mathematics is the supreme nostalgia of our time.




More information about the Mercurial-devel mailing list