Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pyfs
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
pyfs
Commits
13ced551
Commit
13ced551
authored
Apr 07, 2010
by
rfkelly0
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement WatchableFS interface for OSFS using pyinotify on linux
parent
488a35a8
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
384 additions
and
84 deletions
+384
-84
ChangeLog
+1
-0
fs/osfs/__init__.py
+3
-30
fs/osfs/watch.py
+197
-0
fs/osfs/xattrs.py
+67
-0
fs/path.py
+2
-2
fs/tests/test_path.py
+5
-0
fs/tests/test_watch.py
+42
-9
fs/tests/test_xattr.py
+2
-1
fs/watch.py
+65
-42
No files found.
ChangeLog
View file @
13ced551
...
@@ -16,6 +16,7 @@
...
@@ -16,6 +16,7 @@
* expose.sftp: expose an FS object SFTP
* expose.sftp: expose an FS object SFTP
* expose.django_storage: convert FS object to Django Storage object
* expose.django_storage: convert FS object to Django Storage object
* Extended attribute support (getxattr/setxattr/delxattr/listxattrs)
* Extended attribute support (getxattr/setxattr/delxattr/listxattrs)
* Change watching support (add_watcher/del_watcher)
* Insist on unicode paths throughout:
* Insist on unicode paths throughout:
* output paths are always unicode
* output paths are always unicode
* bytestring input paths are decoded as early as possible
* bytestring input paths are decoded as early as possible
...
...
fs/osfs.py
→
fs/osfs
/__init__
.py
View file @
13ced551
...
@@ -21,10 +21,8 @@ from fs.base import *
...
@@ -21,10 +21,8 @@ from fs.base import *
from
fs.path
import
*
from
fs.path
import
*
from
fs
import
_thread_synchronize_default
from
fs
import
_thread_synchronize_default
try
:
from
fs.osfs.xattrs
import
OSFSXAttrMixin
import
xattr
from
fs.osfs.watch
import
OSFSWatchMixin
except
ImportError
:
xattr
=
None
@convert_os_errors
@convert_os_errors
...
@@ -33,7 +31,7 @@ def _os_stat(path):
...
@@ -33,7 +31,7 @@ def _os_stat(path):
return
os
.
stat
(
path
)
return
os
.
stat
(
path
)
class
OSFS
(
FS
):
class
OSFS
(
OSFSXAttrMixin
,
OSFSWatchMixin
,
FS
):
"""Expose the underlying operating-system filesystem as an FS object.
"""Expose the underlying operating-system filesystem as an FS object.
This is the most basic of filesystems, which simply shadows the underlaying
This is the most basic of filesystems, which simply shadows the underlaying
...
@@ -225,28 +223,3 @@ class OSFS(FS):
...
@@ -225,28 +223,3 @@ class OSFS(FS):
return
self
.
_stat
(
path
)
.
st_size
return
self
.
_stat
(
path
)
.
st_size
# Provide native xattr support if available
if
xattr
:
@convert_os_errors
def
setxattr
(
self
,
path
,
key
,
value
):
xattr
.
xattr
(
self
.
getsyspath
(
path
))[
key
]
=
value
@convert_os_errors
def
getxattr
(
self
,
path
,
key
,
default
=
None
):
try
:
return
xattr
.
xattr
(
self
.
getsyspath
(
path
))
.
get
(
key
)
except
KeyError
:
return
default
@convert_os_errors
def
delxattr
(
self
,
path
,
key
):
try
:
del
xattr
.
xattr
(
self
.
getsyspath
(
path
))[
key
]
except
KeyError
:
pass
@convert_os_errors
def
listxattrs
(
self
,
path
):
return
xattr
.
xattr
(
self
.
getsyspath
(
path
))
.
keys
()
fs/osfs/watch.py
0 → 100644
View file @
13ced551
"""
fs.osfs.watch
=============
Change watcher support for OSFS
"""
import
os
import
sys
import
errno
import
threading
from
fs.errors
import
*
from
fs.path
import
*
from
fs.watch
import
*
try
:
import
pyinotify
except
ImportError
:
pyinotify
=
None
if
pyinotify
is
not
None
:
class
OSFSWatchMixin
(
WatchableFSMixin
):
"""Mixin providing change-watcher support via pyinotify."""
__watch_lock
=
threading
.
Lock
()
__watch_manager
=
None
__watch_notifier
=
None
def
close
(
self
):
super
(
OSFSWatchMixin
,
self
)
.
close
()
self
.
__shutdown_watch_manager
(
force
=
True
)
self
.
notify_watchers
(
CLOSED
)
def
add_watcher
(
self
,
callback
,
path
=
"/"
,
events
=
None
,
recursive
=
True
):
w
=
super
(
OSFSWatchMixin
,
self
)
.
add_watcher
(
callback
,
path
,
events
,
recursive
)
syspath
=
self
.
getsyspath
(
path
)
if
isinstance
(
syspath
,
unicode
):
syspath
=
syspath
.
encode
(
sys
.
getfilesystemencoding
())
wm
=
self
.
__get_watch_manager
()
evtmask
=
self
.
__get_event_mask
(
events
)
def
process_events
(
event
):
self
.
__route_event
(
w
,
event
)
kwds
=
dict
(
rec
=
recursive
,
auto_add
=
recursive
,
quiet
=
False
)
try
:
wids
=
wm
.
add_watch
(
syspath
,
evtmask
,
process_events
,
**
kwds
)
except
pyinotify
.
WatchManagerError
,
e
:
raise
OperationFailedError
(
"add_watcher"
,
details
=
e
)
w
.
_pyinotify_id
=
wids
[
syspath
]
return
w
def
del_watcher
(
self
,
watcher_or_callback
):
wm
=
self
.
__get_watch_manager
()
if
isinstance
(
watcher_or_callback
,
Watcher
):
watchers
=
[
watcher_or_callback
]
else
:
watchers
=
self
.
_find_watchers
(
watcher_or_callback
)
for
watcher
in
watchers
:
wm
.
rm_watch
(
watcher
.
_pyinotify_id
,
rec
=
watcher
.
recursive
)
super
(
OSFSWatchMixin
,
self
)
.
del_watcher
(
watcher
)
if
not
wm
.
_wmd
:
self
.
__shutdown_watch_manager
()
def
__get_event_mask
(
self
,
events
):
"""Convert the given set of events into a pyinotify event mask."""
if
events
is
None
:
events
=
(
EVENT
,)
mask
=
0
for
evt
in
events
:
if
issubclass
(
ACCESSED
,
evt
):
mask
|=
pyinotify
.
IN_ACCESS
if
issubclass
(
CREATED
,
evt
):
mask
|=
pyinotify
.
IN_CREATE
if
issubclass
(
REMOVED
,
evt
):
mask
|=
pyinotify
.
IN_DELETE
mask
|=
pyinotify
.
IN_DELETE_SELF
if
issubclass
(
MODIFIED
,
evt
):
mask
|=
pyinotify
.
IN_ATTRIB
mask
|=
pyinotify
.
IN_MODIFY
mask
|=
pyinotify
.
IN_CLOSE_WRITE
if
issubclass
(
MOVED_SRC
,
evt
):
mask
|=
pyinotify
.
IN_MOVED_FROM
mask
|=
pyinotify
.
IN_MOVED_TO
if
issubclass
(
MOVED_DST
,
evt
):
mask
|=
pyinotify
.
IN_MOVED_FROM
mask
|=
pyinotify
.
IN_MOVED_TO
if
issubclass
(
OVERFLOW
,
evt
):
mask
|=
pyinotify
.
IN_Q_OVERFLOW
if
issubclass
(
CLOSED
,
evt
):
mask
|=
pyinotify
.
IN_UNMOUNT
return
mask
def
unsyspath
(
self
,
path
):
"""Convert a system-level path into an FS-level path."""
path
=
normpath
(
path
)
if
not
isprefix
(
self
.
root_path
,
path
):
raise
ValueError
(
"path not within this FS:
%
s"
%
(
path
,))
return
path
[
len
(
self
.
root_path
):]
def
__route_event
(
self
,
watcher
,
inevt
):
"""Convert pyinotify event into fs.watch event, then handle it."""
try
:
path
=
self
.
unsyspath
(
inevt
.
pathname
)
except
ValueError
:
return
try
:
src_path
=
inevt
.
src_pathname
if
src_path
is
not
None
:
src_path
=
self
.
unsyspath
(
src_path
)
except
(
AttributeError
,
ValueError
):
src_path
=
None
if
inevt
.
mask
&
pyinotify
.
IN_ACCESS
:
watcher
.
handle_event
(
ACCESSED
(
self
,
path
))
if
inevt
.
mask
&
pyinotify
.
IN_CREATE
:
watcher
.
handle_event
(
CREATED
(
self
,
path
))
# Recursive watching of directories in pyinotify requires
# the creation of a new watch for each subdir, resulting in
# a race condition whereby events in the subdir are missed.
# We'd prefer to duplicate events than to miss them.
if
inevt
.
mask
&
pyinotify
.
IN_ISDIR
:
try
:
# pyinotify does this for dirs itself, we only.
# need to worry about newly-created files.
for
child
in
self
.
listdir
(
path
,
files_only
=
True
):
cpath
=
pathjoin
(
path
,
child
)
self
.
notify_watchers
(
CREATED
,
cpath
)
self
.
notify_watchers
(
MODIFIED
,
cpath
,
True
,
True
)
except
FSError
:
pass
if
inevt
.
mask
&
pyinotify
.
IN_DELETE
:
watcher
.
handle_event
(
REMOVED
(
self
,
path
))
if
inevt
.
mask
&
pyinotify
.
IN_DELETE_SELF
:
watcher
.
handle_event
(
REMOVED
(
self
,
path
))
if
inevt
.
mask
&
pyinotify
.
IN_ATTRIB
:
watcher
.
handle_event
(
MODIFIED
(
self
,
path
,
True
,
False
))
if
inevt
.
mask
&
pyinotify
.
IN_MODIFY
:
watcher
.
handle_event
(
MODIFIED
(
self
,
path
,
True
,
True
))
if
inevt
.
mask
&
pyinotify
.
IN_CLOSE_WRITE
:
watcher
.
handle_event
(
MODIFIED
(
self
,
path
,
True
,
True
))
if
inevt
.
mask
&
pyinotify
.
IN_MOVED_FROM
:
# Sorry folks, I'm not up for decoding the destination path.
watcher
.
handle_event
(
MOVED_SRC
(
self
,
path
,
None
))
if
inevt
.
mask
&
pyinotify
.
IN_MOVED_TO
:
if
getattr
(
inevt
,
"src_pathname"
,
None
):
watcher
.
handle_event
(
MOVED_SRC
(
self
,
src_path
,
path
))
watcher
.
handle_event
(
MOVED_DST
(
self
,
path
,
src_path
))
else
:
watcher
.
handle_event
(
MOVED_DST
(
self
,
path
,
None
))
if
inevt
.
mask
&
pyinotify
.
IN_Q_OVERFLOW
:
watcher
.
handle_event
(
OVERFLOW
(
self
))
if
inevt
.
mask
&
pyinotify
.
IN_UNMOUNT
:
watcher
.
handle_event
(
CLOSE
(
self
))
def
__get_watch_manager
(
self
):
"""Get the shared watch manager, initializing if necessary."""
if
OSFSWatchMixin
.
__watch_notifier
is
None
:
self
.
__watch_lock
.
acquire
()
try
:
if
self
.
__watch_notifier
is
None
:
wm
=
pyinotify
.
WatchManager
()
n
=
pyinotify
.
ThreadedNotifier
(
wm
)
n
.
start
()
OSFSWatchMixin
.
__watch_manager
=
wm
OSFSWatchMixin
.
__watch_notifier
=
n
finally
:
self
.
__watch_lock
.
release
()
return
OSFSWatchMixin
.
__watch_manager
def
__shutdown_watch_manager
(
self
,
force
=
False
):
"""Stop the shared watch manager, if there are no watches left."""
self
.
__watch_lock
.
acquire
()
try
:
if
OSFSWatchMixin
.
__watch_manager
is
None
:
return
if
not
force
and
OSFSWatchMixin
.
__watch_manager
.
_wmd
:
return
OSFSWatchMixin
.
__watch_notifier
.
stop
()
OSFSWatchMixin
.
__watch_notifier
=
None
OSFSWatchMixin
.
__watch_manager
=
None
finally
:
self
.
__watch_lock
.
release
()
else
:
class
OSFSWatchMixin
(
object
):
"""Mixin disabling change-watcher support."""
def
add_watcher
(
self
,
*
args
,
**
kwds
):
raise
UnsupportedError
def
del_watcher
(
self
,
watcher_or_callback
):
raise
UnsupportedError
fs/osfs/xattrs.py
0 → 100644
View file @
13ced551
"""
fs.osfs.xattrs
==============
Extended-attribute support for OSFS
"""
import
os
import
sys
import
errno
from
fs.errors
import
*
from
fs.path
import
*
from
fs.base
import
FS
try
:
import
xattr
except
ImportError
:
xattr
=
None
if
xattr
is
not
None
:
class
OSFSXAttrMixin
(
FS
):
"""Mixin providing extended-attribute support via the 'xattr' module"""
@convert_os_errors
def
setxattr
(
self
,
path
,
key
,
value
):
xattr
.
xattr
(
self
.
getsyspath
(
path
))[
key
]
=
value
@convert_os_errors
def
getxattr
(
self
,
path
,
key
,
default
=
None
):
try
:
return
xattr
.
xattr
(
self
.
getsyspath
(
path
))
.
get
(
key
)
except
KeyError
:
return
default
@convert_os_errors
def
delxattr
(
self
,
path
,
key
):
try
:
del
xattr
.
xattr
(
self
.
getsyspath
(
path
))[
key
]
except
KeyError
:
pass
@convert_os_errors
def
listxattrs
(
self
,
path
):
return
xattr
.
xattr
(
self
.
getsyspath
(
path
))
.
keys
()
else
:
class
OSFSXAttrMixin
(
object
):
"""Mixin disable extended-attribute support."""
def
getxattr
(
self
,
path
,
key
):
raise
UnsupportedError
def
setxattr
(
self
,
path
,
key
,
value
):
raise
UnsupportedError
def
delxattr
(
self
,
path
,
key
):
raise
UnsupportedError
def
listxattrs
(
self
,
path
):
raise
UnsupportedError
fs/path.py
View file @
13ced551
...
@@ -455,8 +455,8 @@ class PathMap(object):
...
@@ -455,8 +455,8 @@ class PathMap(object):
m
=
m
[
name
]
m
=
m
[
name
]
except
KeyError
:
except
KeyError
:
return
return
for
nm
in
m
:
for
(
nm
,
subm
)
in
m
.
iteritems
()
:
if
nm
:
if
nm
and
subm
:
yield
nm
yield
nm
def
names
(
self
,
root
=
"/"
):
def
names
(
self
,
root
=
"/"
):
...
...
fs/tests/test_path.py
View file @
13ced551
...
@@ -128,4 +128,9 @@ class Test_PathMap(unittest.TestCase):
...
@@ -128,4 +128,9 @@ class Test_PathMap(unittest.TestCase):
self
.
assertEquals
(
set
(
map
.
iternames
(
"hello"
)),
set
((
"world"
,
"kitty"
)))
self
.
assertEquals
(
set
(
map
.
iternames
(
"hello"
)),
set
((
"world"
,
"kitty"
)))
self
.
assertEquals
(
set
(
map
.
iternames
(
"/hello/kitty"
)),
set
((
"islame"
,)))
self
.
assertEquals
(
set
(
map
.
iternames
(
"/hello/kitty"
)),
set
((
"islame"
,)))
del
map
[
"hello/kitty/islame"
]
self
.
assertEquals
(
set
(
map
.
iternames
(
"/hello/kitty"
)),
set
())
self
.
assertEquals
(
set
(
map
.
iterkeys
()),
set
((
"/hello/world"
,
"/hello/world/howareya"
,
"/hello/world/iamfine"
,
"/hello/kitty"
,
"/batman/isawesome"
)))
self
.
assertEquals
(
set
(
map
.
values
()),
set
(
range
(
1
,
7
))
-
set
((
5
,)))
fs/tests/test_watch.py
View file @
13ced551
...
@@ -13,6 +13,11 @@ from fs.errors import *
...
@@ -13,6 +13,11 @@ from fs.errors import *
from
fs.watch
import
*
from
fs.watch
import
*
from
fs.tests
import
FSTestCases
from
fs.tests
import
FSTestCases
try
:
import
pyinotify
except
ImportError
:
pyinotify
=
None
class
WatcherTestCases
:
class
WatcherTestCases
:
"""Testcases for filesystems providing change watcher support.
"""Testcases for filesystems providing change watcher support.
...
@@ -34,6 +39,8 @@ class WatcherTestCases:
...
@@ -34,6 +39,8 @@ class WatcherTestCases:
self
.
watchfs
.
_poll_cond
.
wait
()
self
.
watchfs
.
_poll_cond
.
wait
()
self
.
watchfs
.
_poll_cond
.
wait
()
self
.
watchfs
.
_poll_cond
.
wait
()
self
.
watchfs
.
_poll_cond
.
release
()
self
.
watchfs
.
_poll_cond
.
release
()
else
:
time
.
sleep
(
0.5
)
def
assertEventOccurred
(
self
,
cls
,
path
=
None
,
**
attrs
):
def
assertEventOccurred
(
self
,
cls
,
path
=
None
,
**
attrs
):
if
not
self
.
checkEventOccurred
(
cls
,
path
,
**
attrs
):
if
not
self
.
checkEventOccurred
(
cls
,
path
,
**
attrs
):
...
@@ -92,6 +99,30 @@ class WatcherTestCases:
...
@@ -92,6 +99,30 @@ class WatcherTestCases:
self
.
fs
.
setcontents
(
"hello"
,
"hello again world"
)
self
.
fs
.
setcontents
(
"hello"
,
"hello again world"
)
self
.
assertEventOccurred
(
MODIFIED
,
"/hello"
)
self
.
assertEventOccurred
(
MODIFIED
,
"/hello"
)
def
test_watch_single_file
(
self
):
self
.
fs
.
setcontents
(
"hello"
,
"hello world"
)
events
=
[]
self
.
watchfs
.
add_watcher
(
events
.
append
,
"/hello"
,(
MODIFIED
,))
self
.
fs
.
setcontents
(
"hello"
,
"hello again world"
)
self
.
fs
.
remove
(
"hello"
)
self
.
waitForEvents
()
for
evt
in
events
:
assert
isinstance
(
evt
,
MODIFIED
)
self
.
assertEquals
(
evt
.
path
,
"/hello"
)
def
test_watch_single_file_remove
(
self
):
self
.
fs
.
makedir
(
"testing"
)
self
.
fs
.
setcontents
(
"testing/hello"
,
"hello world"
)
events
=
[]
self
.
watchfs
.
add_watcher
(
events
.
append
,
"/testing/hello"
,(
REMOVED
,))
self
.
fs
.
setcontents
(
"testing/hello"
,
"hello again world"
)
self
.
waitForEvents
()
self
.
fs
.
remove
(
"testing/hello"
)
self
.
waitForEvents
()
self
.
assertEquals
(
len
(
events
),
1
)
assert
isinstance
(
events
[
0
],
REMOVED
)
self
.
assertEquals
(
events
[
0
]
.
path
,
"/testing/hello"
)
def
test_watch_iter_changes
(
self
):
def
test_watch_iter_changes
(
self
):
changes
=
iter_changes
(
self
.
watchfs
)
changes
=
iter_changes
(
self
.
watchfs
)
self
.
fs
.
makedir
(
"test1"
)
self
.
fs
.
makedir
(
"test1"
)
...
@@ -101,23 +132,23 @@ class WatcherTestCases:
...
@@ -101,23 +132,23 @@ class WatcherTestCases:
self
.
waitForEvents
()
self
.
waitForEvents
()
self
.
watchfs
.
close
()
self
.
watchfs
.
close
()
# Locate the CREATED(test1) event
# Locate the CREATED(test1) event
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
while
not
isinstance
(
event
,
CREATED
)
or
event
.
path
!=
"/test1"
:
while
not
isinstance
(
event
,
CREATED
)
or
event
.
path
!=
"/test1"
:
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
# Locate the CREATED(test1/hello) event
# Locate the CREATED(test1/hello) event
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
while
not
isinstance
(
event
,
CREATED
)
or
event
.
path
!=
"/test1/hello"
:
while
not
isinstance
(
event
,
CREATED
)
or
event
.
path
!=
"/test1/hello"
:
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
# Locate the REMOVED(test1) event
# Locate the REMOVED(test1) event
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
while
not
isinstance
(
event
,
REMOVED
)
or
event
.
path
!=
"/test1"
:
while
not
isinstance
(
event
,
REMOVED
)
or
event
.
path
!=
"/test1"
:
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
# Locate the CLOSED event
# Locate the CLOSED event
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
while
not
isinstance
(
event
,
CLOSED
):
while
not
isinstance
(
event
,
CLOSED
):
event
=
changes
.
next
()
event
=
changes
.
next
(
timeout
=
1
)
# That should be the last event in the list
# That should be the last event in the list
self
.
assertRaises
(
StopIteration
,
changes
.
next
)
self
.
assertRaises
(
StopIteration
,
changes
.
next
,
timeout
=
1
)
changes
.
close
()
changes
.
close
()
...
@@ -128,6 +159,8 @@ class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases):
...
@@ -128,6 +159,8 @@ class TestWatchers_TempFS(unittest.TestCase,FSTestCases,WatcherTestCases):
self
.
fs
=
tempfs
.
TempFS
()
self
.
fs
=
tempfs
.
TempFS
()
watchfs
=
osfs
.
OSFS
(
self
.
fs
.
root_path
)
watchfs
=
osfs
.
OSFS
(
self
.
fs
.
root_path
)
self
.
watchfs
=
ensure_watchable
(
watchfs
,
poll_interval
=
0.1
)
self
.
watchfs
=
ensure_watchable
(
watchfs
,
poll_interval
=
0.1
)
if
pyinotify
is
not
None
:
self
.
assertEquals
(
watchfs
,
self
.
watchfs
)
def
tearDown
(
self
):
def
tearDown
(
self
):
self
.
watchfs
.
close
()
self
.
watchfs
.
close
()
...
...
fs/tests/test_xattr.py
View file @
13ced551
...
@@ -139,7 +139,8 @@ from fs import tempfs
...
@@ -139,7 +139,8 @@ from fs import tempfs
class
TestXAttr_TempFS
(
unittest
.
TestCase
,
FSTestCases
,
XAttrTestCases
):
class
TestXAttr_TempFS
(
unittest
.
TestCase
,
FSTestCases
,
XAttrTestCases
):
def
setUp
(
self
):
def
setUp
(
self
):
self
.
fs
=
ensure_xattrs
(
tempfs
.
TempFS
())
fs
=
tempfs
.
TempFS
()
self
.
fs
=
ensure_xattrs
(
fs
)
def
tearDown
(
self
):
def
tearDown
(
self
):
td
=
self
.
fs
.
_temp_dir
td
=
self
.
fs
.
_temp_dir
...
...
fs/watch.py
View file @
13ced551
...
@@ -25,11 +25,12 @@ An FS object that wants to be "watchable" must provide the following methods:
...
@@ -25,11 +25,12 @@ An FS object that wants to be "watchable" must provide the following methods:
import
weakref
import
weakref
import
threading
import
threading
from
Queue
import
Queue
import
Queue
from
fs.path
import
*
from
fs.path
import
*
from
fs.errors
import
*
from
fs.errors
import
*
from
fs.wrapfs
import
WrapFS
from
fs.wrapfs
import
WrapFS
from
fs.base
import
FS
class
EVENT
(
object
):
class
EVENT
(
object
):
...
@@ -65,17 +66,21 @@ class MODIFIED(EVENT):
...
@@ -65,17 +66,21 @@ class MODIFIED(EVENT):
self
.
meta
=
meta
self
.
meta
=
meta
self
.
data
=
data
self
.
data
=
data
class
MOVED_
FROM
(
EVENT
):
class
MOVED_
DST
(
EVENT
):
"""Event fired when a file or directory is
moved
."""
"""Event fired when a file or directory is
the target of a move
."""
def
__init__
(
self
,
fs
,
path
,
source
):
def
__init__
(
self
,
fs
,
path
,
source
):
super
(
MOVED_FROM
,
self
)
.
__init__
(
fs
,
path
)
super
(
MOVED_DST
,
self
)
.
__init__
(
fs
,
path
)
self
.
source
=
abspath
(
normpath
(
source
))
if
source
is
not
None
:
source
=
abspath
(
normpath
(
source
))
self
.
source
=
source
class
MOVED_
TO
(
EVENT
):
class
MOVED_
SRC
(
EVENT
):
"""Event fired when a file or directory is
moved
."""
"""Event fired when a file or directory is
the source of a move
."""
def
__init__
(
self
,
fs
,
path
,
destination
):
def
__init__
(
self
,
fs
,
path
,
destination
):
super
(
MOVED_TO
,
self
)
.
__init__
(
fs
,
path
)
super
(
MOVED_SRC
,
self
)
.
__init__
(
fs
,
path
)
self
.
destination
=
abspath
(
normpath
(
destination
))
if
destination
is
not
None
:
destination
=
abspath
(
normpath
(
destination
))
self
.
destination
=
destination
class
CLOSED
(
EVENT
):
class
CLOSED
(
EVENT
):
"""Event fired when the filesystem is closed."""
"""Event fired when the filesystem is closed."""
...
@@ -130,7 +135,7 @@ class Watcher(object):
...
@@ -130,7 +135,7 @@ class Watcher(object):
self
.
callback
(
event
)
self
.
callback
(
event
)
class
WatchableFSMixin
(
object
):
class
WatchableFSMixin
(
FS
):
"""Mixin class providing watcher management functions."""
"""Mixin class providing watcher management functions."""
def
__init__
(
self
,
*
args
,
**
kwds
):
def
__init__
(
self
,
*
args
,
**
kwds
):
...
@@ -154,7 +159,15 @@ class WatchableFSMixin(object):
...
@@ -154,7 +159,15 @@ class WatchableFSMixin(object):
del
watchers
[
i
]
del
watchers
[
i
]
break
break
def
_find_watchers
(
self
,
callback
):
"""Find watchers registered with the given callback."""
for
watchers
in
self
.
_watchers
.
itervalues
():
for
watcher
in
watchers
:
if
watcher
.
callback
is
callback
:
yield
watcher
def
notify_watchers
(
self
,
event_class
,
path
=
None
,
*
args
,
**
kwds
):
def
notify_watchers
(
self
,
event_class
,
path
=
None
,
*
args
,
**
kwds
):
"""Notify watchers of the given event data."""
event
=
event_class
(
self
,
path
,
*
args
,
**
kwds
)
event
=
event_class
(
self
,
path
,
*
args
,
**
kwds
)
if
path
is
None
:
if
path
is
None
:
for
watchers
in
self
.
_watchers
.
itervalues
():
for
watchers
in
self
.
_watchers
.
itervalues
():
...
@@ -254,8 +267,8 @@ class WatchableFS(WrapFS,WatchableFSMixin):
...
@@ -254,8 +267,8 @@ class WatchableFS(WrapFS,WatchableFSMixin):
super
(
WatchableFS
,
self
)
.
rename
(
src
,
dst
)
super
(
WatchableFS
,
self
)
.
rename
(
src
,
dst
)
if
d_existed
:
if
d_existed
:
self
.
notify_watchers
(
REMOVED
,
dst
)
self
.
notify_watchers
(
REMOVED
,
dst
)
self
.
notify_watchers
(
MOVED_
TO
,
src
,
dst
)
self
.
notify_watchers
(
MOVED_
SRC
,
src
,
dst
)
self
.
notify_watchers
(
MOVED_
FROM
,
dst
,
src
)
self
.
notify_watchers
(
MOVED_
DST
,
dst
,
src
)
def
copy
(
self
,
src
,
dst
,
**
kwds
):
def
copy
(
self
,
src
,
dst
,
**
kwds
):
d
=
self
.
_pre_copy
(
src
,
dst
)
d
=
self
.
_pre_copy
(
src
,
dst
)
...
@@ -339,9 +352,9 @@ class PollingWatchableFS(WatchableFS):
...
@@ -339,9 +352,9 @@ class PollingWatchableFS(WatchableFS):
def
__init__
(
self
,
wrapped_fs
,
poll_interval
=
60
*
5
):
def
__init__
(
self
,
wrapped_fs
,
poll_interval
=
60
*
5
):
super
(
PollingWatchableFS
,
self
)
.
__init__
(
wrapped_fs
)
super
(
PollingWatchableFS
,
self
)
.
__init__
(
wrapped_fs
)
self
.
poll_interval
=
poll_interval
self
.
poll_interval
=
poll_interval
self
.
add_watcher
(
self
.
_on_path_modify
,
"/"
,(
CREATED
,
MOVED_
TO
,))
self
.
add_watcher
(
self
.
_on_path_modify
,
"/"
,(
CREATED
,
MOVED_
DST
,))
self
.
add_watcher
(
self
.
_on_path_modify
,
"/"
,(
MODIFIED
,
ACCESSED
,))
self
.
add_watcher
(
self
.
_on_path_modify
,
"/"
,(
MODIFIED
,
ACCESSED
,))
self
.
add_watcher
(
self
.
_on_path_delete
,
"/"
,(
REMOVED
,
MOVED_
FROM
,))
self
.
add_watcher
(
self
.
_on_path_delete
,
"/"
,(
REMOVED
,
MOVED_
SRC
,))
self
.
_path_info
=
PathMap
()
self
.
_path_info
=
PathMap
()
self
.
_poll_thread
=
threading
.
Thread
(
target
=
self
.
_poll_for_changes
)
self
.
_poll_thread
=
threading
.
Thread
(
target
=
self
.
_poll_for_changes
)
self
.
_poll_cond
=
threading
.
Condition
()
self
.
_poll_cond
=
threading
.
Condition
()
...
@@ -364,35 +377,40 @@ class PollingWatchableFS(WatchableFS):
...
@@ -364,35 +377,40 @@ class PollingWatchableFS(WatchableFS):
pass
pass
def
_on_path_delete
(
self
,
event
):
def
_on_path_delete
(
self
,
event
):
print
"DELETE"
,
event
.
path
self
.
_path_info
.
clear
(
event
.
path
)
self
.
_path_info
.
clear
(
event
.
path
)
def
_poll_for_changes
(
self
):
def
_poll_for_changes
(
self
):
while
not
self
.
_poll_close_event
.
isSet
():
try
:
# Walk all directories looking for changes.
while
not
self
.
_poll_close_event
.
isSet
():
# Come back to any that give us an error.
# Walk all directories looking for changes.
error_paths
=
set
()
# Come back to any that give us an error.
for
dirnm
in
self
.
wrapped_fs
.
walkdirs
():
error_paths
=
set
()
if
self
.
_poll_close_event
.
isSet
():
for
dirnm
in
self
.
wrapped_fs
.
walkdirs
():
break
if
self
.
_poll_close_event
.
isSet
():
try
:
break
self
.
_check_for_changes
(
dirnm
)
except
FSError
:
error_paths
.
add
(
dirnm
)
# Retry the directories that gave us an error, until
# we have successfully updated them all
while
error_paths
and
not
self
.
_poll_close_event
.
isSet
():
dirnm
=
error_paths
.
pop
()
if
self
.
wrapped_fs
.
isdir
(
dirnm
):
try
:
try
:
self
.
_check_for_changes
(
dirnm
)
self
.
_check_for_changes
(
dirnm
)
except
FSError
:
except
FSError
:
error_paths
.
add
(
dirnm
)
error_paths
.
add
(
dirnm
)
# Notify that we have completed a polling run
# Retry the directories that gave us an error, until
self
.
_poll_cond
.
acquire
()
# we have successfully updated them all
self
.
_poll_cond
.
notifyAll
()
while
error_paths
and
not
self
.
_poll_close_event
.
isSet
():
self
.
_poll_cond
.
release
()
dirnm
=
error_paths
.
pop
()
# Sleep for the specified interval, or until closed.
if
self
.
wrapped_fs
.
isdir
(
dirnm
):
self
.
_poll_close_event
.
wait
(
timeout
=
self
.
poll_interval
)
try
:
self
.
_check_for_changes
(
dirnm
)
except
FSError
:
error_paths
.
add
(
dirnm
)
# Notify that we have completed a polling run
self
.
_poll_cond
.
acquire
()
self
.
_poll_cond
.
notifyAll
()
self
.
_poll_cond
.
release
()
# Sleep for the specified interval, or until closed.
self
.
_poll_close_event
.
wait
(
timeout
=
self
.
poll_interval
)
except
FSError
:
if
not
self
.
closed
:
raise
def
_check_for_changes
(
self
,
dirnm
):
def
_check_for_changes
(
self
,
dirnm
):
# Check the metadata for the directory itself.
# Check the metadata for the directory itself.
...
@@ -445,6 +463,7 @@ class PollingWatchableFS(WatchableFS):
...
@@ -445,6 +463,7 @@ class PollingWatchableFS(WatchableFS):
return
return
cpath
=
pathjoin
(
dirnm
,
childnm
)
cpath
=
pathjoin
(
dirnm
,
childnm
)
if
not
self
.
wrapped_fs
.
exists
(
cpath
):
if
not
self
.
wrapped_fs
.
exists
(
cpath
):
print
"REMOVED"
,
cpath
self
.
notify_watchers
(
REMOVED
,
cpath
)
self
.
notify_watchers
(
REMOVED
,
cpath
)
...
@@ -456,14 +475,15 @@ def ensure_watchable(fs,wrapper_class=PollingWatchableFS,*args,**kwds):
...
@@ -456,14 +475,15 @@ def ensure_watchable(fs,wrapper_class=PollingWatchableFS,*args,**kwds):
for watcher callbacks. This may be the original object if it supports them
for watcher callbacks. This may be the original object if it supports them
natively, or a wrapper class if they must be simulated.
natively, or a wrapper class if they must be simulated.
"""
"""
w
=
lambda
e
:
None
try
:
try
:
fs
.
add_watcher
(
"/"
,
w
)
w
=
fs
.
add_watcher
(
lambda
e
:
None
,
"/somepaththatsnotlikelytoexist"
)
except
(
AttributeError
,
UnsupportedError
):
except
(
AttributeError
,
UnsupportedError
):
return
wrapper_class
(
fs
,
*
args
,
**
kwds
)
return
wrapper_class
(
fs
,
*
args
,
**
kwds
)
except
FSError
:
pass
else
:
else
:
fs
.
del_watcher
(
w
)
fs
.
del_watcher
(
w
)
return
fs
return
fs
class
iter_changes
(
object
):
class
iter_changes
(
object
):
...
@@ -477,7 +497,7 @@ class iter_changes(object):
...
@@ -477,7 +497,7 @@ class iter_changes(object):
def
__init__
(
self
,
fs
=
None
,
path
=
"/"
,
events
=
None
,
**
kwds
):
def
__init__
(
self
,
fs
=
None
,
path
=
"/"
,
events
=
None
,
**
kwds
):
self
.
closed
=
False
self
.
closed
=
False
self
.
_queue
=
Queue
()
self
.
_queue
=
Queue
.
Queue
()
self
.
_watching
=
set
()
self
.
_watching
=
set
()
if
fs
is
not
None
:
if
fs
is
not
None
:
self
.
add_watcher
(
fs
,
path
,
events
,
**
kwds
)
self
.
add_watcher
(
fs
,
path
,
events
,
**
kwds
)
...
@@ -488,10 +508,13 @@ class iter_changes(object):
...
@@ -488,10 +508,13 @@ class iter_changes(object):
def
__del__
(
self
):
def
__del__
(
self
):
self
.
close
()
self
.
close
()
def
next
(
self
):
def
next
(
self
,
timeout
=
None
):
if
not
self
.
_watching
:
if
not
self
.
_watching
:
raise
StopIteration
raise
StopIteration
event
=
self
.
_queue
.
get
()
try
:
event
=
self
.
_queue
.
get
(
timeout
=
timeout
)
except
Queue
.
Empty
:
raise
StopIteration
if
event
is
None
:
if
event
is
None
:
raise
StopIteration
raise
StopIteration
if
isinstance
(
event
,
CLOSED
):
if
isinstance
(
event
,
CLOSED
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment