mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2024-12-12 09:04:06 +02:00
Fixed:
- No longer missing tasks which have queued as the same key (e.g., for the same operation to the same file). - Some trivial issues have been fixed. New feature: - Reloading Obsidian can be scheduled until that file and database operations are stable.
This commit is contained in:
parent
172b08dbb3
commit
89de2dcc37
@ -10,7 +10,7 @@ import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../li
|
||||
import { serialized } from "../lib/src/concurrency/lock.ts";
|
||||
import { LiveSyncCommands } from "./LiveSyncCommands.ts";
|
||||
import { stripAllPrefixes } from "../lib/src/string_and_binary/path.ts";
|
||||
import { PeriodicProcessor, askYesNo, disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "../common/utils.ts";
|
||||
import { PeriodicProcessor, disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "../common/utils.ts";
|
||||
import { PluginDialogModal } from "../common/dialogs.ts";
|
||||
import { JsonResolveModal } from "../ui/JsonResolveModal.ts";
|
||||
import { QueueProcessor } from '../lib/src/concurrency/processor.ts';
|
||||
@ -466,12 +466,7 @@ export class ConfigSync extends LiveSyncCommands {
|
||||
Logger(`Plugin reloaded: ${pluginManifest.name}`, LOG_LEVEL_NOTICE, "plugin-reload-" + pluginManifest.id);
|
||||
}
|
||||
} else if (data.category == "CONFIG") {
|
||||
scheduleTask("configReload", 250, async () => {
|
||||
if (await askYesNo(this.app, "Do you want to restart and reload Obsidian now?") == "yes") {
|
||||
// @ts-ignore
|
||||
this.app.commands.executeCommandById("app:reload")
|
||||
}
|
||||
})
|
||||
this.plugin.askReload();
|
||||
}
|
||||
return true;
|
||||
} catch (ex) {
|
||||
|
@ -432,13 +432,14 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
|
||||
// If something changes left, notify for reloading Obsidian.
|
||||
if (updatedCount != 0) {
|
||||
this.plugin.askInPopup(`updated-any-hidden`, `Hidden files have been synchronized, Press {HERE} to reload Obsidian, or press elsewhere to dismiss this message.`, (anchor) => {
|
||||
anchor.text = "HERE";
|
||||
anchor.addEventListener("click", () => {
|
||||
// @ts-ignore
|
||||
this.app.commands.executeCommandById("app:reload");
|
||||
if (!this.plugin.isReloadingScheduled) {
|
||||
this.plugin.askInPopup(`updated-any-hidden`, `Hidden files have been synchronised, Press {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`, (anchor) => {
|
||||
anchor.text = "HERE";
|
||||
anchor.addEventListener("click", () => {
|
||||
this.plugin.scheduleAppReload();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -471,6 +472,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
children: [],
|
||||
deleted: false,
|
||||
type: "newnote",
|
||||
eden: {},
|
||||
};
|
||||
} else {
|
||||
if (await isDocContentSame(readAsBlob(old), content) && !forceWrite) {
|
||||
@ -521,6 +523,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
children: [],
|
||||
deleted: true,
|
||||
type: "newnote",
|
||||
eden: {}
|
||||
};
|
||||
} else {
|
||||
// Remove all conflicted before deleting.
|
||||
|
2
src/lib
2
src/lib
@ -1 +1 @@
|
||||
Subproject commit 57f0be04647ac7ac2cb246e7cafe7905ee5fd132
|
||||
Subproject commit 13f8370ef52682888ebddccfa60b6b66201e49c1
|
149
src/main.ts
149
src/main.ts
@ -32,7 +32,7 @@ import { LogPaneView, VIEW_TYPE_LOG } from "./ui/LogPaneView.ts";
|
||||
import { LRUCache } from "./lib/src/memory/LRUCache.ts";
|
||||
import { SerializedFileAccess } from "./storages/SerializedFileAccess.js";
|
||||
import { QueueProcessor } from "./lib/src/concurrency/processor.js";
|
||||
import { reactive, reactiveSource } from "./lib/src/dataobject/reactive.js";
|
||||
import { reactive, reactiveSource, type ReactiveValue } from "./lib/src/dataobject/reactive.js";
|
||||
import { initializeStores } from "./common/stores.js";
|
||||
import { JournalSyncMinio } from "./lib/src/replication/journal/objectstore/JournalSyncMinio.js";
|
||||
import { LiveSyncJournalReplicator, type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
|
||||
@ -1422,55 +1422,62 @@ We can perform a command in this file.
|
||||
}
|
||||
async handleFileEvent(queue: FileEventItem): Promise<any> {
|
||||
const file = queue.args.file;
|
||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||
const last = Number(await this.kvDB.get(key) || 0);
|
||||
let mtime = file.mtime;
|
||||
if (queue.type == "DELETE") {
|
||||
await this.deleteFromDBbyPath(file.path);
|
||||
mtime = file.mtime - 1;
|
||||
const keyD1 = `file-last-proc-CREATE-${file.path}`;
|
||||
const keyD2 = `file-last-proc-CHANGED-${file.path}`;
|
||||
await this.kvDB.set(keyD1, mtime);
|
||||
await this.kvDB.set(keyD2, mtime);
|
||||
} else if (queue.type == "INTERNAL") {
|
||||
await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path);
|
||||
await this.addOnConfigSync.watchVaultRawEventsAsync(file.path);
|
||||
} else {
|
||||
const targetFile = this.vaultAccess.getAbstractFileByPath(file.path);
|
||||
if (!(targetFile instanceof TFile)) {
|
||||
Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO);
|
||||
return;
|
||||
}
|
||||
if (file.mtime == last) {
|
||||
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// const cache = queue.args.cache;
|
||||
if (queue.type == "CREATE" || queue.type == "CHANGED") {
|
||||
fireAndForget(() => this.checkAndApplySettingFromMarkdown(queue.args.file.path, true));
|
||||
const keyD1 = `file-last-proc-DELETED-${file.path}`;
|
||||
const lockKey = `handleFile:${file.path}`;
|
||||
return await serialized(lockKey, async () => {
|
||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||
const last = Number(await this.kvDB.get(key) || 0);
|
||||
let mtime = file.mtime;
|
||||
if (queue.type == "DELETE") {
|
||||
await this.deleteFromDBbyPath(file.path);
|
||||
mtime = file.mtime - 1;
|
||||
const keyD1 = `file-last-proc-CREATE-${file.path}`;
|
||||
const keyD2 = `file-last-proc-CHANGED-${file.path}`;
|
||||
await this.kvDB.set(keyD1, mtime);
|
||||
if (!await this.updateIntoDB(targetFile, undefined)) {
|
||||
Logger(`STORAGE -> DB: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL_INFO);
|
||||
// cancel running queues and remove one of atomic operation
|
||||
this.cancelRelativeEvent(queue);
|
||||
await this.kvDB.set(keyD2, mtime);
|
||||
} else if (queue.type == "INTERNAL") {
|
||||
await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path);
|
||||
await this.addOnConfigSync.watchVaultRawEventsAsync(file.path);
|
||||
} else {
|
||||
const targetFile = this.vaultAccess.getAbstractFileByPath(file.path);
|
||||
if (!(targetFile instanceof TFile)) {
|
||||
Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO);
|
||||
return;
|
||||
}
|
||||
if (file.mtime == last) {
|
||||
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// const cache = queue.args.cache;
|
||||
if (queue.type == "CREATE" || queue.type == "CHANGED") {
|
||||
fireAndForget(() => this.checkAndApplySettingFromMarkdown(queue.args.file.path, true));
|
||||
const keyD1 = `file-last-proc-DELETED-${file.path}`;
|
||||
await this.kvDB.set(keyD1, mtime);
|
||||
if (!await this.updateIntoDB(targetFile, undefined)) {
|
||||
Logger(`STORAGE -> DB: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL_INFO);
|
||||
// cancel running queues and remove one of atomic operation
|
||||
this.cancelRelativeEvent(queue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (queue.type == "RENAME") {
|
||||
// Obsolete
|
||||
await this.watchVaultRenameAsync(targetFile, queue.args.oldPath);
|
||||
}
|
||||
}
|
||||
if (queue.type == "RENAME") {
|
||||
// Obsolete
|
||||
await this.watchVaultRenameAsync(targetFile, queue.args.oldPath);
|
||||
}
|
||||
}
|
||||
await this.kvDB.set(key, mtime);
|
||||
await this.kvDB.set(key, mtime);
|
||||
});
|
||||
}
|
||||
|
||||
pendingFileEventCount = reactiveSource(0);
|
||||
processingFileEventCount = reactiveSource(0);
|
||||
fileEventQueue =
|
||||
new QueueProcessor(
|
||||
(items: FileEventItem[]) => this.handleFileEvent(items[0]),
|
||||
async (items: FileEventItem[]) => {
|
||||
await this.handleFileEvent(items[0]);
|
||||
return []
|
||||
}
|
||||
,
|
||||
{ suspended: true, batchSize: 1, concurrentLimit: 5, delay: 100, yieldThreshold: FileWatchEventQueueMax, totalRemainingReactiveSource: this.pendingFileEventCount, processingEntitiesReactiveSource: this.processingFileEventCount }
|
||||
).replaceEnqueueProcessor((items, newItem) => this.queueNextFileEvent(items, newItem));
|
||||
|
||||
@ -1894,7 +1901,7 @@ We can perform a command in this file.
|
||||
observeForLogs() {
|
||||
const padSpaces = `\u{2007}`.repeat(10);
|
||||
// const emptyMark = `\u{2003}`;
|
||||
const rerenderTimer = new Map<string, [ReturnType<typeof setTimeout>, number]>;
|
||||
const rerenderTimer = new Map<string, [ReturnType<typeof setTimeout>, number]>();
|
||||
const tick = reactiveSource(0);
|
||||
function padLeftSp(num: number, mark: string) {
|
||||
const numLen = `${num}`.length + 1;
|
||||
@ -2005,9 +2012,9 @@ We can perform a command in this file.
|
||||
};
|
||||
})
|
||||
const statusBarLabels = reactive(() => {
|
||||
|
||||
const scheduleMessage = this.isReloadingScheduled ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : "";
|
||||
const { message } = statusLineLabel.value;
|
||||
const status = this.statusLog.value;
|
||||
const status = scheduleMessage + this.statusLog.value;
|
||||
return {
|
||||
message, status
|
||||
}
|
||||
@ -3174,5 +3181,61 @@ Or if you are sure know what had been happened, we can unlock the database from
|
||||
// @ts-ignore
|
||||
this.app.commands.executeCommandById(id)
|
||||
}
|
||||
|
||||
_totalProcessingCount?: ReactiveValue<number>;
|
||||
get isReloadingScheduled() {
|
||||
return this._totalProcessingCount !== undefined;
|
||||
}
|
||||
askReload(message?: string) {
|
||||
if (this.isReloadingScheduled) {
|
||||
Logger(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
scheduleTask("configReload", 250, async () => {
|
||||
const RESTART_NOW = "Yes, restart immediately";
|
||||
const RESTART_AFTER_STABLE = "Yes, schedule a restart after stabilisation";
|
||||
const RETRY_LATER = "No, Leave it to me";
|
||||
const ret = await askSelectString(this.app, message || "Do you want to restart and reload Obsidian now?", [RESTART_AFTER_STABLE, RESTART_NOW, RETRY_LATER]);
|
||||
if (ret == RESTART_NOW) {
|
||||
this.performAppReload();
|
||||
} else if (ret == RESTART_AFTER_STABLE) {
|
||||
this.scheduleAppReload();
|
||||
}
|
||||
})
|
||||
}
|
||||
scheduleAppReload() {
|
||||
if (!this._totalProcessingCount) {
|
||||
const __tick = reactiveSource(0);
|
||||
this._totalProcessingCount = reactive(() => {
|
||||
const dbCount = this.databaseQueueCount.value;
|
||||
const replicationCount = this.replicationResultCount.value;
|
||||
const storageApplyingCount = this.storageApplyingCount.value;
|
||||
const chunkCount = collectingChunks.value;
|
||||
const pluginScanCount = pluginScanningCount.value;
|
||||
const hiddenFilesCount = hiddenFilesEventCount.value + hiddenFilesProcessingCount.value;
|
||||
const conflictProcessCount = this.conflictProcessQueueCount.value;
|
||||
const e = this.pendingFileEventCount.value;
|
||||
const proc = this.processingFileEventCount.value;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const __ = __tick.value;
|
||||
return dbCount + replicationCount + storageApplyingCount + chunkCount + pluginScanCount + hiddenFilesCount + conflictProcessCount + e + proc;
|
||||
})
|
||||
this.registerInterval(setInterval(() => {
|
||||
__tick.value++;
|
||||
}, 1000) as unknown as number);
|
||||
|
||||
let stableCheck = 3;
|
||||
this._totalProcessingCount.onChanged(e => {
|
||||
if (e.value == 0) {
|
||||
if (stableCheck-- <= 0) {
|
||||
this.performAppReload();
|
||||
}
|
||||
Logger(`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`, LOG_LEVEL_NOTICE, "restart-notice");
|
||||
} else {
|
||||
stableCheck = 3;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import { Logger } from "../lib/src/common/logger.ts";
|
||||
import { checkSyncInfo, isCloudantURI } from "../lib/src/pouchdb/utils_couchdb.ts";
|
||||
import { testCrypt } from "../lib/src/encryption/e2ee_v2.ts";
|
||||
import ObsidianLiveSyncPlugin from "../main.ts";
|
||||
import { askYesNo, performRebuildDB, requestToCouchDB, scheduleTask } from "../common/utils.ts";
|
||||
import { askYesNo, performRebuildDB, requestToCouchDB } from "../common/utils.ts";
|
||||
import { request, type ButtonComponent, TFile } from "obsidian";
|
||||
import { shouldBeIgnored } from "../lib/src/string_and_binary/path.ts";
|
||||
import MultipleRegExpControl from './components/MultipleRegExpControl.svelte';
|
||||
@ -51,15 +51,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await replicator.tryConnectRemote(trialSetting);
|
||||
}
|
||||
|
||||
askReload(message?: string) {
|
||||
scheduleTask("configReload", 250, async () => {
|
||||
if (await askYesNo(this.app, message || "Do you want to restart and reload Obsidian now?") == "yes") {
|
||||
// @ts-ignore
|
||||
this.app.commands.executeCommandById("app:reload")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
closeSetting() {
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.close()
|
||||
@ -217,7 +208,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
text.setButtonText("Enable").onClick(async () => {
|
||||
this.plugin.settings.isConfigured = true;
|
||||
await this.plugin.saveSettings();
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -231,7 +222,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettingData();
|
||||
await this.plugin.resetLocalDatabase();
|
||||
// await this.plugin.initializeDatabase();
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
}
|
||||
}).setWarning()
|
||||
})
|
||||
@ -1317,7 +1308,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
||||
Logger("All done! Please set up subsequent devices with 'Copy current settings as a new setup URI' and 'Use the copied setup URI'.", LOG_LEVEL_NOTICE);
|
||||
await this.plugin.addOnSetup.command_copySetupURI();
|
||||
} else {
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1976,7 +1967,7 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.isConfigured = false;
|
||||
await this.plugin.saveSettings();
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
}));
|
||||
const hatchWarn = containerHatchEl.createEl("div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
||||
hatchWarn.addClass("op-warn-info");
|
||||
@ -2167,7 +2158,7 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
toggle.setValue(this.plugin.settings.suspendFileWatching).onChange(async (value) => {
|
||||
this.plugin.settings.suspendFileWatching = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
})
|
||||
);
|
||||
new Setting(containerHatchEl)
|
||||
@ -2177,7 +2168,7 @@ ${stringifyYaml(pluginConfig)}`;
|
||||
toggle.setValue(this.plugin.settings.suspendParseReplicationResult).onChange(async (value) => {
|
||||
this.plugin.settings.suspendParseReplicationResult = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.askReload();
|
||||
this.plugin.askReload();
|
||||
})
|
||||
);
|
||||
new Setting(containerHatchEl)
|
||||
|
Loading…
Reference in New Issue
Block a user