Compare commits

...

4 Commits

Author SHA1 Message Date
vorotamoroz
7c06750d93 bump 2023-05-02 18:00:55 +09:00
vorotamoroz
808fdc0944 Fixed:
- Fixed garbage collection error while unreferenced chunks exist many.
- Fixed filename validation on Linux.

Improved:
- Showing status is now thinned for performance.
- Enhance caching while collecting chunks.
2023-05-02 17:59:58 +09:00
vorotamoroz
ce25eee74b bump 2023-04-30 11:31:09 +09:00
vorotamoroz
146c170dec Fixed:
- Fixed hidden file handling on Linux

Improved:
- Now customization sync works more smoothly.
2023-04-30 11:28:39 +09:00
10 changed files with 127 additions and 80 deletions

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.19.0", "version": "0.19.2",
"minAppVersion": "0.9.12", "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.", "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", "author": "vorotamoroz",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.0", "version": "0.19.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.0", "version": "0.19.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.0", "version": "0.19.2",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "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", "main": "main.js",
"type": "module", "type": "module",

View File

@@ -10,7 +10,7 @@ import { WrappedNotice } from "./lib/src/wrapper";
import { base64ToArrayBuffer, arrayBufferToBase64, readString, writeString, uint8ArrayToHexString } from "./lib/src/strbin"; import { base64ToArrayBuffer, arrayBufferToBase64, readString, writeString, uint8ArrayToHexString } from "./lib/src/strbin";
import { runWithLock } from "./lib/src/lock"; import { runWithLock } from "./lib/src/lock";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { addPrefix, stripAllPrefixes } from "./lib/src/path"; import { stripAllPrefixes } from "./lib/src/path";
import { PeriodicProcessor, askYesNo, disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "./utils"; import { PeriodicProcessor, askYesNo, disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask } from "./utils";
import { Semaphore } from "./lib/src/semaphore"; import { Semaphore } from "./lib/src/semaphore";
import { PluginDialogModal } from "./dialogs"; import { PluginDialogModal } from "./dialogs";
@@ -67,6 +67,7 @@ export class ConfigSync extends LiveSyncCommands {
pluginDialog: PluginDialogModal = null; pluginDialog: PluginDialogModal = null;
periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false)); periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false));
pluginList: PluginDataExDisplay[] = [];
showPluginSyncModal() { showPluginSyncModal() {
if (!this.settings.usePluginSync) { if (!this.settings.usePluginSync) {
return; return;
@@ -141,27 +142,31 @@ export class ConfigSync extends LiveSyncCommands {
} }
} }
async onResume() { async onResume() {
if (this.plugin.suspended) if (this.plugin.suspended) {
return; return;
}
if (this.settings.autoSweepPlugins) { if (this.settings.autoSweepPlugins) {
await this.scanAllConfigFiles(true); await this.scanAllConfigFiles(false);
} }
this.periodicPluginSweepProcessor.enable(this.settings.autoSweepPluginsPeriodic && !this.settings.watchInternalFileChanges ? (PERIODIC_PLUGIN_SWEEP * 1000) : 0); this.periodicPluginSweepProcessor.enable(this.settings.autoSweepPluginsPeriodic && !this.settings.watchInternalFileChanges ? (PERIODIC_PLUGIN_SWEEP * 1000) : 0);
}
async reloadPluginList() {
pluginList.set([])
await this.updatePluginList();
} }
async updatePluginList(updatedDocumentPath?: FilePathWithPrefix): Promise<void> { async reloadPluginList(showMessage: boolean) {
this.pluginList = [];
pluginList.set(this.pluginList)
await this.updatePluginList(showMessage);
}
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
// pluginList.set([]); // pluginList.set([]);
if (!this.settings.usePluginSync) { if (!this.settings.usePluginSync) {
pluginList.set([]); this.pluginList = [];
pluginList.set(this.pluginList)
return; return;
} }
await runWithLock("update-plugin-list", false, async () => { await runWithLock("update-plugin-list", false, async () => {
if (updatedDocumentPath != "") pluginList.update(e => e.filter(ee => ee.documentPath != updatedDocumentPath)); // if (updatedDocumentPath != "") pluginList.update(e => e.filter(ee => ee.documentPath != updatedDocumentPath));
// const work: Record<string, Record<string, Record<string, Record<string, PluginDataEntryEx>>>> = {}; // const work: Record<string, Record<string, Record<string, Record<string, PluginDataEntryEx>>>> = {};
const entries = [] as PluginDataExDisplay[] const entries = [] as PluginDataExDisplay[]
const plugins = this.localDatabase.findEntries(ICXHeader + "", `${ICXHeader}\u{10ffff}`, { include_docs: true }); const plugins = this.localDatabase.findEntries(ICXHeader + "", `${ICXHeader}\u{10ffff}`, { include_docs: true });
@@ -169,16 +174,21 @@ export class ConfigSync extends LiveSyncCommands {
const processes = [] as Promise<void>[]; const processes = [] as Promise<void>[];
let count = 0; let count = 0;
pluginIsEnumerating.set(true); pluginIsEnumerating.set(true);
let processed = false;
try { try {
for await (const plugin of plugins) { for await (const plugin of plugins) {
const path = plugin.path || this.getPath(plugin); const path = plugin.path || this.getPath(plugin);
if (updatedDocumentPath && updatedDocumentPath != path) { if (updatedDocumentPath && updatedDocumentPath != path) {
continue; continue;
} }
processed = true;
const oldEntry = (this.pluginList.find(e => e.documentPath == path));
if (oldEntry && oldEntry.mtime == plugin.mtime) continue;
processes.push((async (v) => { processes.push((async (v) => {
const release = await semaphore.acquire(1); const release = await semaphore.acquire(1);
try { try {
Logger(`Enumerating files... ${count++}`, LOG_LEVEL.NOTICE, "get-plugins"); Logger(`Enumerating files... ${count++}`, logLevel, "get-plugins");
Logger(`plugin-${path}`, LOG_LEVEL.VERBOSE); Logger(`plugin-${path}`, LOG_LEVEL.VERBOSE);
const wx = await this.localDatabase.getDBEntry(path, null, false, false); const wx = await this.localDatabase.getDBEntry(path, null, false, false);
@@ -199,7 +209,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
} catch (ex) { } catch (ex) {
//TODO //TODO
Logger(`Something happened at enumerating customization :${v.path}`); Logger(`Something happened at enumerating customization :${v.path}`, LOG_LEVEL.NOTICE);
console.warn(ex); console.warn(ex);
} finally { } finally {
release(); release();
@@ -208,16 +218,18 @@ export class ConfigSync extends LiveSyncCommands {
)(plugin)); )(plugin));
} }
await Promise.all(processes); await Promise.all(processes);
pluginList.update(e => { let newList = [...this.pluginList];
let newList = [...e]; for (const item of entries) {
for (const item of entries) { newList = newList.filter(x => x.documentPath != item.documentPath);
console.log(item.documentPath); newList.push(item)
newList = newList.filter(x => x.documentPath != item.documentPath); }
newList.push(item) if (updatedDocumentPath != "" && !processed) newList = newList.filter(e => e.documentPath != updatedDocumentPath);
}
return newList; this.pluginList = newList;
}) pluginList.set(newList);
Logger(`All files enumerated`, LOG_LEVEL.NOTICE, "get-plugins");
Logger(`All files enumerated`, logLevel, "get-plugins");
} finally { } finally {
pluginIsEnumerating.set(false); pluginIsEnumerating.set(false);
} }
@@ -293,7 +305,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
const uPath = `${baseDir}/${loadedData.files[0].filename}` as FilePath; const uPath = `${baseDir}/${loadedData.files[0].filename}` as FilePath;
await this.storeCustomizationFiles(uPath); await this.storeCustomizationFiles(uPath);
await this.updatePluginList(uPath); await this.updatePluginList(true, uPath);
await delay(100); await delay(100);
Logger(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL.NOTICE); Logger(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL.NOTICE);
if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") { if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") {
@@ -329,6 +341,7 @@ export class ConfigSync extends LiveSyncCommands {
try { try {
if (data.documentPath) { if (data.documentPath) {
await this.deleteConfigOnDatabase(data.documentPath); await this.deleteConfigOnDatabase(data.documentPath);
await this.updatePluginList(false, data.documentPath);
Logger(`Delete: ${data.documentPath}`, LOG_LEVEL.NOTICE); Logger(`Delete: ${data.documentPath}`, LOG_LEVEL.NOTICE);
} }
return true; return true;
@@ -338,8 +351,11 @@ export class ConfigSync extends LiveSyncCommands {
} }
} }
parseReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) { async parseReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
if (docs._id.startsWith(ICXHeader)) { if (docs._id.startsWith(ICXHeader)) {
if (this.plugin.settings.usePluginSync) {
await this.updatePluginList(false, docs.path ? docs.path : this.getPath(docs));
}
if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) { if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) { if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
const fragment = createFragment((doc) => { const fragment = createFragment((doc) => {
@@ -347,7 +363,7 @@ export class ConfigSync extends LiveSyncCommands {
a.appendText(`Some configuration has been arrived, Press `); a.appendText(`Some configuration has been arrived, Press `);
a.appendChild(a.createEl("a", null, (anchor) => { a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE"; anchor.text = "HERE";
anchor.addEventListener("click", async () => { anchor.addEventListener("click", () => {
this.showPluginSyncModal(); this.showPluginSyncModal();
}); });
})); }));
@@ -375,10 +391,7 @@ export class ConfigSync extends LiveSyncCommands {
disposeMemoObject(updatedPluginKey); disposeMemoObject(updatedPluginKey);
}); });
}); });
} else {
this.updatePluginList(docs.path ? docs.path : this.getPath(docs));
} }
} }
return true; return true;
} }
@@ -502,7 +515,9 @@ export class ConfigSync extends LiveSyncCommands {
// Logger(`Configuration saving: ${prefixedFileName}`); // Logger(`Configuration saving: ${prefixedFileName}`);
if (dt.files.length == 0) { if (dt.files.length == 0) {
Logger(`Nothing left: deleting.. ${path}`); Logger(`Nothing left: deleting.. ${path}`);
return await this.deleteConfigOnDatabase(prefixedFileName); await this.deleteConfigOnDatabase(prefixedFileName);
await this.updatePluginList(false, prefixedFileName);
return
} }
const content = stringifyYaml(dt); const content = stringifyYaml(dt);
@@ -540,6 +555,7 @@ export class ConfigSync extends LiveSyncCommands {
}; };
} }
const ret = await this.localDatabase.putDBEntry(saveData); const ret = await this.localDatabase.putDBEntry(saveData);
await this.updatePluginList(false, saveData.path);
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Done`); Logger(`STORAGE --> DB:${prefixedFileName}: (config) Done`);
return ret; return ret;
} catch (ex) { } catch (ex) {
@@ -548,7 +564,6 @@ export class ConfigSync extends LiveSyncCommands {
return false; return false;
} }
}) })
// })
} }
async watchVaultRawEventsAsync(path: FilePath) { async watchVaultRawEventsAsync(path: FilePath) {
@@ -591,6 +606,7 @@ export class ConfigSync extends LiveSyncCommands {
for (const vp of deleteCandidate) { for (const vp of deleteCandidate) {
await this.deleteConfigOnDatabase(vp); await this.deleteConfigOnDatabase(vp);
} }
this.updatePluginList(false).then(/* fire and forget */);
} }
async deleteConfigOnDatabase(prefixedFileName: FilePathWithPrefix, forceWrite = false) { async deleteConfigOnDatabase(prefixedFileName: FilePathWithPrefix, forceWrite = false) {
@@ -618,6 +634,7 @@ export class ConfigSync extends LiveSyncCommands {
}; };
} }
await this.localDatabase.putRaw(saveData); await this.localDatabase.putRaw(saveData);
await this.updatePluginList(false, prefixedFileName);
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Done`); Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Done`);
} catch (ex) { } catch (ex) {
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Failed`); Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Failed`);

View File

@@ -207,21 +207,21 @@
const local = list.find((e) => e.term == thisTerm); const local = list.find((e) => e.term == thisTerm);
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
if (selectedItem && (await applyData(selectedItem))) { if (selectedItem && (await applyData(selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(local.documentPath)); scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(true, local.documentPath));
} }
} }
async function compareSelected() { async function compareSelected() {
const local = list.find((e) => e.term == thisTerm); const local = list.find((e) => e.term == thisTerm);
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
if (local && selectedItem && (await compareData(local, selectedItem))) { if (local && selectedItem && (await compareData(local, selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(local.documentPath)); scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(true, local.documentPath));
} }
} }
async function deleteSelected() { async function deleteSelected() {
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
const deletedPath = selectedItem.documentPath; // const deletedPath = selectedItem.documentPath;
if (selectedItem && (await deleteData(selectedItem))) { if (selectedItem && (await deleteData(selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(deletedPath)); scheduleTask("update-plugin-list", 250, () => addOn.reloadPluginList(true));
} }
} }
async function duplicateItem() { async function duplicateItem() {
@@ -234,7 +234,7 @@
} }
const key = `${plugin.app.vault.configDir}/${local.files[0].filename}`; const key = `${plugin.app.vault.configDir}/${local.files[0].filename}`;
await addOn.storeCustomizationFiles(key as FilePath, duplicateTermName); await addOn.storeCustomizationFiles(key as FilePath, duplicateTermName);
await addOn.updatePluginList(addOn.filenameToUnifiedKey(key, duplicateTermName)); await addOn.updatePluginList(false, addOn.filenameToUnifiedKey(key, duplicateTermName));
} }
} }
</script> </script>
@@ -281,6 +281,7 @@
<style> <style>
.spacer { .spacer {
min-width: 1px;
flex-grow: 1; flex-grow: 1;
} }
button { button {
@@ -311,4 +312,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
:global(.is-mobile) .spacer {
margin-left: auto;
}
</style> </style>

View File

@@ -18,10 +18,10 @@
let applyAllPluse = 0; let applyAllPluse = 0;
let isMaintenanceMode = false; let isMaintenanceMode = false;
async function requestUpdate() { async function requestUpdate() {
await addOn.updatePluginList(); await addOn.updatePluginList(true);
} }
async function requestReload() { async function requestReload() {
await addOn.reloadPluginList(); await addOn.reloadPluginList(true);
} }
pluginList.subscribe((e) => { pluginList.subscribe((e) => {
list = e; list = e;
@@ -169,6 +169,7 @@
align-items: center; align-items: center;
border-top: 1px solid var(--background-modifier-border); border-top: 1px solid var(--background-modifier-border);
padding: 4px; padding: 4px;
flex-wrap: wrap;
} }
.filerow { .filerow {
margin-left: 1.25em; margin-left: 1.25em;
@@ -176,6 +177,7 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding-right: 4px; padding-right: 4px;
flex-wrap: wrap;
} }
.filerow.hideeven:has(.even), .filerow.hideeven:has(.even),
.labelrow.hideeven:has(.even) { .labelrow.hideeven:has(.even) {
@@ -199,9 +201,11 @@
flex-direction: row; flex-direction: row;
justify-content: flex-end; justify-content: flex-end;
margin-top: 8px; margin-top: 8px;
flex-wrap: wrap;
} }
.buttons > button { .buttons > button {
margin-left: 4px; margin-left: 4px;
width: auto;
} }
label { label {
@@ -212,6 +216,11 @@
label > span { label > span {
margin-right: 0.25em; margin-right: 0.25em;
} }
:global(.is-mobile) .title,
:global(.is-mobile) .filetitle {
width: 100%;
}
.center { .center {
display: flex; display: flex;
justify-content: center; justify-content: center;

Submodule src/lib updated: 75f24a27b0...fb3070851f

View File

@@ -1350,7 +1350,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
const docMtime = ~~(doc.mtime / 1000); const docMtime = ~~(doc.mtime / 1000);
//TODO: some margin required. //TODO: some margin required.
if (localMtime >= docMtime) { if (localMtime >= docMtime) {
Logger(`${doc._id} Skipped, older than storage.`, LOG_LEVEL.VERBOSE); Logger(`${path} (${doc._id}, ${doc._rev}) Skipped, older than storage.`, LOG_LEVEL.VERBOSE);
return; return;
} }
} }
@@ -1361,12 +1361,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin
missingChildren: [] as string[], missingChildren: [] as string[],
timeout: now + this.chunkWaitTimeout, timeout: now + this.chunkWaitTimeout,
}; };
// If `Read chunks online` is enabled, retrieve chunks from the remote CouchDB directly. // If `Read chunks online` is disabled, chunks should be transferred before here.
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
if ((!this.settings.readChunksOnline) && "children" in doc) { if ((!this.settings.readChunksOnline) && "children" in doc) {
const c = await this.localDatabase.allDocsRaw<EntryDoc>({ keys: doc.children, include_docs: false }); const c = await this.localDatabase.collectChunksWithCache(doc.children)
const missing = c.rows.filter((e) => "error" in e).map((e) => e.key); const missing = c.filter((e) => !e.chunk).map((e) => e.id);
// fetch from remote if (missing.length > 0) Logger(`${path} (${doc._id}, ${doc._rev}) Queued (waiting ${missing.length} items)`, LOG_LEVEL.VERBOSE);
if (missing.length > 0) Logger(`${doc._id}(${doc._rev}) Queued (waiting ${missing.length} items)`, LOG_LEVEL.VERBOSE);
newQueue.missingChildren = missing; newQueue.missingChildren = missing;
this.queuedFiles.push(newQueue); this.queuedFiles.push(newQueue);
} else { } else {
@@ -1509,27 +1509,25 @@ export default class ObsidianLiveSyncPlugin extends Plugin
return; return;
} }
logHideTimer: NodeJS.Timeout = null;
setStatusBarText(message: string = null, log: string = null) { setStatusBarText(message: string = null, log: string = null) {
if (!this.statusBar) return; if (!this.statusBar) return;
const newMsg = typeof message == "string" ? message : this.lastMessage; const newMsg = typeof message == "string" ? message : this.lastMessage;
const newLog = typeof log == "string" ? log : this.lastLog; const newLog = typeof log == "string" ? log : this.lastLog;
if (`${this.lastMessage}-${this.lastLog}` != `${newMsg}-${newLog}`) { if (`${this.lastMessage}-${this.lastLog}` != `${newMsg}-${newLog}`) {
this.statusBar.setText(newMsg.split("\n")[0]); scheduleTask("update-display", 50, () => {
this.statusBar.setText(newMsg.split("\n")[0]);
if (this.settings.showStatusOnEditor) { if (this.settings.showStatusOnEditor) {
const root = activeDocument.documentElement; const root = activeDocument.documentElement;
const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`); const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`);
q.forEach(e => e.setAttr("data-log", '' + (newMsg + "\n" + newLog) + '')) q.forEach(e => e.setAttr("data-log", '' + (newMsg + "\n" + newLog) + ''))
} else { } else {
const root = activeDocument.documentElement; const root = activeDocument.documentElement;
const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`); const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`);
q.forEach(e => e.setAttr("data-log", '')) q.forEach(e => e.setAttr("data-log", ''))
} }
if (this.logHideTimer != null) { }, true);
clearTimeout(this.logHideTimer); scheduleTask("log-hide", 3000, () => this.setStatusBarText(null, ""));
}
this.logHideTimer = setTimeout(() => this.setStatusBarText(null, ""), 3000);
this.lastMessage = newMsg; this.lastMessage = newMsg;
this.lastLog = newLog; this.lastLog = newLog;
} }
@@ -2137,16 +2135,10 @@ Or if you are sure know what had been happened, we can unlock the database from
conflictedCheckFiles: FilePath[] = []; conflictedCheckFiles: FilePath[] = [];
// queueing the conflicted file check // queueing the conflicted file check
conflictedCheckTimer: number;
queueConflictedCheck(file: TFile) { queueConflictedCheck(file: TFile) {
this.conflictedCheckFiles = this.conflictedCheckFiles.filter((e) => e != file.path); this.conflictedCheckFiles = this.conflictedCheckFiles.filter((e) => e != file.path);
this.conflictedCheckFiles.push(getPathFromTFile(file)); this.conflictedCheckFiles.push(getPathFromTFile(file));
if (this.conflictedCheckTimer != null) { scheduleTask("check-conflict", 100, async () => {
window.clearTimeout(this.conflictedCheckTimer);
}
this.conflictedCheckTimer = window.setTimeout(async () => {
this.conflictedCheckTimer = null;
const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[]; const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[];
for (const filename of checkFiles) { for (const filename of checkFiles) {
try { try {
@@ -2158,7 +2150,7 @@ Or if you are sure know what had been happened, we can unlock the database from
Logger(ex); Logger(ex);
} }
} }
}, 100); });
} }
async showIfConflicted(filename: FilePathWithPrefix) { async showIfConflicted(filename: FilePathWithPrefix) {

View File

@@ -44,7 +44,10 @@ export function getPathFromTFile(file: TAbstractFile) {
} }
const tasks: { [key: string]: ReturnType<typeof setTimeout> } = {}; const tasks: { [key: string]: ReturnType<typeof setTimeout> } = {};
export function scheduleTask(key: string, timeout: number, proc: (() => Promise<any> | void)) { export function scheduleTask(key: string, timeout: number, proc: (() => Promise<any> | void), skipIfTaskExist?: boolean) {
if (skipIfTaskExist && key in tasks) {
return;
}
cancelTask(key); cancelTask(key);
tasks[key] = setTimeout(async () => { tasks[key] = setTimeout(async () => {
delete tasks[key]; delete tasks[key];
@@ -663,6 +666,14 @@ export const remoteDatabaseCleanup = async (plugin: ObsidianLiveSyncPlugin, dryR
return Number.parseInt((info as any)?.sizes?.[key] ?? 0); return Number.parseInt((info as any)?.sizes?.[key] ?? 0);
} }
await runWithLock("clean-up:remote", true, async () => { await runWithLock("clean-up:remote", true, async () => {
const CHUNK_SIZE = 100;
function makeChunkedArrayFromArray<T>(items: T[]): T[][] {
const chunked = [];
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
chunked.push(items.slice(i, i + CHUNK_SIZE));
}
return chunked;
}
try { try {
const ret = await plugin.replicator.connectRemoteCouchDBWithSetting(plugin.settings, plugin.isMobile); const ret = await plugin.replicator.connectRemoteCouchDBWithSetting(plugin.settings, plugin.isMobile);
if (typeof ret === "string") { if (typeof ret === "string") {
@@ -701,14 +712,17 @@ export const remoteDatabaseCleanup = async (plugin: ObsidianLiveSyncPlugin, dryR
return; return;
} }
Logger(`Deleting unreferenced chunks: ${removeItems}`, LOG_LEVEL.NOTICE, "clean-up-db"); Logger(`Deleting unreferenced chunks: ${removeItems}`, LOG_LEVEL.NOTICE, "clean-up-db");
const rets = await _requestToCouchDBFetch( const buffer = makeChunkedArrayFromArray(Object.entries(payload));
`${plugin.settings.couchDB_URI}/${plugin.settings.couchDB_DBNAME}`, for (const chunkedPayload of buffer) {
plugin.settings.couchDB_USER, const rets = await _requestToCouchDBFetch(
plugin.settings.couchDB_PASSWORD, `${plugin.settings.couchDB_URI}/${plugin.settings.couchDB_DBNAME}`,
"_purge", plugin.settings.couchDB_USER,
payload, "POST"); plugin.settings.couchDB_PASSWORD,
// const result = await rets(); "_purge",
Logger(JSON.stringify(await rets.json()), LOG_LEVEL.VERBOSE); chunkedPayload.reduce((p, c) => ({ ...p, [c[0]]: c[1] }), {}), "POST");
// const result = await rets();
Logger(JSON.stringify(await rets.json()), LOG_LEVEL.VERBOSE);
}
Logger(`Compacting database...`, LOG_LEVEL.NOTICE, "clean-up-db"); Logger(`Compacting database...`, LOG_LEVEL.NOTICE, "clean-up-db");
await db.compact(); await db.compact();
const endInfo = await db.info(); const endInfo = await db.info();

View File

@@ -14,4 +14,15 @@ I hope you will give it a try.
#### Minors #### Minors
- 0.19.1
- Fixed: Fixed hidden file handling on Linux
- Improved: Now customization sync works more smoothly.
- 0.19.2
- Fixed:
- Fixed garbage collection error while unreferenced chunks exist many.
- Fixed filename validation on Linux.
- Improved:
- Showing status is now thinned for performance.
- Enhance caching while collecting chunks.
... To continue on to `updates_old.md`. ... To continue on to `updates_old.md`.