Compare commits

...

19 Commits

Author SHA1 Message Date
vorotamoroz
331acd463d bump 2022-10-25 11:48:21 +09:00
vorotamoroz
9d4f41bbf9 Fixed failure of detection 2022-10-25 11:47:02 +09:00
vorotamoroz
8831165965 bump 2022-10-25 11:34:09 +09:00
vorotamoroz
ed62e9331b Implemented:
- A configuration information reporting tools has been implemented.

Improved:
- Fixed detection of IBM Cloudant
2022-10-25 11:33:37 +09:00
vorotamoroz
799e604eb2 bump 2022-10-21 18:22:02 +09:00
vorotamoroz
d9b69d9a1b Fixed:
- Fixed the Infinity loop
2022-10-21 18:20:03 +09:00
vorotamoroz
c18b5c24b4 bump 2022-10-14 17:39:16 +09:00
vorotamoroz
07f16e3d7d Added missing log updates. 2022-10-14 17:37:25 +09:00
vorotamoroz
486f1aa4a0 Bump 2022-10-05 17:14:52 +09:00
vorotamoroz
075c6beb68 New feature:
- Monitor hidden files, Now we can use internal file sync without scan.
Fixed:
- Periodic synchronisation sometimes failed.
- Status-display had not been cleared in some cases.
- `Skip patterns default` has been changed to more clear name.
2022-10-05 17:14:32 +09:00
vorotamoroz
d6121b0c1e bump 2022-10-03 10:57:39 +09:00
vorotamoroz
3292a48054 Fixed
- The boot sequence has been corrected and now boots smoothly.
- Auto applying of batch save will be processed earlier than before.
2022-10-03 10:52:31 +09:00
vorotamoroz
ee37764040 bump 2022-10-02 01:48:50 +09:00
vorotamoroz
b6f7fced22 Use new library for batching the chunk retrieving 2022-10-02 01:45:44 +09:00
vorotamoroz
13456c0854 Fixed: deleted debug message 2022-10-02 01:44:39 +09:00
vorotamoroz
2663a52fd7 bump 2022-09-29 16:58:53 +09:00
vorotamoroz
d4bbf79514 Fixed:
- Fixed a bug about deleting empty directory
- Weird behaviour on boot-sequence on mobile devices.
2022-09-29 16:58:39 +09:00
vorotamoroz
5f96cc6b82 bump 2022-09-28 17:57:23 +09:00
vorotamoroz
8c8f5d045f Fixed:
- Fixed bug about renaming file
2022-09-28 17:56:34 +09:00
9 changed files with 278 additions and 125 deletions

View File

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

