feat: use Nextcloud filename for indexed file title + reconcile on rename
The vector-sync pipeline derived an indexed file's display title from the document's embedded metadata (e.g. a PDF's /Title), falling back to the filename only when absent. That embedded title frequently disagrees with how the user named the file in Nextcloud and is confusing in the astrolabe vector-viz UI (a passive consumer of the `title` payload field). For files, always derive the title from the Nextcloud filename via a shared `file_title_from_path` helper. Notes/deck/news keep their metadata titles. A rename/move in Nextcloud keeps the fileid (doc_id) and content (etag/mtime) but changes the path, so both the dedup claim and the scanner freshness gate skip re-embedding and the stored file_path/title go stale. Add `reconcile_document_path`: a metadata-only set_payload that refreshes file_path + title on the existing real chunks without re-fetch/re-embed. Wire it into both skip paths: - dedup hit (etag unchanged on rename) via claim_existing_index(current_path=...) - scanner incremental skip (etag changed, mtime stable) Both reuse already-fetched payloads, so steady-state scans add no extra round-trip (reconcile is a no-op when the path is unchanged). Refs: Deck #204 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
967298ddbe
commit
bded41de5d
@@ -32,7 +32,10 @@ from nextcloud_mcp_server.vector.placeholder import (
|
||||
)
|
||||
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
|
||||
from nextcloud_mcp_server.vector.queue.ports import TaskProducer
|
||||
from nextcloud_mcp_server.vector.sharing_state import claim_existing_index
|
||||
from nextcloud_mcp_server.vector.sharing_state import (
|
||||
claim_existing_index,
|
||||
reconcile_document_path,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -466,7 +469,9 @@ async def scan_user_documents(
|
||||
# it. Eliminates the per-user reprocessing ping-pong that arises
|
||||
# because chunk point IDs are user-agnostic (note 386945 #5).
|
||||
etag = str(file_info.get("etag") or "")
|
||||
if etag and await claim_existing_index(file_id, "file", etag, user_id):
|
||||
if etag and await claim_existing_index(
|
||||
file_id, "file", etag, user_id, current_path=file_path
|
||||
):
|
||||
_potentially_deleted.pop((user_id, file_id), None)
|
||||
logger.debug(
|
||||
"Dedup: file %s (ID: %s) already indexed in tenant; "
|
||||
@@ -582,6 +587,28 @@ async def scan_user_documents(
|
||||
)
|
||||
)
|
||||
file_queued += 1
|
||||
elif existing_metadata is not None:
|
||||
# Unchanged content (not re-queued) but the file may have
|
||||
# been renamed/moved: a rename keeps the fileid while
|
||||
# changing the path, and the dedup miss here means the
|
||||
# etag changed without a modified_at bump. Refresh the
|
||||
# stale path/title metadata without re-embedding. No-op
|
||||
# when the path is unchanged.
|
||||
try:
|
||||
await reconcile_document_path(
|
||||
file_id,
|
||||
"file",
|
||||
existing_metadata.get("file_path"),
|
||||
file_path,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — non-fatal
|
||||
logger.warning(
|
||||
"Path reconcile failed for file %s (ID: %s) (%s); "
|
||||
"next scan retries",
|
||||
file_path,
|
||||
file_id,
|
||||
exc,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"[SCAN-%s] Found %s tagged PDFs for %s", scan_id, file_count, user_id
|
||||
|
||||
Reference in New Issue
Block a user