Compare commits

...

4 Commits

Author SHA1 Message Date
vorotamoroz
e2f06181fa bumped 2022-07-21 13:06:57 +09:00
vorotamoroz
bb6d787607 Imprinting version numbers to boot log. 2022-07-21 13:06:42 +09:00
vorotamoroz
cb406e2db6 New feature.
- Local database name can now be customized.
- Buttons to back skip-patterns of Hidden file sync to default.
2022-07-21 13:05:35 +09:00
vorotamoroz
0a1248c5fc Fixed:
- Now Notification is less noisy.
- Some synchronization won't be missed.
- Scanning speed improved.

Implemented:

- Implemented notifications to reload the plugin.
- Rescue button to updating all hidden files for overwriting them on other vaults.
2022-07-20 16:57:21 +09:00
8 changed files with 334 additions and 68 deletions

View File

@@ -3,7 +3,7 @@ import process from "process";
import builtins from "builtin-modules"; import builtins from "builtin-modules";
import sveltePlugin from "esbuild-svelte"; import sveltePlugin from "esbuild-svelte";
import sveltePreprocess from "svelte-preprocess"; import sveltePreprocess from "svelte-preprocess";
import fs from "node:fs";
const banner = `/* const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin if you want to view the source, please visit the github repository of this plugin
@@ -11,7 +11,8 @@ if you want to view the source, please visit the github repository of this plugi
`; `;
const prod = process.argv[2] === "production"; const prod = process.argv[2] === "production";
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json"));
const packageJson = JSON.parse(fs.readFileSync("./package.json"));
esbuild esbuild
.build({ .build({
banner: { banner: {
@@ -19,6 +20,10 @@ esbuild
}, },
entryPoints: ["src/main.ts"], entryPoints: ["src/main.ts"],
bundle: true, bundle: true,
define: {
"MANIFEST_VERSION": `"${manifestJson.version}"`,
"PACKAGE_VERSION": `"${packageJson.version}"`,
},
external: ["obsidian", "electron", ...builtins], external: ["obsidian", "electron", ...builtins],
format: "cjs", format: "cjs",
watch: !prod, watch: !prod,

View File

@@ -1,7 +1,7 @@
{ {
"id": "obsidian-livesync", "id": "obsidian-livesync",
"name": "Self-hosted LiveSync", "name": "Self-hosted LiveSync",
"version": "0.12.0", "version": "0.12.2",
"minAppVersion": "0.9.12", "minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz", "author": "vorotamoroz",

4
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.12.0", "version": "0.12.2",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.", "description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js", "main": "main.js",
"type": "module", "type": "module",

View File

@@ -1,4 +1,4 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl } from "obsidian"; import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, RequestUrlParam, requestUrl, TextAreaComponent } from "obsidian";
import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types"; import { EntryDoc, LOG_LEVEL, RemoteDBSettings } from "./lib/src/types";
import { path2id, id2path } from "./utils"; import { path2id, id2path } from "./utils";
import { delay, runWithLock } from "./lib/src/utils"; import { delay, runWithLock } from "./lib/src/utils";
@@ -591,6 +591,31 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
let newDatabaseName = this.plugin.settings.additionalSuffixOfDatabaseName + "";
new Setting(containerLocalDatabaseEl)
.setName("Database suffix")
.setDesc("Set unique name for using same vault name on different directory.")
.addText((text) => {
text.setPlaceholder("")
.setValue(newDatabaseName)
.onChange((value) => {
newDatabaseName = value;
});
}).addButton((button) => {
button.setButtonText("Change")
.onClick(async () => {
if (this.plugin.settings.additionalSuffixOfDatabaseName == newDatabaseName) {
Logger("Suffix was not changed.", LOG_LEVEL.NOTICE);
return;
}
this.plugin.settings.additionalSuffixOfDatabaseName = newDatabaseName;
await this.plugin.saveSettings();
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL.NOTICE);
await this.plugin.initializeDatabase();
})
})
addScreenElement("10", containerLocalDatabaseEl); addScreenElement("10", containerLocalDatabaseEl);
const containerGeneralSettingsEl = containerEl.createDiv(); const containerGeneralSettingsEl = containerEl.createDiv();
@@ -787,6 +812,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
); );
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Scan hidden files periodicaly.") .setName("Scan hidden files periodicaly.")
.setDesc("Seconds, zero to disable.")
.addText((text) => { .addText((text) => {
text.setPlaceholder("") text.setPlaceholder("")
.setValue(this.plugin.settings.syncInternalFilesInterval + "") .setValue(this.plugin.settings.syncInternalFilesInterval + "")
@@ -800,12 +826,15 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
let skipPatternTextArea: TextAreaComponent = null;
const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, \\/obsidian-livesync\\/";
const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$";
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
.setName("Skip patterns") .setName("Skip patterns")
.setDesc( .setDesc(
"Regular expression" "Regular expression, If you use hidden file sync between desktop and mobile, adding `workspace$` is recommended."
) )
.addTextArea((text) => .addTextArea((text) => {
text text
.setValue(this.plugin.settings.syncInternalFilesIgnorePatterns) .setValue(this.plugin.settings.syncInternalFilesIgnorePatterns)
.setPlaceholder("\\/node_modules\\/, \\/\\.git\\/") .setPlaceholder("\\/node_modules\\/, \\/\\.git\\/")
@@ -813,7 +842,52 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
this.plugin.settings.syncInternalFilesIgnorePatterns = value; this.plugin.settings.syncInternalFilesIgnorePatterns = value;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
skipPatternTextArea = text;
return text;
}
); );
new Setting(containerSyncSettingEl)
.setName("Skip patterns defaults")
.addButton((button) => {
button.setButtonText("Default")
.onClick(async () => {
skipPatternTextArea.setValue(defaultSkipPattern);
this.plugin.settings.syncInternalFilesIgnorePatterns = defaultSkipPattern;
await this.plugin.saveSettings();
})
}).addButton((button) => {
button.setButtonText("Cross-platform")
.onClick(async () => {
skipPatternTextArea.setValue(defaultSkipPatternXPlat);
this.plugin.settings.syncInternalFilesIgnorePatterns = defaultSkipPatternXPlat;
await this.plugin.saveSettings();
})
})
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)
.setClass("sls-btn-left")
.onClick(async () => {
const filesAll = await this.plugin.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", { containerSyncSettingEl.createEl("h3", {
text: sanitizeHTMLToDom(`Advanced settings`), text: sanitizeHTMLToDom(`Advanced settings`),
}); });

Submodule src/lib updated: 1f67fb604c...1133f82732

View File

@@ -29,7 +29,7 @@ import { DocumentHistoryModal } from "./DocumentHistoryModal";
//@ts-ignore //@ts-ignore
import PluginPane from "./PluginPane.svelte"; import PluginPane from "./PluginPane.svelte";
import { id2path, path2id } from "./utils"; import { clearAllPeriodic, clearAllTriggers, disposeMemoObject, id2path, memoIfNotExist, memoObject, path2id, retriveMemoObject, setTrigger } from "./utils";
import { decrypt, encrypt } from "./lib/src/e2ee_v2"; import { decrypt, encrypt } from "./lib/src/e2ee_v2";
const isDebug = false; const isDebug = false;
@@ -189,6 +189,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
deviceAndVaultName: string; deviceAndVaultName: string;
isMobile = false; isMobile = false;
getVaultName(): string {
return this.app.vault.getName() + (this.settings?.additionalSuffixOfDatabaseName ? ("-" + this.settings.additionalSuffixOfDatabaseName) : "");
}
setInterval(handler: () => any, timeout?: number): number { setInterval(handler: () => any, timeout?: number): number {
const timer = window.setInterval(handler, timeout); const timer = window.setInterval(handler, timeout);
this.registerInterval(timer); this.registerInterval(timer);
@@ -214,7 +218,12 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
async onload() { async onload() {
setLogger(this.addLog.bind(this)); // Logger moved to global. setLogger(this.addLog.bind(this)); // Logger moved to global.
Logger("loading plugin"); Logger("loading plugin");
const lsname = "obsidian-live-sync-ver" + this.app.vault.getName(); //@ts-ignore
const manifestVersion = MANIFEST_VERSION || "-";
//@ts-ignore
const packageVersion = PACKAGE_VERSION || "-";
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsname = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsname); const last_version = localStorage.getItem(lsname);
await this.loadSettings(); await this.loadSettings();
//@ts-ignore //@ts-ignore
@@ -296,6 +305,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.settings.autoSweepPlugins = false; this.settings.autoSweepPlugins = false;
this.settings.usePluginSync = false; this.settings.usePluginSync = false;
this.settings.suspendFileWatching = true; this.settings.suspendFileWatching = true;
this.settings.syncInternalFiles = false;
await this.saveSettings(); await this.saveSettings();
await this.openDatabase(); await this.openDatabase();
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured."; const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
@@ -545,6 +555,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
this.localDatabase.closeReplication(); this.localDatabase.closeReplication();
this.localDatabase.close(); this.localDatabase.close();
} }
clearAllPeriodic();
clearAllTriggers();
window.removeEventListener("visibilitychange", this.watchWindowVisiblity); window.removeEventListener("visibilitychange", this.watchWindowVisiblity);
Logger("unloading plugin"); Logger("unloading plugin");
} }
@@ -553,7 +565,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.localDatabase != null) { if (this.localDatabase != null) {
this.localDatabase.close(); this.localDatabase.close();
} }
const vaultName = this.app.vault.getName(); const vaultName = this.getVaultName();
Logger("Open Database..."); Logger("Open Database...");
//@ts-ignore //@ts-ignore
const isMobile = this.app.isMobile; const isMobile = this.app.isMobile;
@@ -580,7 +592,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
// So, use history is always enabled. // So, use history is always enabled.
this.settings.useHistory = true; this.settings.useHistory = true;
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName(); const lsname = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
if (this.settings.deviceAndVaultName != "") { if (this.settings.deviceAndVaultName != "") {
if (!localStorage.getItem(lsname)) { if (!localStorage.getItem(lsname)) {
this.deviceAndVaultName = this.settings.deviceAndVaultName; this.deviceAndVaultName = this.settings.deviceAndVaultName;
@@ -596,7 +608,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
async saveSettings() { async saveSettings() {
const lsname = "obsidian-live-sync-vaultanddevicename-" + this.app.vault.getName(); const lsname = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
localStorage.setItem(lsname, this.deviceAndVaultName || ""); localStorage.setItem(lsname, this.deviceAndVaultName || "");
await this.saveData(this.settings); await this.saveData(this.settings);
@@ -859,7 +871,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL.VERBOSE) { if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL.VERBOSE) {
return; return;
} }
const valutName = this.app.vault.getName(); const valutName = this.getVaultName();
const timestamp = new Date().toLocaleString(); const timestamp = new Date().toLocaleString();
const messagecontent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2); const messagecontent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
const newmessage = timestamp + "->" + messagecontent; const newmessage = timestamp + "->" + messagecontent;
@@ -1113,11 +1125,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
saveQueuedFiles() { saveQueuedFiles() {
const saveData = JSON.stringify(this.queuedFiles.filter((e) => !e.done).map((e) => e.entry._id)); const saveData = JSON.stringify(this.queuedFiles.filter((e) => !e.done).map((e) => e.entry._id));
const lsname = "obsidian-livesync-queuefiles-" + this.app.vault.getName(); const lsname = "obsidian-livesync-queuefiles-" + this.getVaultName();
localStorage.setItem(lsname, saveData); localStorage.setItem(lsname, saveData);
} }
async loadQueuedFiles() { async loadQueuedFiles() {
const lsname = "obsidian-livesync-queuefiles-" + this.app.vault.getName(); const lsname = "obsidian-livesync-queuefiles-" + this.getVaultName();
const ids = JSON.parse(localStorage.getItem(lsname) || "[]") as string[]; const ids = JSON.parse(localStorage.getItem(lsname) || "[]") as string[];
const ret = await this.localDatabase.localDatabase.allDocs({ keys: ids, include_docs: true }); const ret = await this.localDatabase.localDatabase.allDocs({ keys: ids, include_docs: true });
for (const doc of ret.rows) { for (const doc of ret.rows) {
@@ -2224,7 +2236,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
} }
} }
await this.localDatabase.putDBEntry(saveData, true); await this.localDatabase.putDBEntry(saveData, true);
Logger(`internal files STORAGE --> DB:${file.path}: Done`); Logger(`STORAGE --> DB:${file.path}: (hidden) Done`);
}); });
} }
@@ -2328,8 +2340,22 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
}); });
} }
filterTargetFiles(files: InternalFileInfo[], targetFiles: string[] | false = false) {
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns.toLocaleLowerCase()
.replace(/\n| /g, "")
.split(",").filter(e => e).map(e => new RegExp(e));
// const files = await this.scanInternalFiles();
return files.filter(file => !ignorePatterns.some(e => file.path.match(e))).filter(file => !targetFiles || (targetFiles && targetFiles.indexOf(file.path) !== -1))
//if (ignorePatterns.some(e => filename.match(e))) continue;
//if (targetFiles !== false && targetFiles.indexOf(filename) == -1) continue;
}
async applyMTimeToFile(file: InternalFileInfo) {
await this.app.vault.adapter.append(file.path, "", { ctime: file.ctime, mtime: file.mtime });
}
confirmPopup: WrappedNotice = null; confirmPopup: WrappedNotice = null;
confirmPopupTimer: number = null;
//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", showMessage: boolean, files: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) {
const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO; const logLevel = showMessage ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO;
Logger("Scanning hidden files.", logLevel, "sync_internal"); Logger("Scanning hidden files.", logLevel, "sync_internal");
@@ -2338,7 +2364,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
.split(",").filter(e => e).map(e => new RegExp(e)); .split(",").filter(e => e).map(e => new RegExp(e));
if (!files) files = await this.scanInternalFiles(); if (!files) files = await this.scanInternalFiles();
const filesOnDB = (await this.localDatabase.localDatabase.allDocs({ startkey: "i:", endkey: "i;", include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]; const filesOnDB = (await this.localDatabase.localDatabase.allDocs({ startkey: "i:", endkey: "i;", include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[];
const allFileNames = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(e._id.substring("i:".length))))])]; const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => normalizePath(id2path(e._id.substring("i:".length))))])];
const allFileNames = allFileNamesSrc.filter(filename => !targetFiles || (targetFiles && targetFiles.indexOf(filename) !== -1))
function compareMTime(a: number, b: number) { function compareMTime(a: number, b: number) {
const wa = ~~(a / 1000); const wa = ~~(a / 1000);
const wb = ~~(b / 1000); const wb = ~~(b / 1000);
@@ -2351,35 +2378,68 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
let filesChanged = 0; let filesChanged = 0;
const p = Parallels(); const p = Parallels();
const limit = 10; const limit = 10;
// count updated files up as like this below:
// .obsidian: 2
// .obsidian/workspace: 1
// .obsidian/plugins: 1
// .obsidian/plugins/recent-files-obsidian: 1
// .obsidian/plugins/recent-files-obsidian/data.json: 1
const updatedFolders: { [key: string]: number } = {}
const countUpdatedFolder = (path: string) => {
const pieces = path.split("/");
let c = pieces.shift();
let pathPieces = "";
filesChanged++;
while (c) {
pathPieces += (pathPieces != "" ? "/" : "") + c;
pathPieces = normalizePath(pathPieces);
if (!(pathPieces in updatedFolders)) {
updatedFolders[pathPieces] = 0;
}
updatedFolders[pathPieces]++;
c = pieces.shift();
}
}
// Cache update time information for files which have already been processed (mainly for files that were skipped due to the same content)
let caches: { [key: string]: { storageMtime: number; docMtime: number } } = {};
caches = await this.localDatabase.kvDB.get<{ [key: string]: { storageMtime: number; docMtime: number } }>("diff-caches-internal") || {};
for (const filename of allFileNames) { for (const filename of allFileNames) {
// Logger(`Processing:${filename}`, LOG_LEVEL.VERBOSE);
processed++; processed++;
if (processed % 100 == 0) Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal"); if (processed % 100 == 0) Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
if (ignorePatterns.some(e => filename.match(e))) continue; if (ignorePatterns.some(e => filename.match(e))) continue;
if (targetFiles !== false && targetFiles.indexOf(filename) == -1) continue;
const fileOnStorage = files.find(e => e.path == filename); const fileOnStorage = files.find(e => e.path == filename);
const fileOnDatabase = filesOnDB.find(e => e._id == "i:" + id2path(filename)); const fileOnDatabase = filesOnDB.find(e => e._id == "i:" + id2path(filename));
let proc: () => Promise<void> | null = null; // TODO: Fix this somehow smart.
let proc: Promise<void> | null;
if (fileOnStorage && fileOnDatabase) { if (fileOnStorage && fileOnDatabase) {
// Both => Synchronize // Both => Synchronize
const cache = filename in caches ? caches[filename] : { storageMtime: 0, docMtime: 0 };
if (fileOnDatabase.mtime == cache.docMtime && fileOnStorage.mtime == cache.storageMtime) {
continue;
}
const nw = compareMTime(fileOnStorage.mtime, fileOnDatabase.mtime); const nw = compareMTime(fileOnStorage.mtime, fileOnDatabase.mtime);
if (nw == 0) continue; if (nw == 0) continue;
if (nw > 0) { if (nw > 0) {
proc = async () => { proc = (async (fileOnStorage) => {
await this.storeInternaFileToDatabase(fileOnStorage); await this.storeInternaFileToDatabase(fileOnStorage);
} cache.docMtime = fileOnDatabase.mtime;
cache.storageMtime = fileOnStorage.mtime;
caches[filename] = cache;
})(fileOnStorage);
} }
if (nw < 0) { if (nw < 0) {
proc = async () => { proc = (async (filename) => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; cache.docMtime = fileOnDatabase.mtime;
cache.storageMtime = fileOnStorage.mtime;
caches[filename] = cache;
countUpdatedFolder(filename);
} }
} })(filename);
} }
} else if (!fileOnStorage && fileOnDatabase) { } else if (!fileOnStorage && fileOnDatabase) {
@@ -2387,71 +2447,137 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
if (fileOnDatabase.deleted) { if (fileOnDatabase.deleted) {
// await this.storeInternaFileToDatabase(fileOnStorage); // await this.storeInternaFileToDatabase(fileOnStorage);
} else { } else {
proc = async () => { proc = (async () => {
await this.deleteInternaFileOnDatabase(filename); await this.deleteInternaFileOnDatabase(filename);
} })();
} }
} else if (direction == "pull") { } else if (direction == "pull") {
proc = async () => { proc = (async () => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; countUpdatedFolder(filename);
} }
} })();
} else if (direction == "safe") { } else if (direction == "safe") {
if (fileOnDatabase.deleted) { if (fileOnDatabase.deleted) {
// await this.storeInternaFileToDatabase(fileOnStorage); // await this.storeInternaFileToDatabase(fileOnStorage);
} else { } else {
proc = async () => { proc = (async () => {
if (await this.extractInternaFileFromDatabase(filename)) { if (await this.extractInternaFileFromDatabase(filename)) {
filesChanged++; countUpdatedFolder(filename);
} }
} })();
} }
} }
} else if (fileOnStorage && !fileOnDatabase) { } else if (fileOnStorage && !fileOnDatabase) {
proc = async () => { proc = (async () => {
await this.storeInternaFileToDatabase(fileOnStorage); await this.storeInternaFileToDatabase(fileOnStorage);
} })();
} else { } else {
throw new Error("Invalid state on hidden file sync"); throw new Error("Invalid state on hidden file sync");
// Something corrupted? // Something corrupted?
} }
if (proc) p.add(proc()); if (proc) p.add(proc);
proc = null;
await p.wait(limit); await p.wait(limit);
} }
await p.all(); await p.all();
// Show notification to restart obsidian. await this.localDatabase.kvDB.set("diff-caches-internal", caches);
// When files has been retreived from the database. they must be reloaded.
if (direction == "pull" && filesChanged != 0) { if (direction == "pull" && 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) {
// Numbers of updated files that is below of configDir.
let updatedCount = updatedFolders[configDir];
try {
//@ts-ignore
const manifests = Object.values(this.app.plugins.manifests) as PluginManifest[];
//@ts-ignore
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
const enabledPluginManifests = manifests.filter(e => enabledPlugins.has(e.id));
for (const manifest of enabledPluginManifests) {
if (manifest.dir in updatedFolders) {
// If notified about plug-ins, reloading Obsidian may not be necessary.
updatedCount -= updatedFolders[manifest.dir];
const updatePluginId = manifest.id;
const updatePluginName = manifest.name;
const fragment = createFragment((doc) => {
doc.createEl("span", null, (a) => {
a.appendText(`Files in ${updatePluginName} has been updated, Press `)
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE";
anchor.addEventListener("click", async () => {
Logger(`Unloading plugin: ${updatePluginName}`, LOG_LEVEL.NOTICE, "pluin-reload-" + updatePluginId);
// @ts-ignore
await this.app.plugins.unloadPlugin(updatePluginId);
// @ts-ignore
await this.app.plugins.loadPlugin(updatePluginId);
Logger(`Plugin reloaded: ${updatePluginName}`, LOG_LEVEL.NOTICE, "pluin-reload-" + updatePluginId);
});
}))
const fragment = createFragment((doc) => { a.appendText(` to reload ${updatePluginName}, or press elsewhere to dismiss this message.`)
doc.createEl("span", null, (a) => { });
a.appendText(`Hidden files have been synchronized, Press `) });
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE"; const updatedPluginKey = "popupUpdated-" + updatePluginId;
anchor.addEventListener("click", () => { setTrigger(updatedPluginKey, 1000, async () => {
// @ts-ignore const popup = await memoIfNotExist(updatedPluginKey, () => new Notice(fragment, 0));
this.app.commands.executeCommandById("app:reload") //@ts-ignore
const isShown = popup?.noticeEl?.isShown();
if (!isShown) {
memoObject(updatedPluginKey, new Notice(fragment, 0))
}
setTrigger(updatedPluginKey + "-close", 20000, () => {
const popup = retriveMemoObject<Notice>(updatedPluginKey)
if (!popup) return;
//@ts-ignore
if (popup?.noticeEl?.isShown()) {
popup.hide();
}
disposeMemoObject(updatedPluginKey);
})
})
}
}
} catch (ex) {
Logger("Error on checking plugin status.");
Logger(ex, LOG_LEVEL.VERBOSE);
}
// If something changes left, notify for reloading Obsidian.
if (updatedCount != 0) {
const fragment = createFragment((doc) => {
doc.createEl("span", null, (a) => {
a.appendText(`Hidden files have been synchronized, Press `)
a.appendChild(a.createEl("a", null, (anchor) => {
anchor.text = "HERE";
anchor.addEventListener("click", () => {
// @ts-ignore
this.app.commands.executeCommandById("app:reload")
});
}))
a.appendText(` to reload obsidian, or press elsewhere to dismiss this message.`)
}); });
})) });
a.appendText(` to reload obsidian, or press elsewhere to dismiss this message.`) setTrigger("popupUpdated-" + configDir, 1000, () => {
}); //@ts-ignore
}); const isShown = this.confirmPopup?.noticeEl?.isShown();
//@ts-ignore if (!isShown) {
const isShown = this.confirmPopup?.noticeEl?.isShown(); this.confirmPopup = new Notice(fragment, 0);
if (!isShown) { }
this.confirmPopup = new Notice(fragment, 0); setTrigger("popupClose" + configDir, 20000, () => {
this.confirmPopup?.hide();
this.confirmPopup = null;
})
})
}
} }
if (this.confirmPopupTimer != null) {
clearTimeout(this.confirmPopupTimer);
}
setTimeout(() => {
this.confirmPopup?.hide();
this.confirmPopup = null;
}, 10000)
} }
Logger(`Hidden files scanned`, logLevel, "sync_internal"); Logger(`Hidden files scanned: ${filesChanged} files had been modified`, logLevel, "sync_internal");
} }
} }

View File

@@ -12,3 +12,64 @@ export function path2id(filename: string): string {
export function id2path(filename: string): string { export function id2path(filename: string): string {
return id2path_base(normalizePath(filename)); return id2path_base(normalizePath(filename));
} }
const triggers: { [key: string]: ReturnType<typeof setTimeout> } = {};
export function setTrigger(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearTrigger(key);
triggers[key] = setTimeout(async () => {
delete triggers[key];
await proc();
}, timeout);
}
export function clearTrigger(key: string) {
if (key in triggers) {
clearTimeout(triggers[key]);
}
}
export function clearAllTriggers() {
for (const v in triggers) {
clearTimeout(triggers[v]);
}
}
const intervals: { [key: string]: ReturnType<typeof setInterval> } = {};
export function setPeriodic(key: string, timeout: number, proc: (() => Promise<any> | void)) {
clearPeriodic(key);
intervals[key] = setInterval(async () => {
delete intervals[key];
await proc();
}, timeout);
}
export function clearPeriodic(key: string) {
if (key in intervals) {
clearInterval(intervals[key]);
}
}
export function clearAllPeriodic() {
for (const v in intervals) {
clearInterval(intervals[v]);
}
}
const memos: { [key: string]: any } = {};
export function memoObject<T>(key: string, obj: T): T {
memos[key] = obj;
return memos[key] as T;
}
export async function memoIfNotExist<T>(key: string, func: () => T | Promise<T>): Promise<T> {
if (!(key in memos)) {
const w = func();
const v = w instanceof Promise ? (await w) : w;
memos[key] = v;
}
return memos[key] as T;
}
export function retriveMemoObject<T>(key: string): T | false {
if (key in memos) {
return memos[key];
} else {
return false;
}
}
export function disposeMemoObject(key: string) {
delete memos[key];
}