diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/indexer.py | 66 |
1 files changed, 54 insertions, 12 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py index eea5b4f..33e7210 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py @@ -609,8 +609,7 @@ class DirectoryIndexer: for root, available in changed: if available: log.info("Root %r is back — rescanning", root.name) - self._drop_root_entries(root) - await self._scan_root(root) + await self._rescan_root(root) touched = True else: # Frozen: entries stay, marked unavailable to members through @@ -711,17 +710,61 @@ class DirectoryIndexer: return [e for e in self._index.entries if fold(e.path).split("/", 1)[0] == prefix] + # Everything on an IndexEntry that a scan does not produce. `_scan_root` + # fills id/name/path/size/type/added_at/hash_version from the file itself; + # every field below was derived by one of the enrichment passes and is + # nowhere on disk to be read back. + _ENRICHED_FIELDS = ( + "duration", "thumb_hash", "width", "height", + "display_title", "season", "episode", + "artist", "album", "track_no", "taken_at", "camera", + "uploader_id", "uploader_pk", + ) + + async def _rescan_root(self, root: Root) -> int: + """ + Rebuild one root's entries from disk, keeping what the files still say. + + The two callers — `reconcile` when a root reappears, `plug_root` when + the operator plugs one back in — have to re-walk: the drive may have + changed while it was away. What they must not do is throw away the + enrichment. An entry's id is its content hash, so an entry that comes + back under the same id, name and path is the same bytes in the same + place, and every field the Videos, Music and Photos passes derived from + it still holds. Re-deriving them means minutes of tag reads, ffprobe + runs and rate-limited metadata lookups during which the operator's + library sits empty — which is exactly what a replug looked like. + + Anything that does *not* match is left bare on purpose: a different id + is different content, and a different name or path can change the + filename and folder fallbacks that `display_title`, `track_no`, + `artist` and `album` fall back to. Those are the entries + `daemon._broadcast_index_change` re-enriches, off `rescanned_ids`. + """ + carried = {(e.id, e.name, e.path): e for e in self._entries_under(root)} + self._drop_root_entries(root) + count = await self._scan_root(root) + for entry in self._entries_under(root): + old = carried.get((entry.id, entry.name, entry.path)) + if old is None: + continue + for field in self._ENRICHED_FIELDS: + setattr(entry, field, getattr(old, field)) + # It came back intact, so it is not one of the entries the daemon + # needs to enrich again. + self.rescanned_ids.discard(entry.id) + return count + def _drop_root_entries(self, root: Root) -> None: """ - Throw away a root's entries, always in order to rescan it. + Throw away a root's entries. Only ever called to rebuild them. - Both callers — `reconcile` when a root reappears, `plug_root` when the - operator plugs one back in — rebuild immediately, so nothing outside - ever observes the gap: no deletion is broadcast, and the entries that - come back have the same content-hash ids they had before. What they do - not have is anything enrichment put on them, which is why the ids are - recorded for `daemon._broadcast_index_change` to re-enrich rather than - simply forgotten. + The ids are recorded because nothing outside can otherwise tell they + were rebuilt: no deletion is broadcast (the rescan is immediate) and + the entries come back under the same content-hash ids, so a diff + against the last broadcast reports neither an addition nor a deletion. + `_rescan_root` clears the ones it managed to carry over intact; what is + left is genuinely new to the apps and is re-enriched by the daemon. """ for entry in self._entries_under(root): self.rescanned_ids.add(entry.id) @@ -794,8 +837,7 @@ class DirectoryIndexer: root.available = root.is_live() if root.available: log.info("Root %r plugged — rescanning", root.name) - self._drop_root_entries(root) - await self._scan_root(root) + await self._rescan_root(root) self._restart_observer() self._index.roots = self.roots.describe() self._index.version = int(time.time()) |