Skip to content

Commit

Permalink
creating backups under uuid
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoine-lb committed Feb 12, 2025
1 parent b9c620b commit 35e9c7f
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 18 deletions.
22 changes: 20 additions & 2 deletions src/api/id.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@

import { randomBytes } from 'crypto';
export type ID = string;

export function generateId(): ID {
// Generates a v4 UUID
return globalThis.crypto.randomUUID();
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {
return globalThis.crypto.randomUUID();
} else {
// Fallback using Node's crypto module
const bytes = randomBytes(16);
// Per RFC4122 v4, set version and variant bits.
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = bytes.toString('hex');
return (
hex.substr(0, 8) + '-' +
hex.substr(8, 4) + '-' +
hex.substr(12, 4) + '-' +
hex.substr(16, 4) + '-' +
hex.substr(20, 12)
);
}
}

44 changes: 31 additions & 13 deletions src/backend/backup-scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* When restoring from backup, the persistent storage (y-indexeddb) is cleared, a new Y.Doc is created,
* the backup update is applied to it, and the y-indexeddb provider is reinitialized.
* Finally, the application reloads so that all components reinitialize using the new document.
* Finally, the application is reloaded so that all components reinitialize using the new document.
*
* This approach mimics the Dexie behavior:
* 1. Delete the persistent database.
Expand Down Expand Up @@ -37,32 +37,47 @@ function getWeekStart(): Date {
export default class BackupScheduler implements DataBackup {
#ydoc: Y.Doc;
#backupDirectory: string;
#sessionId: string;
#lastBackupIndex: number = 0;
#lastBackupDate: Date = new Date(0);

/**
* @param ydoc The Y.Doc that holds all data (files, tags, locations, searches, etc).
* @param directory The folder path where backup files are stored.
* @param directory The base folder path where backup files are stored.
* @param sessionId The unique session ID for this installation.
*/
constructor(ydoc: Y.Doc, directory: string) {
constructor(ydoc: Y.Doc, directory: string, sessionId: string) {
this.#ydoc = ydoc;
this.#backupDirectory = directory;
this.#sessionId = sessionId;
// Set backupDirectory as the provided directory joined with the sessionId.
this.#backupDirectory = path.join(directory, sessionId);
}

static async init(ydoc: Y.Doc, backupDirectory: string): Promise<BackupScheduler> {
await fse.ensureDir(backupDirectory);
return new BackupScheduler(ydoc, backupDirectory);
/**
* Initializes the BackupScheduler by ensuring the session-specific backup folder exists.
* @param ydoc The Y.Doc instance.
* @param backupDirectory The base backup directory.
* @param sessionId The unique session id.
*/
static async init(
ydoc: Y.Doc,
backupDirectory: string,
sessionId: string,
): Promise<BackupScheduler> {
const directory = path.join(backupDirectory, sessionId);
await fse.ensureDir(directory);
return new BackupScheduler(ydoc, backupDirectory, sessionId);
}

/**
* Updates the backup directory to a new path.
* This is useful for setting the backup directory based on a user-defined location.
* @param newDir The new backup directory path.
* Updates the backup directory to a new base path.
* The actual backup path will be the new directory joined with the sessionId.
* @param newDir The new base backup directory.
*/
async updateBackupDirectory(newDir: string): Promise<void> {
this.#backupDirectory = newDir;
await fse.ensureDir(newDir);
console.log('BackupScheduler: Updated backup directory to', newDir);
this.#backupDirectory = path.join(newDir, this.#sessionId);
await fse.ensureDir(this.#backupDirectory);
console.log('BackupScheduler: Updated backup directory to', this.#backupDirectory);
}

/**
Expand Down Expand Up @@ -142,6 +157,7 @@ export default class BackupScheduler implements DataBackup {
/**
* Creates a single-file snapshot of the entire Y.Doc state.
* The state is encoded as a Yjs update (Uint8Array) and written to disk.
* @param filePath The destination file path.
*/
async backupToFile(filePath: string): Promise<void> {
console.info('Yjs: Exporting document backup...', filePath);
Expand All @@ -156,6 +172,7 @@ export default class BackupScheduler implements DataBackup {
* This method performs a full overwrite by first clearing the persistent y-indexeddb storage,
* then creating a new Y.Doc, applying the backup update, and reinitializing the y-indexeddb provider.
* Finally, the application is reloaded so that all components reinitialize with the restored state.
* @param filePath The backup file to restore from.
*/
async restoreFromFile(filePath: string): Promise<void> {
console.info('Yjs: Importing document backup...', filePath);
Expand Down Expand Up @@ -196,6 +213,7 @@ export default class BackupScheduler implements DataBackup {
/**
* Mimics Dexie's "peekFile" by creating a temporary Y.Doc,
* applying the backup update, and returning the counts of tags and files.
* @param filePath The backup file to peek into.
*/
async peekFile(filePath: string): Promise<[numTags: number, numFiles: number]> {
console.info('Yjs: Peeking document backup...', filePath);
Expand Down
12 changes: 9 additions & 3 deletions src/renderer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

/**
* Updated renderer.tsx to remove all Dexie references and use our Yjs backend.
* We still do the same general initialization and pass the Yjs-based backend
Expand Down Expand Up @@ -59,8 +60,10 @@ async function runMainApp(root: Root): Promise<void> {
// 2. Create the Y.Doc that holds our data
const ydoc = new Y.Doc();

// 3. Create BackupScheduler for full Yjs backups using the default directory
const backup = await BackupScheduler.init(ydoc, defaultBackupDirectory);
// 3. Create BackupScheduler for full Yjs backups using the default directory.
// Retrieve the local session ID and pass it along.
const sessionId = await RendererMessenger.getSessionId();
const backup = await BackupScheduler.init(ydoc, defaultBackupDirectory, sessionId);

// 4. Create the Yjs-based backend
// We pass a "notifyChange" callback that schedules an auto-backup
Expand Down Expand Up @@ -144,8 +147,10 @@ async function runMainApp(root: Root): Promise<void> {

async function runPreviewApp(root: Root): Promise<void> {
const ydoc = new Y.Doc();
// Retrieve sessionId for consistency (even if backups are not really used in preview)
const sessionId = await RendererMessenger.getSessionId();
// We won't do real backups in preview mode. Just pass an empty or in-memory path
const backup = await BackupScheduler.init(ydoc, ''); // no real backup dir
const backup = await BackupScheduler.init(ydoc, '', sessionId);
const backend = await Backend.init(ydoc, () => {});

const rootStore = await RootStore.preview(backend, backup);
Expand Down Expand Up @@ -221,3 +226,4 @@ main()
RendererMessenger.toggleDevTools();
}
});

0 comments on commit 35e9c7f

Please sign in to comment.