daemon: relocate any non-.md file that lands in /vault to matching /data path
/vault is notes-only by design; binaries saved there by hand, by an agent, or by a sync client now get moved to /data automatically instead of sitting in the wrong tree. Wired into live inotify (file create + file move) and the periodic reconcile sweep so pre-existing drift self-heals too. Leaves the vault copy in place with a warning ntfy if a same-named file already exists at the /data destination.
This commit is contained in:
+61
-3
@@ -8,6 +8,9 @@ structurally mirrored, in BOTH directions, for changes made by ANY tool
|
||||
Rules (confirmed with Ryan 2026-09-15):
|
||||
|
||||
FORWARD (/vault -> /data):
|
||||
- any non-`.md` file created or moved INTO /vault (a binary dropped or
|
||||
saved there by mistake -- /vault is notes-only by design) -> relocate
|
||||
it to the matching path under /data instead of leaving it in /vault.
|
||||
- rename/move of an ID folder in /vault -> mirror the rename in /data.
|
||||
- delete of an ID folder in /vault -> do NOT delete /data's copy.
|
||||
Instead: move the /data folder into the NEAREST existing `.09` archive
|
||||
@@ -309,6 +312,49 @@ def handle_vault_moved(src_path, dest_path):
|
||||
)
|
||||
|
||||
|
||||
def handle_vault_binary_created(path):
|
||||
"""
|
||||
/vault is notes-only by design (see vault-librarian skill). Any
|
||||
non-`.md` file that lands there -- Ryan saving a photo/PDF by hand,
|
||||
an agent writing to the wrong tree, a sync client dropping a stray
|
||||
file -- gets relocated to the matching path under /data, never left
|
||||
in /vault. Idempotent: no-op if the file is already gone (raced with
|
||||
another mover) or the /data destination already exists (name clash --
|
||||
log and leave the vault copy in place rather than silently overwriting
|
||||
or deleting real content).
|
||||
"""
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
if path.endswith(".md"):
|
||||
return
|
||||
if _in_skip(path, VAULT):
|
||||
return
|
||||
if not _debounced("vault_binary_created", path):
|
||||
return
|
||||
rel = os.path.relpath(path, VAULT)
|
||||
if rel.split(os.sep)[0] == vl.SYSTEM_META:
|
||||
return # "0 System area" has no /data counterpart at all
|
||||
data_path = os.path.join(DATA, rel)
|
||||
if os.path.exists(data_path):
|
||||
log(f"SKIP relocate (already exists in /data, leaving vault copy): {rel}")
|
||||
ntfy_post(
|
||||
"Vault sync: binary needs manual move",
|
||||
f"Non-.md file in /vault couldn't relocate -- /data already "
|
||||
f"has {rel}. Left in place, resolve by hand.",
|
||||
tags="warning",
|
||||
priority="high",
|
||||
)
|
||||
return
|
||||
os.makedirs(os.path.dirname(data_path), exist_ok=True)
|
||||
shutil.move(path, data_path)
|
||||
log(f"RELOCATED non-md file /vault -> /data: {rel}")
|
||||
ntfy_post(
|
||||
"Vault sync: moved binary out of /vault",
|
||||
f"{rel} isn't a note -- moved to /data.",
|
||||
tags="package",
|
||||
)
|
||||
|
||||
|
||||
def handle_journal_file_created(path):
|
||||
"""
|
||||
A new dated journal entry (YYYY-MM-DD.md) appeared under R.16.11 --
|
||||
@@ -689,6 +735,7 @@ class VaultHandler(FileSystemEventHandler):
|
||||
|
||||
def on_moved(self, event):
|
||||
if not event.is_directory:
|
||||
_event_queue.put(("vault_file_moved", event.src_path, event.dest_path))
|
||||
return
|
||||
_event_queue.put(("vault_moved", event.src_path, event.dest_path))
|
||||
|
||||
@@ -728,7 +775,11 @@ def _worker_loop():
|
||||
elif kind == "vault_file_created":
|
||||
_, path = item
|
||||
time.sleep(0.5) # let the writer (Obsidian/editor) finish writing content
|
||||
handle_vault_binary_created(path)
|
||||
handle_journal_file_created(path)
|
||||
elif kind == "vault_file_moved":
|
||||
_, src_path, dest_path = item
|
||||
handle_vault_binary_created(dest_path)
|
||||
elif kind == "vault_moved":
|
||||
_, src_path, dest_path = item
|
||||
handle_vault_moved(src_path, dest_path)
|
||||
@@ -797,7 +848,8 @@ def reconcile():
|
||||
handle_data_created(dirpath)
|
||||
data_count += 1
|
||||
vault_count = 0
|
||||
for dirpath, dirnames, _ in os.walk(VAULT):
|
||||
vault_file_count = 0
|
||||
for dirpath, dirnames, filenames in os.walk(VAULT):
|
||||
dirnames.sort()
|
||||
if dirpath == VAULT:
|
||||
continue
|
||||
@@ -807,8 +859,14 @@ def reconcile():
|
||||
with _lock:
|
||||
handle_vault_created(dirpath)
|
||||
vault_count += 1
|
||||
log(f"reconcile: swept {data_count} /data dirs, {vault_count} /vault dirs "
|
||||
f"in {time.time() - t0:.1f}s")
|
||||
for fname in filenames:
|
||||
if fname.endswith(".md"):
|
||||
continue
|
||||
with _lock:
|
||||
handle_vault_binary_created(os.path.join(dirpath, fname))
|
||||
vault_file_count += 1
|
||||
log(f"reconcile: swept {data_count} /data dirs, {vault_count} /vault dirs, "
|
||||
f"{vault_file_count} /vault non-md files in {time.time() - t0:.1f}s")
|
||||
|
||||
|
||||
def _reconcile_loop():
|
||||
|
||||
Reference in New Issue
Block a user