1
0
mirror of https://github.com/vrtmrz/obsidian-livesync.git synced 2024-12-12 09:04:06 +02:00
- 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:
vorotamoroz 2024-05-10 11:33:59 +01:00
parent 172b08dbb3
commit 89de2dcc37
5 changed files with 125 additions and 73 deletions

View File

@ -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) {

View File

@ -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.

@ -1 +1 @@
Subproject commit 57f0be04647ac7ac2cb246e7cafe7905ee5fd132
Subproject commit 13f8370ef52682888ebddccfa60b6b66201e49c1

View File

@ -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;
}
})
}
}
}

View File

@ -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)