a90b7443e4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
"""Registra un vault en la app de escritorio Obsidian.
|
|
|
|
Opera sobre el archivo de configuracion de la app (~/.config/obsidian/obsidian.json),
|
|
NO sobre el sistema de archivos del vault. Anade (o actualiza) la entrada del vault
|
|
en la clave "vaults" de ese JSON para que Obsidian lo conozca en su lista de vaults.
|
|
|
|
Funcion impura: lee y escribe el archivo de configuracion de la app y crea un
|
|
backup .bak antes de sobreescribir. Idempotente: si ya existe una entrada con ese
|
|
path no la duplica, solo actualiza su flag "open" si difiere.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import secrets
|
|
import shutil
|
|
import time
|
|
|
|
|
|
def _default_config_path() -> str:
|
|
"""Ruta por defecto del obsidian.json de la app de escritorio Obsidian."""
|
|
return os.path.expanduser("~/.config/obsidian/obsidian.json")
|
|
|
|
|
|
def _load_config(config_path: str) -> dict:
|
|
"""Carga obsidian.json. Si no existe devuelve la estructura vacia base."""
|
|
if not os.path.exists(config_path):
|
|
return {"vaults": {}}
|
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
if not isinstance(data, dict):
|
|
raise ValueError(f"obsidian config is not a JSON object: {config_path}")
|
|
if "vaults" not in data or not isinstance(data.get("vaults"), dict):
|
|
data["vaults"] = {}
|
|
return data
|
|
|
|
|
|
def _save_config(config_path: str, data: dict) -> str:
|
|
"""Escribe obsidian.json haciendo backup .bak previo si ya existia.
|
|
|
|
Crea los directorios padre que falten. Devuelve la ruta del backup creado
|
|
(cadena vacia si no habia archivo previo que respaldar).
|
|
"""
|
|
parent = os.path.dirname(config_path)
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
|
|
backup_path = ""
|
|
if os.path.exists(config_path):
|
|
backup_path = config_path + ".bak"
|
|
shutil.copy2(config_path, backup_path)
|
|
|
|
with open(config_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
return backup_path
|
|
|
|
|
|
def register_obsidian_vault(
|
|
vault_path: str,
|
|
open: bool = False,
|
|
config_path: str = "",
|
|
) -> dict:
|
|
"""Registra un vault en la lista de vaults de la app Obsidian.
|
|
|
|
Args:
|
|
vault_path: ruta al directorio del vault. Se normaliza a ruta absoluta.
|
|
open: valor del flag "open" de la entrada del vault (si Obsidian deberia
|
|
abrirlo al arrancar). Por defecto False.
|
|
config_path: ruta al obsidian.json de la app. Vacio -> default
|
|
~/.config/obsidian/obsidian.json.
|
|
|
|
Returns:
|
|
dict con:
|
|
- id: id hex de 16 chars de la entrada (existente o recien creada).
|
|
- path: ruta absoluta normalizada del vault.
|
|
- registered: True si se creo una entrada nueva.
|
|
- already: True si ya existia una entrada con ese path.
|
|
- open: flag "open" final de la entrada.
|
|
- config_path: ruta del obsidian.json usado.
|
|
- backup_path: ruta del backup .bak escrito (vacio si no habia archivo previo).
|
|
|
|
Raises:
|
|
ValueError: si el obsidian.json existente no es un objeto JSON valido.
|
|
OSError: si la lectura/escritura del archivo falla por I/O.
|
|
"""
|
|
cfg_path = config_path or _default_config_path()
|
|
abs_path = os.path.abspath(os.path.expanduser(vault_path))
|
|
|
|
data = _load_config(cfg_path)
|
|
vaults = data["vaults"]
|
|
|
|
# Idempotencia: buscar entrada existente por path.
|
|
existing_id = None
|
|
for vid, entry in vaults.items():
|
|
if isinstance(entry, dict) and entry.get("path") == abs_path:
|
|
existing_id = vid
|
|
break
|
|
|
|
if existing_id is not None:
|
|
entry = vaults[existing_id]
|
|
changed = entry.get("open") != open
|
|
if changed:
|
|
entry["open"] = open
|
|
backup_path = _save_config(cfg_path, data) if changed else ""
|
|
return {
|
|
"id": existing_id,
|
|
"path": abs_path,
|
|
"registered": False,
|
|
"already": True,
|
|
"open": entry.get("open", open),
|
|
"config_path": cfg_path,
|
|
"backup_path": backup_path,
|
|
}
|
|
|
|
# Entrada nueva.
|
|
new_id = secrets.token_hex(8)
|
|
while new_id in vaults:
|
|
new_id = secrets.token_hex(8)
|
|
vaults[new_id] = {
|
|
"path": abs_path,
|
|
"ts": int(time.time() * 1000),
|
|
"open": open,
|
|
}
|
|
backup_path = _save_config(cfg_path, data)
|
|
return {
|
|
"id": new_id,
|
|
"path": abs_path,
|
|
"registered": True,
|
|
"already": False,
|
|
"open": open,
|
|
"config_path": cfg_path,
|
|
"backup_path": backup_path,
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import tempfile
|
|
|
|
tmp = tempfile.mkdtemp()
|
|
cfg = os.path.join(tmp, "obsidian.json")
|
|
vault = os.path.join(tmp, "MiVault")
|
|
os.makedirs(vault, exist_ok=True)
|
|
|
|
r1 = register_obsidian_vault(vault, open=True, config_path=cfg)
|
|
assert r1["registered"] is True and r1["already"] is False, r1
|
|
assert r1["open"] is True, r1
|
|
assert len(r1["id"]) == 16, r1
|
|
|
|
r2 = register_obsidian_vault(vault, open=True, config_path=cfg)
|
|
assert r2["already"] is True and r2["registered"] is False, r2
|
|
assert r2["id"] == r1["id"], (r1, r2)
|
|
assert os.path.isfile(cfg + ".bak") is False or True # backup solo si cambio
|
|
|
|
print("register_obsidian_vault smoke OK")
|