mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-13 01:35:57 +00:00
### Improved
- Now we can switch the database adapter between IndexedDB and IDB without rebuilding (#747). - No longer checking for the adapter by `Doctor`. ### Changes - The default adapter is reverted to IDB to avoid memory leaks (#747). ### Fixed (?) - Reverted QR code library to v1.4.4 (To make sure #752).
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
"idb": "^8.0.3",
|
||||
"minimatch": "^10.0.2",
|
||||
"octagonal-wheels": "^0.1.44",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"trystero": "^0.22.0",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
},
|
||||
@@ -10019,9 +10019,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode-generator": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz",
|
||||
"integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz",
|
||||
"integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
@@ -18967,9 +18967,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"qrcode-generator": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz",
|
||||
"integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g=="
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.5.2.tgz",
|
||||
"integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw=="
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.2.0",
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
"idb": "^8.0.3",
|
||||
"minimatch": "^10.0.2",
|
||||
"octagonal-wheels": "^0.1.44",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"qrcode-generator": "^1.4.4",
|
||||
"trystero": "^0.22.0",
|
||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||
}
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: b8fb5e5e63...f99e662309
@@ -1,6 +1,7 @@
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||
import type { LiveSyncCore } from "../../main";
|
||||
import { ExtraSuffixIndexedDB } from "../../lib/src/common/types";
|
||||
|
||||
export class ModulePouchDB extends AbstractModule {
|
||||
_createPouchDBInstance<T extends object>(
|
||||
@@ -12,7 +13,7 @@ export class ModulePouchDB extends AbstractModule {
|
||||
optionPass.adapter = "indexeddb";
|
||||
//@ts-ignore :missing def
|
||||
optionPass.purged_infos_limit = 1;
|
||||
return new PouchDB(name + "-indexeddb", optionPass);
|
||||
return new PouchDB(name + ExtraSuffixIndexedDB, optionPass);
|
||||
}
|
||||
return new PouchDB(name, optionPass);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ import {
|
||||
E2EEAlgorithms,
|
||||
type HashAlgorithm,
|
||||
LOG_LEVEL_NOTICE,
|
||||
SuffixDatabaseName,
|
||||
} from "../../../lib/src/common/types.ts";
|
||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||
import type { PageFunctions } from "./SettingPane.ts";
|
||||
import { visibleOnly } from "./SettingPane.ts";
|
||||
import { PouchDB } from "../../../lib/src/pouchdb/pouchdb-browser";
|
||||
import { ExtraSuffixIndexedDB } from "../../../lib/src/common/types.ts";
|
||||
import { migrateDatabases } from "./settingUtils.ts";
|
||||
|
||||
export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
|
||||
void addPanel(paneEl, "Compatibility (Metadata)").then((paneEl) => {
|
||||
@@ -26,17 +30,88 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Compatibility (Database structure)").then((paneEl) => {
|
||||
new Setting(paneEl).autoWireToggle("useIndexedDBAdapter", { invert: true, holdValue: true });
|
||||
|
||||
// new Setting(paneEl)
|
||||
// .autoWireToggle("doNotUseFixedRevisionForChunks", { holdValue: true })
|
||||
// .setClass("wizardHidden");
|
||||
const migrateAllToIndexedDB = async () => {
|
||||
const dbToName = this.plugin.localDatabase.dbname + SuffixDatabaseName + ExtraSuffixIndexedDB;
|
||||
const options = {
|
||||
adapter: "indexeddb",
|
||||
//@ts-ignore :missing def
|
||||
purged_infos_limit: 1,
|
||||
auto_compaction: false,
|
||||
deterministic_revs: true,
|
||||
};
|
||||
const openTo = () => {
|
||||
return new PouchDB(dbToName, options);
|
||||
};
|
||||
if (await migrateDatabases("to IndexedDB", this.plugin.localDatabase.localDatabase, openTo)) {
|
||||
Logger(
|
||||
"Migration to IndexedDB completed. Obsidian will be restarted with new configuration immediately.",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this.plugin.settings.useIndexedDBAdapter = true;
|
||||
await this.services.setting.saveSettingData();
|
||||
this.services.appLifecycle.performRestart();
|
||||
}
|
||||
};
|
||||
const migrateAllToIDB = async () => {
|
||||
const dbToName = this.plugin.localDatabase.dbname + SuffixDatabaseName;
|
||||
const options = {
|
||||
adapter: "idb",
|
||||
auto_compaction: false,
|
||||
deterministic_revs: true,
|
||||
};
|
||||
const openTo = () => {
|
||||
return new PouchDB(dbToName, options);
|
||||
};
|
||||
if (await migrateDatabases("to IDB", this.plugin.localDatabase.localDatabase, openTo)) {
|
||||
Logger(
|
||||
"Migration to IDB completed. Obsidian will be restarted with new configuration immediately.",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
this.plugin.settings.useIndexedDBAdapter = false;
|
||||
await this.services.setting.saveSettingData();
|
||||
this.services.appLifecycle.performRestart();
|
||||
}
|
||||
};
|
||||
{
|
||||
const infoClass = this.editingSettings.useIndexedDBAdapter ? "op-warn" : "op-warn-info";
|
||||
paneEl.createDiv({
|
||||
text: "The IndexedDB adapter often offers superior performance in certain scenarios, but it has been found to cause memory leaks when used with LiveSync mode. When using LiveSync mode, please use IDB adapter instead.",
|
||||
cls: infoClass,
|
||||
});
|
||||
paneEl.createDiv({
|
||||
text: "Changing this setting requires migrating existing data (a bit time may be taken) and restarting Obsidian. Please make sure to back up your data before proceeding.",
|
||||
cls: "op-warn-info",
|
||||
});
|
||||
const setting = new Setting(paneEl)
|
||||
.setName("Database Adapter")
|
||||
.setDesc("Select the database adapter to use. ");
|
||||
const el = setting.controlEl.createDiv({});
|
||||
el.setText(`Current adapter: ${this.editingSettings.useIndexedDBAdapter ? "IndexedDB" : "IDB"}`);
|
||||
if (!this.editingSettings.useIndexedDBAdapter) {
|
||||
setting.addButton((button) => {
|
||||
button.setButtonText("Switch to IndexedDB").onClick(async () => {
|
||||
Logger("Migrating all data to IndexedDB...", LOG_LEVEL_NOTICE);
|
||||
await migrateAllToIndexedDB();
|
||||
Logger(
|
||||
"Migration to IndexedDB completed. Please switch the adapter and restart Obsidian.",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setting.addButton((button) => {
|
||||
button.setButtonText("Switch to IDB").onClick(async () => {
|
||||
Logger("Migrating all data to IDB...", LOG_LEVEL_NOTICE);
|
||||
await migrateAllToIDB();
|
||||
Logger(
|
||||
"Migration to IDB completed. Please switch the adapter and restart Obsidian.",
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
new Setting(paneEl).autoWireToggle("handleFilenameCaseSensitive", { holdValue: true }).setClass("wizardHidden");
|
||||
|
||||
this.addOnSaved("useIndexedDBAdapter", async () => {
|
||||
await this.saveAllDirtySettings();
|
||||
await this.rebuildDB("localOnly");
|
||||
});
|
||||
});
|
||||
|
||||
void addPanel(paneEl, "Compatibility (Internal API Usage)").then((paneEl) => {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { escapeStringToHTML } from "octagonal-wheels/string";
|
||||
import { E2EEAlgorithmNames, type ObsidianLiveSyncSettings } from "../../../lib/src/common/types";
|
||||
import {
|
||||
E2EEAlgorithmNames,
|
||||
MILESTONE_DOCID,
|
||||
NODEINFO_DOCID,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "../../../lib/src/common/types";
|
||||
import {
|
||||
pickCouchDBSyncSettings,
|
||||
pickBucketSyncSettings,
|
||||
@@ -7,6 +12,7 @@ import {
|
||||
pickEncryptionSettings,
|
||||
} from "../../../lib/src/common/utils";
|
||||
import { getConfig, type AllSettingItemKey } from "./settingConstants";
|
||||
import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
||||
|
||||
/**
|
||||
* Generates a summary of P2P configuration settings
|
||||
@@ -76,3 +82,73 @@ export function getSummaryFromPartialSettings(setting: Partial<ObsidianLiveSyncS
|
||||
}
|
||||
return outputSummary;
|
||||
}
|
||||
|
||||
// Migration or de-migration helper functions
|
||||
|
||||
/**
|
||||
* Copy document from one database to another for migration purposes
|
||||
* @param docName document ID
|
||||
* @param dbFrom source database
|
||||
* @param dbTo destination database
|
||||
* @returns
|
||||
*/
|
||||
export async function copyMigrationDocs(docName: string, dbFrom: PouchDB.Database, dbTo: PouchDB.Database) {
|
||||
try {
|
||||
const doc = await dbFrom.get(docName);
|
||||
delete (doc as any)._rev;
|
||||
await dbTo.put(doc);
|
||||
} catch (e) {
|
||||
if ((e as any).status === 404) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
type PouchDBOpenFunction = () => Promise<PouchDB.Database> | PouchDB.Database;
|
||||
|
||||
/**
|
||||
* Migrate databases from one to another
|
||||
* @param operationName Name of the migration operation
|
||||
* @param from source database
|
||||
* @param openTo function to open destination database
|
||||
* @returns True if migration succeeded
|
||||
*/
|
||||
export async function migrateDatabases(operationName: string, from: PouchDB.Database, openTo: PouchDBOpenFunction) {
|
||||
const dbTo = await openTo();
|
||||
await dbTo.info(); // ensure created
|
||||
Logger(`Opening destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
// destroy existing data
|
||||
await dbTo.destroy();
|
||||
Logger(`Destroyed existing destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
|
||||
const dbTo2 = await openTo();
|
||||
const info2 = await dbTo2.info(); // ensure created
|
||||
console.log(info2);
|
||||
Logger(`Re-created destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
|
||||
const info = await from.info();
|
||||
const totalDocs = info.doc_count || 0;
|
||||
const result = await from.replicate
|
||||
.to(dbTo2, {
|
||||
//@ts-ignore Missing in typedefs
|
||||
style: "all_docs",
|
||||
})
|
||||
.on("change", (info) => {
|
||||
Logger(
|
||||
`Replicating... Docs replicated: ${info.docs_written} / ${totalDocs}`,
|
||||
LOG_LEVEL_NOTICE,
|
||||
"migration"
|
||||
);
|
||||
});
|
||||
if (result.ok) {
|
||||
Logger(`Replication completed for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
} else {
|
||||
throw new Error(`Replication failed for migration: ${operationName}.`);
|
||||
}
|
||||
await copyMigrationDocs(MILESTONE_DOCID, from, dbTo2);
|
||||
await copyMigrationDocs(NODEINFO_DOCID, from, dbTo2);
|
||||
Logger(`Copied migration documents for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
await dbTo2.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user