34
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.15.6",
"version": "0.16.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.15.6",
"version": "0.16.4",
"license": "MIT",
"dependencies": {
"diff-match-patch": "^1.0.5",
@@ -30,7 +30,7 @@
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4",
"obsidian": "^0.16.3",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1",
@@ -2571,9 +2571,9 @@
}
},
"node_modules/moment": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"dev": true,
"engines": {
"node": "*"
@@ -2671,13 +2671,13 @@
}
},
"node_modules/obsidian": {
"version": "0.15.4",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.15.4.tgz",
"integrity": "sha512-FE11CxxpVD6t/DBvjLvlT7q7YYW91ubTqPKIIp286LdnyLipS8Xi3Tif8i8ALPv87Vg9obKM43aWcPsYLxLllQ==",
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.16.3.tgz",
"integrity": "sha512-hal9qk1A0GMhHSeLr2/+o3OpLmImiP+Y+sx2ewP13ds76KXsziG96n+IPFT0mSkup1zSwhEu+DeRhmbcyCCXWw==",
"dev": true,
"dependencies": {
"@types/codemirror": "0.0.108",
"moment": "2.29.3"
"moment": "2.29.4"
},
"peerDependencies": {
"@codemirror/state": "^6.0.0",
@@ -5490,9 +5490,9 @@
}
},
"moment": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"dev": true
},
"ms": {
@@ -5560,13 +5560,13 @@
}
},
"obsidian": {
"version": "0.15.4",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.15.4.tgz",
"integrity": "sha512-FE11CxxpVD6t/DBvjLvlT7q7YYW91ubTqPKIIp286LdnyLipS8Xi3Tif8i8ALPv87Vg9obKM43aWcPsYLxLllQ==",
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-0.16.3.tgz",
"integrity": "sha512-hal9qk1A0GMhHSeLr2/+o3OpLmImiP+Y+sx2ewP13ds76KXsziG96n+IPFT0mSkup1zSwhEu+DeRhmbcyCCXWw==",
"dev": true,
"requires": {
"@types/codemirror": "0.0.108",
"moment": "2.29.3"
"moment": "2.29.4"
}
},
"once": {

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.15.6",
"version": "0.16.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",
@@ -27,7 +27,7 @@
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.25.2",
"obsidian": "^0.15.4",
"obsidian": "^0.16.3",
"postcss": "^8.4.14",
"postcss-load-config": "^3.1.4",
"rollup": "^2.32.1",

View File

@@ -5,7 +5,7 @@ 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 { isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
import { isCloudantURI, isValidRemoteCouchDBURI } from "./lib/src/utils_couchdb.js";
import { id2path, path2id } from "./utils.js";
export class LocalPouchDB extends LocalPouchDBBase {
@@ -77,7 +77,7 @@ export class LocalPouchDB extends LocalPouchDBBase {
const opts_length = opts.body.toString().length;
if (opts_length > 1024 * 1024 * 10) {
// over 10MB
if (uri.contains(".cloudantnosqldb.")) {
if (isCloudantURI(uri)) {
this.last_successful_post = false;
Logger("This request should fail on IBM Cloudant.", LOG_LEVEL.VERBOSE);
throw new Error("This request should fail on IBM Cloudant.");

View File

@@ -1,12 +1,29 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent, MarkdownRenderer } from "obsidian";
import { DEFAULT_SETTINGS, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
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, versionNumberString2Number } from "./lib/src/utils";
import { Logger } from "./lib/src/logger";
import { checkSyncInfo } from "./lib/src/utils_couchdb.js";
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb.js";
import { testCrypt } from "./lib/src/e2ee_v2";
import ObsidianLiveSyncPlugin from "./main";
const requestToCouchDB = async (baseUri: string, username: string, password: string, origin: string, key?: string, body?: string) => {
const utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(`${username}:${password}`));
const encoded = window.btoa(utf8str);
const authHeader = "Basic " + encoded;
// const origin = "capacitor://localhost";
const transformedHeaders: Record<string, string> = { authorization: authHeader, origin: origin };
const uri = `${baseUri}/_node/_local/_config${key ? "/" + key : ""}`;
const requestParam: RequestUrlParam = {
url: uri,
method: body ? "PUT" : "GET",
headers: transformedHeaders,
contentType: "application/json",
body: body ? JSON.stringify(body) : undefined,
};
return await requestUrl(requestParam);
};
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
plugin: ObsidianLiveSyncPlugin;
@@ -67,9 +84,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
element.removeClass("selected");
(element.querySelector("input[type=radio]") as HTMLInputElement).checked = false;
});
console.log(`.sls-setting-label.c-${screen}`)
w.querySelectorAll(`.sls-setting-label.c-${screen}`).forEach((element) => {
console.log(element)
element.addClass("selected");
(element.querySelector("input[type=radio]") as HTMLInputElement).checked = true;
});
@@ -381,15 +396,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
})
)
.addButton((button) =>
button
.setButtonText("Apply w/o rebuilding")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
);
button
.setButtonText("Apply w/o rebuilding")
.setWarning()
.setDisabled(false)
.setClass("sls-btn-right")
.onClick(async () => {
await applyEncryption(false);
})
);
const rebuildDB = async (method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") => {
@@ -476,23 +491,10 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
.onClick(async () => {
const checkConfig = async () => {
try {
const requestToCouchDB = async (baseUri: string, username: string, password: string, origin: string, key?: string, body?: string) => {
const utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(`${username}:${password}`));
const encoded = window.btoa(utf8str);
const authHeader = "Basic " + encoded;
// const origin = "capacitor://localhost";
const transformedHeaders: Record<string, string> = { authorization: authHeader, origin: origin };
const uri = `${baseUri}/_node/_local/_config${key ? "/" + key : ""}`;
const requestParam: RequestUrlParam = {
url: uri,
method: body ? "PUT" : "GET",
headers: transformedHeaders,
contentType: "application/json",
body: body ? JSON.stringify(body) : undefined,
};
return await requestUrl(requestParam);
};
if (isCloudantURI(this.plugin.settings.couchDB_URI)) {
Logger("This feature cannot be used with IBM Cloudant.", LOG_LEVEL.NOTICE);
return;
}
const r = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, window.origin);
@@ -575,7 +577,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
addResult("✔ httpd.enable_cors is ok.");
}
// If the server is not cloudant, configure request size
if (!this.plugin.settings.couchDB_URI.contains(".cloudantnosqldb.")) {
if (!isCloudantURI(this.plugin.settings.couchDB_URI)) {
// REQUEST SIZE
if (Number(responseConfig?.chttpd?.max_http_request_size ?? 0) < 4294967296) {
addResult("❗ chttpd.max_http_request_size is low)");
@@ -639,7 +641,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
addResult("--Done--", ["ob-btn-config-head"]);
addResult("If you have some trouble with Connection-check even though all Config-check has been passed, Please check your reverse proxy's configuration.", ["ob-btn-config-info"]);
} catch (ex) {
Logger(`Checking configuration failed`);
Logger(`Checking configuration failed`, LOG_LEVEL.NOTICE);
Logger(ex);
}
};
@@ -677,7 +679,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
if (this.plugin.settings.couchDB_URI.contains(".cloudantnosqldb.")) {
if (isCloudantURI(this.plugin.settings.couchDB_URI)) {
this.plugin.settings.customChunkSize = 0;
} else {
this.plugin.settings.customChunkSize = 100;
@@ -698,7 +700,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
if (!this.plugin.settings.encrypt) {
this.plugin.settings.passphrase = "";
}
if (this.plugin.settings.couchDB_URI.contains(".cloudantnosqldb.")) {
if (isCloudantURI(this.plugin.settings.couchDB_URI)) {
this.plugin.settings.customChunkSize = 0;
} else {
this.plugin.settings.customChunkSize = 100;
@@ -964,8 +966,17 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);
new Setting(containerSyncSettingEl)
.setName("Monitor changes to internal files")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.watchInternalFileChanges).onChange(async (value) => {
this.plugin.settings.watchInternalFileChanges = value;
await this.plugin.saveSettings();
})
);
new Setting(containerSyncSettingEl)
.setName("Scan for hidden files before replication")
.setDesc("This configuration will be ignored if monitoring changes is enabled.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.syncInternalFilesBeforeReplication).onChange(async (value) => {
this.plugin.settings.syncInternalFilesBeforeReplication = value;
@@ -974,7 +985,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
);
new Setting(containerSyncSettingEl)
.setName("Scan hidden files periodically")
.setDesc("Seconds, 0 to disable.")
.setDesc("Seconds, 0 to disable. This configuration will be ignored if monitoring changes is enabled.")
.addText((text) => {
text.setPlaceholder("")
.setValue(this.plugin.settings.syncInternalFilesInterval + "")
@@ -990,7 +1001,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
});
let skipPatternTextArea: TextAreaComponent = null;
const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, \\/obsidian-livesync\\/";
const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$";
const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$ ,\\/workspace.json$";
new Setting(containerSyncSettingEl)
.setName("Skip patterns")
.setDesc(
@@ -1009,7 +1020,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}
);
new Setting(containerSyncSettingEl)
.setName("Skip patterns defaults")
.setName("Restore the skip pattern to default")
.addButton((button) => {
button.setButtonText("Default")
.onClick(async () => {
@@ -1256,6 +1267,50 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
containerHatchEl.createEl("h3", { text: "Hatch" });
new Setting(containerHatchEl)
.setName("Make report to inform the issue")
.setDesc("Verify and repair all files and update database without restoring")
.addButton((button) =>
button
.setButtonText("Make report")
.setDisabled(false)
.onClick(async () => {
let responseConfig: any = {};
const REDACTED = "𝑅𝐸𝐷𝐴𝐶𝑇𝐸𝐷";
try {
const r = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, window.origin);
Logger(JSON.stringify(r.json, null, 2));
responseConfig = r.json;
responseConfig["couch_httpd_auth"].secret = REDACTED;
responseConfig["couch_httpd_auth"].authentication_db = REDACTED;
responseConfig["couch_httpd_auth"].authentication_redirect = REDACTED;
responseConfig["couchdb"].uuid = REDACTED;
responseConfig["admins"] = REDACTED;
} catch (ex) {
responseConfig = "Requesting information to the remote CouchDB has been failed. If you are using IBM Cloudant, it is the normal behaviour."
}
const pluginConfig = JSON.parse(JSON.stringify(this.plugin.settings)) as ObsidianLiveSyncSettings;
pluginConfig.couchDB_DBNAME = REDACTED;
pluginConfig.couchDB_PASSWORD = REDACTED;
pluginConfig.couchDB_URI = isCloudantURI(pluginConfig.couchDB_URI) ? "cloudant" : "self-hosted";
pluginConfig.couchDB_USER = REDACTED;
pluginConfig.passphrase = REDACTED;
pluginConfig.workingPassphrase = REDACTED;
const msgConfig = `----remote config----
${stringifyYaml(responseConfig)}
---- Plug-in config ---
${stringifyYaml(pluginConfig)}`;
console.log(msgConfig);
await navigator.clipboard.writeText(msgConfig);
Logger(`Information has been copied to clipboard`, LOG_LEVEL.NOTICE);
})
);
if (this.plugin.localDatabase.remoteLockedAndDeviceNotAccepted) {
const c = containerHatchEl.createEl("div", {
text: "To prevent unwanted vault corruption, the remote database has been locked for synchronization, and this device was not marked as 'resolved'. it caused by some operations like this. re-initialized. Local database initialization should be required. please back your vault up, reset local database, and press 'Mark this device as resolved'. ",

Submodule src/lib updated: d8d83b7f46...b8a765f8e8

View File

@@ -35,6 +35,7 @@ import { decrypt, encrypt } from "./lib/src/e2ee_v2";
const isDebug = false;
import { InputStringDialog, PluginDialogModal, PopoverSelectString } from "./dialogs";
import { isCloudantURI } from "./lib/src/utils_couchdb";
setNoticeClass(Notice);
@@ -98,7 +99,7 @@ let touchedFiles: string[] = [];
function touch(file: TFile | string) {
const f = file instanceof TFile ? file : app.vault.getAbstractFileByPath(file) as TFile;
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
touchedFiles.push(key);
touchedFiles.unshift(key);
touchedFiles = touchedFiles.slice(0, 100);
}
function recentlyTouched(file: TFile) {
@@ -111,9 +112,9 @@ function clearTouched() {
}
type CacheData = string | ArrayBuffer;
type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "RENAME";
type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "RENAME" | "INTERNAL";
type FileEventArgs = {
file: TAbstractFile;
file: TAbstractFile | InternalFileInfo;
cache?: CacheData;
oldPath?: string;
ctx?: any;
@@ -297,21 +298,23 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.watchVaultCreate = this.watchVaultCreate.bind(this);
this.watchVaultDelete = this.watchVaultDelete.bind(this);
this.watchVaultRename = this.watchVaultRename.bind(this);
this.watchVaultRawEvents = this.watchVaultRawEvents.bind(this);
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 1000, false);
this.watchWindowVisibility = debounce(this.watchWindowVisibility.bind(this), 1000, false);
this.watchOnline = debounce(this.watchOnline.bind(this), 500, false);
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.periodicSync = this.periodicSync.bind(this);
this.setPeriodicSync = this.setPeriodicSync.bind(this);
this.periodicSync = this.periodicSync.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
this.getPluginList = this.getPluginList.bind(this);
// this.registerWatchEvents();
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
this.registerFileWatchEvents();
this.app.workspace.onLayoutReady(async () => {
this.registerFileWatchEvents();
if (this.localDatabase.isReady)
try {
if (this.isRedFlagRaised()) {
@@ -695,6 +698,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.deviceAndVaultName = "";
}
}
if (isCloudantURI(this.settings.couchDB_URI) && this.settings.customChunkSize != 0) {
Logger("Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.", LOG_LEVEL.NOTICE)
this.settings.customChunkSize = 0;
}
this.deviceAndVaultName = localStorage.getItem(lsKey) || "";
}
@@ -718,6 +725,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.registerEvent(this.app.vault.on("delete", this.watchVaultDelete));
this.registerEvent(this.app.vault.on("rename", this.watchVaultRename));
this.registerEvent(this.app.vault.on("create", this.watchVaultCreate));
//@ts-ignore : Internal API
this.registerEvent(this.app.vault.on("raw", this.watchVaultRawEvents));
}
registerWatchEvents() {
@@ -769,9 +778,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
// Cache file and waiting to can be proceed.
async appendWatchEvent(type: FileEventType, file: TAbstractFile, oldPath?: string, ctx?: any) {
async appendWatchEvent(type: FileEventType, file: TAbstractFile | InternalFileInfo, oldPath?: string, ctx?: any) {
// check really we can process.
if (!this.isTargetFile(file)) return;
if (file instanceof TFile && !this.isTargetFile(file)) return;
if (this.settings.suspendFileWatching) return;
let cache: null | string | ArrayBuffer;
@@ -830,34 +839,52 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (!applyBatch && this.watchedFileEventQueue.length < FileWatchEventQueueMax) {
// Defer till applying batch save or queue has been grown enough.
// or 120 seconds after.
setTrigger("applyBatchAuto", 120000, () => {
setTrigger("applyBatchAuto", 30000, () => {
this.procFileEvent(true);
})
return;
}
}
clearTrigger("applyBatchAuto");
const ret = await runWithLock("procFiles", false, async () => {
const procs = [...this.watchedFileEventQueue];
this.watchedFileEventQueue = [];
for (const queue of procs) {
const file = queue.args.file;
const cache = queue.args.cache;
if ((queue.type == "CREATE" || queue.type == "CHANGED") && file instanceof TFile) {
await this.updateIntoDB(file, false, cache);
}
if (queue.type == "DELETE") {
const ret = await runWithLock("procFiles", true, async () => {
do {
const procs = [...this.watchedFileEventQueue];
this.watchedFileEventQueue = [];
for (const queue of procs) {
const file = queue.args.file;
const key = `file-last-proc-${queue.type}-${file.path}`;
const last = Number(await this.localDatabase.kvDB.get(key) || 0);
if (file instanceof TFile && file.stat.mtime == last) {
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL.VERBOSE);
continue;
}
const cache = queue.args.cache;
if ((queue.type == "CREATE" || queue.type == "CHANGED") && file instanceof TFile) {
await this.updateIntoDB(file, false, cache);
}
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") {
if (file instanceof TFile) {
await this.watchVaultRenameAsync(file, queue.args.oldPath);
}
}
if (queue.type == "INTERNAL") {
await this.watchVaultRawEventsAsync(file.path);
}
if (file instanceof TFile) {
await this.deleteFromDB(file);
} else if (file instanceof TFolder) {
await this.deleteFolderOnDB(file);
await this.localDatabase.kvDB.set(key, file.stat.mtime);
}
}
if (queue.type == "RENAME") {
await this.watchVaultRenameAsync(file, queue.args.oldPath);
}
}
this.refreshStatusText();
this.refreshStatusText();
} while (this.watchedFileEventQueue.length != 0);
return true;
})
this.refreshStatusText();
return ret;
@@ -898,9 +925,55 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async applyBatchChange() {
await this.procFileEvent(true);
if (this.settings.batchSave) {
return await this.procFileEvent(true);
}
}
// Watch raw events (Internal API)
watchVaultRawEvents(path: string) {
if (!this.settings.syncInternalFiles) return;
if (!this.settings.watchInternalFileChanges) return;
if (!path.startsWith(this.app.vault.configDir)) return;
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns.toLocaleLowerCase()
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e));
if (ignorePatterns.some(e => path.match(e))) return;
this.appendWatchEvent("INTERNAL", { path, mtime: 0, ctime: 0, size: 0 }, "", null);
}
recentProcessedInternalFiles = [] as string[];
async watchVaultRawEventsAsync(path: string) {
const stat = await this.app.vault.adapter.stat(path);
// sometimes folder is coming.
if (stat && stat.type != "file") return;
const storageMTime = ~~((stat && stat.mtime || 0) / 1000);
const key = `${path}-${storageMTime}`;
if (this.recentProcessedInternalFiles.contains(key)) {
//If recently processed, it may caused by self.
return;
}
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
const id = filename2idInternalChunk(path);
const filesOnDB = await this.localDatabase.getDBEntryMeta(id);
const dbMTime = ~~((filesOnDB && filesOnDB.mtime || 0) / 1000);
// Skip unchanged file.
if (dbMTime == storageMTime) {
// Logger(`STORAGE --> DB:${path}: (hidden) Nothing changed`);
return;
}
// Do not compare timestamp. Always local data should be preferred except this plugin wrote one.
if (storageMTime == 0) {
await this.deleteInternalFileOnDatabase(path);
} else {
await this.storeInternalFileToDatabase({ path: path, ...stat });
}
}
GetAllFilesRecursively(file: TAbstractFile): TFile[] {
if (file instanceof TFile) {
return [file];
@@ -930,11 +1003,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async watchVaultRenameAsync(file: TAbstractFile, oldFile: any, cache?: CacheData) {
Logger(`${oldFile} renamed to ${file.path}`, LOG_LEVEL.VERBOSE);
try {
await this.applyBatchChange();
} catch (ex) {
Logger(ex);
}
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.
@@ -1111,7 +1179,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
async deleteVaultItem(file: TFile | TFolder) {
if (!this.isTargetFile(file)) return;
if (file instanceof TFile) {
if (!this.isTargetFile(file)) return;
}
const dir = file.parent;
if (this.settings.trashInsteadDelete) {
await this.app.vault.trash(file, false);
@@ -1230,6 +1300,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}
}
);
this.refreshStatusText();
}
async handleDBChangedAsync(change: EntryBody) {
@@ -1290,6 +1361,7 @@ 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);
@@ -1596,6 +1668,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
updateStatusBarText() { }
async replicate(showMessage?: boolean) {
if (!this.isReady) return;
if (this.settings.versionUpFlash != "") {
Logger("Open settings and check message, please.", LOG_LEVEL.NOTICE);
return;
@@ -1605,7 +1678,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
await this.sweepPlugin(false);
}
await this.loadQueuedFiles();
if (this.settings.syncInternalFiles && this.settings.syncInternalFilesBeforeReplication) {
if (this.settings.syncInternalFiles && this.settings.syncInternalFilesBeforeReplication && !this.settings.watchInternalFileChanges) {
await this.syncInternalFilesAndDatabase("push", showMessage);
}
this.localDatabase.openReplication(this.settings, false, showMessage, this.parseReplicationResult);
@@ -1717,9 +1790,14 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
});
if (!initialScan) {
await runAll("UPDATE STORAGE", onlyInDatabase, async (e) => {
Logger(`Check or pull from db:${e}`);
await this.pullFile(e, filesStorage, false, null, false);
Logger(`Check or pull from db:${e} OK`);
const w = await this.localDatabase.getDBEntryMeta(e);
if (w) {
Logger(`Check or pull from db:${e}`);
await this.pullFile(e, filesStorage, false, null, false);
Logger(`Check or pull from db:${e} OK`);
} else {
Logger(`entry not found, maybe deleted (it is normal behavior):${e}`);
}
});
}
if (!initialScan) {
@@ -2322,7 +2400,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.periodicInternalFileScanHandler != null) {
this.clearInternalFileScan();
}
if (this.settings.syncInternalFiles && this.settings.syncInternalFilesInterval > 0) {
if (this.settings.syncInternalFiles && this.settings.syncInternalFilesInterval > 0 && !this.settings.watchInternalFileChanges) {
this.periodicPluginSweepHandler = this.setInterval(async () => await this.periodicInternalFileScan(), this.settings.syncInternalFilesInterval * 1000);
}
}

View File

@@ -1,5 +1,19 @@
### 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`.
- Due to using an internal API, this feature may become unusable with a major update. If this happens, please disable this once.
#### Minors
- 0.16.1 Added missing log updates.
- 0.16.2 Fixed many problems caused by combinations of `Sync On Save` and the tracking logic that changed at 0.15.6.
- 0.16.3
- Fixed detection of IBM Cloudant (And if there are some issues, be fixed automatically).
- A configuration information reporting tool has been implemented.
- 0.16.4 Fixed detection failure. Please set the `Chunk size` again when using a self-hosted database.
### 0.15.0
- Outdated configuration items have been removed.
- Outdated configuration items have been removed.
- Setup wizard has been implemented!
I appreciate for reviewing and giving me advice @Pouhon158!
@@ -11,30 +25,11 @@ I appreciate for reviewing and giving me advice @Pouhon158!
- 0.15.4 Fixed issues about asynchronous processing (e.g., Conflict check or hidden file detection)
- 0.15.5 Add new features for setting Self-hosted LiveSync up more easier.
- 0.15.6 File tracking logic has been refined.
### 0.14.1
- The target selecting filter was implemented.
Now we can set what files are synchronised by regular expression.
- We can configure the size of chunks.
We can use larger chunks to improve performance.
(This feature can not be used with IBM Cloudant)
- Read chunks online.
Now we can synchronise only metadata and retrieve chunks on demand. It reduces local database size and time for replication.
- Added this note.
- Use local chunks in preference to remote them if present,
#### Recommended configuration for Self-hosted CouchDB
- Set chunk size to around 100 to 250 (10MB - 25MB per chunk)
- *Set batch size to 100 and batch limit to 20 (0.14.2)*
- Be sure to `Read chunks online` checked.
#### Minors
- 0.14.2 Fixed issue about retrieving files if synchronisation has been interrupted or failed
- 0.14.3 New test items have been added to `Check database configuration`.
- 0.14.4 Fixed issue of importing configurations.
- 0.14.5 Auto chunk size adjusting implemented.
- 0.14.6 Change Target to ES2018
- 0.14.7 Refactor and fix typos.
- 0.14.8 Refactored again. There should be no change in behaviour, but please let me know if there is any.
- 0.15.7 Fixed bug about renaming file.
- 0.15.8 Fixed bug about deleting empty directory, weird behaviour on boot-sequence on mobile devices.
- 0.15.9 Improved chunk retrieving, now chunks are retrieved in batch on continuous requests.
- 0.15.10 Fixed:
- The boot sequence has been corrected and now boots smoothly.
- Auto applying of batch save will be processed earlier than before.
... To continue on to `updates_old.md`.

View File

@@ -1,3 +1,28 @@
### 0.14.1
- The target selecting filter was implemented.
Now we can set what files are synchronised by regular expression.
- We can configure the size of chunks.
We can use larger chunks to improve performance.
(This feature can not be used with IBM Cloudant)
- Read chunks online.
Now we can synchronise only metadata and retrieve chunks on demand. It reduces local database size and time for replication.
- Added this note.
- Use local chunks in preference to remote them if present,
#### Recommended configuration for Self-hosted CouchDB
- Set chunk size to around 100 to 250 (10MB - 25MB per chunk)
- *Set batch size to 100 and batch limit to 20 (0.14.2)*
- Be sure to `Read chunks online` checked.
#### Minors
- 0.14.2 Fixed issue about retrieving files if synchronisation has been interrupted or failed
- 0.14.3 New test items have been added to `Check database configuration`.
- 0.14.4 Fixed issue of importing configurations.
- 0.14.5 Auto chunk size adjusting implemented.
- 0.14.6 Change Target to ES2018
- 0.14.7 Refactor and fix typos.
- 0.14.8 Refactored again. There should be no change in behaviour, but please let me know if there is any.
### 0.13.0
- The metadata of the deleted files will be kept on the database by default. If you want to delete this as the previous version, please turn on `Delete metadata of deleted files.`. And, if you have upgraded from the older version, please ensure every device has been upgraded.