summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/indexer/indexer.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/indexer/indexer.py57
1 files changed, 54 insertions, 3 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
index 33e7210..852c061 100644
--- a/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
+++ b/packages/meshbay-node/src/meshbay_node/indexer/indexer.py
@@ -436,7 +436,7 @@ class DirectoryIndexer:
cached = await self._cache.lookup(
str(file_path), st.st_size, st.st_mtime, expected_hv)
if cached is not None:
- return IndexEntry(
+ return await self._attribute(IndexEntry(
id=cached.hash,
name=file_path.name,
path=_virtual_dir(root, file_path),
@@ -444,7 +444,7 @@ class DirectoryIndexer:
type=cached.type,
added_at=cached.added_at,
hash_version=cached.hash_version,
- )
+ ), file_path, st)
loop = asyncio.get_event_loop()
entry = await loop.run_in_executor(self._executor, _scan_file, root, file_path)
@@ -452,8 +452,51 @@ class DirectoryIndexer:
await self._cache.put(str(file_path), st.st_size, st.st_mtime,
entry.id, entry.type, entry.added_at,
entry.hash_version)
+ return await self._attribute(entry, file_path, st)
+
+ async def _attribute(self, entry: IndexEntry | None, file_path: Path,
+ st) -> IndexEntry | None:
+ """Stamp an entry with whoever sent the file, if a member did.
+
+ Here, rather than beside each `add_entry`, because this is the one
+ funnel every entry passes through: the initial scan, the watchdog,
+ reconcile and a replug all build theirs from `_hash_or_cached`.
+
+ The attribution used to be written at the end of the *upload* instead,
+ by walking the index for an entry that by construction did not exist
+ yet — the watchdog has not fired, and the `.part` the file was until the
+ rename is not indexable. It matched nothing, silently, so every uploaded
+ file was owned by nobody and `file_delete` refused everyone but the
+ operator, where MESHBAY_DESIGN.md §5.4 grants it to any non-revoked
+ device of the uploading account.
+ """
+ if entry is None or self._cache is None:
+ return entry
+ who = await self._cache.uploader(str(file_path), st.st_size, st.st_mtime)
+ if who is not None:
+ entry.uploader_id, entry.uploader_pk = who
return entry
+ async def record_upload(self, file_path: Path, user_id: str,
+ pk_ed25519: str) -> None:
+ """Remember who sent this file, for the entry that does not exist yet.
+
+ Called by the transport once the last chunk has landed and the file is
+ at its final name. Durable rather than in-memory: the index is rebuilt
+ from disk at every start, and an owner the node forgets on restart is an
+ owner who cannot delete their own file tomorrow.
+ """
+ if self._cache is None or not user_id:
+ return
+ try:
+ st = file_path.stat()
+ except OSError:
+ # Gone between the rename and here. Nothing to attribute, and
+ # nothing for anyone to delete either.
+ return
+ await self._cache.record_upload(
+ str(file_path), st.st_size, st.st_mtime, user_id, pk_ed25519 or "")
+
def _report_collisions(self) -> None:
"""
Names that are the same file on a case-insensitive filesystem.
@@ -749,7 +792,15 @@ class DirectoryIndexer:
if old is None:
continue
for field in self._ENRICHED_FIELDS:
- setattr(entry, field, getattr(old, field))
+ # What the rescan itself established wins. `uploader_id` and
+ # `uploader_pk` now come off the durable record (`_attribute`),
+ # and the entry being replaced is memory this process happens
+ # to still hold — so copying over them would let a stale blank
+ # beat the thing that survives a restart. Every other field is
+ # None on a freshly scanned entry, so for those this is exactly
+ # the carry-over it has always been.
+ if getattr(entry, field) is None:
+ 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)