Compare commits

...

16 Commits

Author SHA1 Message Date
vorotamoroz
bc158e9f2b bump 2023-01-17 17:46:06 +09:00
vorotamoroz
6513c53c7e Fixed:
- Document history is now displayed again.

Reorganised:
- Many files have been refactored.
2023-01-17 17:39:26 +09:00
vorotamoroz
5d1074065c bump 2023-01-16 17:33:31 +09:00
vorotamoroz
b444082b0c Fixed:
- Performance improvement
- Now `Chunk size` can be set to under one hundred.

New feature:
- The number of transfers required before replication stabilises is now displayed.
2023-01-16 17:31:37 +09:00
vorotamoroz
d5e6419504 bump 2023-01-15 11:17:08 +09:00
vorotamoroz
1bf1e1540d Fix diff check. 2023-01-15 11:09:23 +09:00
vorotamoroz
be1e6b11ac Fixed
- Large files addressed.
2023-01-13 19:43:39 +09:00
vorotamoroz
a486788572 bump 2023-01-06 16:33:54 +09:00
vorotamoroz
e5784a1da6 Fixed:
- Conflict merge of internal files is no longer broken.
Improved:
- Smoother status display inside the editor.
2023-01-06 16:27:39 +09:00
vorotamoroz
2100e22276 bump 2022-12-27 18:12:00 +09:00
vorotamoroz
ec08dc5fe8 Improved:
- Performance improved
  Prebuilt PouchDB is no longer used.

Fixed:
- Merging hidden files is also fixed.

New Feature:
- Now we can synchronise automatically after merging conflicts.
2022-12-27 18:09:51 +09:00
vorotamoroz
c92e94e552 fix eslint configuration 2022-12-27 17:58:42 +09:00
vorotamoroz
c7db8592c6 bump 2022-12-26 16:16:46 +09:00
vorotamoroz
fc3617d9f9 Fixed:
- Fixed merging issues.
- Fixed button styling.

Changed:
- Default behaviour of conflict checking on synchronising.
2022-12-26 16:12:57 +09:00
vorotamoroz
34c1b040db bump 2022-12-24 21:05:32 +09:00
vorotamoroz
6b85aecafe Fixed:
- Now our renamed/deleted files have been surely deleted again.
2022-12-24 21:05:09 +09:00
17 changed files with 1117 additions and 239 deletions

View File

@@ -1,3 +1,4 @@
npm node_modules
node_modules
build
.eslintrc.js.bak
.eslintrc.js.bak
src/lib/src/patches/pouchdb-utils

View File

