diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/indexer.py | 62 |
1 files changed, 54 insertions, 8 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py index 5e139bb..732b499 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py @@ -1165,14 +1165,16 @@ class DirectoryIndexer: # ── Internal update ─────────────────────────────────────────────────────── - def _schedule_update(self, file_path: Path, deleted: bool = False) -> None: + def _schedule_update(self, file_path: Path, deleted: bool = False, + directory: bool = False) -> None: """Called from watchdog thread — schedule debounced async update.""" if not self._loop: return key = str(file_path) - self._loop.call_soon_threadsafe(self._debounce, key, file_path, deleted) + self._loop.call_soon_threadsafe(self._debounce, key, file_path, deleted, directory) - def _debounce(self, key: str, file_path: Path, deleted: bool) -> None: + def _debounce(self, key: str, file_path: Path, deleted: bool, + directory: bool = False) -> None: old = self._pending_timers.pop(key, None) if old: old.cancel() @@ -1204,7 +1206,7 @@ class DirectoryIndexer: def fire() -> None: self._pending_timers.pop(key, None) - spawn(self._update_entry(file_path, deleted)) + spawn(self._update_entry(file_path, deleted, directory)) self._pending_timers[key] = self._loop.call_later(self.debounce_secs, fire) @@ -1218,7 +1220,36 @@ class DirectoryIndexer: if self._entry_path(root, entry) == resolved: self._index.remove_entry(entry.id) - async def _update_entry(self, file_path: Path, deleted: bool) -> None: + def _entries_in_dir(self, root: Root, dir_path: Path) -> list[str]: + """Ids of the entries at or below `dir_path`, matched on the index path + alone: the directory is gone, so there is nothing left to resolve.""" + try: + rel = dir_path.relative_to(root.path).as_posix() + except ValueError: + return [] + if rel in ("", "."): + return [] + vdir = fold(f"{root.name}/{rel}") + return [e.id for e in self._entries_under(root) + if fold(e.path) == vdir or fold(e.path).startswith(vdir + "/")] + + @staticmethod + def _dir_left_the_root(dir_path: Path) -> bool: + """ + Blocking. The directory is gone and its parent is not. + + The parent is what tells a directory that was deleted, or moved out of + the root, from a volume that went away: a vanishing volume takes the + parent with it. A parent deleted as well reports its own event, which + covers this directory too. + """ + try: + return not dir_path.exists() and dir_path.parent.is_dir() + except OSError: + return False + + async def _update_entry(self, file_path: Path, deleted: bool, + directory: bool = False) -> None: try: root = self._root_for(file_path) if root is None: @@ -1243,7 +1274,19 @@ class DirectoryIndexer: if not root.available: return - self._remove_by_path(root, file_path) + if directory: + if file_path == root.path or not await off_disk( + self.roots, self._dir_left_the_root, file_path): + return + gone = self._entries_in_dir(root, file_path) + if not gone: + return + for entry_id in gone: + self._index.remove_entry(entry_id) + log.info("Watchdog: %s left the root, %d entries removed", + file_path, len(gone)) + else: + self._remove_by_path(root, file_path) if not deleted: entry = await self._hash_or_cached(root, file_path) @@ -1288,8 +1331,11 @@ class _WatchdogHandler(FileSystemEventHandler): self._indexer._schedule_update(Path(event.src_path)) def on_deleted(self, event: FileSystemEvent): - if not event.is_directory: - self._indexer._schedule_update(Path(event.src_path), deleted=True) + # A directory moved out of the root arrives as this one event, with + # nothing for the files it held: without it they stayed indexed until + # the next reconcile, up to RECONCILE_BACKOFF_CAP later. + self._indexer._schedule_update(Path(event.src_path), deleted=True, + directory=event.is_directory) def on_moved(self, event: FileSystemEvent): if not event.is_directory: |