mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-03-09 11:28:47 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d3827d335 | ||
|
|
92d3a0cfa2 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.25.37",
|
||||
"version": "0.25.38",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.37",
|
||||
"version": "0.25.38",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.37",
|
||||
"version": "0.25.38",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.25.37",
|
||||
"version": "0.25.38",
|
||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -3,7 +3,9 @@ import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { Logger } from "octagonal-wheels/common/logger";
|
||||
const databaseCache: { [key: string]: IDBPDatabase<any> } = {};
|
||||
export const OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatabase> => {
|
||||
export { OpenKeyValueDatabase } from "./KeyValueDBv2.ts";
|
||||
|
||||
export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatabase> => {
|
||||
if (dbKey in databaseCache) {
|
||||
databaseCache[dbKey].close();
|
||||
delete databaseCache[dbKey];
|
||||
@@ -82,7 +84,7 @@ export const OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatab
|
||||
},
|
||||
close() {
|
||||
delete databaseCache[dbKey];
|
||||
return closeDB();
|
||||
return Promise.resolve(closeDB());
|
||||
},
|
||||
async destroy() {
|
||||
delete databaseCache[dbKey];
|
||||
|
||||
154
src/common/KeyValueDBv2.ts
Normal file
154
src/common/KeyValueDBv2.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "@/lib/src/common/logger";
|
||||
import type { KeyValueDatabase } from "@/lib/src/interfaces/KeyValueDatabase";
|
||||
import { deleteDB, openDB, type IDBPDatabase } from "idb";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
|
||||
const databaseCache = new Map<string, IDBKeyValueDatabase>();
|
||||
|
||||
export async function OpenKeyValueDatabase(dbKey: string): Promise<KeyValueDatabase> {
|
||||
return await serialized(`OpenKeyValueDatabase-${dbKey}`, async () => {
|
||||
const cachedDB = databaseCache.get(dbKey);
|
||||
if (cachedDB) {
|
||||
if (!cachedDB.isDestroyed) {
|
||||
return cachedDB;
|
||||
}
|
||||
await cachedDB.ensuredDestroyed;
|
||||
databaseCache.delete(dbKey);
|
||||
}
|
||||
const newDB = new IDBKeyValueDatabase(dbKey);
|
||||
try {
|
||||
await newDB.getIsReady();
|
||||
databaseCache.set(dbKey, newDB);
|
||||
return newDB;
|
||||
} catch (e) {
|
||||
databaseCache.delete(dbKey);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class IDBKeyValueDatabase implements KeyValueDatabase {
|
||||
protected _dbPromise: Promise<IDBPDatabase<any>> | null = null;
|
||||
protected dbKey: string;
|
||||
protected storeKey: string;
|
||||
protected _isDestroyed: boolean = false;
|
||||
protected destroyedPromise: Promise<void> | null = null;
|
||||
|
||||
get isDestroyed() {
|
||||
return this._isDestroyed;
|
||||
}
|
||||
get ensuredDestroyed(): Promise<void> {
|
||||
if (this.destroyedPromise) {
|
||||
return this.destroyedPromise;
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async getIsReady(): Promise<boolean> {
|
||||
await this.ensureDB();
|
||||
return this.isDestroyed === false;
|
||||
}
|
||||
|
||||
protected ensureDB() {
|
||||
if (this._isDestroyed) {
|
||||
throw new Error("Database is destroyed");
|
||||
}
|
||||
if (this._dbPromise) {
|
||||
return this._dbPromise;
|
||||
}
|
||||
this._dbPromise = openDB(this.dbKey, undefined, {
|
||||
upgrade: (db, _oldVersion, _newVersion, _transaction, _event) => {
|
||||
if (!db.objectStoreNames.contains(this.storeKey)) {
|
||||
return db.createObjectStore(this.storeKey);
|
||||
}
|
||||
},
|
||||
blocking: (currentVersion, blockedVersion, event) => {
|
||||
Logger(
|
||||
`Blocking database open for ${this.dbKey}: currentVersion=${currentVersion}, blockedVersion=${blockedVersion}`,
|
||||
LOG_LEVEL_VERBOSE
|
||||
);
|
||||
// This `this` is not this openDB instance, previously opened DB. Let it be closed in the terminated handler.
|
||||
void this.closeDB(true);
|
||||
},
|
||||
blocked: (currentVersion, blockedVersion, event) => {
|
||||
Logger(
|
||||
`Database open blocked for ${this.dbKey}: currentVersion=${currentVersion}, blockedVersion=${blockedVersion}`,
|
||||
LOG_LEVEL_VERBOSE
|
||||
);
|
||||
},
|
||||
terminated: () => {
|
||||
Logger(`Database connection terminated for ${this.dbKey}`, LOG_LEVEL_VERBOSE);
|
||||
this._dbPromise = null;
|
||||
},
|
||||
}).catch((e) => {
|
||||
this._dbPromise = null;
|
||||
throw e;
|
||||
});
|
||||
return this._dbPromise;
|
||||
}
|
||||
protected async closeDB(setDestroyed: boolean = false) {
|
||||
if (this._dbPromise) {
|
||||
const tempPromise = this._dbPromise;
|
||||
this._dbPromise = null;
|
||||
try {
|
||||
const dbR = await tempPromise;
|
||||
dbR.close();
|
||||
} catch (e) {
|
||||
Logger(`Error closing database`);
|
||||
Logger(e, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
}
|
||||
this._dbPromise = null;
|
||||
if (setDestroyed) {
|
||||
this._isDestroyed = true;
|
||||
this.destroyedPromise = Promise.resolve();
|
||||
}
|
||||
}
|
||||
get DB(): Promise<IDBPDatabase<any>> {
|
||||
if (this._isDestroyed) {
|
||||
return Promise.reject(new Error("Database is destroyed"));
|
||||
}
|
||||
return this.ensureDB();
|
||||
}
|
||||
|
||||
constructor(dbKey: string) {
|
||||
this.dbKey = dbKey;
|
||||
this.storeKey = dbKey;
|
||||
}
|
||||
async get<U>(key: IDBValidKey): Promise<U> {
|
||||
const db = await this.DB;
|
||||
return await db.get(this.storeKey, key);
|
||||
}
|
||||
async set<U>(key: IDBValidKey, value: U): Promise<IDBValidKey> {
|
||||
const db = await this.DB;
|
||||
await db.put(this.storeKey, value, key);
|
||||
return key;
|
||||
}
|
||||
async del(key: IDBValidKey): Promise<void> {
|
||||
const db = await this.DB;
|
||||
return await db.delete(this.storeKey, key);
|
||||
}
|
||||
async clear(): Promise<void> {
|
||||
const db = await this.DB;
|
||||
return await db.clear(this.storeKey);
|
||||
}
|
||||
async keys(query?: IDBValidKey | IDBKeyRange, count?: number): Promise<IDBValidKey[]> {
|
||||
const db = await this.DB;
|
||||
return await db.getAllKeys(this.storeKey, query, count);
|
||||
}
|
||||
async close(): Promise<void> {
|
||||
await this.closeDB();
|
||||
}
|
||||
async destroy(): Promise<void> {
|
||||
this._isDestroyed = true;
|
||||
this.destroyedPromise = (async () => {
|
||||
await this.closeDB();
|
||||
await deleteDB(this.dbKey, {
|
||||
blocked: () => {
|
||||
Logger(`Database delete blocked for ${this.dbKey}`);
|
||||
},
|
||||
});
|
||||
})();
|
||||
await this.destroyedPromise;
|
||||
}
|
||||
}
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: bc761fcf57...8fbd310e7f
@@ -6,9 +6,9 @@ import { AbstractModule } from "../AbstractModule.ts";
|
||||
import type { LiveSyncCore } from "../../main.ts";
|
||||
|
||||
export class ModuleKeyValueDB extends AbstractModule {
|
||||
tryCloseKvDB() {
|
||||
async tryCloseKvDB() {
|
||||
try {
|
||||
this.core.kvDB?.close();
|
||||
await this.core.kvDB?.close();
|
||||
return true;
|
||||
} catch (e) {
|
||||
this._log("Failed to close KeyValueDB", LOG_LEVEL_VERBOSE);
|
||||
@@ -19,7 +19,7 @@ export class ModuleKeyValueDB extends AbstractModule {
|
||||
async openKeyValueDB(): Promise<boolean> {
|
||||
await delay(10);
|
||||
try {
|
||||
this.tryCloseKvDB();
|
||||
await this.tryCloseKvDB();
|
||||
await delay(10);
|
||||
await yieldMicrotask();
|
||||
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
|
||||
@@ -33,12 +33,12 @@ export class ModuleKeyValueDB extends AbstractModule {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
_onDBUnload(db: LiveSyncLocalDB) {
|
||||
if (this.core.kvDB) this.core.kvDB.close();
|
||||
async _onDBUnload(db: LiveSyncLocalDB) {
|
||||
if (this.core.kvDB) await this.core.kvDB.close();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
_onDBClose(db: LiveSyncLocalDB) {
|
||||
if (this.core.kvDB) this.core.kvDB.close();
|
||||
async _onDBClose(db: LiveSyncLocalDB) {
|
||||
if (this.core.kvDB) await this.core.kvDB.close();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,14 @@ Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||
|
||||
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||
|
||||
## 0.25.38
|
||||
|
||||
17th January, 2026
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue where indexedDB would not close correctly on some environments, causing unexpected errors during database operations.
|
||||
|
||||
## 0.25.37
|
||||
|
||||
15th January, 2026
|
||||
|
||||
Reference in New Issue
Block a user