mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-02-23 04:28:48 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e75917b8d | ||
|
|
3322d13b55 | ||
|
|
851c9f8a71 | ||
|
|
b02596dfa1 | ||
|
|
02c69b202e | ||
|
|
6b2c7b56a5 | ||
|
|
820168a5ab | ||
|
|
40015642e4 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-livesync",
|
||||
"name": "Self-hosted LiveSync",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.4",
|
||||
"minAppVersion": "0.9.12",
|
||||
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
||||
"author": "vorotamoroz",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"diff-match-patch": "^1.0.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-livesync",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.4",
|
||||
"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",
|
||||
|
||||
@@ -215,7 +215,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
}
|
||||
|
||||
//TODO: Tidy up. Even though it is experimental feature, So dirty...
|
||||
async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe", showMessage: boolean, files: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) {
|
||||
async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe" | "pullForce" | "pushForce", showMessage: boolean, files: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) {
|
||||
await this.resolveConflictOnInternalFiles();
|
||||
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
|
||||
Logger("Scanning hidden files.", logLevel, "sync_internal");
|
||||
@@ -289,43 +289,43 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
|
||||
p.push(addProc(async () => {
|
||||
const xFileOnStorage = fileOnStorage;
|
||||
const xfileOnDatabase = fileOnDatabase;
|
||||
if (xFileOnStorage && xfileOnDatabase) {
|
||||
const xFileOnDatabase = fileOnDatabase;
|
||||
if (xFileOnStorage && xFileOnDatabase) {
|
||||
// Both => Synchronize
|
||||
if (xfileOnDatabase.mtime == cache.docMtime && xFileOnStorage.mtime == cache.storageMtime) {
|
||||
if ((direction != "pullForce" && direction != "pushForce") && xFileOnDatabase.mtime == cache.docMtime && xFileOnStorage.mtime == cache.storageMtime) {
|
||||
return;
|
||||
}
|
||||
const nw = compareMTime(xFileOnStorage.mtime, xfileOnDatabase.mtime);
|
||||
if (nw > 0) {
|
||||
const nw = compareMTime(xFileOnStorage.mtime, xFileOnDatabase.mtime);
|
||||
if (nw > 0 || direction == "pushForce") {
|
||||
await this.storeInternalFileToDatabase(xFileOnStorage);
|
||||
}
|
||||
if (nw < 0) {
|
||||
if (nw < 0 || direction == "pullForce") {
|
||||
// skip if not extraction performed.
|
||||
if (!await this.extractInternalFileFromDatabase(filename))
|
||||
return;
|
||||
}
|
||||
// If process successfully updated or file contents are same, update cache.
|
||||
cache.docMtime = xfileOnDatabase.mtime;
|
||||
cache.docMtime = xFileOnDatabase.mtime;
|
||||
cache.storageMtime = xFileOnStorage.mtime;
|
||||
caches[filename] = cache;
|
||||
countUpdatedFolder(filename);
|
||||
} else if (!xFileOnStorage && xfileOnDatabase) {
|
||||
if (direction == "push") {
|
||||
if (xfileOnDatabase.deleted)
|
||||
} else if (!xFileOnStorage && xFileOnDatabase) {
|
||||
if (direction == "push" || direction == "pushForce") {
|
||||
if (xFileOnDatabase.deleted)
|
||||
return;
|
||||
await this.deleteInternalFileOnDatabase(filename, false);
|
||||
} else if (direction == "pull") {
|
||||
} else if (direction == "pull" || direction == "pullForce") {
|
||||
if (await this.extractInternalFileFromDatabase(filename)) {
|
||||
countUpdatedFolder(filename);
|
||||
}
|
||||
} else if (direction == "safe") {
|
||||
if (xfileOnDatabase.deleted)
|
||||
if (xFileOnDatabase.deleted)
|
||||
return;
|
||||
if (await this.extractInternalFileFromDatabase(filename)) {
|
||||
countUpdatedFolder(filename);
|
||||
}
|
||||
}
|
||||
} else if (xFileOnStorage && !xfileOnDatabase) {
|
||||
} else if (xFileOnStorage && !xFileOnDatabase) {
|
||||
await this.storeInternalFileToDatabase(xFileOnStorage);
|
||||
} else {
|
||||
throw new Error("Invalid state on hidden file sync");
|
||||
@@ -337,7 +337,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
await this.kvDB.set("diff-caches-internal", caches);
|
||||
|
||||
// When files has been retrieved from the database. they must be reloaded.
|
||||
if (direction == "pull" && filesChanged != 0) {
|
||||
if ((direction == "pull" || direction == "pullForce") && filesChanged != 0) {
|
||||
const configDir = normalizePath(this.app.vault.configDir);
|
||||
// Show notification to restart obsidian when something has been changed in configDir.
|
||||
if (configDir in updatedFolders) {
|
||||
@@ -468,7 +468,7 @@ export class HiddenFileSync extends LiveSyncCommands {
|
||||
};
|
||||
} else {
|
||||
if (isDocContentSame(old.data, content) && !forceWrite) {
|
||||
// Logger(`internal files STORAGE --> DB:${file.path}: Not changed`);
|
||||
// Logger(`STORAGE --> DB:${file.path}: (hidden) Not changed`, LOG_LEVEL.VERBOSE);
|
||||
return;
|
||||
}
|
||||
saveData =
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PouchDB } from "./lib/src/pouchdb-browser.js";
|
||||
import { askSelectString, askYesNo, askString } from "./utils";
|
||||
import { decrypt, encrypt } from "./lib/src/e2ee_v2";
|
||||
import { LiveSyncCommands } from "./LiveSyncCommands";
|
||||
import { delay } from "./lib/src/utils";
|
||||
|
||||
export class SetupLiveSync extends LiveSyncCommands {
|
||||
onunload() { }
|
||||
@@ -97,7 +98,8 @@ export class SetupLiveSync extends LiveSyncCommands {
|
||||
const setupAsNew = "Set it up as secondary or subsequent device";
|
||||
const setupAgain = "Reconfigure and reconstitute the data";
|
||||
const setupManually = "Leave everything to me";
|
||||
|
||||
newSettingW.syncInternalFiles = false;
|
||||
newSettingW.usePluginSync = false;
|
||||
const setupType = await askSelectString(this.app, "How would you like to set it up?", [setupAsNew, setupAgain, setupJustImport, setupManually]);
|
||||
if (setupType == setupJustImport) {
|
||||
this.plugin.settings = newSettingW;
|
||||
@@ -106,11 +108,7 @@ export class SetupLiveSync extends LiveSyncCommands {
|
||||
} else if (setupType == setupAsNew) {
|
||||
this.plugin.settings = newSettingW;
|
||||
this.plugin.usedPassphrase = "";
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await this.plugin.localDatabase.initializeDatabase();
|
||||
await this.plugin.markRemoteResolved();
|
||||
await this.plugin.replicate(true);
|
||||
await this.fetchLocal();
|
||||
} else if (setupType == setupAgain) {
|
||||
const confirm = "I know this operation will rebuild all my databases with files on this device, and files that are on the remote database and I didn't synchronize to any other devices will be lost and want to proceed indeed.";
|
||||
if (await askSelectString(this.app, "Do you really want to do this?", ["Cancel", confirm]) != confirm) {
|
||||
@@ -118,15 +116,7 @@ export class SetupLiveSync extends LiveSyncCommands {
|
||||
}
|
||||
this.plugin.settings = newSettingW;
|
||||
this.plugin.usedPassphrase = "";
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await this.plugin.localDatabase.initializeDatabase();
|
||||
await this.plugin.initializeDatabase(true);
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.markRemoteResolved();
|
||||
await this.plugin.replicate(true);
|
||||
|
||||
await this.rebuildEverything();
|
||||
} else if (setupType == setupManually) {
|
||||
const keepLocalDB = await askYesNo(this.app, "Keep local DB?");
|
||||
const keepRemoteDB = await askYesNo(this.app, "Keep remote DB?");
|
||||
@@ -134,6 +124,8 @@ export class SetupLiveSync extends LiveSyncCommands {
|
||||
// nothing to do. so peaceful.
|
||||
this.plugin.settings = newSettingW;
|
||||
this.plugin.usedPassphrase = "";
|
||||
this.suspendAllSync();
|
||||
this.suspendExtraSync();
|
||||
await this.plugin.saveSettings();
|
||||
const replicate = await askYesNo(this.app, "Unlock and replicate?");
|
||||
if (replicate == "yes") {
|
||||
@@ -189,4 +181,52 @@ export class SetupLiveSync extends LiveSyncCommands {
|
||||
Logger("Couldn't parse or decrypt configuration uri.", LOG_LEVEL.NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
suspendExtraSync() {
|
||||
Logger("Hidden files and plugin synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE)
|
||||
this.plugin.settings.syncInternalFiles = false;
|
||||
this.plugin.settings.usePluginSync = false;
|
||||
this.plugin.settings.autoSweepPlugins = false;
|
||||
}
|
||||
suspendAllSync() {
|
||||
this.plugin.settings.liveSync = false;
|
||||
this.plugin.settings.periodicReplication = false;
|
||||
this.plugin.settings.syncOnSave = false;
|
||||
this.plugin.settings.syncOnStart = false;
|
||||
this.plugin.settings.syncOnFileOpen = false;
|
||||
this.plugin.settings.syncAfterMerge = false;
|
||||
//this.suspendExtraSync();
|
||||
}
|
||||
async fetchLocal() {
|
||||
this.suspendExtraSync();
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await delay(1000);
|
||||
await this.plugin.markRemoteResolved();
|
||||
await this.plugin.openDatabase();
|
||||
this.plugin.isReady = true;
|
||||
await delay(500);
|
||||
await this.plugin.replicateAllFromServer(true);
|
||||
}
|
||||
async rebuildRemote() {
|
||||
this.suspendExtraSync();
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await delay(500);
|
||||
await this.plugin.replicateAllToServer(true);
|
||||
}
|
||||
async rebuildEverything() {
|
||||
this.suspendExtraSync();
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await delay(1000);
|
||||
await this.plugin.initializeDatabase(true);
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await delay(500);
|
||||
await this.plugin.replicateAllToServer(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,25 +420,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
if (!encrypt) {
|
||||
passphrase = "";
|
||||
}
|
||||
this.plugin.settings.liveSync = false;
|
||||
this.plugin.settings.periodicReplication = false;
|
||||
this.plugin.settings.syncOnSave = false;
|
||||
this.plugin.settings.syncOnStart = false;
|
||||
this.plugin.settings.syncOnFileOpen = false;
|
||||
this.plugin.settings.syncAfterMerge = false;
|
||||
this.plugin.addOnSetup.suspendAllSync();
|
||||
this.plugin.addOnSetup.suspendExtraSync();
|
||||
this.plugin.settings.encrypt = encrypt;
|
||||
this.plugin.settings.passphrase = passphrase;
|
||||
this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
|
||||
this.plugin.settings.usePathObfuscation = usePathObfuscation;
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
markDirtyControl();
|
||||
if (sendToServer) {
|
||||
await this.plugin.initializeDatabase(true);
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.replicateAllToServer(true);
|
||||
await this.plugin.addOnSetup.rebuildRemote()
|
||||
} else {
|
||||
await this.plugin.markRemoteResolved();
|
||||
await this.plugin.replicate(true);
|
||||
@@ -489,47 +480,27 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
if (!encrypt) {
|
||||
passphrase = "";
|
||||
}
|
||||
this.plugin.settings.liveSync = false;
|
||||
this.plugin.settings.periodicReplication = false;
|
||||
this.plugin.settings.syncOnSave = false;
|
||||
this.plugin.settings.syncOnStart = false;
|
||||
this.plugin.settings.syncOnFileOpen = false;
|
||||
this.plugin.settings.syncAfterMerge = false;
|
||||
this.plugin.settings.syncInternalFiles = false;
|
||||
this.plugin.settings.usePluginSync = false;
|
||||
this.plugin.addOnSetup.suspendAllSync();
|
||||
this.plugin.addOnSetup.suspendExtraSync();
|
||||
this.plugin.settings.encrypt = encrypt;
|
||||
this.plugin.settings.passphrase = passphrase;
|
||||
this.plugin.settings.useDynamicIterationCount = useDynamicIterationCount;
|
||||
this.plugin.settings.usePathObfuscation = usePathObfuscation;
|
||||
Logger("Hidden files and plugin synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE)
|
||||
Logger("All synchronizations have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL.NOTICE)
|
||||
await this.plugin.saveSettings();
|
||||
markDirtyControl();
|
||||
applyDisplayEnabled();
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.close()
|
||||
this.plugin.app.setting.close();
|
||||
await delay(2000);
|
||||
if (method == "localOnly") {
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await delay(1000);
|
||||
await this.plugin.markRemoteResolved();
|
||||
await this.plugin.openDatabase();
|
||||
this.plugin.isReady = true;
|
||||
await this.plugin.replicateAllFromServer(true);
|
||||
await this.plugin.addOnSetup.fetchLocal();
|
||||
}
|
||||
if (method == "remoteOnly") {
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.replicateAllToServer(true);
|
||||
await this.plugin.addOnSetup.rebuildRemote();
|
||||
}
|
||||
if (method == "rebuildBothByThisDevice") {
|
||||
await this.plugin.resetLocalDatabase();
|
||||
await delay(1000);
|
||||
await this.plugin.initializeDatabase(true);
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.tryResetRemoteDatabase();
|
||||
await this.plugin.markRemoteLocked();
|
||||
await this.plugin.replicateAllToServer(true);
|
||||
await this.plugin.addOnSetup.rebuildEverything();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +937,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
});
|
||||
c.addClass("op-warn");
|
||||
}
|
||||
|
||||
containerSyncSettingEl.createEl("h3", { text: "Synchronization Methods" });
|
||||
const syncLive: Setting[] = [];
|
||||
const syncNonLive: Setting[] = [];
|
||||
syncLive.push(
|
||||
@@ -1019,6 +990,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
text.inputEl.setAttribute("type", "number");
|
||||
}),
|
||||
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Sync on Save")
|
||||
.setDesc("When you save file, sync automatically")
|
||||
@@ -1060,7 +1032,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
})
|
||||
),
|
||||
);
|
||||
|
||||
containerSyncSettingEl.createEl("h3", { text: "File deletion" });
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Use Trash for deleted files")
|
||||
.setDesc("Do not delete files that are deleted in remote, just move to trash.")
|
||||
@@ -1081,6 +1053,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
})
|
||||
);
|
||||
|
||||
containerSyncSettingEl.createEl("h3", { text: "Conflict resolution" });
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Use newer file if conflicted (beta)")
|
||||
.setDesc("Resolve conflicts by newer files automatically.")
|
||||
@@ -1119,14 +1092,73 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Sync hidden files")
|
||||
.addToggle((toggle) =>
|
||||
toggle.setValue(this.plugin.settings.syncInternalFiles).onChange(async (value) => {
|
||||
this.plugin.settings.syncInternalFiles = value;
|
||||
await this.plugin.saveSettings();
|
||||
containerSyncSettingEl.createEl("h3", { text: "Hidden files" });
|
||||
const LABEL_ENABLED = "🔁 : Enabled";
|
||||
const LABEL_DISABLED = "⏹️ : Disabled"
|
||||
|
||||
const hiddenFileSyncSetting = new Setting(containerSyncSettingEl)
|
||||
.setName("Hidden file synchronization")
|
||||
const hiddenFileSyncSettingEl = hiddenFileSyncSetting.settingEl
|
||||
const hiddenFileSyncSettingDiv = hiddenFileSyncSettingEl.createDiv("");
|
||||
hiddenFileSyncSettingDiv.innerText = this.plugin.settings.syncInternalFiles ? LABEL_ENABLED : LABEL_DISABLED;
|
||||
|
||||
if (this.plugin.settings.syncInternalFiles) {
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Disable Hidden files sync")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Disable")
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.syncInternalFiles = false;
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
})
|
||||
})
|
||||
);
|
||||
} else {
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Enable Hidden files sync")
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Merge")
|
||||
.onClick(async () => {
|
||||
this.plugin.addOnSetup.suspendExtraSync();
|
||||
this.display();
|
||||
Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE);
|
||||
await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("safe", true);
|
||||
this.plugin.settings.syncInternalFiles = true;
|
||||
await this.plugin.saveSettings();
|
||||
Logger(`Done!`, LOG_LEVEL.NOTICE);
|
||||
})
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Fetch")
|
||||
.onClick(async () => {
|
||||
this.plugin.addOnSetup.suspendExtraSync();
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.close()
|
||||
Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE);
|
||||
await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("pullForce", true);
|
||||
this.plugin.settings.syncInternalFiles = true;
|
||||
await this.plugin.saveSettings();
|
||||
Logger(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL.NOTICE);
|
||||
// this.display();
|
||||
})
|
||||
})
|
||||
.addButton((button) => {
|
||||
button.setButtonText("Overwrite")
|
||||
.onClick(async () => {
|
||||
this.plugin.addOnSetup.suspendExtraSync();
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.close()
|
||||
Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL.NOTICE);
|
||||
// this.display();
|
||||
await this.plugin.addOnHiddenFileSync.syncInternalFilesAndDatabase("pushForce", true);
|
||||
this.plugin.settings.syncInternalFiles = true;
|
||||
await this.plugin.saveSettings();
|
||||
Logger(`Done!`, LOG_LEVEL.NOTICE);
|
||||
// this.display();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Scan for hidden files before replication")
|
||||
@@ -1191,31 +1223,9 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
})
|
||||
})
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Touch hidden files")
|
||||
.setDesc("Update the modified time of all hidden files to the current time.")
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText("Touch")
|
||||
.setWarning()
|
||||
.setDisabled(false)
|
||||
.onClick(async () => {
|
||||
const filesAll = await this.plugin.addOnHiddenFileSync.scanInternalFiles();
|
||||
const targetFiles = await this.plugin.filterTargetFiles(filesAll);
|
||||
const now = Date.now();
|
||||
const newFiles = targetFiles.map(e => ({ ...e, mtime: now }));
|
||||
let i = 0;
|
||||
const maxFiles = newFiles.length;
|
||||
for (const file of newFiles) {
|
||||
i++;
|
||||
Logger(`Touched:${file.path} (${i}/${maxFiles})`, LOG_LEVEL.NOTICE, "touch-files");
|
||||
await this.plugin.applyMTimeToFile(file);
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
containerSyncSettingEl.createEl("h3", {
|
||||
text: sanitizeHTMLToDom(`Experimental`),
|
||||
text: sanitizeHTMLToDom(`Synchronization filters`),
|
||||
});
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Regular expression to ignore files")
|
||||
@@ -1263,7 +1273,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
return text;
|
||||
}
|
||||
);
|
||||
|
||||
containerSyncSettingEl.createEl("h3", { text: "Efficiency" });
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Chunk size")
|
||||
.setDesc("Customize chunk size for binary files (0.1MBytes). This cannot be increased when using IBM Cloudant.")
|
||||
@@ -1282,7 +1292,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
});
|
||||
|
||||
new Setting(containerSyncSettingEl)
|
||||
.setName("Read chunks online.")
|
||||
.setName("Read chunks online")
|
||||
.setDesc("If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended.")
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
@@ -1292,8 +1302,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
return toggle;
|
||||
}
|
||||
);
|
||||
});
|
||||
containerSyncSettingEl.createEl("h3", {
|
||||
text: sanitizeHTMLToDom(`Advanced settings`),
|
||||
});
|
||||
@@ -1487,8 +1496,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||
}
|
||||
}
|
||||
this.plugin.saveSettings();
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
this.display();
|
||||
await this.plugin.realizeSettingSyncMode();
|
||||
if (inWizard) {
|
||||
// @ts-ignore
|
||||
this.plugin.app.setting.close()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Plugin_2, TAbstractFile, TFile, TFolder } from "./deps";
|
||||
import { isPlainText, shouldBeIgnored } from "./lib/src/path";
|
||||
import { getGlobalStore } from "./lib/src/store";
|
||||
import { ObsidianLiveSyncSettings } from "./lib/src/types";
|
||||
import { FilePath, ObsidianLiveSyncSettings } from "./lib/src/types";
|
||||
import { FileEventItem, FileEventType, FileInfo, InternalFileInfo, queueItem } from "./types";
|
||||
import { recentlyTouched } from "./utils";
|
||||
|
||||
@@ -57,13 +57,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
||||
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||
if (file instanceof TFile) {
|
||||
this.appendWatchEvent([
|
||||
{ type: "DELETE", file: { path: oldFile as FilePath, mtime: file.stat.mtime, ctime: file.stat.ctime, size: file.stat.size, deleted: true } },
|
||||
{ type: "CREATE", file },
|
||||
{ type: "DELETE", file: { path: oldFile, mtime: file.stat.mtime, ctime: file.stat.ctime, size: file.stat.size, deleted: true } }
|
||||
], ctx);
|
||||
}
|
||||
}
|
||||
// Watch raw events (Internal API)
|
||||
watchVaultRawEvents(path: string) {
|
||||
watchVaultRawEvents(path: FilePath) {
|
||||
if (!this.plugin.settings.syncInternalFiles) return;
|
||||
if (!this.plugin.settings.watchInternalFileChanges) return;
|
||||
if (!path.startsWith(app.vault.configDir)) return;
|
||||
|
||||
2
src/lib
2
src/lib
Submodule src/lib updated: f644c8dfc3...f5db618612
45
src/main.ts
45
src/main.ts
@@ -4,7 +4,7 @@ import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "di
|
||||
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, RequestUrlParam, RequestUrlResponse, requestUrl } from "./deps";
|
||||
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, ConfigPassphraseStore, CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, DatabaseConnectingStatus, EntryHasPath, DocumentID, FilePathWithPrefix, FilePath, AnyEntry } from "./lib/src/types";
|
||||
import { InternalFileInfo, queueItem, CacheData, FileEventItem, FileWatchEventQueueMax } from "./types";
|
||||
import { delay, getDocData, isDocContentSame } from "./lib/src/utils";
|
||||
import { getDocData, isDocContentSame } from "./lib/src/utils";
|
||||
import { Logger } from "./lib/src/logger";
|
||||
import { PouchDB } from "./lib/src/pouchdb-browser.js";
|
||||
import { LogDisplayModal } from "./LogDisplayModal";
|
||||
@@ -387,25 +387,13 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
try {
|
||||
if (this.isRedFlagRaised() || this.isRedFlag2Raised() || this.isRedFlag3Raised()) {
|
||||
this.settings.batchSave = false;
|
||||
this.settings.liveSync = false;
|
||||
this.settings.periodicReplication = false;
|
||||
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.addOnSetup.suspendAllSync();
|
||||
this.addOnSetup.suspendExtraSync();
|
||||
this.settings.suspendFileWatching = true;
|
||||
this.settings.syncInternalFiles = false;
|
||||
await this.saveSettings();
|
||||
if (this.isRedFlag2Raised()) {
|
||||
Logger(`${FLAGMD_REDFLAG2} has been detected! Self-hosted LiveSync suspends all sync and rebuild everything.`, LOG_LEVEL.NOTICE);
|
||||
await this.resetLocalDatabase();
|
||||
await this.initializeDatabase(true);
|
||||
await this.markRemoteLocked();
|
||||
await this.tryResetRemoteDatabase();
|
||||
await this.markRemoteLocked();
|
||||
await this.replicateAllToServer(true);
|
||||
await this.addOnSetup.rebuildEverything();
|
||||
await this.deleteRedFlag2();
|
||||
if (await askYesNo(this.app, "Do you want to disable Suspend file watching and restart obsidian now?") == "yes") {
|
||||
this.settings.suspendFileWatching = false;
|
||||
@@ -415,12 +403,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
}
|
||||
} else if (this.isRedFlag3Raised()) {
|
||||
Logger(`${FLAGMD_REDFLAG3} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`, LOG_LEVEL.NOTICE);
|
||||
await this.resetLocalDatabase();
|
||||
await delay(1000);
|
||||
await this.markRemoteResolved();
|
||||
await this.openDatabase();
|
||||
this.isReady = true;
|
||||
await this.replicateAllFromServer(true);
|
||||
await this.addOnSetup.fetchLocal();
|
||||
await this.deleteRedFlag3();
|
||||
if (await askYesNo(this.app, "Do you want to disable Suspend file watching and restart obsidian now?") == "yes") {
|
||||
this.settings.suspendFileWatching = false;
|
||||
@@ -912,8 +895,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
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);
|
||||
} else {
|
||||
@@ -930,6 +919,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
|
||||
const cache = queue.args.cache;
|
||||
if (queue.type == "CREATE" || queue.type == "CHANGED") {
|
||||
const keyD1 = `file-last-proc-DELETED-${file.path}`;
|
||||
await this.kvDB.set(keyD1, mtime);
|
||||
if (!await this.updateIntoDB(targetFile, false, cache)) {
|
||||
Logger(`DB -> STORAGE: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL.INFO);
|
||||
// cancel running queues and remove one of atomic operation
|
||||
@@ -942,7 +933,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
await this.watchVaultRenameAsync(targetFile, queue.args.oldPath);
|
||||
}
|
||||
}
|
||||
await this.kvDB.set(key, file.mtime);
|
||||
await this.kvDB.set(key, mtime);
|
||||
} while (this.vaultManager.getQueueLength() > 0);
|
||||
return true;
|
||||
})
|
||||
@@ -1114,13 +1105,16 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
// This occurs not only when files are deleted, but also when conflicts are resolved.
|
||||
// We have to check no other revisions are left.
|
||||
const lastDocs = await this.localDatabase.getDBEntry(path);
|
||||
if (path != file.path) {
|
||||
Logger(`delete skipped: ${file.path} :Not exactly matched`, LOG_LEVEL.VERBOSE);
|
||||
}
|
||||
if (lastDocs === false) {
|
||||
await this.deleteVaultItem(file);
|
||||
} else {
|
||||
// it perhaps delete some revisions.
|
||||
// may be we have to reload this
|
||||
await this.pullFile(path, null, true);
|
||||
Logger(`delete skipped:${lastDocs._id}`, LOG_LEVEL.VERBOSE);
|
||||
Logger(`delete skipped:${file.path}`, LOG_LEVEL.VERBOSE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1381,8 +1375,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin
|
||||
|
||||
//---> Sync
|
||||
async parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): Promise<void> {
|
||||
const docsSorted = docs.sort((a, b) => b.mtime - a.mtime);
|
||||
L1:
|
||||
for (const change of docs) {
|
||||
for (const change of docsSorted) {
|
||||
for (const proc of this.addOns) {
|
||||
if (await proc.parseReplicationResultItem(change)) {
|
||||
continue L1;
|
||||
|
||||
18
src/utils.ts
18
src/utils.ts
@@ -325,14 +325,16 @@ export function isValidPath(filename: string) {
|
||||
let touchedFiles: string[] = [];
|
||||
|
||||
export function getAbstractFileByPath(path: FilePath): 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);
|
||||
} else {
|
||||
return app.vault.getAbstractFileByPath(path);
|
||||
}
|
||||
// Disabled temporary.
|
||||
return app.vault.getAbstractFileByPath(path);
|
||||
// // Hidden API but so useful.
|
||||
// // @ts-ignore
|
||||
// if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
|
||||
// // @ts-ignore
|
||||
// return app.vault.getAbstractFileByPathInsensitive(path);
|
||||
// } else {
|
||||
// return app.vault.getAbstractFileByPath(path);
|
||||
// }
|
||||
}
|
||||
export function trimPrefix(target: string, prefix: string) {
|
||||
return target.startsWith(prefix) ? target.substring(prefix.length) : target;
|
||||
|
||||
18
updates.md
18
updates.md
@@ -8,6 +8,24 @@ Since v0.18.0, they can be obfuscated. so it is no longer possible to decipher t
|
||||
We can configure the `Path Obfuscation` in the `Remote database configuration` pane.
|
||||
Note: **When changing this configuration, we need to rebuild both of the local and the remote databases**.
|
||||
|
||||
#### Minors
|
||||
- 0.18.1
|
||||
- Fixed:
|
||||
- Some messages are fixed (Typo)
|
||||
- File type detection now works fine!
|
||||
- 0.18.2
|
||||
- Improved:
|
||||
- The setting pane has been refined.
|
||||
- We can enable `hidden files sync` with several initial behaviours; `Merge`, `Fetch` remote, and `Overwrite` remote.
|
||||
- No longer `Touch hidden files`.
|
||||
- 0.18.3
|
||||
- Fixed Pop-up is now correctly shown after hidden file synchronisation.
|
||||
- 0.18.4
|
||||
- Fixed:
|
||||
- `Fetch` and `Rebuild database` will work more safely.
|
||||
- Case-sensitive renaming now works fine.
|
||||
Revoked the logic which was made at #130, however, looks fine now.
|
||||
|
||||
### 0.17.0
|
||||
- 0.17.0 has no surfaced changes but the design of saving chunks has been changed. They have compatibility but changing files after upgrading makes different chunks than before 0.16.x.
|
||||
Please rebuild databases once if you have been worried about storage usage.
|
||||
|
||||
Reference in New Issue
Block a user