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:
Bruno Charest 2025-10-31 07:35:39 -04:00
parent cbdb000d4b
commit 57da40f25f
35 changed files with 184 additions and 468 deletions

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
if (enableBackups) {
fs.copyFileSync(absolutePath, backupPath);
}
// Write to temp file
fs.writeFileSync(tempPath, updatedContent, 'utf-8');

View File

@ -112,6 +112,34 @@
</div>
</section>
<!-- Backups Section -->
<section class="parameters-section">
<h2 class="section-title">
<svg class="section-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
Backups (.bak files)
</h2>
<div class="setting-group">
<label class="setting-label">Enable backup files when saving</label>
<div class="toggle-switch">
<input
type="checkbox"
id="enableBackups"
[checked]="backupsEnabled()"
(change)="toggleBackups()"
class="toggle-input" />
<label for="enableBackups" class="toggle-label">
<span class="toggle-slider"></span>
</label>
</div>
<p class="setting-hint">When disabled, existing .bak files in the vault will be removed automatically.</p>
</div>
</section>
<!-- Folder Filtering Section -->
<section class="parameters-section">
<h2 class="section-title">

View File

@ -2,6 +2,7 @@ import { Component, inject, signal, effect } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ThemeService, ThemeMode, ThemeId, Language } from '../../core/services/theme.service';
import { SettingsService } from '../../services/settings.service';
import { ToastService } from '../../shared/toast/toast.service';
import { FolderFilterService, FolderFilterConfig } from '../../services/folder-filter.service';
@ -16,11 +17,13 @@ export class ParametersPage {
private themeService = inject(ThemeService);
private toastService = inject(ToastService);
private folderFilterService = inject(FolderFilterService);
private settingsService = inject(SettingsService);
// Reactive prefs
prefs = signal(this.themeService.prefsValue);
folderFilterConfig = signal<FolderFilterConfig>(this.folderFilterService.getConfig());
newExcludedFolder = '';
backupsEnabled = signal<boolean>(true);
modes: ThemeMode[] = ['system', 'light', 'dark'];
themes: ThemeId[] = ['light', 'dark', 'obsidian', 'nord', 'notion', 'github', 'discord', 'monokai'];
@ -47,6 +50,13 @@ export class ParametersPage {
return () => subscription.unsubscribe();
});
effect(() => {
const sub = this.settingsService.getSettings().subscribe(({ enableBackups }) => {
this.backupsEnabled.set(!!enableBackups);
});
return () => sub.unsubscribe();
});
}
setMode(mode: ThemeMode): void {
@ -128,4 +138,18 @@ export class ParametersPage {
this.folderFilterConfig.set(this.folderFilterService.getConfig());
this.showToast('Folder filters reset to defaults');
}
toggleBackups(): void {
const next = !this.backupsEnabled();
this.settingsService.updateBackups(next).subscribe((res) => {
this.backupsEnabled.set(res.enableBackups);
if (!res.enableBackups && (res.deletedBakFiles ?? 0) > 0) {
this.showToast(`Backups disabled. Deleted ${res.deletedBakFiles} .bak files`);
} else if (!res.enableBackups) {
this.showToast('Backups disabled');
} else {
this.showToast('Backups enabled');
}
});
}
}

View File

@ -0,0 +1,26 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map } from 'rxjs';
export interface AppSettings {
enableBackups: boolean;
}
@Injectable({ providedIn: 'root' })
export class SettingsService {
private http = inject(HttpClient);
getSettings(): Observable<AppSettings> {
return this.http.get<AppSettings>('/api/settings').pipe(
map((res) => ({ enableBackups: res?.enableBackups !== false }))
);
}
updateBackups(enableBackups: boolean): Observable<{ success: boolean; enableBackups: boolean; deletedBakFiles?: number }>
{
return this.http.put<{ success: boolean; enableBackups: boolean; deletedBakFiles?: number }>(
'/api/settings',
{ enableBackups }
);
}
}

View File

