feat: add per-vault reindexing with selective reload based on modified vaults, simplify hidden files whitelist logic to include all sub-hidden files when parent is whitelisted, and auto-uncheck includeHidden when adding whitelist items

This commit is contained in:
2026-03-26 15:08:01 -04:00
parent 80e2a7fc53
commit ac962bd416
4 changed files with 146 additions and 14 deletions
+74
View File
@@ -426,6 +426,80 @@ async def reload_index() -> Dict[str, Any]:
return stats
async def reload_single_vault(vault_name: str) -> Dict[str, Any]:
"""Force a re-index of a single vault and return its statistics.
Args:
vault_name: Name of the vault to reindex.
Returns:
Dict with vault statistics (file_count, tag_count).
Raises:
ValueError: If vault_name is not found in configuration.
"""
global vault_config
# Reload vault config from env vars
vault_config.update(load_vault_config())
# Merge with saved settings
from backend.vault_settings import get_vault_setting
saved_settings = get_vault_setting(vault_name)
if vault_name not in vault_config:
raise ValueError(f"Vault '{vault_name}' not found in configuration")
config = vault_config[vault_name]
# Override with saved settings if present
if saved_settings:
if "includeHidden" in saved_settings:
config["includeHidden"] = saved_settings["includeHidden"]
if "hiddenWhitelist" in saved_settings:
config["hiddenWhitelist"] = saved_settings["hiddenWhitelist"]
# Remove old vault data from index structures
await remove_vault_from_index(vault_name)
# Re-add the vault with updated configuration
vault_path = config["path"]
loop = asyncio.get_event_loop()
vault_data = await loop.run_in_executor(None, _scan_vault, vault_name, vault_path, config)
vault_data["config"] = config
# Build lookup entries for the vault
new_lookup_entries: Dict[str, List[Dict[str, str]]] = {}
for f in vault_data["files"]:
entry = {"vault": vault_name, "path": f["path"]}
fname = f["path"].rsplit("/", 1)[-1].lower()
fpath_lower = f["path"].lower()
for key in (fname, fpath_lower):
if key not in new_lookup_entries:
new_lookup_entries[key] = []
new_lookup_entries[key].append(entry)
async_lock = _get_async_lock()
async with async_lock:
with _index_lock:
index[vault_name] = vault_data
for key, entries in new_lookup_entries.items():
if key not in _file_lookup:
_file_lookup[key] = []
_file_lookup[key].extend(entries)
path_index[vault_name] = vault_data.get("paths", [])
global _index_generation
_index_generation += 1
# Rebuild attachment index for this vault only
from backend.attachment_indexer import build_attachment_index
await build_attachment_index({vault_name: config})
stats = {"file_count": len(vault_data["files"]), "tag_count": len(vault_data["tags"])}
logger.info(f"Vault '{vault_name}' reindexed: {stats['file_count']} files, {stats['tag_count']} tags")
return stats
def get_vault_names() -> List[str]:
"""Return the list of all indexed vault names."""
return list(index.keys())
+22
View File
@@ -1198,6 +1198,28 @@ async def api_reload(current_user=Depends(require_admin)):
return {"status": "ok", "vaults": stats}
@app.get("/api/index/reload/{vault_name}")
async def api_reload_vault(vault_name: str, current_user=Depends(require_admin)):
"""Force a re-index of a single vault.
Args:
vault_name: Name of the vault to reindex.
Returns:
Dict with vault statistics.
"""
try:
from backend.indexer import reload_single_vault
stats = await reload_single_vault(vault_name)
await sse_manager.broadcast("vault_reloaded", {
"vault": vault_name,
"stats": stats,
})
return {"status": "ok", "vault": vault_name, "stats": stats}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
# ---------------------------------------------------------------------------
# SSE endpoint — Server-Sent Events stream
# ---------------------------------------------------------------------------
+14 -8
View File
@@ -5,6 +5,12 @@ from typing import Dict, Any, Tuple
def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]) -> bool:
"""Check if a path should be included based on hidden files configuration.
Simplified logic:
- If includeHidden=True: include ALL hidden files/folders recursively
- If includeHidden=False: check whitelist
- If a whitelisted folder is in the path, include ALL sub-hidden files/folders
- Otherwise, exclude the path
Args:
rel_parts: Tuple of path parts relative to vault root
vault_config: Vault configuration dict with includeHidden and hiddenWhitelist
@@ -23,14 +29,14 @@ def should_include_path(rel_parts: Tuple[str, ...], vault_config: Dict[str, Any]
return True
if include_hidden:
# Include all hidden files/folders
# Include all hidden files/folders recursively
return True
# Check if ALL hidden parts are in the whitelist
# If any hidden part is NOT in the whitelist, exclude the path
for hidden_part in hidden_parts:
if hidden_part not in hidden_whitelist:
return False
# Check if the FIRST hidden part is in the whitelist
# If yes, include all sub-hidden files/folders automatically
first_hidden = hidden_parts[0]
if first_hidden in hidden_whitelist:
return True
# All hidden parts are in the whitelist
return True
# First hidden part not in whitelist, exclude
return False