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):
|
Rules (confirmed with Ryan 2026-09-15):
|
||||||
|
|
||||||
FORWARD (/vault -> /data):
|
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.
|
- 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.
|
- delete of an ID folder in /vault -> do NOT delete /data's copy.
|
||||||
Instead: move the /data folder into the NEAREST existing `.09` archive
|
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):
|
def handle_journal_file_created(path):
|
||||||
"""
|
"""
|
||||||
A new dated journal entry (YYYY-MM-DD.md) appeared under R.16.11 --
|
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):
|
def on_moved(self, event):
|
||||||
if not event.is_directory:
|
if not event.is_directory:
|
||||||
|
_event_queue.put(("vault_file_moved", event.src_path, event.dest_path))
|
||||||
return
|
return
|
||||||
_event_queue.put(("vault_moved", event.src_path, event.dest_path))
|
_event_queue.put(("vault_moved", event.src_path, event.dest_path))
|
||||||
|
|
||||||
@@ -728,7 +775,11 @@ def _worker_loop():
|
|||||||
elif kind == "vault_file_created":
|
elif kind == "vault_file_created":
|
||||||
_, path = item
|
_, path = item
|
||||||
time.sleep(0.5) # let the writer (Obsidian/editor) finish writing content
|
time.sleep(0.5) # let the writer (Obsidian/editor) finish writing content
|
||||||
|
handle_vault_binary_created(path)
|
||||||
handle_journal_file_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":
|
elif kind == "vault_moved":
|
||||||
_, src_path, dest_path = item
|
_, src_path, dest_path = item
|
||||||
handle_vault_moved(src_path, dest_path)
|
handle_vault_moved(src_path, dest_path)
|
||||||
@@ -797,7 +848,8 @@ def reconcile():
|
|||||||
handle_data_created(dirpath)
|
handle_data_created(dirpath)
|
||||||
data_count += 1
|
data_count += 1
|
||||||
vault_count = 0
|
vault_count = 0
|
||||||
for dirpath, dirnames, _ in os.walk(VAULT):
|
vault_file_count = 0
|
||||||
|
for dirpath, dirnames, filenames in os.walk(VAULT):
|
||||||
dirnames.sort()
|
dirnames.sort()
|
||||||
if dirpath == VAULT:
|
if dirpath == VAULT:
|
||||||
continue
|
continue
|
||||||
@@ -807,8 +859,14 @@ def reconcile():
|
|||||||
with _lock:
|
with _lock:
|
||||||
handle_vault_created(dirpath)
|
handle_vault_created(dirpath)
|
||||||
vault_count += 1
|
vault_count += 1
|
||||||
log(f"reconcile: swept {data_count} /data dirs, {vault_count} /vault dirs "
|
for fname in filenames:
|
||||||
f"in {time.time() - t0:.1f}s")
|
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():
|
def _reconcile_loop():
|
||||||
|
|||||||
Reference in New Issue
Block a user