1. JanKanis
  2. python-inotify

Commits

JanKanis  committed a3efbf1 Draft

Some more bugfixes and first testfixes

- Check for IOError instead of OSError in pathresolver.get_symlinkmax -
- Improve state handling of _Watch object when dealing with nonresolving
paths and with updates
- Some other small fixes and a partial fix of some testcases

  • Participants
  • Parent commits fa63fef
  • Branches pathwatcher

Comments (0)

Files changed (3)

File inotify/pathresolver.py

View file
 
       try:
         open(tempdir+'/'+name).close()
-      except OSError as e:
+      except IOError as e:
         if e.errno == errno.ELOOP:
           _symlinkmax = i - 1
           break

File inotify/pathwatcher.py

View file
         operation.
 
         '''
-        pth = PosixPath(path)
-        if pth in self._paths:
+        path = PosixPath(path)
+        if path in self._paths:
             self._paths[path].update(mask=mask, remember_curdir=remember_curdir)
             return
         self._paths[path] = _Watch(self, path, mask, remember_curdir)
         self.path = PosixPath(path)
         self.mask = mask
         self.links = []
-        self.watch_complete = False
+        # watch_complete values:
+        # 0: reconnect needed
+        # 1: no reconnect needed, but the final target is not being
+        # watched (e.g. due to symlink loops)
+        # 2: The path is fully resolved and the final target is being
+        # watched.
+        self.watch_complete = 0
         self._update_curdir(True if remember_curdir is None else remember_curdir)
         self.reconnect()
 
-    def _update_curdir(remember_curdir):
+    def _update_curdir(self, remember_curdir):
         if remember_curdir is True:
             self.cwd = PosixPath.cwd()
         elif remember_curdir is False:
             self.cwd = _Watch.curdir
          
     def reconnect(self):
-        assert not self.watch_complete
+        assert self.watch_complete == 0
         path = self.cwd
         rest = self.path
+        linkcount = [0]
         if self.links:
-            path = self.links[-1].path
-            rest = self.links[-1].rest
+            path = PosixPath(self.links[-1].path)
+            rest = PosixPath(self.links[-1].rest)
+            linkcount[0] = self.links[-1].linkcount
 
-        linkcount = [0]
         symlinkmax = pathresolver.get_symlinkmax()
         try:
-            for path, rest in pathresolver.resolve_symlink(path, rest, set(), {}, linkcount):
+            pathsiter = pathresolver.resolve_symlink(path, rest, set(), {}, linkcount)
+            if self.links:
+                # The first yielded path pair is the one the last link is watching.
+                next(pathsiter)
+            for path, rest in pathsiter:
                 if linkcount[0] > symlinkmax:
                     raise pathresolver.SymlinkLoopError(str(self.path))
-                if path == _Watch.curdir:
+                if rest == _Watch.curdir:
                     break
-                self.add_path_element(path, rest)
+                self.add_path_element(path, rest, linkcount[0])
         except OSError as e:
             if e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT, errno.ELOOP):
                 # Basically any kind of path fault. Mark the reconnect as
                 # completed. If this was caused by concurrent filesystem
                 # modifications this will be picked up in an inotify event.
-                self.watch_complete = True
+                self.watch_complete = 1
                 return
             else:
                 raise
                 
-        assert path == _Watch.curdir or linkcount[0] > symlinkmax
+        assert rest == _Watch.curdir
         self.add_leaf(path)
-        self.watch_complete = True
+        self.watch_complete = 2
 
-    def add_leaf(self, pth):
-        mask = self.mask
-        self.links.append(_Link(len(self.links), 'leaf', self, mask, pth, None))
-        self.complete_watch = True
+    def add_leaf(self, path):
+        self.links.append(_Link(len(self.links), self, self.mask, path, None, _Watch.curdir, None))
 
-    def add_path_element(self, path, rest):
+    def add_path_element(self, path, rest, linkcount):
         assert rest != _Watch.curdir
         mask = IN_UNMOUNT | IN_ONLYDIR | IN_EXCL_UNLINK
         if rest.parts[0] == '..':
         else:
             mask |= IN_MOVE | IN_DELETE | IN_CREATE
             name = rest.parts[0]
-        self.links.append(_Link(len(self.links), self, mask, path, name, rest))
+        self.links.append(_Link(len(self.links), self, mask, path, name, rest, linkcount))
         
     _eventmap = {IN_MOVE | IN_MOVE_SELF: IN_PATH_MOVED,
                  IN_DELETE | IN_DELETE_SELF: IN_PATH_DELETE,
                  IN_UNMOUNT: IN_PATH_UNMOUNT,
                 }
     def handle_event(self, event, link):
-        if self.watch_complete and link.idx == len(self.links) - 1:
+        if self.watch_complete == 2 and link.idx == len(self.links) - 1:
             assert event.mask & self.mask
             yield Event(event, str(self.path))
         else:
             i = link.idx
             if event.mask & (IN_MOVE | IN_DELETE | IN_CREATE):
                 i += 1
+            if i >= len(self.links):
+                return
             for p in self.links[i:]:
                 p.remove()
             del self.links[i:]
-            self.watch_complete = False
+            self.watch_complete = 0
             self.watcher._reconnect_required(self)
             name = str(link.path[link.rest[0:1]])
             for m, t in _Watch._eventmap.items():
     def _queue_overflow(self):
         for p in self.links[1:]:
             p.remove()
-        self.watch_complete = False
+        self.watch_complete = 0
         self.watcher._reconnect_required(self)
 
     def update(self, newmask=0, remember_curdir=None):
             self.mask &= newmask
         else:
             self.mask = newmask
-        if self.watch_complete:
+        if self.watch_complete == 2:
             oldlink = self.links.pop()
-            self.links.append(_Link(len(self.links), self, self.mask,
-                                    oldlink.path, None, oldlink.rest))
+            self.add_leaf(oldlink.path)
             oldlink.remove()
 
     def remove(self):
         for p in self.links:
             p.remove()
-        self.watch_complete = False
         del self.links[:]
+        self.watch_complete = 0
 
     def __str__(self):
         return '<_Watch for {}>'.format(str(self.path))
                  'mask',
                  'path',
                  'rest',
+                 'linkcount',
                  'wd',
                 )
 
-    def __init__(self, idx, watch, mask, path, name, rest):
+    def __init__(self, idx, watch, mask, path, name, rest, linkcount):
         self.idx = idx
         self.watch = watch
         self.mask = mask
         self.path = str(path)
         self.rest = str(rest)
+        self.linkcount = linkcount
         self.wd = watch.watcher._createwatch(path, name, mask, self.handle_event)
 
     def handle_event(self, event):

File test/testpath.py

View file
 
 
 import inotify
-from inotify import newwatcher as watcher
 
 print("\nTesting inotify module from", inotify.__file__)
 
 
 # from IPython.terminal.ipapp import TerminalIPythonApp
-# from IPython.terminal.embed import embed as ipythonembed
+from IPython import embed as ipythonembed
 # ipapp = TerminalIPythonApp.instance()
 # ipapp.initialize(argv=[]) # argv=[] instructs IPython to ignore sys.argv
 
 
 @pytest.fixture
 def w():
-  return watcher.Watcher()
+  return inotify.PathWatcher()
 
 
 def test_open(w):
   mask = inotify.IN_OPEN | inotify.IN_CLOSE
   w.add('testfile', mask)
   watch = w._paths[P('testfile')]
+  import pdb; pdb.set_trace()
 
-  assert len(watch.links) == 1
+  assert len(watch.links) == 2
   assert watch.path == P('testfile')
   assert watch.watcher == w
-  st = os.stat('testfile')
-  # assert watch.inode == (st.st_dev, st.st_ino)
   assert watch.mask == mask
   link = watch.links[0]
   assert link.idx == 0
-  assert link.path == P('testfile')
-  linkmask = mask | inotify.IN_MOVE_SELF | inotify.IN_DELETE_SELF
+  assert link.path == str(P.getcwd())
+  assert link.rest == 'testfile'
+  link = watch.links[1]
+  assert link.idx == 1
+  assert link.path == str(P.getcwd()['testfile'])
+  assert link.rest == P('.')
+  linkmask = mask | inotify.IN_MOVE | inotify.IN_DELETE
   assert link.mask == linkmask
   assert link.watch == watch
   wd = link.wd