@ -1,24 +0,0 @@
{
"items": [
{
"type": "group",
"ctime": 1759433919563,
"title": "test",
"items": [
{
"type": "file",
"ctime": 1759433952208,
"path": "HOME.md",
"title": "HOME"
}
]
},
{
"type": "file",
"ctime": 1759434060575,
"path": "test.md",
"title": "Page de test Markdown"
}
],
"rev": "9mgl-40"
}

View File

@ -1,22 +0,0 @@
{
"collapse-filter": false,
"search": "",
"showTags": false,
"showAttachments": false,
"hideUnresolved": false,
"showOrphans": true,
"collapse-color-groups": false,
"colorGroups": [],
"collapse-display": false,
"showArrow": false,
"textFadeMultiplier": 0,
"nodeSizeMultiplier": 1,
"lineSizeMultiplier": 1,
"collapse-forces": false,
"centerStrength": 0.3,
"repelStrength": 17,
"linkStrength": 0.5,
"linkDistance": 200,
"scale": 1,
"close": false
}

3
vault/.obsidian/obsiviewer.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"enableBackups": false
}

View File

@ -178,10 +178,10 @@
},
"active": "8309e5042a8fb85c",
"lastOpenFiles": [
"dessin.excalidraw.md",
"Dessin-02.png",
"mixe/Relaxing Music relax music music _hls-480_.mp4",
"Dessin-02.excalidraw.md",
"dessin.excalidraw.md",
"Drawing-20251028-1452.excalidraw.md",
"Dessin-02.excalidraw.md.tmp",
"Dessin-02.excalidraw.md.bak",

View File

@ -1,25 +0,0 @@
---
titre: archived-note
auteur: Bruno Charest
creation_date: 2025-10-19T11:13:12-04:00
modification_date: 2025-10-19T12:09:46-04:00
catégorie: ""
tags:
- bruno
- configuration
aliases: []
status: en-cours
publish: true
favoris: true
template: true
task: true
archive: false
draft: true
private: true
---
# Archived Note
#bruno
This note was archived and moved to trash.

View File

@ -1 +0,0 @@
nouveau message !!!

View File

@ -1,23 +0,0 @@
---
titre: old-note-2
auteur: Bruno Charest
creation_date: 2025-10-19T11:13:12-04:00
modification_date: 2025-10-19T12:09:46-04:00
catégorie: ""
aliases: []
status: en-cours
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
tags:
- configuration
- bruno
- markdown
---
# Old Note 2
This note is in a subfolder of trash.

View File

@ -1,3 +0,0 @@
# Old Note 3
Another note in the old-folder subfolder.

View File

@ -1,22 +0,0 @@
---
titre: old
auteur: Bruno Charest
creation_date: 2025-10-19T12:28:31-04:00
modification_date: 2025-10-19T12:28:31-04:00
catégorie: ""
tags: []
aliases: []
status: en-cours
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---
# Vieux fichier
## Section 1

View File

@ -1,3 +0,0 @@
# Deleted Note 1
This is a test note in trash.

View File

@ -1,3 +0,0 @@
# Deleted Note 1
This is a test note in trash.

View File

@ -1,56 +0,0 @@
---
titre: "test-add-properties"
auteur: "Bruno Charest"
creation_date: "2025-10-23T13:11:50-04:00"
modification_date: "2025-10-23T13:11:50-04:00"
aliases: [""]
status: "en-cours"
publish: false
favoris: true
template: false
task: true
archive: false
draft: false
private: true
color: "#22C55E"
tags:
- tag1
- accueil
- tag3
---
Allo ceci est un tests
toto
# Test 1 Markdown
## Titres
# Niveau 1
#tag1 #tag2 #test #test2
# Nouveau-markdown
## sous-titre
- [ ] allo
- [ ] toto
- [ ] tata
## sous-titre 2
#tag1 #tag2 #tag3 #tag4
## sous-titre 3
## sous-titre 4
## sous-titre 5
test
## sous-titre 6
test
## sous-titre 7
test
## sous-titre 8

View File

@ -1,19 +0,0 @@
---
titre: test-new-file
auteur: Bruno Charest
creation_date: 2025-10-19T12:15:21-04:00
modification_date: 2025-10-19T12:15:21-04:00
catégorie: ""
aliases: []
status: en-cours
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
tags:
- home
- accueil
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 3"
auteur: "Bruno Charest"
creation_date: "2025-10-24T15:44:07.120Z"
modification_date: "2025-10-24T15:44:07.120Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 16"
auteur: "Bruno Charest"
creation_date: "2025-10-27T18:01:11.627Z"
modification_date: "2025-10-27T18:01:11.627Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,16 +0,0 @@
---
titre: "Test Note"
auteur: "Bruno Charest"
creation_date: "2025-10-23"
modification_date: "2025-10-23"
status: "draft"
publish: false
favoris: false
template: false
task: false
archive: false
draft: true
private: false
---
Contenu de test

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 11"
auteur: "Bruno Charest"
creation_date: "2025-10-26T14:55:56.550Z"
modification_date: "2025-10-26T14:55:56.550Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 12"
auteur: "Bruno Charest"
creation_date: "2025-10-26T14:55:57.870Z"
modification_date: "2025-10-26T14:55:57.870Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 7"
auteur: "Bruno Charest"
creation_date: "2025-10-26T13:17:42.008Z"
modification_date: "2025-10-26T13:17:42.008Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 8"
auteur: "Bruno Charest"
creation_date: "2025-10-26T14:55:08.540Z"
modification_date: "2025-10-26T14:55:08.540Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 9"
auteur: "Bruno Charest"
creation_date: "2025-10-26T14:55:54.071Z"
modification_date: "2025-10-26T14:55:54.071Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note"
auteur: "Bruno Charest"
creation_date: "2025-10-26T13:14:53.395Z"
modification_date: "2025-10-26T13:14:53.395Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File

@ -1,52 +0,0 @@
---
titre: "test-add-properties"
auteur: "Bruno Charest"
creation_date: "2025-10-23T13:11:50-04:00"
modification_date: "2025-10-27T15:13:08-04:00"
status: "en-cours"
publish: false
favoris: true
template: false
task: true
archive: false
draft: false
private: true
tags:
- tagtag
---
Allo ceci est un tests
toto
# Test 1 Markdown
## Titres
# Niveau 1
#tag1 #tag2 #test #test2
# Nouveau-markdown
## sous-titre
- [ ] allo
- [ ] toto
- [ ] tata
## sous-titre 2
#tag1 #tag2 #tag3 #tag4
## sous-titre 3
## sous-titre 4
## sous-titre 5
test
## sous-titre 6
test
## sous-titre 7
test
## sous-titre 8

View File

View File

@ -1,3 +0,0 @@
# Test File
This is a test file for API testing.

View File

@ -1,55 +0,0 @@
---
titre: "Nouveau-markdown"
auteur: "Bruno Charest"
creation_date: "2025-10-19T21:42:53-04:00"
modification_date: "2025-10-19T21:43:06-04:00"
status: "en-cours"
publish: true
favoris: false
template: true
task: true
archive: true
draft: true
private: true
toto: "tata"
color: "#EF4444"
---
Allo ceci est un tests
toto
# Test 1 Markdown
## Titres
# Niveau 1
#tag1 #tag2 #test #test2
# Nouveau-markdown
## sous-titre
- [ ] allo
- [ ] toto
- [ ] tata
## sous-titre 2
#tag1 #tag2 #tag3 #tag4
## sous-titre 3
## sous-titre 4
## sous-titre 5
test
## sous-titre 6
test
## sous-titre 7
test
## sous-titre 8

View File

@ -1,15 +0,0 @@
---
titre: "Nouvelle note 2"
auteur: "Bruno Charest"
creation_date: "2025-10-24T12:24:03.706Z"
modification_date: "2025-10-24T12:24:03.706Z"
status: "en-cours"
publish: false
favoris: false
template: false
task: false
archive: false
draft: false
private: false
---

View File