diff options
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/daemon.py | 18 | ||||
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/indexer/indexer.py | 25 |
2 files changed, 43 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/daemon.py b/packages/meshbay-node/src/meshbay_node/daemon.py index 74fff4c..f9e992c 100644 --- a/packages/meshbay-node/src/meshbay_node/daemon.py +++ b/packages/meshbay-node/src/meshbay_node/daemon.py @@ -1172,6 +1172,24 @@ class NodeDaemon: # this broadcast — enrichment fields arrive later as their own # INDEX_DELTA update (_on_enriched below). new_entries = delta.additions if delta is not None else list(idx.entries) + + # A root that was ejected and plugged back in, or that fell off and + # re-mounted, has had its entries thrown away and rebuilt from disk + # (`indexer._drop_root_entries`). The rebuilt entry has the same + # content-hash id and none of the enrichment fields, so the diff above + # reports neither an addition nor a deletion — and `_enriched_attempted` + # still says "done" for a file whose album and cover no longer exist. + # Found live: a Music library came back with its files and without its + # albums, and stayed that way, because only a restart (which starts + # with no snapshot, making every entry an addition) could clear either + # gate. Treated here as what it is — those entries are new again. + rebuilt_ids = indexer.drain_rescanned_ids() + if rebuilt_ids: + rebuilt = [e for e in idx.entries if e.id in rebuilt_ids] + for entry in rebuilt: + self._enriched_attempted.discard((group_id, entry.id)) + seen = {e.id for e in new_entries} + new_entries = new_entries + [e for e in rebuilt if e.id not in seen] asyncio.ensure_future(self._enrich_new_video_entries(indexer, new_entries)) # Music app (docs/musicbay.md §6): same shape, gated on audio_root # exactly like video_root above (added later — musicbay.md's diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py index f7ffdca..eea5b4f 100644 --- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py +++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py @@ -314,6 +314,14 @@ class DirectoryIndexer: # stay up for exactly as long as the slow part (hashing) is running. self._burst_inflight = 0 self._burst_sizes: dict[str, int] = {} + # Ids whose entry this indexer threw away and rebuilt from disk, since + # the last time a consumer drained this. A rebuilt entry carries only + # what `_hash_or_cached` fills in — every enrichment field the Videos, + # Music and Photos apps put there is gone — but its id is the file's + # content hash, so a diff against the last broadcast sees no addition + # and no deletion and nothing downstream can tell the fields were + # wiped. See `_drop_root_entries`. + self.rescanned_ids: set[str] = set() @property def index(self) -> GroupIndex: @@ -704,9 +712,26 @@ class DirectoryIndexer: if fold(e.path).split("/", 1)[0] == prefix] def _drop_root_entries(self, root: Root) -> None: + """ + Throw away a root's entries, always in order to rescan it. + + 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. + """ for entry in self._entries_under(root): + self.rescanned_ids.add(entry.id) self._index.remove_entry(entry.id) + def drain_rescanned_ids(self) -> set[str]: + """Take the ids rebuilt since the last call; leave the set empty.""" + drained, self.rescanned_ids = self.rescanned_ids, set() + return drained + @staticmethod def _entry_path(root: Root, entry: IndexEntry) -> Path | None: _, _, tail = entry.path.partition("/") |