@@ -25,14 +25,16 @@ esbuild
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
"UPDATE_INFO": `${updateInfo}`,
"global":"window",
},
external: ["obsidian", "electron", ...builtins],
external: ["obsidian", "electron", "crypto"],
format: "cjs",
watch: !prod,
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
platform: "browser",
plugins: [
sveltePlugin({
preprocess: sveltePreprocess(),

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.17.5",
"version": "0.17.13",
"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",

858
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.17.5",
"version": "0.17.13",
"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",
@@ -24,11 +24,20 @@
"eslint": "^8.28.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"events": "^3.3.0",
"obsidian": "^0.16.3",
"postcss": "^8.4.19",
"postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.0",
"pouchdb-adapter-idb": "^8.0.0",
"pouchdb-core": "^8.0.0",
"pouchdb-find": "^8.0.0",
"pouchdb-mapreduce": "^8.0.0",
"pouchdb-replication": "^8.0.0",
"pouchdb-utils": "file:src/lib/src/patches/pouchdb-utils",
"svelte": "^3.53.1",
"svelte-preprocess": "^4.10.7",
"transform-pouch": "^2.0.0",
"tslib": "^2.4.1",
"typescript": "^4.9.3"
},

View File

@@ -1,7 +1,7 @@
import { App, Modal } from "obsidian";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT } from "diff-match-patch";
import { diff_result } from "./lib/src/types";
import { escapeStringToHTML } from "./lib/src/utils";
import { escapeStringToHTML } from "./lib/src/strbin";
export class ConflictResolveModal extends Modal {
// result: Array<[number, string]>;

View File

@@ -1,10 +1,12 @@
import { TFile, Modal, App } from "obsidian";
import { path2id } from "./utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML, isValidPath } from "./lib/src/utils";
import { base64ToArrayBuffer, base64ToString, escapeStringToHTML } from "./lib/src/strbin";
import { isValidPath } from "./lib/src/path";
import ObsidianLiveSyncPlugin from "./main";
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { LoadedEntry, LOG_LEVEL } from "./lib/src/types";
import { Logger } from "./lib/src/logger";
import { getDocData } from "./lib/src/utils";
export class DocumentHistoryModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
@@ -64,7 +66,7 @@ export class DocumentHistoryModal extends Modal {
this.currentDoc = w;
this.info.innerHTML = `Modified:${new Date(w.mtime).toLocaleString()}`;
let result = "";
const w1data = w.datatype == "plain" ? w.data : base64ToString(w.data);
const w1data = w.datatype == "plain" ? getDocData(w.data) : base64ToString(w.data);
this.currentDeleted = w.deleted;
this.currentText = w1data;
if (this.showDiff) {
@@ -74,7 +76,7 @@ export class DocumentHistoryModal extends Modal {
const w2 = await db.getDBEntry(path2id(this.file), { rev: oldRev }, false, false, true);
if (w2 != false) {
const dmp = new diff_match_patch();
const w2data = w2.datatype == "plain" ? w2.data : base64ToString(w2.data);
const w2data = w2.datatype == "plain" ? getDocData(w2.data) : base64ToString(w2.data);
const diff = dmp.diff_main(w2data, w1data);
dmp.diff_cleanupSemantic(diff);
for (const v of diff) {
@@ -176,7 +178,7 @@ export class DocumentHistoryModal extends Modal {
Logger("Path is not valid to write content.", LOG_LEVEL.INFO);
}
if (this.currentDoc?.datatype == "plain") {
await this.app.vault.adapter.write(pathToWrite, this.currentDoc.data);
await this.app.vault.adapter.write(pathToWrite, getDocData(this.currentDoc.data));
await focusFile(pathToWrite);
this.close();
} else if (this.currentDoc?.datatype == "newnote") {

View File

@@ -4,7 +4,7 @@ import { LocalPouchDBBase } from "./lib/src/LocalPouchDBBase.js";
import { Logger } from "./lib/src/logger.js";
import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { EntryDoc, LOG_LEVEL } from "./lib/src/types.js";
import { enableEncryption } from "./lib/src/utils.js";
import { enableEncryption } from "./lib/src/utils_couchdb.js";
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
import { id2path, path2id } from "./utils.js";

View File

@@ -1,21 +1,17 @@
import { App, Modal } from "obsidian";
import { escapeStringToHTML } from "./lib/src/utils";
import { logMessageStore } from "./lib/src/stores";
import { escapeStringToHTML } from "./lib/src/strbin";
import ObsidianLiveSyncPlugin from "./main";
export class LogDisplayModal extends Modal {
plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement;
unsubscribe: () => void;
constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
super(app);
this.plugin = plugin;
}
updateLog() {
let msg = "";
for (const v of this.plugin.logMessage) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
}
onOpen() {
const { contentEl } = this;
@@ -25,13 +21,18 @@ export class LogDisplayModal extends Modal {
div.addClass("op-scrollable");
div.addClass("op-pre");
this.logEl = div;
this.updateLog = this.updateLog.bind(this);
this.plugin.addLogHook = this.updateLog;
this.updateLog();
this.unsubscribe = logMessageStore.observe((e) => {
let msg = "";
for (const v of e) {
msg += escapeStringToHTML(v) + "<br>";
}
this.logEl.innerHTML = msg;
})
logMessageStore.invalidate();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.plugin.addLogHook = null;
if (this.unsubscribe) this.unsubscribe();
}
}

View File

@@ -1,7 +1,9 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "obsidian";
import { DEFAULT_SETTINGS, LOG_LEVEL, ObsidianLiveSyncSettings, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils";
import { delay, Semaphore, versionNumberString2Number } from "./lib/src/utils";
import { delay } from "./lib/src/utils";
import { Semaphore } from "./lib/src/semaphore";
import { versionNumberString2Number } from "./lib/src/strbin";
import { Logger } from "./lib/src/logger";
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
import { testCrypt } from "./lib/src/e2ee_v2";
@@ -133,6 +135,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (this.plugin.settings.syncOnFileOpen) return true;
if (this.plugin.settings.syncOnSave) return true;
if (this.plugin.settings.syncOnStart) return true;
if (this.plugin.settings.syncAfterMerge) return true;
if (this.plugin.localDatabase.syncStatus == "CONNECTED") return true;
if (this.plugin.localDatabase.syncStatus == "PAUSED") return true;
return false;
@@ -144,7 +147,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
new Setting(setupWizardEl)
.setName("Discard the existing configuration and set up")
.addButton((text) => {
text.setButtonText("Next").onClick(async () => {
text.setButtonText("Next").onClick(() => {
if (JSON.stringify(this.plugin.settings) != JSON.stringify(DEFAULT_SETTINGS)) {
this.plugin.localDatabase.closeReplication();
this.plugin.settings = { ...DEFAULT_SETTINGS };
@@ -170,6 +173,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.localDatabase.closeReplication();
await this.plugin.saveSettings();
containerEl.addClass("isWizard");
@@ -226,7 +230,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
syncLive.forEach((e) => {
e.setDisabled(false).setTooltip("");
});
} else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication) {
} else if (this.plugin.settings.syncOnFileOpen || this.plugin.settings.syncOnSave || this.plugin.settings.syncOnStart || this.plugin.settings.periodicReplication || this.plugin.settings.syncAfterMerge) {
syncNonLive.forEach((e) => {
e.setDisabled(false).setTooltip("");
});
@@ -387,6 +391,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
this.plugin.settings.encrypt = this.plugin.settings.workingEncrypt;
this.plugin.settings.passphrase = this.plugin.settings.workingPassphrase;
this.plugin.settings.useDynamicIterationCount = this.plugin.settings.workingUseDynamicIterationCount;
@@ -412,7 +417,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Apply")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(true);
})
@@ -422,7 +426,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Apply w/o rebuilding")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
@@ -435,7 +438,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
await this.plugin.saveSettings();
applyDisplayEnabled();
@@ -470,7 +473,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Send")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("remoteOnly");
})
@@ -485,7 +487,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Rebuild")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("rebuildBothByThisDevice");
})
@@ -697,7 +698,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Next")
.setClass("mod-cta")
.setDisabled(false)
.onClick(async () => {
.onClick(() => {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
@@ -718,7 +719,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Discard exist database and proceed")
.setDisabled(false)
.setWarning()
.onClick(async () => {
.onClick(() => {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
@@ -760,7 +761,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Fetch")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
await rebuildDB("localOnly");
})
@@ -798,7 +798,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
button
.setButtonText("Next")
.setDisabled(false)
.onClick(async () => {
.onClick(() => {
changeDisplay("40");
})
);
@@ -954,7 +954,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
applyDisplayEnabled();
})
)
),
new Setting(containerSyncSettingEl)
.setName("Sync after merging file")
.setDesc("Sync automatically after merging files")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.syncAfterMerge).onChange(async (value) => {
this.plugin.settings.syncAfterMerge = value;
await this.plugin.saveSettings();
applyDisplayEnabled();
})
),
);
new Setting(containerSyncSettingEl)
@@ -1103,7 +1113,6 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setButtonText("Touch")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-left")
.onClick(async () => {
const filesAll = await this.plugin.scanInternalFiles();
const targetFiles = await this.plugin.filterTargetFiles(filesAll);
@@ -1177,8 +1186,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.customChunkSize + "")
.onChange(async (value) => {
let v = Number(value);
if (isNaN(v) || v < 100) {
v = 100;
if (isNaN(v) || v < 1) {
v = 1;
}
this.plugin.settings.customChunkSize = v;
await this.plugin.saveSettings();
@@ -1278,6 +1287,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = false;
this.plugin.settings.syncOnFileOpen = false;
this.plugin.settings.syncAfterMerge = false;
if (currentPreset == "LIVESYNC") {
this.plugin.settings.liveSync = true;
Logger("Synchronization setting configured as LiveSync.", LOG_LEVEL.NOTICE);
@@ -1287,6 +1297,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncOnSave = false;
this.plugin.settings.syncOnStart = true;
this.plugin.settings.syncOnFileOpen = true;
this.plugin.settings.syncAfterMerge = true;
Logger("Synchronization setting configured as Periodic sync with batch database update.", LOG_LEVEL.NOTICE);
} else {
Logger("All synchronization disabled.", LOG_LEVEL.NOTICE);
@@ -1414,7 +1425,7 @@ ${stringifyYaml(pluginConfig)}`;
const releaser = await semaphore.acquire(1, "verifyAndRepair");
try {
Logger(`Update into ${file.path}`);
Logger(`UPDATE DATABASE ${file.path}`);
await this.plugin.updateIntoDB(file, false, null, true);
i++;
Logger(`${i}/${files.length}\n${file.path}`, LOG_LEVEL.NOTICE, "verify");

View File

@@ -2,7 +2,7 @@
import ObsidianLiveSyncPlugin from "./main";
import { onMount } from "svelte";
import { DevicePluginList, PluginDataEntry } from "./types";
import { versionNumberString2Number } from "./lib/src/utils";
import { versionNumberString2Number } from "./lib/src/strbin";
type JudgeResult = "" | "NEWER" | "EVEN" | "EVEN_BUT_DIFFERENT" | "OLDER" | "REMOTE_ONLY";

Submodule src/lib updated: 9fe5ce421f...133bae3607

View File

@@ -1,34 +1,14 @@
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App, } from "obsidian";
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, PluginManifest, App } from "obsidian";
import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, InternalFileEntry } from "./lib/src/types";
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo } from "./types";
import {
base64ToString,
arrayBufferToBase64,
base64ToArrayBuffer,
isValidPath,
versionNumberString2Number,
runWithLock,
shouldBeIgnored,
getProcessingCounts,
setLockNotifier,
isPlainText,
setNoticeClass,
NewNotice,
getLocks,
WrappedNotice,
Semaphore,
} from "./lib/src/utils";
import { Logger, setLogger } from "./lib/src/logger";
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, InternalFileInfo, queueItem } from "./types";
import { getDocData, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { LocalPouchDB } from "./LocalPouchDB";
import { LogDisplayModal } from "./LogDisplayModal";
import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { applyPatch, clearAllPeriodic, clearAllTriggers, clearTrigger, disposeMemoObject, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, memoIfNotExist, memoObject, path2id, retrieveMemoObject, setTrigger, tryParseJSON } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee_v2";
@@ -36,6 +16,13 @@ const isDebug = false;
import { InputStringDialog, PluginDialogModal, PopoverSelectString } from "./dialogs";
import { isCloudantURI } from "./lib/src/utils_couchdb";
import { getGlobalStore, observeStores } from "./lib/src/store";
import { lockStore, logMessageStore, logStore } from "./lib/src/stores";
import { NewNotice, setNoticeClass, WrappedNotice } from "./lib/src/wrapper";
import { base64ToString, versionNumberString2Number, base64ToArrayBuffer, arrayBufferToBase64 } from "./lib/src/strbin";
import { isPlainText, isValidPath, shouldBeIgnored } from "./lib/src/path";
import { runWithLock } from "./lib/src/lock";
import { Semaphore } from "./lib/src/semaphore";
setNoticeClass(Notice);
@@ -46,6 +33,7 @@ const FileWatchEventQueueMax = 10;
function getAbstractFileByPath(path: string): TAbstractFile | null {
// Hidden API but so useful.
// @ts-ignore
if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
// @ts-ignore
return app.vault.getAbstractFileByPathInsensitive(path);
@@ -133,10 +121,10 @@ type FileEventItem = {
type: FileEventType,
args: FileEventArgs
}
export default class ObsidianLiveSyncPlugin extends Plugin {
settings: ObsidianLiveSyncSettings;
localDatabase: LocalPouchDB;
logMessage: string[] = [];
statusBar: HTMLElement;
statusBar2: HTMLElement;
suspended: boolean;
@@ -283,7 +271,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async onload() {
setLogger(this.addLog.bind(this)); // Logger moved to global.
logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
Logger("loading plugin");
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
@@ -309,6 +297,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.syncOnSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
@@ -342,7 +331,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
this.refreshStatusText = this.refreshStatusText.bind(this);
this.statusBar2 = this.addStatusBarItem();
this.watchVaultChange = this.watchVaultChange.bind(this);
@@ -357,6 +345,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.setPeriodicSync = this.setPeriodicSync.bind(this);
this.clearPeriodicSync = this.clearPeriodicSync.bind(this);
this.periodicSync = this.periodicSync.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
@@ -375,6 +364,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.syncOnSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.autoSweepPlugins = false;
this.settings.usePluginSync = false;
this.settings.suspendFileWatching = true;
@@ -630,9 +620,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.triggerCheckPluginUpdate = debounce(this.triggerCheckPluginUpdate.bind(this), 3000);
setLockNotifier(() => {
this.refreshStatusText();
});
this.addCommand({
id: "livesync-plugin-dialog",
name: "Show Plugins and their settings",
@@ -722,9 +710,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//@ts-ignore
const isMobile = this.app.isMobile;
this.localDatabase = new LocalPouchDB(this.settings, vaultName, isMobile);
this.localDatabase.updateInfo = () => {
this.refreshStatusText();
};
this.observeForLogs();
return await this.localDatabase.initializeDatabase();
}
@@ -866,6 +852,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
if (this.watchedFileEventQueue[i].type != type) break;
this.watchedFileEventQueue.remove(this.watchedFileEventQueue[i]);
this.queuedFilesStore.set({ queuedItems: this.queuedFiles, fileEventItems: this.watchedFileEventQueue });
}
}
@@ -878,7 +865,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
ctx
}
})
this.refreshStatusText();
this.queuedFilesStore.set({ queuedItems: this.queuedFiles, fileEventItems: this.watchedFileEventQueue });
if (this.isReady) {
await this.procFileEvent();
}
@@ -917,8 +904,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (queue.type == "DELETE") {
if (file instanceof TFile) {
await this.deleteFromDB(file);
} else if (file instanceof TFolder) {
await this.deleteFolderOnDB(file);
}
}
if (queue.type == "RENAME") {
@@ -933,11 +918,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.localDatabase.kvDB.set(key, file.stat.mtime);
}
}
this.refreshStatusText();
} while (this.watchedFileEventQueue.length != 0);
return true;
})
this.refreshStatusText();
return ret;
}
@@ -1052,30 +1035,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
return this.getFilePath(file.parent) + "/" + file.name;
}
async watchVaultRenameAsync(file: TAbstractFile, oldFile: any, cache?: CacheData) {
async watchVaultRenameAsync(file: TFile, oldFile: any, cache?: CacheData) {
Logger(`${oldFile} renamed to ${file.path}`, LOG_LEVEL.VERBOSE);
if (file instanceof TFolder) {
const newFiles = this.GetAllFilesRecursively(file);
// for guard edge cases. this won't happen and each file's event will be raise.
for (const i of newFiles) {
try {
const newFilePath = normalizePath(this.getFilePath(i));
const newFile = getAbstractFileByPath(newFilePath);
if (newFile instanceof TFile) {
Logger(`save ${newFile.path} into db`);
await this.updateIntoDB(newFile);
}
} catch (ex) {
Logger(ex);
}
}
Logger(`delete below ${oldFile} from db`);
await this.deleteFromDBbyPath(oldFile);
} else if (file instanceof TFile) {
if (file instanceof TFile) {
try {
Logger(`file save ${file.path} into db`);
// Logger(`RENAMING.. ${file.path} into db`);
await this.updateIntoDB(file, false, cache);
Logger(`deleted ${oldFile} from db`);
// Logger(`deleted ${oldFile} from db`);
await this.deleteFromDBbyPath(oldFile);
} catch (ex) {
Logger(ex);
@@ -1083,7 +1049,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
}
addLogHook: () => void = null;
//--> Basic document Functions
notifies: { [key: string]: { notice: Notice; timer: NodeJS.Timeout; count: number } } = {};
@@ -1104,12 +1069,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
const newMessage = timestamp + "->" + messageContent;
this.logMessage = [].concat(this.logMessage).concat([newMessage]).slice(-100);
console.log(vaultName + ":" + newMessage);
logMessageStore.apply(e => [...e, newMessage].slice(-100));
this.setStatusBarText(null, messageContent.substring(0, 30));
// if (message instanceof Error) {
// console.trace(message);
// }
if (level >= LOG_LEVEL.NOTICE) {
if (!key) key = messageContent;
@@ -1148,7 +1110,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
};
}
}
if (this.addLogHook != null) this.addLogHook();
}
async ensureDirectory(fullPath: string) {
@@ -1196,7 +1157,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
ctime: doc.ctime,
mtime: doc.mtime,
});
// this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
Logger(msg + path);
touch(newFile);
this.app.vault.trigger("create", newFile);
@@ -1212,11 +1172,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
await this.ensureDirectory(path);
try {
const newFile = await this.app.vault.create(normalizePath(path), doc.data, {
const newFile = await this.app.vault.create(normalizePath(path), getDocData(doc.data), {
ctime: doc.ctime,
mtime: doc.mtime,
});
// this.batchFileChange = this.batchFileChange.filter((e) => e != newFile.path);
Logger(msg + path);
touch(newFile);
this.app.vault.trigger("create", newFile);
@@ -1239,11 +1198,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} else {
await this.app.vault.delete(file);
}
Logger(`deleted:${file.path}`);
Logger(`other items:${dir.children.length}`);
Logger(`xxx <- STORAGE (deleted) ${file.path}`);
Logger(`files: ${dir.children.length}`);
if (dir.children.length == 0) {
if (!this.settings.doNotDeleteFolder) {
Logger(`all files deleted by replication, so delete dir`);
Logger(`All files under the parent directory (${dir}) have been deleted, so delete this one.`);
await this.deleteVaultItem(dir);
}
}
@@ -1302,7 +1261,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
await this.ensureDirectory(path);
try {
await this.app.vault.modify(file, doc.data, { ctime: doc.ctime, mtime: doc.mtime });
await this.app.vault.modify(file, getDocData(doc.data), { ctime: doc.ctime, mtime: doc.mtime });
Logger(msg + path);
// this.batchFileChange = this.batchFileChange.filter((e) => e != file.path);
const xf = getAbstractFileByPath(file.path) as TFile;
@@ -1351,7 +1310,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
}
);
this.refreshStatusText();
}
async handleDBChangedAsync(change: EntryBody) {
@@ -1382,7 +1340,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.doc2storage_modify(doc, file);
queueConflictCheck();
} else {
const d = await this.localDatabase.getDBEntryMeta(id2path(change._id), { conflicts: true })
const d = await this.localDatabase.getDBEntryMeta(id2path(change._id), { conflicts: true }, true);
if (d && !d._conflicts) {
await this.doc2storage_modify(doc, file);
} else {
@@ -1396,13 +1354,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
}
queuedFiles: {
entry: EntryBody;
missingChildren: string[];
timeout?: number;
done?: boolean;
warned?: boolean;
}[] = [];
queuedFiles = [] as queueItem[];
queuedFilesStore = getGlobalStore("queuedFiles", { queuedItems: [] as queueItem[], fileEventItems: [] as FileEventItem[] });
chunkWaitTimeout = 60000;
saveQueuedFiles() {
@@ -1429,7 +1382,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.syncInternalFilesAndDatabase("pull", false, false, w);
Logger(`Applying hidden ${w.length} files changed`);
});
this.refreshStatusText();
}
procInternalFile(filename: string) {
this.procInternalFiles.push(filename);
@@ -1461,6 +1413,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
}
this.queuedFiles = this.queuedFiles.filter((e) => !e.done);
this.queuedFilesStore.set({ queuedItems: this.queuedFiles, fileEventItems: this.watchedFileEventQueue });
this.saveQueuedFiles();
}
parseIncomingChunk(chunk: PouchDB.Core.ExistingDocument<EntryDoc>) {
@@ -1524,7 +1477,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
//---> Sync
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
this.refreshStatusText();
for (const change of docs) {
if (isPluginChunk(change._id)) {
if (this.settings.notifyPluginOrSettingUpdated) {
@@ -1596,8 +1548,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
setPeriodicSync() {
this.clearPeriodicSync();
if (this.settings.periodicReplication && this.settings.periodicReplicationInterval > 0) {
this.clearPeriodicSync();
this.periodicSyncHandler = this.setInterval(async () => await this.periodicSync(), Math.max(this.settings.periodicReplicationInterval, 30) * 1000);
}
}
@@ -1639,7 +1591,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
if (this.settings.liveSync) {
this.localDatabase.openReplication(this.settings, true, false, this.parseReplicationResult);
this.refreshStatusText();
}
if (this.settings.syncInternalFiles) {
await this.syncInternalFilesAndDatabase("safe", false);
@@ -1651,63 +1602,80 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
lastMessage = "";
observeForLogs() {
const observer__ = observeStores(this.queuedFilesStore, lockStore);
const observer = observeStores(observer__, this.localDatabase.stat);
observer.observe(e => {
const sent = e.sent;
const arrived = e.arrived;
const maxPullSeq = e.maxPullSeq;
const maxPushSeq = e.maxPushSeq;
const lastSyncPullSeq = e.lastSyncPullSeq;
const lastSyncPushSeq = e.lastSyncPushSeq;
let pushLast = "";
let pullLast = "";
let w = "";
switch (e.syncStatus) {
case "CLOSED":
case "COMPLETED":
case "NOT_CONNECTED":
w = "⏹";
break;
case "STARTED":
w = "🌀";
break;
case "PAUSED":
w = "💤";
break;
case "CONNECTED":
w = "⚡";
pushLast = ((lastSyncPushSeq == 0) ? "" : (lastSyncPushSeq >= maxPushSeq ? " (LIVE)" : ` (${maxPushSeq - lastSyncPushSeq})`));
pullLast = ((lastSyncPullSeq == 0) ? "" : (lastSyncPullSeq >= maxPullSeq ? " (LIVE)" : ` (${maxPullSeq - lastSyncPullSeq})`));
break;
case "ERRORED":
w = "⚠";
break;
default:
w = "?";
}
this.statusBar.title = e.syncStatus;
let waiting = "";
if (this.settings.batchSave) {
waiting = " " + this.watchedFileEventQueue.map((e) => "🛫").join("");
waiting = waiting.replace(/(🛫){10}/g, "🚀");
}
let queued = "";
const queue = Object.entries(e.queuedItems).filter((e) => !e[1].warned);
const queuedCount = queue.length;
if (queuedCount) {
const pieces = queue.map((e) => e[1].missingChildren).reduce((prev, cur) => prev + cur.length, 0);
queued = ` 🧩 ${queuedCount} (${pieces})`;
}
const processes = e.count;
const processesDisp = processes == 0 ? "" : `${processes}`;
const message = `Sync: ${w}${sent}${pushLast}${arrived}${pullLast}${waiting}${processesDisp}${queued}`;
// const locks = getLocks();
const pendingTask = e.pending.length
? "\nPending: " +
Object.entries(e.pending.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ")
: "";
const runningTask = e.running.length
? "\nRunning: " +
Object.entries(e.running.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ")
: "";
this.setStatusBarText(message + pendingTask + runningTask);
})
}
refreshStatusText() {
const sent = this.localDatabase.docSent;
const arrived = this.localDatabase.docArrived;
let w = "";
switch (this.localDatabase.syncStatus) {
case "CLOSED":
case "COMPLETED":
case "NOT_CONNECTED":
w = "⏹";
break;
case "STARTED":
w = "🌀";
break;
case "PAUSED":
w = "💤";
break;
case "CONNECTED":
w = "⚡";
break;
case "ERRORED":
w = "⚠";
break;
default:
w = "?";
}
this.statusBar.title = this.localDatabase.syncStatus;
let waiting = "";
if (this.settings.batchSave) {
waiting = " " + this.watchedFileEventQueue.map((e) => "🛫").join("");
waiting = waiting.replace(/(🛫){10}/g, "🚀");
}
let queued = "";
const queue = Object.entries(this.queuedFiles).filter((e) => !e[1].warned);
const queuedCount = queue.length;
if (queuedCount) {
const pieces = queue.map((e) => e[1].missingChildren).reduce((prev, cur) => prev + cur.length, 0);
queued = ` 🧩 ${queuedCount} (${pieces})`;
}
const processes = getProcessingCounts();
const processesDisp = processes == 0 ? "" : `${processes}`;
const message = `Sync: ${w}${sent}${arrived}${waiting}${processesDisp}${queued}`;
const locks = getLocks();
const pendingTask = locks.pending.length
? "\nPending: " +
Object.entries(locks.pending.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ")
: "";
const runningTask = locks.running.length
? "\nRunning: " +
Object.entries(locks.running.reduce((p, c) => ({ ...p, [c]: (p[c] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ")
: "";
this.setStatusBarText(message + pendingTask + runningTask);
return;
}
logHideTimer: NodeJS.Timeout = null;
@@ -1720,10 +1688,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.settings.showStatusOnEditor) {
const root = activeDocument.documentElement;
root.style.setProperty("--slsmessage", '"' + (newMsg + "\n" + newLog).split("\n").join("\\a ") + '"');
const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`);
q.forEach(e => e.setAttr("data-log", '' + (newMsg + "\n" + newLog) + ''))
} else {
const root = activeDocument.documentElement;
root.style.setProperty("--slsmessage", '""');
const q = root.querySelectorAll(`.CodeMirror-wrap,.cm-s-obsidian>.cm-editor,.canvas-wrapper`);
q.forEach(e => e.setAttr("data-log", ''))
}
if (this.logHideTimer != null) {
clearTimeout(this.logHideTimer);
@@ -1860,7 +1830,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
};
await runAll("UPDATE DATABASE", onlyInStorage, async (e) => {
Logger(`Update into ${e.path}`);
Logger(`UPDATE DATABASE ${e.path}`);
await this.updateIntoDB(e, initialScan);
});
if (!initialScan) {
@@ -1951,18 +1921,18 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
try {
const doc = await this.localDatabase.getDBEntry(path, { rev: rev }, false, false, true);
if (doc === false) return false;
let data = doc.data;
let data = getDocData(doc.data)
if (doc.datatype == "newnote") {
data = base64ToString(doc.data);
data = base64ToString(data);
} else if (doc.datatype == "plain") {
data = doc.data;
// NO OP.
}
return {
deleted: doc.deleted || doc._deleted,
ctime: doc.ctime,
mtime: doc.mtime,
rev: rev,
data: data,
data: data
};
} catch (ex) {
if (ex.status && ex.status == 404) {
@@ -2176,7 +2146,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
const conflictedRevNo = Number(conflictedRev.split("-")[0]);
//Search
const revFrom = (await this.localDatabase.localDatabase.get(id2path(path), { revs_info: true })) as unknown as LoadedEntry & PouchDB.Core.GetMeta;
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first().rev ?? "";
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? "";
let p = undefined;
if (commonBase) {
if (isSensibleMargeApplicable(path)) {
@@ -2297,6 +2267,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
await this.pullFile(filename);
Logger("concat both file");
if (this.settings.syncAfterMerge && !this.suspended) {
await this.replicate();
}
setTimeout(() => {
//resolved, check again.
this.showIfConflicted(filename);
@@ -2304,9 +2277,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} else if (toDelete == null) {
Logger("Leave it still conflicted");
} else {
Logger(`Conflict resolved:${filename}`);
await this.localDatabase.deleteDBEntry(filename, { rev: toDelete });
await this.pullFile(filename, null, true, toKeep);
Logger(`Conflict resolved:${filename}`);
if (this.settings.syncAfterMerge && !this.suspended) {
await this.replicate();
}
setTimeout(() => {
//resolved, check again.
this.showIfConflicted(filename);
@@ -2354,6 +2330,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
if (conflictCheckResult === true) {
//auto resolved, but need check again;
if (this.settings.syncAfterMerge && !this.suspended) {
await this.replicate();
}
Logger("conflict:Automatically merged, but we have to check it again");
setTimeout(() => {
this.showIfConflicted(filename);
@@ -2447,11 +2426,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (shouldBeIgnored(file.path)) {
return;
}
let content = "";
let content: string | string[];
let datatype: "plain" | "newnote" = "newnote";
if (!cache) {
if (!isPlainText(file.name)) {
Logger(`Reading : ${file.path}`, LOG_LEVEL.VERBOSE);
const contentBin = await this.app.vault.readBinary(file);
Logger(`Processing: ${file.path}`, LOG_LEVEL.VERBOSE);
content = await arrayBufferToBase64(contentBin);
datatype = "newnote";
} else {
@@ -2487,12 +2468,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
try {
const old = await this.localDatabase.getDBEntry(fullPath, null, false, false);
if (old !== false) {
const oldData = { data: old.data, deleted: old._deleted || old.deleted, };
const oldData = { data: old.data, deleted: old._deleted || old.deleted };
const newData = { data: d.data, deleted: d._deleted || d.deleted };
if (JSON.stringify(oldData) == JSON.stringify(newData)) {
Logger(msg + "Skipped (not changed) " + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
return true;
}
if (oldData.deleted != newData.deleted) return false;
if (!isDocContentSame(old.data, newData.data)) return false;
Logger(msg + "Skipped (not changed) " + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL.VERBOSE);
return true;
// d._rev = old._rev;
}
} catch (ex) {
@@ -2554,7 +2535,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async getPluginList(): Promise<{ plugins: PluginList; allPlugins: DevicePluginList; thisDevicePlugins: DevicePluginList }> {
const db = this.localDatabase.localDatabase;
const docList = await db.allDocs<PluginDataEntry>({ startkey: PSCHeader, endkey: PSCHeaderEnd, include_docs: false });
const oldDocs: PluginDataEntry[] = ((await Promise.all(docList.rows.map(async (e) => await this.localDatabase.getDBEntry(e.id)))).filter((e) => e !== false) as LoadedEntry[]).map((e) => JSON.parse(e.data));
const oldDocs: PluginDataEntry[] = ((await Promise.all(docList.rows.map(async (e) => await this.localDatabase.getDBEntry(e.id)))).filter((e) => e !== false) as LoadedEntry[]).map((e) => JSON.parse(getDocData(e.data)));
const plugins: { [key: string]: PluginDataEntry[] } = {};
const allPlugins: { [key: string]: PluginDataEntry } = {};
const thisDevicePlugins: { [key: string]: PluginDataEntry } = {};
@@ -2962,33 +2943,33 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (doc._conflicts.length == 0) return false;
Logger(`Hidden file conflicted:${id2filenameInternalChunk(id)}`);
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
const revA = doc._rev;
const revB = conflicts[0];
const conflictedRev = conflicts[0];
const conflictedRevNo = Number(conflictedRev.split("-")[0]);
//Search
const revFrom = (await this.localDatabase.localDatabase.get(id, { revs_info: true })) as unknown as LoadedEntry & PouchDB.Core.GetMeta;
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first().rev ?? "";
const result = await this.mergeObject(id, commonBase, doc._rev, conflictedRev);
if (result) {
Logger(`Object merge:${id}`, LOG_LEVEL.INFO);
const filename = id2filenameInternalChunk(id);
const isExists = await this.app.vault.adapter.exists(filename);
if (!isExists) {
await this.ensureDirectoryEx(filename);
if (doc._id.endsWith(".json")) {
const conflictedRev = conflicts[0];
const conflictedRevNo = Number(conflictedRev.split("-")[0]);
//Search
const revFrom = (await this.localDatabase.localDatabase.get(id, { revs_info: true })) as unknown as LoadedEntry & PouchDB.Core.GetMeta;
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? "";
const result = await this.mergeObject(id, commonBase, doc._rev, conflictedRev);
if (result) {
Logger(`Object merge:${id}`, LOG_LEVEL.INFO);
const filename = id2filenameInternalChunk(id);
const isExists = await this.app.vault.adapter.exists(filename);
if (!isExists) {
await this.ensureDirectoryEx(filename);
}
await this.app.vault.adapter.write(filename, result);
const stat = await this.app.vault.adapter.stat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.localDatabase.remove(id, revB);
return this.resolveConflictOnInternalFile(id);
} else {
Logger(`Object merge is not applicable.`, LOG_LEVEL.VERBOSE);
}
await this.app.vault.adapter.write(filename, result);
const stat = await this.app.vault.adapter.stat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.localDatabase.remove(id, revB);
return this.resolveConflictOnInternalFile(id);
} else {
Logger(`Object merge is not applicable.`, LOG_LEVEL.VERBOSE);
}
const revBDoc = await this.localDatabase.localDatabase.get(id, { rev: revB });
// determine which revision should been deleted.
// simply check modified time

View File

@@ -1,5 +1,5 @@
import { PluginManifest } from "obsidian";
import { DatabaseEntry } from "./lib/src/types";
import { DatabaseEntry, EntryBody } from "./lib/src/types";
export interface PluginDataEntry extends DatabaseEntry {
deviceVaultName: string;
@@ -30,3 +30,11 @@ export interface InternalFileInfo {
size: number;
deleted?: boolean;
}
export type queueItem = {
entry: EntryBody;
missingChildren: string[];
timeout?: number;
done?: boolean;
warned?: boolean;
};

View File

@@ -1,6 +1,6 @@
import { normalizePath } from "obsidian";
import { path2id_base, id2path_base } from "./lib/src/utils";
import { path2id_base, id2path_base } from "./lib/src/path";
// For backward compatibility, using the path for determining id.
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".

View File

@@ -85,13 +85,6 @@
} */
.sls-btn-left {
padding-right: 4px;
}
.sls-btn-right {
padding-left: 4px;
}
.sls-header-button {
margin-left: 2em;
@@ -108,7 +101,7 @@
.CodeMirror-wrap::before,
.cm-s-obsidian>.cm-editor::before,
.canvas-wrapper::before {
content: var(--slsmessage);
content: attr(data-log);
text-align: right;
white-space: pre-wrap;
position: absolute;

View File

@@ -28,6 +28,34 @@
- 0.17.5 Now `read chunks online` had been fixed, and a new feature: `Use dynamic iteration count` to reduce the load on encryption/decryption.
Note: `Use dynamic iteration count` is not compatible with earlier versions.
- 0.17.6 Now our renamed/deleted files have been surely deleted again.
- 0.17.7
- Fixed:
- Fixed merging issues.
- Fixed button styling.
- Changed:
- Conflict checking on synchronising has been enabled for every note in default.
- 0.17.8
- Improved: Performance improved. Prebuilt PouchDB is no longer used.
- Fixed: Merging hidden files is also fixed.
- New Feature: Now we can synchronise automatically after merging conflicts.
- 0.17.9
- Fixed: Conflict merge of internal files is no longer broken.
- Improved: Smoother status display inside the editor.
- 0.17.10
- Fixed: Large file synchronising has been now addressed!
Note: When synchronising large files, we have to set `Chunk size` to lower than 50, disable `Read chunks online`, `Batch size` should be set 50-100, and `Batch limit` could be around 20.
- 0.17.11
- Fixed:
- Performance improvement
- Now `Chunk size` can be set to under one hundred.
- New feature:
- The number of transfers required before replication stabilises is now displayed.
- 0.17.12: Skipped.
- 0.17.13
- Fixed: Document history is now displayed again.
- Reorganised: Many files have been refactored.
### 0.16.0
- Now hidden files need not be scanned. Changes will be detected automatically.
- If you want it to back to its previous behaviour, please disable `Monitor changes to internal files`.