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:
parent
80e2a7fc53
commit
ac962bd416
@ -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())
|
||||
|
||||
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
@ -3231,6 +3231,9 @@
|
||||
|
||||
// --- Hidden Files Configuration ---
|
||||
|
||||
// Track which vaults have been modified
|
||||
let modifiedVaults = new Set();
|
||||
|
||||
async function loadHiddenFilesSettings() {
|
||||
const container = document.getElementById("hidden-files-vault-list");
|
||||
if (!container) return;
|
||||
@ -3290,7 +3293,7 @@
|
||||
document.createTextNode("Liste blanche")
|
||||
]);
|
||||
const whitelistHint = el("span", { class: "config-hint", style: "display:block;margin-bottom:8px" }, [
|
||||
document.createTextNode("Dossiers cachés spécifiques à afficher (ex: .obsidian, .github)")
|
||||
document.createTextNode("Dossiers cachés spécifiques à afficher (tous les sous-dossiers cachés seront inclus)")
|
||||
]);
|
||||
|
||||
const whitelistItems = el("div", { class: "hidden-files-whitelist-items", id: `whitelist-items-${vault.name}` });
|
||||
@ -3330,10 +3333,20 @@
|
||||
// Event listeners
|
||||
const addBtn = addRow.querySelector("button");
|
||||
const input = addRow.querySelector("input");
|
||||
const checkbox = toggleRow.querySelector("input[type='checkbox']");
|
||||
|
||||
addBtn.addEventListener("click", () => addWhitelistItem(vault.name));
|
||||
addBtn.addEventListener("click", () => {
|
||||
addWhitelistItem(vault.name);
|
||||
modifiedVaults.add(vault.name);
|
||||
});
|
||||
input.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter") addWhitelistItem(vault.name);
|
||||
if (e.key === "Enter") {
|
||||
addWhitelistItem(vault.name);
|
||||
modifiedVaults.add(vault.name);
|
||||
}
|
||||
});
|
||||
checkbox.addEventListener("change", () => {
|
||||
modifiedVaults.add(vault.name);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -3353,6 +3366,7 @@
|
||||
const removeBtn = item.querySelector("button");
|
||||
removeBtn.addEventListener("click", () => {
|
||||
item.remove();
|
||||
modifiedVaults.add(vaultName);
|
||||
});
|
||||
|
||||
return item;
|
||||
@ -3361,6 +3375,7 @@
|
||||
function addWhitelistItem(vaultName) {
|
||||
const input = document.getElementById(`whitelist-input-${vaultName}`);
|
||||
const container = document.getElementById(`whitelist-items-${vaultName}`);
|
||||
const includeHiddenCheckbox = document.getElementById(`hidden-include-${vaultName}`);
|
||||
|
||||
if (!input || !container) return;
|
||||
|
||||
@ -3378,6 +3393,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-uncheck "Afficher tous les fichiers cachés" when adding to whitelist
|
||||
if (includeHiddenCheckbox && includeHiddenCheckbox.checked) {
|
||||
includeHiddenCheckbox.checked = false;
|
||||
}
|
||||
|
||||
const item = createWhitelistItem(vaultName, normalizedValue);
|
||||
container.appendChild(item);
|
||||
input.value = "";
|
||||
@ -3414,6 +3434,7 @@
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
modifiedVaults.clear();
|
||||
showToast("✓ Paramètres sauvegardés", "success");
|
||||
} catch (err) {
|
||||
console.error("Failed to save hidden files settings:", err);
|
||||
@ -3434,9 +3455,18 @@
|
||||
// Update button text to show reindexing phase
|
||||
if (btn) { btn.textContent = "⏳ Réindexation..."; }
|
||||
|
||||
// Then trigger reindex with saved settings
|
||||
await api("/api/index/reload");
|
||||
showToast("✓ Réindexation terminée", "success");
|
||||
// Reindex only modified vaults if any, otherwise reindex all
|
||||
if (modifiedVaults.size > 0) {
|
||||
const vaultsToReindex = Array.from(modifiedVaults);
|
||||
for (const vaultName of vaultsToReindex) {
|
||||
await api(`/api/index/reload/${encodeURIComponent(vaultName)}`);
|
||||
}
|
||||
showToast(`✓ Réindexation terminée (${vaultsToReindex.length} vault${vaultsToReindex.length > 1 ? 's' : ''})`, "success");
|
||||
} else {
|
||||
await api("/api/index/reload");
|
||||
showToast("✓ Réindexation terminée", "success");
|
||||
}
|
||||
|
||||
loadDiagnostics();
|
||||
await Promise.all([loadVaults(), loadTags()]);
|
||||
} catch (err) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user