feat: add backup file management settings

- Added settings UI to enable/disable .bak file creation when saving files
- Created settings storage in .obsidian/obsiviewer.json to persist backup preferences
- Added API endpoints to read/write backup settings (/api/settings)
- Implemented automatic cleanup of existing .bak files when disabling backups
- Modified file save operations to respect backup setting across notes, bookmarks and graph files
- Removed existing .bak files from version control
This commit is contained in:
2025-10-31 07:35:39 -04:00
parent cbdb000d4b
commit 57da40f25f
35 changed files with 184 additions and 468 deletions
+102 -5
View File
@@ -51,11 +51,74 @@ const distDir = path.join(rootDir, 'dist');
// Centralized vault directory
const vaultDir = path.isAbsolute(CFG_VAULT_PATH) ? CFG_VAULT_PATH : path.join(rootDir, CFG_VAULT_PATH);
const settingsPath = path.join(vaultDir, '.obsidian', 'obsiviewer.json');
let enableBackups = true;
try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
enableBackups = settings.enableBackups;
} catch (error) {
// No settings file or invalid JSON, use default
}
const vaultEventClients = new Set();
// Phase 3: Advanced caching and monitoring
const metadataCache = new MetadataCache({ ttlMs: 5 * 60 * 1000, maxItems: 10_000 });
// ---------- Settings storage helpers ----------
function ensureSettingsStorage() {
const obsidianDir = path.join(vaultDir, '.obsidian');
if (!fs.existsSync(obsidianDir)) {
fs.mkdirSync(obsidianDir, { recursive: true });
}
if (!fs.existsSync(settingsPath)) {
const initial = { enableBackups: true };
try { fs.writeFileSync(settingsPath, JSON.stringify(initial, null, 2), 'utf-8'); } catch {}
}
}
function readSettings() {
ensureSettingsStorage();
try {
const raw = fs.readFileSync(settingsPath, 'utf-8');
const parsed = JSON.parse(raw);
return { enableBackups: parsed.enableBackups !== false };
} catch {
return { enableBackups: true };
}
}
function writeSettings(next) {
ensureSettingsStorage();
try {
fs.writeFileSync(settingsPath, JSON.stringify(next, null, 2), 'utf-8');
} catch {}
}
function deleteAllBakFiles(root) {
let count = 0;
const stack = [root];
while (stack.length) {
const dir = stack.pop();
let entries = [];
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
for (const de of entries) {
const full = path.join(dir, de.name);
if (de.isDirectory()) { stack.push(full); continue; }
if (!de.isFile()) continue;
if (de.name.toLowerCase().endsWith('.bak')) {
try { fs.unlinkSync(full); count++; } catch {}
}
}
}
return count;
}
// Initialize settings on startup
ensureSettingsStorage();
enableBackups = readSettings().enableBackups;
// List all folders under the vault (relative paths, forward slashes)
app.get('/api/folders/list', (req, res) => {
try {
@@ -507,6 +570,39 @@ app.use(cors({
// Exposer les fichiers de la voûte pour un accès direct si nécessaire
app.use('/vault', express.static(vaultDir));
// ---------- Settings API ----------
app.get('/api/settings', (req, res) => {
try {
const current = readSettings();
return res.json(current);
} catch (error) {
console.error('GET /api/settings error:', error);
return res.status(500).json({ error: 'Unable to read settings' });
}
});
app.put('/api/settings', express.json(), (req, res) => {
try {
const body = req.body || {};
const nextEnable = body.enableBackups !== false;
const prev = readSettings();
const next = { enableBackups: nextEnable };
writeSettings(next);
enableBackups = nextEnable;
let deleted = 0;
if (prev.enableBackups && !nextEnable) {
// On disabling backups, remove all existing .bak files
deleted = deleteAllBakFiles(vaultDir);
}
return res.json({ success: true, enableBackups, deletedBakFiles: deleted });
} catch (error) {
console.error('PUT /api/settings error:', error);
return res.status(500).json({ error: 'Unable to save settings' });
}
});
// Résolution des attachements: recherche le fichier en remontant les dossiers depuis la note, puis dans la voûte
app.get('/api/attachments/resolve', (req, res) => {
try {
@@ -1116,7 +1212,7 @@ app.put('/api/files', express.json({ limit: '10mb' }), express.text({ limit: '10
const backup = abs + '.bak';
try {
if (hasExisting) fs.copyFileSync(abs, backup);
if (hasExisting && enableBackups) fs.copyFileSync(abs, backup);
fs.writeFileSync(temp, finalContent, 'utf-8');
fs.renameSync(temp, abs);
console.log('[PUT /api/files] wrote file path=%s bytes=%d', rel, Buffer.byteLength(finalContent, 'utf-8'));
@@ -1283,7 +1379,7 @@ app.put('/api/vault/graph', (req, res) => {
// Create backup before writing
const backupPath = graphPath + '.bak';
if (fs.existsSync(graphPath)) {
if (enableBackups && fs.existsSync(graphPath)) {
fs.copyFileSync(graphPath, backupPath);
}
@@ -1341,7 +1437,7 @@ app.put('/api/vault/bookmarks', (req, res) => {
// Create backup before writing
const backupPath = bookmarksPath + '.bak';
if (fs.existsSync(bookmarksPath)) {
if (enableBackups && fs.existsSync(bookmarksPath)) {
fs.copyFileSync(bookmarksPath, backupPath);
}
@@ -1469,8 +1565,9 @@ app.put(/^\/api\/notes\/(.+?)\/tags$/, express.json(), async (req, res) => {
const backupPath = absolutePath + '.bak';
try {
// Create backup
fs.copyFileSync(absolutePath, backupPath);
if (enableBackups) {
fs.copyFileSync(absolutePath, backupPath);
}
// Write to temp file
fs.writeFileSync(tempPath, updatedContent, 'utf-8');