mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-04-14 21:18:32 +00:00
Compare commits
5 Commits
0.24.0.dev
...
0.24.0.dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9020a3aea | ||
|
|
5b4ae37030 | ||
|
|
6d244a6e34 | ||
|
|
e0e0ab0426 | ||
|
|
7ca5ac5ac7 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.24.0.dev-rc2",
|
"version": "0.24.0.dev-rc7",
|
||||||
"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
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.24.0.dev-rc2",
|
"version": "0.24.0.dev-rc7",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.24.0.dev-rc2",
|
"version": "0.24.0.dev-rc7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.645.0",
|
"@aws-sdk/client-s3": "^3.645.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.24.0.dev-rc2",
|
"version": "0.24.0.dev-rc7",
|
||||||
"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",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const EVENT_LEAF_ACTIVE_CHANGED = "leaf-active-changed";
|
|||||||
export const EVENT_LOG_ADDED = "log-added";
|
export const EVENT_LOG_ADDED = "log-added";
|
||||||
|
|
||||||
export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings";
|
export const EVENT_REQUEST_OPEN_SETTINGS = "request-open-settings";
|
||||||
|
export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
|
||||||
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
||||||
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
|
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, File
|
|||||||
import { CANCELLED, LEAVE_TO_SUBSEQUENT, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_SHINY } from "../../lib/src/common/types.ts";
|
import { CANCELLED, LEAVE_TO_SUBSEQUENT, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_SHINY } from "../../lib/src/common/types.ts";
|
||||||
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../../common/types.ts";
|
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../../common/types.ts";
|
||||||
import { createBlob, createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocData, getDocDataAsArray, isDocContentSame, isLoadedEntry, isObjectDifferent } from "../../lib/src/common/utils.ts";
|
import { createBlob, createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocData, getDocDataAsArray, isDocContentSame, isLoadedEntry, isObjectDifferent } from "../../lib/src/common/utils.ts";
|
||||||
import { Logger } from "../../lib/src/common/logger.ts";
|
|
||||||
import { digestHash } from "../../lib/src/string_and_binary/hash.ts";
|
import { digestHash } from "../../lib/src/string_and_binary/hash.ts";
|
||||||
import { arrayBufferToBase64, decodeBinary, readString } from '../../lib/src/string_and_binary/convert.ts';
|
import { arrayBufferToBase64, decodeBinary, readString } from '../../lib/src/string_and_binary/convert.ts';
|
||||||
import { serialized, shareRunningResult } from "../../lib/src/concurrency/lock.ts";
|
import { serialized, shareRunningResult } from "../../lib/src/concurrency/lock.ts";
|
||||||
@@ -343,7 +342,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
get useSyncPluginEtc() {
|
get useSyncPluginEtc() {
|
||||||
return this.plugin.settings.usePluginEtc;
|
return this.plugin.settings.usePluginEtc;
|
||||||
}
|
}
|
||||||
$isThisModuleEnabled() {
|
_isThisModuleEnabled() {
|
||||||
return this.plugin.settings.usePluginSync;
|
return this.plugin.settings.usePluginSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +351,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
|
|
||||||
pluginList: IPluginDataExDisplay[] = [];
|
pluginList: IPluginDataExDisplay[] = [];
|
||||||
showPluginSyncModal() {
|
showPluginSyncModal() {
|
||||||
if (!this.$isThisModuleEnabled()) {
|
if (!this._isThisModuleEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.pluginDialog) {
|
if (this.pluginDialog) {
|
||||||
@@ -417,19 +416,19 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return this.getFileCategory(filePath) != "";
|
return this.getFileCategory(filePath) != "";
|
||||||
}
|
}
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
if (!this.$isThisModuleEnabled()) return true;
|
if (!this._isThisModuleEnabled()) return true;
|
||||||
try {
|
try {
|
||||||
Logger("Scanning customizations...");
|
this._log("Scanning customizations...");
|
||||||
await this.scanAllConfigFiles(showNotice);
|
await this.scanAllConfigFiles(showNotice);
|
||||||
Logger("Scanning customizations : done");
|
this._log("Scanning customizations : done");
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Scanning customizations : failed");
|
this._log("Scanning customizations : failed");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showNotice: boolean) {
|
async $everyBeforeReplicate(showNotice: boolean) {
|
||||||
if (!this.$isThisModuleEnabled()) return true;
|
if (!this._isThisModuleEnabled()) return true;
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
await this.scanAllConfigFiles(showNotice);
|
await this.scanAllConfigFiles(showNotice);
|
||||||
return true;
|
return true;
|
||||||
@@ -437,8 +436,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyOnResumeProcess(): Promise<boolean> {
|
async $everyOnResumeProcess(): Promise<boolean> {
|
||||||
if (!this.$isThisModuleEnabled()) return true;
|
if (!this._isThisModuleEnabled()) return true;
|
||||||
if (this.$isMainSuspended()) {
|
if (this._isMainSuspended()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
@@ -449,7 +448,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
$everyAfterResumeProcess(): Promise<boolean> {
|
||||||
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
|
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
|
||||||
q?.toggleClass("sls-hidden", !this.$isThisModuleEnabled());
|
q?.toggleClass("sls-hidden", !this._isThisModuleEnabled());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async reloadPluginList(showMessage: boolean) {
|
async reloadPluginList(showMessage: boolean) {
|
||||||
@@ -477,7 +476,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
xFiles.push(work);
|
xFiles.push(work);
|
||||||
}
|
}
|
||||||
if (missingHash) {
|
if (missingHash) {
|
||||||
Logger(`Digest created for ${path} to improve checking`, LOG_LEVEL_VERBOSE);
|
this._log(`Digest created for ${path} to improve checking`, LOG_LEVEL_VERBOSE);
|
||||||
wx.data = serialize(data);
|
wx.data = serialize(data);
|
||||||
fireAndForget(() => this.localDatabase.putDBEntry(createSavingEntryFromLoadedEntry(wx)));
|
fireAndForget(() => this.localDatabase.putDBEntry(createSavingEntryFromLoadedEntry(wx)));
|
||||||
}
|
}
|
||||||
@@ -512,8 +511,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return [];
|
return [];
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE);
|
this._log(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline();
|
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline();
|
||||||
@@ -536,8 +535,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return [];
|
return [];
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE);
|
this._log(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline();
|
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline();
|
||||||
@@ -583,11 +582,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
const d = await this.localDatabase.getDBEntry(unifiedPathV2);
|
const d = await this.localDatabase.getDBEntry(unifiedPathV2);
|
||||||
if (!d) {
|
if (!d) {
|
||||||
Logger(`The file ${unifiedPathV2} is not found`, LOG_LEVEL_VERBOSE);
|
this._log(`The file ${unifiedPathV2} is not found`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!isLoadedEntry(d)) {
|
if (!isLoadedEntry(d)) {
|
||||||
Logger(`The file ${unifiedPathV2} is not a note`, LOG_LEVEL_VERBOSE);
|
this._log(`The file ${unifiedPathV2} is not a note`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
loaded = d;
|
loaded = d;
|
||||||
@@ -613,8 +612,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
this.pluginList.filter(e => e instanceof PluginDataExDisplayV2 && e.confKey == confKey).forEach(e => (e as PluginDataExDisplayV2).applyLoadedManifest());
|
this.pluginList.filter(e => e instanceof PluginDataExDisplayV2 && e.confKey == confKey).forEach(e => (e as PluginDataExDisplayV2).applyLoadedManifest());
|
||||||
pluginList.set(this.pluginList);
|
pluginList.set(this.pluginList);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`The file ${loaded.path} seems to manifest, but could not be decoded as JSON`, LOG_LEVEL_VERBOSE);
|
this._log(`The file ${loaded.path} seems to manifest, but could not be decoded as JSON`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
this.loadedManifest_mTime.set(confKey, file.mtime);
|
this.loadedManifest_mTime.set(confKey, file.mtime);
|
||||||
} else {
|
} else {
|
||||||
@@ -687,22 +686,22 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
|
|
||||||
async migrateV1ToV2(showMessage: boolean, entry: AnyEntry): Promise<void> {
|
async migrateV1ToV2(showMessage: boolean, entry: AnyEntry): Promise<void> {
|
||||||
const v1Path = entry.path;
|
const v1Path = entry.path;
|
||||||
Logger(`Migrating ${entry.path} to V2`, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
this._log(`Migrating ${entry.path} to V2`, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
if (entry.deleted) {
|
if (entry.deleted) {
|
||||||
Logger(`The entry ${v1Path} is already deleted`, LOG_LEVEL_VERBOSE);
|
this._log(`The entry ${v1Path} is already deleted`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!v1Path.endsWith(".md") && !v1Path.startsWith(ICXHeader)) {
|
if (!v1Path.endsWith(".md") && !v1Path.startsWith(ICXHeader)) {
|
||||||
Logger(`The entry ${v1Path} is not a customisation sync binder`, LOG_LEVEL_VERBOSE);
|
this._log(`The entry ${v1Path} is not a customisation sync binder`, LOG_LEVEL_VERBOSE);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (v1Path.indexOf("%") !== -1) {
|
if (v1Path.indexOf("%") !== -1) {
|
||||||
Logger(`The entry ${v1Path} is already migrated`, LOG_LEVEL_VERBOSE);
|
this._log(`The entry ${v1Path} is already migrated`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const loadedEntry = await this.localDatabase.getDBEntry(v1Path);
|
const loadedEntry = await this.localDatabase.getDBEntry(v1Path);
|
||||||
if (!loadedEntry) {
|
if (!loadedEntry) {
|
||||||
Logger(`The entry ${v1Path} is not found`, LOG_LEVEL_VERBOSE);
|
this._log(`The entry ${v1Path} is not found`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,7 +722,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
const relativeFilename = f.filename.split("/").slice(deletePrefixCount).join("/");
|
const relativeFilename = f.filename.split("/").slice(deletePrefixCount).join("/");
|
||||||
const v2Path = (prefixPath + relativeFilename) as FilePathWithPrefix;
|
const v2Path = (prefixPath + relativeFilename) as FilePathWithPrefix;
|
||||||
// console.warn(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`);
|
// console.warn(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`);
|
||||||
Logger(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`, LOG_LEVEL_VERBOSE);
|
this._log(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`, LOG_LEVEL_VERBOSE);
|
||||||
const newId = await this.plugin.$$path2id(v2Path);
|
const newId = await this.plugin.$$path2id(v2Path);
|
||||||
// const buf =
|
// const buf =
|
||||||
|
|
||||||
@@ -742,19 +741,19 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
const r = await this.plugin.localDatabase.putDBEntry(saving);
|
const r = await this.plugin.localDatabase.putDBEntry(saving);
|
||||||
if (r && r.ok) {
|
if (r && r.ok) {
|
||||||
Logger(`Migrated ${v1Path} / ${f.filename} to ${v2Path}`, LOG_LEVEL_INFO);
|
this._log(`Migrated ${v1Path} / ${f.filename} to ${v2Path}`, LOG_LEVEL_INFO);
|
||||||
const delR = await this.deleteConfigOnDatabase(v1Path);
|
const delR = await this.deleteConfigOnDatabase(v1Path);
|
||||||
if (delR) {
|
if (delR) {
|
||||||
Logger(`Deleted ${v1Path} successfully`, LOG_LEVEL_INFO);
|
this._log(`Deleted ${v1Path} successfully`, LOG_LEVEL_INFO);
|
||||||
} else {
|
} else {
|
||||||
Logger(`Failed to delete ${v1Path}`, LOG_LEVEL_NOTICE);
|
this._log(`Failed to delete ${v1Path}`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
|
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
|
||||||
if (!this.$isThisModuleEnabled()) {
|
if (!this._isThisModuleEnabled()) {
|
||||||
this.pluginScanProcessor.clearQueue();
|
this.pluginScanProcessor.clearQueue();
|
||||||
this.pluginList = [];
|
this.pluginList = [];
|
||||||
pluginList.set(this.pluginList)
|
pluginList.set(this.pluginList)
|
||||||
@@ -802,9 +801,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
const fileA = await loadFile(dataA);
|
const fileA = await loadFile(dataA);
|
||||||
const fileB = await loadFile(dataB);
|
const fileB = await loadFile(dataB);
|
||||||
Logger(`Comparing: ${dataA.documentPath} <-> ${dataB.documentPath}`, LOG_LEVEL_VERBOSE);
|
this._log(`Comparing: ${dataA.documentPath} <-> ${dataB.documentPath}`, LOG_LEVEL_VERBOSE);
|
||||||
if (!fileA || !fileB) {
|
if (!fileA || !fileB) {
|
||||||
Logger(`Could not load ${dataA.name} for comparison: ${!fileA ? dataA.term : ""}${!fileB ? dataB.term : ""}`, LOG_LEVEL_NOTICE);
|
this._log(`Could not load ${dataA.name} for comparison: ${!fileA ? dataA.term : ""}${!fileB ? dataB.term : ""}`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let path = stripAllPrefixes(fileA.path.split("/").slice(-1).join("/") as FilePath); // TODO:adjust
|
let path = stripAllPrefixes(fileA.path.split("/").slice(-1).join("/") as FilePath); // TODO:adjust
|
||||||
@@ -813,15 +812,15 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
if (fileA.path.endsWith(".json")) {
|
if (fileA.path.endsWith(".json")) {
|
||||||
return serialized("config:merge-data", () => new Promise<boolean>((res) => {
|
return serialized("config:merge-data", () => new Promise<boolean>((res) => {
|
||||||
Logger("Opening data-merging dialog", LOG_LEVEL_VERBOSE);
|
this._log("Opening data-merging dialog", LOG_LEVEL_VERBOSE);
|
||||||
// const docs = [docA, docB];
|
// const docs = [docA, docB];
|
||||||
const modal = new JsonResolveModal(this.app, path, [fileA, fileB], async (keep, result) => {
|
const modal = new JsonResolveModal(this.app, path, [fileA, fileB], async (keep, result) => {
|
||||||
if (result == null) return res(false);
|
if (result == null) return res(false);
|
||||||
try {
|
try {
|
||||||
res(await this.applyData(dataA, result));
|
res(await this.applyData(dataA, result));
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Could not apply merged file");
|
this._log("Could not apply merged file");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
res(false);
|
res(false);
|
||||||
}
|
}
|
||||||
}, "Local", `${dataB.term}`, "B", true, true, "Difference between local and remote");
|
}, "Local", `${dataB.term}`, "B", true, true, "Difference between local and remote");
|
||||||
@@ -847,7 +846,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
right: { rev: "B", ...fileB, data: docBData },
|
right: { rev: "B", ...fileB, data: docBData },
|
||||||
diff: diff
|
diff: diff
|
||||||
}
|
}
|
||||||
console.dir(diffResult);
|
// console.dir(diffResult);
|
||||||
const d = new ConflictResolveModal(this.app, path, diffResult, true, dataB.term);
|
const d = new ConflictResolveModal(this.app, path, diffResult, true, dataB.term);
|
||||||
d.open();
|
d.open();
|
||||||
const ret = await d.waitForResult();
|
const ret = await d.waitForResult();
|
||||||
@@ -866,7 +865,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
if (content) {
|
if (content) {
|
||||||
// const dt = createBlob(content);
|
// const dt = createBlob(content);
|
||||||
const filename = data.files[0].filename;
|
const filename = data.files[0].filename;
|
||||||
Logger(`Applying ${filename} of ${data.displayName || data.name}..`);
|
this._log(`Applying ${filename} of ${data.displayName || data.name}..`);
|
||||||
const path = `${baseDir}/${filename}` as FilePath;
|
const path = `${baseDir}/${filename}` as FilePath;
|
||||||
await this.plugin.storageAccess.ensureDir(path);
|
await this.plugin.storageAccess.ensureDir(path);
|
||||||
// If the content has applied, modified time will be updated to the current time.
|
// If the content has applied, modified time will be updated to the current time.
|
||||||
@@ -879,7 +878,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
// If files have applied, modified time will be updated to the current time.
|
// If files have applied, modified time will be updated to the current time.
|
||||||
const stat = { mtime: f.mtime, ctime: f.ctime };
|
const stat = { mtime: f.mtime, ctime: f.ctime };
|
||||||
const path = `${baseDir}/${f.filename}` as FilePath;
|
const path = `${baseDir}/${f.filename}` as FilePath;
|
||||||
Logger(`Applying ${f.filename} of ${data.displayName || data.name}..`);
|
this._log(`Applying ${f.filename} of ${data.displayName || data.name}..`);
|
||||||
// const contentEach = createBlob(f.data);
|
// const contentEach = createBlob(f.data);
|
||||||
await this.plugin.storageAccess.ensureDir(path);
|
await this.plugin.storageAccess.ensureDir(path);
|
||||||
|
|
||||||
@@ -888,13 +887,13 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
try {
|
try {
|
||||||
oldData = await this.plugin.storageAccess.readHiddenFileBinary(path);
|
oldData = await this.plugin.storageAccess.readHiddenFileBinary(path);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
|
this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
oldData = new ArrayBuffer(0);
|
oldData = new ArrayBuffer(0);
|
||||||
}
|
}
|
||||||
const content = base64ToArrayBuffer(f.data);
|
const content = base64ToArrayBuffer(f.data);
|
||||||
if (await isDocContentSame(oldData, content)) {
|
if (await isDocContentSame(oldData, content)) {
|
||||||
Logger(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
||||||
@@ -903,30 +902,30 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
try {
|
try {
|
||||||
oldData = await this.plugin.storageAccess.readHiddenFileText(path);
|
oldData = await this.plugin.storageAccess.readHiddenFileText(path);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
|
this._log(`Could not read the file ${f.filename}`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
oldData = "";
|
oldData = "";
|
||||||
}
|
}
|
||||||
const content = getDocData(f.data);
|
const content = getDocData(f.data);
|
||||||
if (await isDocContentSame(oldData, content)) {
|
if (await isDocContentSame(oldData, content)) {
|
||||||
Logger(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
this._log(`The file ${f.filename} is already up-to-date`, LOG_LEVEL_VERBOSE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
||||||
}
|
}
|
||||||
Logger(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
||||||
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName);
|
await this.storeCustomisationFileV2(path, this.plugin.deviceAndVaultName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Applying ${data.displayName || data.name}.. Failed`, LOG_LEVEL_NOTICE);
|
this._log(`Applying ${data.displayName || data.name}.. Failed`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async applyData(data: IPluginDataExDisplay, content?: string): Promise<boolean> {
|
async applyData(data: IPluginDataExDisplay, content?: string): Promise<boolean> {
|
||||||
Logger(`Applying ${data.displayName || data.name
|
this._log(`Applying ${data.displayName || data.name
|
||||||
}..`);
|
}..`);
|
||||||
|
|
||||||
if (data instanceof PluginDataExDisplayV2) {
|
if (data instanceof PluginDataExDisplayV2) {
|
||||||
@@ -941,7 +940,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
const loadedData = deserialize(getDocDataAsArray(dx.data), {}) as PluginDataEx;
|
const loadedData = deserialize(getDocDataAsArray(dx.data), {}) as PluginDataEx;
|
||||||
for (const f of loadedData.files) {
|
for (const f of loadedData.files) {
|
||||||
Logger(`Applying ${f.filename} of ${data.displayName || data.name}..`);
|
this._log(`Applying ${f.filename} of ${data.displayName || data.name}..`);
|
||||||
try {
|
try {
|
||||||
// console.dir(f);
|
// console.dir(f);
|
||||||
const path = `${baseDir}/${f.filename}`;
|
const path = `${baseDir}/${f.filename}`;
|
||||||
@@ -952,11 +951,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
} else {
|
} else {
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
||||||
}
|
}
|
||||||
Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`);
|
this._log(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`);
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Failed`);
|
this._log(`Applying ${f.filename} of ${data.displayName || data.name}.. Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -964,7 +963,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await this.storeCustomizationFiles(uPath);
|
await this.storeCustomizationFiles(uPath);
|
||||||
await this.updatePluginList(true, uPath);
|
await this.updatePluginList(true, uPath);
|
||||||
await delay(100);
|
await delay(100);
|
||||||
Logger(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL_NOTICE);
|
this._log(`Config ${data.displayName || data.name} has been applied`, LOG_LEVEL_NOTICE);
|
||||||
if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") {
|
if (data.category == "PLUGIN_DATA" || data.category == "PLUGIN_MAIN") {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const manifests = Object.values(this.app.plugins.manifests) as any as PluginManifest[];
|
const manifests = Object.values(this.app.plugins.manifests) as any as PluginManifest[];
|
||||||
@@ -972,20 +971,20 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
|
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
|
||||||
const pluginManifest = manifests.find((manifest) => enabledPlugins.has(manifest.id) && manifest.dir == `${baseDir}/plugins/${data.name}`);
|
const pluginManifest = manifests.find((manifest) => enabledPlugins.has(manifest.id) && manifest.dir == `${baseDir}/plugins/${data.name}`);
|
||||||
if (pluginManifest) {
|
if (pluginManifest) {
|
||||||
Logger(`Unloading plugin: ${pluginManifest.name}`, LOG_LEVEL_NOTICE, "plugin-reload-" + pluginManifest.id);
|
this._log(`Unloading plugin: ${pluginManifest.name}`, LOG_LEVEL_NOTICE, "plugin-reload-" + pluginManifest.id);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await this.app.plugins.unloadPlugin(pluginManifest.id);
|
await this.app.plugins.unloadPlugin(pluginManifest.id);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await this.app.plugins.loadPlugin(pluginManifest.id);
|
await this.app.plugins.loadPlugin(pluginManifest.id);
|
||||||
Logger(`Plugin reloaded: ${pluginManifest.name}`, LOG_LEVEL_NOTICE, "plugin-reload-" + pluginManifest.id);
|
this._log(`Plugin reloaded: ${pluginManifest.name}`, LOG_LEVEL_NOTICE, "plugin-reload-" + pluginManifest.id);
|
||||||
}
|
}
|
||||||
} else if (data.category == "CONFIG") {
|
} else if (data.category == "CONFIG") {
|
||||||
this.plugin.$$askReload();
|
this.plugin.$$askReload();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Applying ${data.displayName || data.name}.. Failed`);
|
this._log(`Applying ${data.displayName || data.name}.. Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,22 +1006,22 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await Promise.allSettled(p);
|
await Promise.allSettled(p);
|
||||||
// await this.deleteConfigOnDatabase(data.documentPath);
|
// await this.deleteConfigOnDatabase(data.documentPath);
|
||||||
// await this.updatePluginList(false, data.documentPath);
|
// await this.updatePluginList(false, data.documentPath);
|
||||||
Logger(`Deleted: ${data.category}/${data.name} of ${data.category} (${delList.length} items)`, LOG_LEVEL_NOTICE);
|
this._log(`Deleted: ${data.category}/${data.name} of ${data.category} (${delList.length} items)`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Failed to delete: ${data.documentPath}`, LOG_LEVEL_NOTICE);
|
this._log(`Failed to delete: ${data.documentPath}`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
|
async $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
|
||||||
if (!docs._id.startsWith(ICXHeader)) return undefined;
|
if (!docs._id.startsWith(ICXHeader)) return undefined;
|
||||||
if (this.$isThisModuleEnabled()) {
|
if (this._isThisModuleEnabled()) {
|
||||||
await this.updatePluginList(false, (docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath((docs as AnyEntry)));
|
await this.updatePluginList(false, (docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath((docs as AnyEntry)));
|
||||||
}
|
}
|
||||||
if (this.$isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
|
if (this._isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
|
||||||
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
||||||
const fragment = createFragment((doc) => {
|
const fragment = createFragment((doc) => {
|
||||||
doc.createEl("span", undefined, (a) => {
|
doc.createEl("span", undefined, (a) => {
|
||||||
@@ -1063,9 +1062,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
async $everyRealizeSettingSyncMode(): Promise<boolean> {
|
async $everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
this.periodicPluginSweepProcessor?.disable();
|
this.periodicPluginSweepProcessor?.disable();
|
||||||
if (!this.$isMainReady) return true;
|
if (!this._isMainReady) return true;
|
||||||
if (!this.$isMainSuspended()) return true;
|
if (!this._isMainSuspended()) return true;
|
||||||
if (!this.$isThisModuleEnabled()) return true;
|
if (!this._isThisModuleEnabled()) return true;
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
await this.scanAllConfigFiles(false);
|
await this.scanAllConfigFiles(false);
|
||||||
}
|
}
|
||||||
@@ -1096,13 +1095,13 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
displayName = `${json.name}`;
|
displayName = `${json.name}`;
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Configuration sync data: ${path} looks like manifest, but could not read the version`, LOG_LEVEL_INFO);
|
this._log(`Configuration sync data: ${path} looks like manifest, but could not read the version`, LOG_LEVEL_INFO);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`The file ${path} could not be encoded`);
|
this._log(`The file ${path} could not be encoded`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const mtime = stat.mtime;
|
const mtime = stat.mtime;
|
||||||
@@ -1150,7 +1149,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (isMarkedAsSameChanges(prefixedFileName, [old.mtime, mtime + 1]) == EVEN) {
|
if (isMarkedAsSameChanges(prefixedFileName, [old.mtime, mtime + 1]) == EVEN) {
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Already checked the same)`, LOG_LEVEL_DEBUG);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Already checked the same)`, LOG_LEVEL_DEBUG);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
const docXDoc = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
||||||
@@ -1162,7 +1161,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
const oldContent = dataSrc.substring(dataStart + DUMMY_END.length);
|
const oldContent = dataSrc.substring(dataStart + DUMMY_END.length);
|
||||||
const oldContentArray = base64ToArrayBuffer(oldContent);
|
const oldContentArray = base64ToArrayBuffer(oldContent);
|
||||||
if (await isDocContentSame(oldContentArray, content)) {
|
if (await isDocContentSame(oldContentArray, content)) {
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (the same content)`, LOG_LEVEL_VERBOSE);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (the same content)`, LOG_LEVEL_VERBOSE);
|
||||||
markChangesAreSame(prefixedFileName, old.mtime, mtime + 1);
|
markChangesAreSame(prefixedFileName, old.mtime, mtime + 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1179,12 +1178,12 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const ret = await this.localDatabase.putDBEntry(saveData);
|
const ret = await this.localDatabase.putDBEntry(saveData);
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Done`);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Done`);
|
||||||
fireAndForget(() => this.updatePluginListV2(false, this.filenameWithUnifiedKey(path)));
|
fireAndForget(() => this.updatePluginListV2(false, this.filenameWithUnifiedKey(path)));
|
||||||
return ret;
|
return ret;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Failed`);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1192,7 +1191,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
|
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.deviceAndVaultName;
|
const term = termOverRide || this.plugin.deviceAndVaultName;
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
Logger("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.useV2) {
|
if (this.useV2) {
|
||||||
@@ -1234,7 +1233,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
for (const target of fileTargets) {
|
for (const target of fileTargets) {
|
||||||
const data = await this.makeEntryFromFile(target);
|
const data = await this.makeEntryFromFile(target);
|
||||||
if (data == false) {
|
if (data == false) {
|
||||||
Logger(`Config: skipped (Possibly is not exist): ${target} `, LOG_LEVEL_VERBOSE);
|
this._log(`Config: skipped (Possibly is not exist): ${target} `, LOG_LEVEL_VERBOSE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (data.version) {
|
if (data.version) {
|
||||||
@@ -1249,9 +1248,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
dt.mtime = mtime;
|
dt.mtime = mtime;
|
||||||
|
|
||||||
// Logger(`Configuration saving: ${prefixedFileName}`);
|
// this._log(`Configuration saving: ${prefixedFileName}`);
|
||||||
if (dt.files.length == 0) {
|
if (dt.files.length == 0) {
|
||||||
Logger(`Nothing left: deleting.. ${path}`);
|
this._log(`Nothing left: deleting.. ${path}`);
|
||||||
await this.deleteConfigOnDatabase(prefixedFileName);
|
await this.deleteConfigOnDatabase(prefixedFileName);
|
||||||
await this.updatePluginList(false, prefixedFileName);
|
await this.updatePluginList(false, prefixedFileName);
|
||||||
return
|
return
|
||||||
@@ -1277,7 +1276,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (old.mtime == mtime) {
|
if (old.mtime == mtime) {
|
||||||
// Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same time)`, LOG_LEVEL_VERBOSE);
|
// this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same time)`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
||||||
@@ -1289,7 +1288,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}))
|
}))
|
||||||
const isSame = (await Promise.all(diffs)).every(e => e == true);
|
const isSame = (await Promise.all(diffs)).every(e => e == true);
|
||||||
if (isSame) {
|
if (isSame) {
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1308,25 +1307,24 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
const ret = await this.localDatabase.putDBEntry(saveData);
|
const ret = await this.localDatabase.putDBEntry(saveData);
|
||||||
await this.updatePluginList(false, saveData.path);
|
await this.updatePluginList(false, saveData.path);
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Done`);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Done`);
|
||||||
return ret;
|
return ret;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Failed`);
|
this._log(`STORAGE --> DB:${prefixedFileName}: (config) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
||||||
return await this.watchVaultRawEventsAsync(path);
|
return await this.watchVaultRawEventsAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchVaultRawEventsAsync(path: FilePath) {
|
async watchVaultRawEventsAsync(path: FilePath) {
|
||||||
// if (!this.$isMainReady) return true;
|
if (!this._isMainReady) return false;
|
||||||
// if (!this.$isMainSuspended()) return true;
|
if (this._isMainSuspended()) return false;
|
||||||
if (!this.$isThisModuleEnabled()) return true;
|
if (!this._isThisModuleEnabled()) return false;
|
||||||
if (!this.isTargetPath(path)) return false;
|
// if (!this.isTargetPath(path)) return false;
|
||||||
const stat = await this.plugin.storageAccess.statHidden(path);
|
const stat = await this.plugin.storageAccess.statHidden(path);
|
||||||
// Make sure that target is a file.
|
// Make sure that target is a file.
|
||||||
if (stat && stat.type != "file")
|
if (stat && stat.type != "file")
|
||||||
@@ -1337,10 +1335,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
e.mode != MODE_SELECTIVE && e.mode != MODE_SHINY
|
e.mode != MODE_SELECTIVE && e.mode != MODE_SHINY
|
||||||
).map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
|
).map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
|
||||||
if (synchronisedInConfigSync.some(e => e.startsWith(path.toLowerCase()))) {
|
if (synchronisedInConfigSync.some(e => e.startsWith(path.toLowerCase()))) {
|
||||||
Logger(`Customization file skipped: ${path}`, LOG_LEVEL_VERBOSE);
|
this._log(`Customization file skipped: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
// This file could be handled by the other module.
|
// This file could be handled by the other module.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// this._log(`Customization file detected: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
const storageMTime = ~~((stat && stat.mtime || 0) / 1000);
|
const storageMTime = ~~((stat && stat.mtime || 0) / 1000);
|
||||||
const key = `${path}-${storageMTime}`;
|
const key = `${path}-${storageMTime}`;
|
||||||
if (this.recentProcessedInternalFiles.contains(key)) {
|
if (this.recentProcessedInternalFiles.contains(key)) {
|
||||||
@@ -1359,16 +1358,13 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async scanAllConfigFiles(showMessage: boolean) {
|
async scanAllConfigFiles(showMessage: boolean) {
|
||||||
await shareRunningResult("scanAllConfigFiles", async () => {
|
await shareRunningResult("scanAllConfigFiles", async () => {
|
||||||
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
Logger("Scanning customizing files.", logLevel, "scan-all-config");
|
this._log("Scanning customizing files.", logLevel, "scan-all-config");
|
||||||
const term = this.plugin.deviceAndVaultName;
|
const term = this.plugin.deviceAndVaultName;
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
Logger("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filesAll = await this.scanInternalFiles();
|
const filesAll = await this.scanInternalFiles();
|
||||||
@@ -1396,8 +1392,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await this.deleteConfigOnDatabase(unifiedFilenameWithKey);
|
await this.deleteConfigOnDatabase(unifiedFilenameWithKey);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`scanAllConfigFiles - Error: ${item._id}`, LOG_LEVEL_VERBOSE);
|
this._log(`scanAllConfigFiles - Error: ${item._id}`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
} finally {
|
} finally {
|
||||||
releaser();
|
releaser();
|
||||||
}
|
}
|
||||||
@@ -1412,8 +1408,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
try {
|
try {
|
||||||
await this.storeCustomisationFileV2(filePath, term);
|
await this.storeCustomisationFileV2(filePath, term);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`scanAllConfigFiles - Error: ${filePath}`, LOG_LEVEL_VERBOSE);
|
this._log(`scanAllConfigFiles - Error: ${filePath}`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
releaser();
|
releaser();
|
||||||
@@ -1430,7 +1426,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
for (const vp of virtualPathsOfLocalFiles) {
|
for (const vp of virtualPathsOfLocalFiles) {
|
||||||
const p = files.find(e => e.key == vp)?.file;
|
const p = files.find(e => e.key == vp)?.file;
|
||||||
if (!p) {
|
if (!p) {
|
||||||
Logger(`scanAllConfigFiles - File not found: ${vp}`, LOG_LEVEL_VERBOSE);
|
this._log(`scanAllConfigFiles - File not found: ${vp}`, LOG_LEVEL_VERBOSE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await this.storeCustomizationFiles(p);
|
await this.storeCustomizationFiles(p);
|
||||||
@@ -1453,11 +1449,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, false) as InternalFileEntry | false;
|
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, false) as InternalFileEntry | false;
|
||||||
let saveData: InternalFileEntry;
|
let saveData: InternalFileEntry;
|
||||||
if (old === false) {
|
if (old === false) {
|
||||||
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted (Not found on database)`);
|
this._log(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted (Not found on database)`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (old.deleted) {
|
if (old.deleted) {
|
||||||
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted`);
|
this._log(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
saveData =
|
saveData =
|
||||||
@@ -1472,11 +1468,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
await this.localDatabase.putRaw(saveData);
|
await this.localDatabase.putRaw(saveData);
|
||||||
await this.updatePluginList(false, prefixedFileName);
|
await this.updatePluginList(false, prefixedFileName);
|
||||||
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Done`);
|
this._log(`STORAGE -x> DB:${prefixedFileName}: (config) Done`);
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) Failed`);
|
this._log(`STORAGE -x> DB:${prefixedFileName}: (config) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1492,14 +1488,13 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean, enableOverwrite?: boolean }) {
|
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean, enableOverwrite?: boolean }) {
|
||||||
const message = `Would you like to enable \`Customization sync\`?
|
const message = `Would you like to enable **Customization sync**?
|
||||||
This feature allows you to sync your customisations -- such as configurations, themes, snippets, and plugins -- across your devices in a fully controlled manner, unlike the fully automatic behaviour of hidden file synchronisation.
|
|
||||||
|
|
||||||
You may use this feature alongside hidden file synchronisation. When both features are enabled, items configured as \`Automatic\` in this feature will be managed by hidden file synchronisation.
|
> [!DETAILS]-
|
||||||
|
> This feature allows you to sync your customisations -- such as configurations, themes, snippets, and plugins -- across your devices in a fully controlled manner, unlike the fully automatic behaviour of hidden file synchronisation.
|
||||||
Do not worry, you will be prompted to enable or keep disabled hidden file synchronisation after this dialogue.
|
>
|
||||||
|
> You may use this feature alongside hidden file synchronisation. When both features are enabled, items configured as \`Automatic\` in this feature will be managed by **hidden file synchronisation**.
|
||||||
Of course, you can enable or disable this feature at any time.
|
> Do not worry, you will be prompted to enable or keep disabled **hidden file synchronisation** after this dialogue.
|
||||||
`
|
`
|
||||||
const CHOICE_CUSTOMIZE = "Yes, Enable it";
|
const CHOICE_CUSTOMIZE = "Yes, Enable it";
|
||||||
const CHOICE_DISABLE = "No, Disable it";
|
const CHOICE_DISABLE = "No, Disable it";
|
||||||
@@ -1531,7 +1526,7 @@ Of course, you can enable or disable this feature at any time.
|
|||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
$allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
||||||
Logger("Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL_NOTICE)
|
this._log("Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL_NOTICE)
|
||||||
this.plugin.settings.usePluginSync = false;
|
this.plugin.settings.usePluginSync = false;
|
||||||
this.plugin.settings.autoSweepPlugins = false;
|
this.plugin.settings.autoSweepPlugins = false;
|
||||||
}
|
}
|
||||||
@@ -1576,6 +1571,7 @@ Of course, you can enable or disable this feature at any time.
|
|||||||
this.plugin.deviceAndVaultName = name;
|
this.plugin.deviceAndVaultName = name;
|
||||||
}
|
}
|
||||||
this.plugin.settings.usePluginSync = true;
|
this.plugin.settings.usePluginSync = true;
|
||||||
|
this.plugin.settings.useAdvancedMode = true;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
await this.scanAllConfigFiles(true);
|
await this.scanAllConfigFiles(true);
|
||||||
}
|
}
|
||||||
@@ -1590,8 +1586,8 @@ Of course, you can enable or disable this feature at any time.
|
|||||||
try {
|
try {
|
||||||
w = await this.app.vault.adapter.list(path);
|
w = await this.app.vault.adapter.list(path);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Could not traverse(ConfigSync):${path}`, LOG_LEVEL_INFO);
|
this._log(`Could not traverse(ConfigSync):${path}`, LOG_LEVEL_INFO);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
let files = [
|
let files = [
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ export class JsonResolveModal extends Modal {
|
|||||||
this.hideLocal = hideLocal ?? false;
|
this.hideLocal = hideLocal ?? false;
|
||||||
void waitForSignal(`cancel-internal-conflict:${filename}`).then(() => this.close());
|
void waitForSignal(`cancel-internal-conflict:${filename}`).then(() => this.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
async UICallback(keepRev?: string, mergedStr?: string) {
|
async UICallback(keepRev?: string, mergedStr?: string) {
|
||||||
|
if (this.callback) {
|
||||||
|
await this.callback(keepRev, mergedStr);
|
||||||
|
}
|
||||||
this.close();
|
this.close();
|
||||||
await this.callback?.(keepRev, mergedStr);
|
|
||||||
this.callback = undefined;
|
this.callback = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { normalizePath, type PluginManifest, type ListedFiles } from "../../deps.ts";
|
import { normalizePath, type PluginManifest, type ListedFiles } from "../../deps.ts";
|
||||||
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID } from "../../lib/src/common/types.ts";
|
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID, type UXStat, MODE_AUTOMATIC } from "../../lib/src/common/types.ts";
|
||||||
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts";
|
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "../../common/types.ts";
|
||||||
import { readAsBlob, isDocContentSame, sendSignal, readContent, createBlob, fireAndForget } from "../../lib/src/common/utils.ts";
|
import { readAsBlob, isDocContentSame, sendSignal, readContent, createBlob, fireAndForget } from "../../lib/src/common/utils.ts";
|
||||||
import { Logger } from "../../lib/src/common/logger.ts";
|
import { BASE_IS_NEW, compareMTime, EVEN, getPath, isInternalMetadata, isMarkedAsSameChanges, markChangesAreSame, PeriodicProcessor, TARGET_IS_NEW } from "../../common/utils.ts";
|
||||||
import { getPath, isInternalMetadata, PeriodicProcessor } from "../../common/utils.ts";
|
|
||||||
import { serialized } from "../../lib/src/concurrency/lock.ts";
|
import { serialized } from "../../lib/src/concurrency/lock.ts";
|
||||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
@@ -14,11 +13,11 @@ import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
|||||||
|
|
||||||
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
|
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
|
||||||
|
|
||||||
$isThisModuleEnabled() {
|
_isThisModuleEnabled() {
|
||||||
return this.plugin.settings.syncInternalFiles;
|
return this.plugin.settings.syncInternalFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(this.plugin, async () => this.$isThisModuleEnabled() && this.$isDatabaseReady() && await this.syncInternalFilesAndDatabase("push", false));
|
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(this.plugin, async () => this._isThisModuleEnabled() && this._isDatabaseReady() && await this.syncInternalFilesAndDatabase("push", false));
|
||||||
|
|
||||||
get kvDB() {
|
get kvDB() {
|
||||||
return this.plugin.kvDB;
|
return this.plugin.kvDB;
|
||||||
@@ -39,44 +38,55 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
if (this.$isThisModuleEnabled()) {
|
if (this._isThisModuleEnabled()) {
|
||||||
try {
|
try {
|
||||||
Logger("Synchronizing hidden files...");
|
this._log("Synchronizing hidden files...");
|
||||||
await this.syncInternalFilesAndDatabase("push", showNotice);
|
await this.syncInternalFilesAndDatabase("push", showNotice);
|
||||||
Logger("Synchronizing hidden files done");
|
this._log("Synchronizing hidden files done");
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Synchronizing hidden files failed");
|
this._log("Synchronizing hidden files failed");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showNotice: boolean) {
|
async $everyBeforeReplicate(showNotice: boolean) {
|
||||||
if (this.$isThisModuleEnabled() && this.$isDatabaseReady() && this.settings.syncInternalFilesBeforeReplication && !this.settings.watchInternalFileChanges) {
|
if (this._isThisModuleEnabled() && this._isDatabaseReady() && this.settings.syncInternalFilesBeforeReplication && !this.settings.watchInternalFileChanges) {
|
||||||
await this.syncInternalFilesAndDatabase("push", showNotice);
|
await this.syncInternalFilesAndDatabase("push", showNotice);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
|
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
|
||||||
|
.replace(/\n| /g, "")
|
||||||
|
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
||||||
|
this.ignorePatterns = ignorePatterns;
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
async $everyOnResumeProcess(): Promise<boolean> {
|
async $everyOnResumeProcess(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this.$isMainSuspended())
|
if (this._isMainSuspended())
|
||||||
return true;
|
return true;
|
||||||
if (this.$isThisModuleEnabled()) {
|
if (this._isThisModuleEnabled()) {
|
||||||
await this.syncInternalFilesAndDatabase("safe", false);
|
await this.syncInternalFilesAndDatabase("safe", false);
|
||||||
}
|
}
|
||||||
this.periodicInternalFileScanProcessor.enable(this.$isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
|
this.periodicInternalFileScanProcessor.enable(this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this.$isMainSuspended())
|
if (this._isMainSuspended())
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
if (!this.plugin.isReady)
|
if (!this.plugin.isReady)
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
this.periodicInternalFileScanProcessor.enable(this.$isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
|
this.periodicInternalFileScanProcessor.enable(this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
|
||||||
|
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
|
||||||
|
.replace(/\n| /g, "")
|
||||||
|
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
||||||
|
this.ignorePatterns = ignorePatterns;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,56 +95,56 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
internalFileProcessor = new QueueProcessor<string, any>(
|
internalFileProcessor = new QueueProcessor<string, any>(
|
||||||
async (filenames) => {
|
async (filenames) => {
|
||||||
Logger(`START :Applying hidden ${filenames.length} files change`, LOG_LEVEL_VERBOSE);
|
this._log(`START :Applying hidden ${filenames.length} files change`, LOG_LEVEL_VERBOSE);
|
||||||
await this.syncInternalFilesAndDatabase("pull", false, false, filenames);
|
await this.syncInternalFilesAndDatabase("pull", false, false, filenames);
|
||||||
Logger(`DONE :Applying hidden ${filenames.length} files change`, LOG_LEVEL_VERBOSE);
|
this._log(`DONE :Applying hidden ${filenames.length} files change`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}, { batchSize: 100, concurrentLimit: 1, delay: 10, yieldThreshold: 100, suspended: false, totalRemainingReactiveSource: hiddenFilesEventCount }
|
}, { batchSize: 100, concurrentLimit: 1, delay: 10, yieldThreshold: 100, suspended: false, totalRemainingReactiveSource: hiddenFilesEventCount }
|
||||||
);
|
);
|
||||||
|
|
||||||
recentProcessedInternalFiles = [] as string[];
|
|
||||||
|
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
||||||
return await this.watchVaultRawEventsAsync(path);
|
return await this.watchVaultRawEventsAsync(path);
|
||||||
}
|
}
|
||||||
async watchVaultRawEventsAsync(path: FilePath): Promise<boolean | undefined> {
|
async watchVaultRawEventsAsync(path: FilePath): Promise<boolean | undefined> {
|
||||||
if (!this.$isThisModuleEnabled()) return false;
|
if (!this._isMainReady) return false;
|
||||||
if (!isInternalMetadata(path)) return false;
|
if (this._isMainSuspended()) return false;
|
||||||
|
if (!this._isThisModuleEnabled()) return false;
|
||||||
|
|
||||||
// Exclude files handled by customization sync
|
// Exclude files handled by customization sync
|
||||||
const configDir = normalizePath(this.app.vault.configDir);
|
const configDir = normalizePath(this.app.vault.configDir);
|
||||||
const synchronisedInConfigSync = !this.settings.usePluginSync ? [] : Object.values(this.settings.pluginSyncExtendedSetting).filter(e => e.mode == MODE_SELECTIVE || e.mode == MODE_PAUSED).map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
|
const synchronisedInConfigSync = !this.settings.usePluginSync ? [] :
|
||||||
|
Object.values(this.settings.pluginSyncExtendedSetting).
|
||||||
|
filter(e => e.mode == MODE_SELECTIVE || e.mode == MODE_PAUSED).
|
||||||
|
map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
|
||||||
if (synchronisedInConfigSync.some(e => e.startsWith(path.toLowerCase()))) {
|
if (synchronisedInConfigSync.some(e => e.startsWith(path.toLowerCase()))) {
|
||||||
Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
|
this._log(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stat = await this.plugin.storageAccess.statHidden(path);
|
const stat = await this.plugin.storageAccess.statHidden(path);
|
||||||
// sometimes folder is coming.
|
// sometimes folder is coming.
|
||||||
if (stat != null && stat.type != "file") {
|
if (stat != null && stat.type != "file") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const mtime = stat == null ? 0 : stat?.mtime ?? 0;
|
|
||||||
const storageMTime = ~~((mtime) / 1000);
|
if (this.isKnownChange(path, stat?.mtime ?? 0)) {
|
||||||
const key = `${path}-${storageMTime}`;
|
// This could be caused by self. so return true to prevent further processing.
|
||||||
if (mtime != 0 && this.recentProcessedInternalFiles.contains(key)) {
|
|
||||||
//If recently processed, it may caused by self.
|
|
||||||
// Return true to prevent further processing.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
|
const mtime = stat == null ? 0 : stat?.mtime ?? 0;
|
||||||
// const id = await this.path2id(path, ICHeader);
|
const storageMTime = ~~((mtime) / 1000);
|
||||||
|
|
||||||
const prefixedFileName = addPrefix(path, ICHeader);
|
const prefixedFileName = addPrefix(path, ICHeader);
|
||||||
const filesOnDB = await this.localDatabase.getDBEntryMeta(prefixedFileName);
|
const filesOnDB = await this.localDatabase.getDBEntryMeta(prefixedFileName);
|
||||||
const dbMTime = ~~((filesOnDB && filesOnDB.mtime || 0) / 1000);
|
const dbMTime = ~~((filesOnDB && filesOnDB.mtime || 0) / 1000);
|
||||||
|
|
||||||
// Skip unchanged file.
|
// Skip unchanged file.
|
||||||
if (dbMTime == storageMTime) {
|
if (dbMTime == storageMTime) {
|
||||||
// Logger(`STORAGE --> DB:${path}: (hidden) Nothing changed`);
|
// this._log(`STORAGE --> DB:${path}: (hidden) Nothing changed`);
|
||||||
// Handled, but nothing changed. also return true to prevent further processing.
|
// Handled, but nothing changed. also return true to prevent further processing.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not compare timestamp. Always local data should be preferred except this plugin wrote one.
|
|
||||||
try {
|
try {
|
||||||
if (storageMTime == 0) {
|
if (storageMTime == 0) {
|
||||||
await this.deleteInternalFileOnDatabase(path);
|
await this.deleteInternalFileOnDatabase(path);
|
||||||
@@ -144,8 +154,8 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
// Surely processed.
|
// Surely processed.
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Failed to process hidden file:${path}`);
|
this._log(`Failed to process hidden file:${path}`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
// Could not be processed. but it was own task. so return true to prevent further processing.
|
// Could not be processed. but it was own task. so return true to prevent further processing.
|
||||||
return true;
|
return true;
|
||||||
@@ -164,8 +174,8 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("something went wrong on resolving all conflicted internal files");
|
this._log("something went wrong on resolving all conflicted internal files");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
await this.conflictResolutionProcessor.startPipeline().waitForAllProcessed();
|
await this.conflictResolutionProcessor.startPipeline().waitForAllProcessed();
|
||||||
}
|
}
|
||||||
@@ -176,12 +186,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
// simply check modified time
|
// simply check modified time
|
||||||
const mtimeCurrent = ("mtime" in currentDoc && currentDoc.mtime) || 0;
|
const mtimeCurrent = ("mtime" in currentDoc && currentDoc.mtime) || 0;
|
||||||
const mtimeConflicted = ("mtime" in conflictedDoc && conflictedDoc.mtime) || 0;
|
const mtimeConflicted = ("mtime" in conflictedDoc && conflictedDoc.mtime) || 0;
|
||||||
// Logger(`Revisions:${new Date(mtimeA).toLocaleString} and ${new Date(mtimeB).toLocaleString}`);
|
// this._log(`Revisions:${new Date(mtimeA).toLocaleString} and ${new Date(mtimeB).toLocaleString}`);
|
||||||
// console.log(`mtime:${mtimeA} - ${mtimeB}`);
|
// console.log(`mtime:${mtimeA} - ${mtimeB}`);
|
||||||
const delRev = mtimeCurrent < mtimeConflicted ? currentRev : conflictedRev;
|
const delRev = mtimeCurrent < mtimeConflicted ? currentRev : conflictedRev;
|
||||||
// delete older one.
|
// delete older one.
|
||||||
await this.localDatabase.removeRevision(id, delRev);
|
await this.localDatabase.removeRevision(id, delRev);
|
||||||
Logger(`Older one has been deleted:${path}`);
|
this._log(`Older one has been deleted:${path}`);
|
||||||
const cc = await this.localDatabase.getRaw(id, { conflicts: true });
|
const cc = await this.localDatabase.getRaw(id, { conflicts: true });
|
||||||
if (cc._conflicts?.length === 0) {
|
if (cc._conflicts?.length === 0) {
|
||||||
await this.extractInternalFileFromDatabase(stripAllPrefixes(path))
|
await this.extractInternalFileFromDatabase(stripAllPrefixes(path))
|
||||||
@@ -204,7 +214,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
if (doc._conflicts === undefined) return [];
|
if (doc._conflicts === undefined) return [];
|
||||||
if (doc._conflicts.length == 0)
|
if (doc._conflicts.length == 0)
|
||||||
return [];
|
return [];
|
||||||
Logger(`Hidden file conflicted:${path}`);
|
this._log(`Hidden file conflicted:${path}`);
|
||||||
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
||||||
const revA = doc._rev;
|
const revA = doc._rev;
|
||||||
const revB = conflicts[0];
|
const revB = conflicts[0];
|
||||||
@@ -217,7 +227,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
const commonBase = revFrom._revs_info?.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? "";
|
const commonBase = revFrom._revs_info?.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? "";
|
||||||
const result = await this.plugin.localDatabase.mergeObject(path, commonBase, doc._rev, conflictedRev);
|
const result = await this.plugin.localDatabase.mergeObject(path, commonBase, doc._rev, conflictedRev);
|
||||||
if (result) {
|
if (result) {
|
||||||
Logger(`Object merge:${path}`, LOG_LEVEL_INFO);
|
this._log(`Object merge:${path}`, LOG_LEVEL_INFO);
|
||||||
const filename = stripAllPrefixes(path);
|
const filename = stripAllPrefixes(path);
|
||||||
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(filename);
|
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(filename);
|
||||||
if (!isExists) {
|
if (!isExists) {
|
||||||
@@ -234,7 +244,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.conflictResolutionProcessor.enqueue(path);
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
Logger(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
|
this._log(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
return [{ path, revA, revB, id, doc }];
|
return [{ path, revA, revB, id, doc }];
|
||||||
}
|
}
|
||||||
@@ -242,8 +252,8 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
await this.resolveByNewerEntry(id, path, doc, revA, revB);
|
||||||
return [];
|
return [];
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Failed to resolve conflict (Hidden): ${path}`);
|
this._log(`Failed to resolve conflict (Hidden): ${path}`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -274,14 +284,14 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
async $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
||||||
if (isInternalMetadata(doc._id) && this.$isThisModuleEnabled()) {
|
if (isInternalMetadata(doc._id) && this._isThisModuleEnabled()) {
|
||||||
//system file
|
//system file
|
||||||
const filename = getPath(doc);
|
const filename = getPath(doc);
|
||||||
if (await this.plugin.$$isTargetFile(filename)) {
|
if (await this.plugin.$$isTargetFile(filename)) {
|
||||||
this.procInternalFile(filename);
|
this.procInternalFile(filename);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Logger(`Skipped (Not target:${filename})`, LOG_LEVEL_VERBOSE);
|
this._log(`Skipped (Not target:${filename})`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,30 +301,46 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
this.conflictResolutionProcessor.enqueue(path);
|
this.conflictResolutionProcessor.enqueue(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
knownChanges: { [key: string]: number; } = {};
|
||||||
|
markAsKnownChange(path: string, mtime: number) {
|
||||||
|
this.knownChanges[path] = mtime;
|
||||||
|
}
|
||||||
|
isKnownChange(path: string, mtime: number) {
|
||||||
|
return this.knownChanges[path] == mtime;
|
||||||
|
}
|
||||||
|
ignorePatterns: RegExp[] = [];
|
||||||
//TODO: Tidy up. Even though it is experimental feature, So dirty...
|
//TODO: Tidy up. Even though it is experimental feature, So dirty...
|
||||||
async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe" | "pullForce" | "pushForce", showMessage: boolean, filesAll: InternalFileInfo[] | false = false, targetFiles: string[] | false = false) {
|
async syncInternalFilesAndDatabase(direction: "push" | "pull" | "safe" | "pullForce" | "pushForce", showMessage: boolean, filesAll: InternalFileInfo[] | false = false, targetFilesSrc: string[] | false = false) {
|
||||||
|
const targetFiles = targetFilesSrc ? targetFilesSrc.map(e => stripAllPrefixes(e as FilePathWithPrefix)) : false;
|
||||||
|
// debugger;
|
||||||
await this.resolveConflictOnInternalFiles();
|
await this.resolveConflictOnInternalFiles();
|
||||||
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");
|
this._log("Scanning hidden files.", logLevel, "sync_internal");
|
||||||
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
|
|
||||||
.replace(/\n| /g, "")
|
|
||||||
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
|
||||||
|
|
||||||
const configDir = normalizePath(this.app.vault.configDir);
|
const configDir = normalizePath(this.app.vault.configDir);
|
||||||
let files: InternalFileInfo[] =
|
let files: InternalFileInfo[] =
|
||||||
filesAll ? filesAll : (await this.scanInternalFiles())
|
filesAll ? filesAll : (await this.scanInternalFiles())
|
||||||
|
const allowedInHiddenFileSync = this.settings.usePluginSync ? Object.values(this.settings.pluginSyncExtendedSetting).filter(e => e.mode == MODE_AUTOMATIC).map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase()) : undefined;
|
||||||
|
if (allowedInHiddenFileSync) {
|
||||||
|
const systemOrNot = files.reduce((acc, cur) => {
|
||||||
|
if (cur.path.startsWith(configDir)) {
|
||||||
|
acc.system.push(cur);
|
||||||
|
} else {
|
||||||
|
acc.user.push(cur);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, { system: [] as InternalFileInfo[], user: [] as InternalFileInfo[] });
|
||||||
|
|
||||||
const synchronisedInConfigSync = !this.settings.usePluginSync ? [] : Object.values(this.settings.pluginSyncExtendedSetting).filter(e => e.mode == MODE_SELECTIVE || e.mode == MODE_PAUSED).map(e => e.files).flat().map(e => `${configDir}/${e}`.toLowerCase());
|
files =
|
||||||
files = files.filter(file => synchronisedInConfigSync.every(filterFile => !file.path.toLowerCase().startsWith(filterFile)))
|
[...systemOrNot.user,
|
||||||
|
...systemOrNot.system.filter(file => allowedInHiddenFileSync.some(filterFile => file.path.toLowerCase().startsWith(filterFile)))];
|
||||||
|
}
|
||||||
|
|
||||||
const filesOnDB = ((await this.localDatabase.allDocsRaw({ startkey: ICHeader, endkey: ICHeaderEnd, include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]).filter(e => !e.deleted);
|
const filesOnDB = ((await this.localDatabase.allDocsRaw({ startkey: ICHeader, endkey: ICHeaderEnd, include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]).filter(e => !e.deleted);
|
||||||
const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => stripAllPrefixes(this.getPath(e)))])];
|
const allFileNamesSrc = [...new Set([...files.map(e => normalizePath(e.path)), ...filesOnDB.map(e => stripAllPrefixes(this.getPath(e)))])];
|
||||||
const allFileNames = allFileNamesSrc.filter(filename => !targetFiles || (targetFiles && targetFiles.indexOf(filename) !== -1)).filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile)))
|
let allFileNames = allFileNamesSrc.filter(filename => !targetFiles || (targetFiles && targetFiles.indexOf(filename) !== -1));
|
||||||
function compareMTime(a: number, b: number) {
|
if (allowedInHiddenFileSync) {
|
||||||
const wa = ~~(a / 1000);
|
allFileNames = allFileNames.filter(file => allowedInHiddenFileSync.some(filterFile => file.toLowerCase().startsWith(filterFile)));
|
||||||
const wb = ~~(b / 1000);
|
|
||||||
const diff = wa - wb;
|
|
||||||
return diff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileCount = allFileNames.length;
|
const fileCount = allFileNames.length;
|
||||||
@@ -342,9 +368,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
c = pieces.shift();
|
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.kvDB.get<{ [key: string]: { storageMtime: number; docMtime: number; }; }>("diff-caches-internal") || {};
|
|
||||||
const filesMap = files.reduce((acc, cur) => {
|
const filesMap = files.reduce((acc, cur) => {
|
||||||
acc[cur.path] = cur;
|
acc[cur.path] = cur;
|
||||||
return acc;
|
return acc;
|
||||||
@@ -357,10 +381,10 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
const filename = filenames[0];
|
const filename = filenames[0];
|
||||||
processed++;
|
processed++;
|
||||||
if (processed % 100 == 0) {
|
if (processed % 100 == 0) {
|
||||||
Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
|
this._log(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
|
||||||
}
|
}
|
||||||
if (!filename) return [];
|
if (!filename) return [];
|
||||||
if (ignorePatterns.some(e => filename.match(e)))
|
if (this.ignorePatterns.some(e => filename.match(e)))
|
||||||
return [];
|
return [];
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(filename)) {
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(filename)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -384,25 +408,24 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
fileOnDatabase: xFileOnDatabase
|
fileOnDatabase: xFileOnDatabase
|
||||||
} = params[0];
|
} = params[0];
|
||||||
if (xFileOnStorage && xFileOnDatabase) {
|
if (xFileOnStorage && xFileOnDatabase) {
|
||||||
const cache = filename in caches ? caches[filename] : { storageMtime: 0, docMtime: 0 };
|
|
||||||
// Both => Synchronize
|
// Both => Synchronize
|
||||||
if ((direction != "pullForce" && direction != "pushForce") && xFileOnDatabase.mtime == cache.docMtime && xFileOnStorage.mtime == cache.storageMtime) {
|
if ((direction != "pullForce" && direction != "pushForce") && isMarkedAsSameChanges(filename, [xFileOnDatabase.mtime, xFileOnStorage.mtime]) == EVEN) {
|
||||||
|
this._log(`Hidden file skipped: ${filename} is marked as same`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nw = compareMTime(xFileOnStorage.mtime, xFileOnDatabase.mtime);
|
const nw = compareMTime(xFileOnStorage.mtime, xFileOnDatabase.mtime);
|
||||||
if (nw > 0 || direction == "pushForce") {
|
if (nw == BASE_IS_NEW || direction == "pushForce") {
|
||||||
await this.storeInternalFileToDatabase(xFileOnStorage);
|
if (await this.storeInternalFileToDatabase(xFileOnStorage) !== false) {
|
||||||
}
|
// countUpdatedFolder(filename);
|
||||||
if (nw < 0 || direction == "pullForce") {
|
}
|
||||||
|
} else if (nw == TARGET_IS_NEW || direction == "pullForce") {
|
||||||
// skip if not extraction performed.
|
// skip if not extraction performed.
|
||||||
if (!await this.extractInternalFileFromDatabase(filename))
|
if (await this.extractInternalFileFromDatabase(filename))
|
||||||
return;
|
countUpdatedFolder(filename);
|
||||||
|
} else {
|
||||||
|
// Even, or not forced. skip.
|
||||||
}
|
}
|
||||||
// If process successfully updated or file contents are same, update cache.
|
|
||||||
cache.docMtime = xFileOnDatabase.mtime;
|
|
||||||
cache.storageMtime = xFileOnStorage.mtime;
|
|
||||||
caches[filename] = cache;
|
|
||||||
countUpdatedFolder(filename);
|
|
||||||
} else if (!xFileOnStorage && xFileOnDatabase) {
|
} else if (!xFileOnStorage && xFileOnDatabase) {
|
||||||
if (direction == "push" || direction == "pushForce") {
|
if (direction == "push" || direction == "pushForce") {
|
||||||
if (xFileOnDatabase.deleted)
|
if (xFileOnDatabase.deleted)
|
||||||
@@ -423,7 +446,9 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
if (direction == "push" || direction == "pushForce" || direction == "safe") {
|
if (direction == "push" || direction == "pushForce" || direction == "safe") {
|
||||||
await this.storeInternalFileToDatabase(xFileOnStorage);
|
await this.storeInternalFileToDatabase(xFileOnStorage);
|
||||||
} else {
|
} else {
|
||||||
await this.extractInternalFileFromDatabase(xFileOnStorage.path);
|
// if (await this.extractInternalFileFromDatabase(xFileOnStorage.path)) {
|
||||||
|
// countUpdatedFolder(xFileOnStorage.path);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Invalid state on hidden file sync");
|
throw new Error("Invalid state on hidden file sync");
|
||||||
@@ -435,8 +460,6 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
.enqueueAll(allFileNames)
|
.enqueueAll(allFileNames)
|
||||||
.startPipeline().waitForAllDoneAndTerminate();
|
.startPipeline().waitForAllDoneAndTerminate();
|
||||||
|
|
||||||
await this.kvDB.set("diff-caches-internal", caches);
|
|
||||||
|
|
||||||
// When files has been retrieved from the database. they must be reloaded.
|
// When files has been retrieved from the database. they must be reloaded.
|
||||||
if ((direction == "pull" || direction == "pullForce") && filesChanged != 0) {
|
if ((direction == "pull" || direction == "pullForce") && filesChanged != 0) {
|
||||||
// Show notification to restart obsidian when something has been changed in configDir.
|
// Show notification to restart obsidian when something has been changed in configDir.
|
||||||
@@ -459,12 +482,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
anchor.text = "HERE";
|
anchor.text = "HERE";
|
||||||
anchor.addEventListener("click", () => {
|
anchor.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
Logger(`Unloading plugin: ${updatePluginName}`, LOG_LEVEL_NOTICE, "plugin-reload-" + updatePluginId);
|
this._log(`Unloading plugin: ${updatePluginName}`, LOG_LEVEL_NOTICE, "plugin-reload-" + updatePluginId);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await this.app.plugins.unloadPlugin(updatePluginId);
|
await this.app.plugins.unloadPlugin(updatePluginId);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await this.app.plugins.loadPlugin(updatePluginId);
|
await this.app.plugins.loadPlugin(updatePluginId);
|
||||||
Logger(`Plugin reloaded: ${updatePluginName}`, LOG_LEVEL_NOTICE, "plugin-reload-" + updatePluginId);
|
this._log(`Plugin reloaded: ${updatePluginName}`, LOG_LEVEL_NOTICE, "plugin-reload-" + updatePluginId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -472,8 +495,8 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Error on checking plugin status.");
|
this._log("Error on checking plugin status.");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,17 +514,19 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger(`Hidden files scanned: ${filesChanged} files had been modified`, logLevel, "sync_internal");
|
this._log(`Hidden files scanned: ${filesChanged} files had been modified`, logLevel, "sync_internal");
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeInternalFileToDatabase(file: InternalFileInfo, forceWrite = false) {
|
async storeInternalFileToDatabase(file: InternalFileInfo, forceWrite = false) {
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file.path)) {
|
const storeFilePath = file.path;
|
||||||
return
|
const storageFilePath = file.path;
|
||||||
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = await this.path2id(file.path, ICHeader);
|
const id = await this.path2id(storeFilePath, ICHeader);
|
||||||
const prefixedFileName = addPrefix(file.path, ICHeader);
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
const content = createBlob(await this.plugin.storageAccess.readHiddenFileAuto(file.path));
|
const content = createBlob(await this.plugin.storageAccess.readHiddenFileAuto(storageFilePath));
|
||||||
const mtime = file.mtime;
|
const mtime = file.mtime;
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
try {
|
try {
|
||||||
@@ -523,8 +548,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (await isDocContentSame(readAsBlob(old), content) && !forceWrite) {
|
if (await isDocContentSame(readAsBlob(old), content) && !forceWrite) {
|
||||||
// Logger(`STORAGE --> DB:${file.path}: (hidden) Not changed`, LOG_LEVEL_VERBOSE);
|
// this._log(`STORAGE --> DB:${file.path}: (hidden) Not changed`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
const stat = await this.plugin.storageAccess.statHidden(storageFilePath);
|
||||||
|
if (stat) {
|
||||||
|
markChangesAreSame(storageFilePath, old.mtime, stat.mtime);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
saveData =
|
saveData =
|
||||||
{
|
{
|
||||||
@@ -539,24 +568,32 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
const ret = await this.localDatabase.putDBEntry(saveData);
|
const ret = await this.localDatabase.putDBEntry(saveData);
|
||||||
Logger(`STORAGE --> DB:${file.path}: (hidden) Done`);
|
if (ret !== false) {
|
||||||
return ret;
|
this._log(`STORAGE --> DB:${storageFilePath}: (hidden) Done`);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this._log(`STORAGE --> DB:${storageFilePath}: (hidden) Failed`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE --> DB:${file.path}: (hidden) Failed`);
|
this._log(`STORAGE --> DB:${storageFilePath}: (hidden) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteInternalFileOnDatabase(filename: FilePath, forceWrite = false) {
|
async deleteInternalFileOnDatabase(filenameSrc: FilePath, forceWrite = false) {
|
||||||
const id = await this.path2id(filename, ICHeader);
|
const storeFilePath = filenameSrc;
|
||||||
const prefixedFileName = addPrefix(filename, ICHeader);
|
const storageFilePath = filenameSrc;
|
||||||
|
const displayFileName = filenameSrc;
|
||||||
|
const id = await this.path2id(storeFilePath, ICHeader);
|
||||||
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
const mtime = new Date().getTime();
|
const mtime = new Date().getTime();
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(filename)) {
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
||||||
return
|
return undefined
|
||||||
}
|
}
|
||||||
await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
try {
|
try {
|
||||||
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, true) as InternalFileEntry | false;
|
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, true) as InternalFileEntry | false;
|
||||||
let saveData: InternalFileEntry;
|
let saveData: InternalFileEntry;
|
||||||
@@ -578,12 +615,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
if (conflicts._conflicts !== undefined) {
|
if (conflicts._conflicts !== undefined) {
|
||||||
for (const conflictRev of conflicts._conflicts) {
|
for (const conflictRev of conflicts._conflicts) {
|
||||||
await this.localDatabase.removeRevision(old._id, conflictRev);
|
await this.localDatabase.removeRevision(old._id, conflictRev);
|
||||||
Logger(`STORAGE -x> DB:${filename}: (hidden) conflict removed ${old._rev} => ${conflictRev}`, LOG_LEVEL_VERBOSE);
|
this._log(`STORAGE -x> DB: ${displayFileName}: (hidden) conflict removed ${old._rev} => ${conflictRev}`, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (old.deleted) {
|
if (old.deleted) {
|
||||||
Logger(`STORAGE -x> DB:${filename}: (hidden) already deleted`);
|
this._log(`STORAGE -x> DB: ${displayFileName}: (hidden) already deleted`);
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
saveData =
|
saveData =
|
||||||
{
|
{
|
||||||
@@ -595,85 +632,104 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
type: "newnote",
|
type: "newnote",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await this.localDatabase.putRaw(saveData);
|
const ret = await this.localDatabase.putRaw(saveData);
|
||||||
Logger(`STORAGE -x> DB:${filename}: (hidden) Done`);
|
if (ret && ret.ok) {
|
||||||
|
this._log(`STORAGE -x> DB: ${displayFileName}: (hidden) Done`);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this._log(`STORAGE -x> DB: ${displayFileName}: (hidden) Failed`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE -x> DB:${filename}: (hidden) Failed`);
|
this._log(`STORAGE -x> DB: ${displayFileName}: (hidden) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async extractInternalFileFromDatabase(filename: FilePath, force = false) {
|
async extractInternalFileFromDatabase(filenameSrc: FilePath, force = false) {
|
||||||
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(filename);
|
const storeFilePath = filenameSrc;
|
||||||
const prefixedFileName = addPrefix(filename, ICHeader);
|
const storageFilePath = filenameSrc;
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(filename)) {
|
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(storageFilePath);
|
||||||
return;
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
|
const displayFileName = `${storeFilePath}`;
|
||||||
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
try {
|
try {
|
||||||
// Check conflicted status
|
// Check conflicted status
|
||||||
const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, true, true);
|
const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, true, true);
|
||||||
if (fileOnDB === false)
|
if (fileOnDB === false)
|
||||||
throw new Error(`File not found on database.:${filename}`);
|
throw new Error(`File not found on database.:${displayFileName}`);
|
||||||
// Prevent overwrite for Prevent overwriting while some conflicted revision exists.
|
// Prevent overwrite for Prevent overwriting while some conflicted revision exists.
|
||||||
if (fileOnDB?._conflicts?.length) {
|
if (fileOnDB?._conflicts?.length) {
|
||||||
Logger(`Hidden file ${filename} has conflicted revisions, to keep in safe, writing to storage has been prevented`, LOG_LEVEL_INFO);
|
this._log(`Hidden file ${displayFileName} has conflicted revisions, to keep in safe, writing to storage has been prevented`, LOG_LEVEL_INFO);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const deleted = fileOnDB.deleted || fileOnDB._deleted || false;
|
const deleted = fileOnDB.deleted || fileOnDB._deleted || false;
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
if (!isExists) {
|
if (!isExists) {
|
||||||
Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
|
this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
|
||||||
} else {
|
} else {
|
||||||
Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`);
|
this._log(`STORAGE <x- DB: ${displayFileName}: deleted (hidden).`);
|
||||||
await this.plugin.storageAccess.removeHidden(filename);
|
await this.plugin.storageAccess.removeHidden(storageFilePath);
|
||||||
try {
|
try {
|
||||||
// -- @ts-ignore internalAPI
|
// -- @ts-ignore internalAPI
|
||||||
// await this.app.vault.adapter.reconcileInternalFile(filename);
|
// await this.app.vault.adapter.reconcileInternalFile(filename);
|
||||||
await this.plugin.storageAccess.triggerHiddenFile(filename);
|
await this.plugin.storageAccess.triggerHiddenFile(storageFilePath);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!isExists) {
|
if (!isExists) {
|
||||||
await this.plugin.storageAccess.ensureDir(filename);
|
await this.plugin.storageAccess.ensureDir(storageFilePath);
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(filename, readContent(fileOnDB), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
|
await this.plugin.storageAccess.writeHiddenFileAuto(storageFilePath, readContent(fileOnDB), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
|
||||||
try {
|
try {
|
||||||
//@ts-ignore internalAPI
|
//@ts-ignore internalAPI
|
||||||
await this.app.vault.adapter.reconcileInternalFile(filename);
|
await this.app.vault.adapter.reconcileInternalFile(filename);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`);
|
this._log(`STORAGE <-- DB: ${displayFileName}: written (hidden,new${force ? ", force" : ""})`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const content = await this.plugin.storageAccess.readHiddenFileAuto(filename);
|
const content = await this.plugin.storageAccess.readHiddenFileAuto(storageFilePath);
|
||||||
const docContent = readContent(fileOnDB);
|
const docContent = readContent(fileOnDB);
|
||||||
if (await isDocContentSame(content, docContent) && !force) {
|
if (await isDocContentSame(content, docContent) && !force) {
|
||||||
// Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE);
|
// this._log(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
const stat = await this.plugin.storageAccess.statHidden(storageFilePath);
|
||||||
|
if (stat) {
|
||||||
|
markChangesAreSame(storageFilePath, fileOnDB.mtime, stat.mtime);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(filename, docContent, { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
|
if (await this.plugin.storageAccess.writeHiddenFileAuto(storageFilePath, docContent, { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime })) {
|
||||||
try {
|
const stat = await this.plugin.storageAccess.statHidden(storageFilePath) as UXStat;
|
||||||
// await this.app.vault.adapter.reconcileInternalFile(filename);
|
this.markAsKnownChange(storageFilePath, stat.mtime);
|
||||||
await this.plugin.storageAccess.triggerHiddenFile(filename);
|
try {
|
||||||
} catch (ex) {
|
// await this.app.vault.adapter.reconcileInternalFile(filename);
|
||||||
Logger("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
await this.plugin.storageAccess.triggerHiddenFile(storageFilePath);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
} catch (ex) {
|
||||||
}
|
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
||||||
Logger(`STORAGE <-- DB:${filename}: written (hidden, overwrite${force ? ", force" : ""})`);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
}
|
||||||
|
this._log(`STORAGE <-- DB: ${displayFileName}: written (hidden, overwrite${force ? ", force" : ""})`);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this._log(`STORAGE <-- DB: ${displayFileName}: written (hidden, overwrite${force ? ", force" : ""}) Failed`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`STORAGE <-- DB:${filename}: written (hidden, overwrite${force ? ", force" : ""}) Failed`);
|
this._log(`STORAGE <-- DB: ${displayFileName}: written (hidden, overwrite${force ? ", force" : ""}) Failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -683,16 +739,20 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
|
|
||||||
showJSONMergeDialogAndMerge(docA: LoadedEntry, docB: LoadedEntry): Promise<boolean> {
|
showJSONMergeDialogAndMerge(docA: LoadedEntry, docB: LoadedEntry): Promise<boolean> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
Logger("Opening data-merging dialog", LOG_LEVEL_VERBOSE);
|
this._log("Opening data-merging dialog", LOG_LEVEL_VERBOSE);
|
||||||
const docs = [docA, docB];
|
const docs = [docA, docB];
|
||||||
const path = stripAllPrefixes(docA.path);
|
const strippedPath = stripAllPrefixes(docA.path);
|
||||||
const modal = new JsonResolveModal(this.app, path, [docA, docB], async (keep, result) => {
|
const storageFilePath = strippedPath;
|
||||||
|
const storeFilePath = strippedPath;
|
||||||
|
const displayFilename = `${storeFilePath}`;
|
||||||
|
// const path = this.prefixedConfigDir2configDir(stripAllPrefixes(docA.path)) || docA.path;
|
||||||
|
const modal = new JsonResolveModal(this.app, storageFilePath, [docA, docB], async (keep, result) => {
|
||||||
// modal.close();
|
// modal.close();
|
||||||
try {
|
try {
|
||||||
const filename = path;
|
// const filename = storeFilePath;
|
||||||
let needFlush = false;
|
let needFlush = false;
|
||||||
if (!result && !keep) {
|
if (!result && !keep) {
|
||||||
Logger(`Skipped merging: ${filename}`);
|
this._log(`Skipped merging: ${displayFilename}`);
|
||||||
res(false);
|
res(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -701,41 +761,44 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
if (doc._rev != keep) {
|
if (doc._rev != keep) {
|
||||||
if (await this.localDatabase.deleteDBEntry(this.getPath(doc), { rev: doc._rev })) {
|
if (await this.localDatabase.deleteDBEntry(this.getPath(doc), { rev: doc._rev })) {
|
||||||
Logger(`Conflicted revision has been deleted: ${filename}`);
|
this._log(`Conflicted revision has been deleted: ${displayFilename}`);
|
||||||
needFlush = true;
|
needFlush = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!keep && result) {
|
if (!keep && result) {
|
||||||
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(filename);
|
const isExists = await this.plugin.storageAccess.isExistsIncludeHidden(storageFilePath);
|
||||||
if (!isExists) {
|
if (!isExists) {
|
||||||
await this.plugin.storageAccess.ensureDir(filename);
|
await this.plugin.storageAccess.ensureDir(storageFilePath);
|
||||||
}
|
}
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(filename, result);
|
await this.plugin.storageAccess.writeHiddenFileAuto(storageFilePath, result);
|
||||||
const stat = await this.plugin.storageAccess.statHidden(filename);
|
const stat = await this.plugin.storageAccess.statHidden(storageFilePath);
|
||||||
if (!stat) {
|
if (!stat) {
|
||||||
throw new Error("Stat failed");
|
throw new Error("Stat failed");
|
||||||
}
|
}
|
||||||
const mtime = stat?.mtime ?? 0;
|
const mtime = stat?.mtime ?? 0;
|
||||||
await this.storeInternalFileToDatabase({ path: filename, mtime, ctime: stat?.ctime ?? mtime, size: stat?.size ?? 0 }, true);
|
await this.storeInternalFileToDatabase({ path: storageFilePath, mtime, ctime: stat?.ctime ?? mtime, size: stat?.size ?? 0 }, true);
|
||||||
try {
|
try {
|
||||||
//@ts-ignore internalAPI
|
//@ts-ignore internalAPI
|
||||||
await this.app.vault.adapter.reconcileInternalFile(filename);
|
await this.app.vault.adapter.reconcileInternalFile(storageFilePath);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
this._log("Failed to call internal API(reconcileInternalFile)", LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
Logger(`STORAGE <-- DB:${filename}: written (hidden,merged)`);
|
this._log(`STORAGE <-- DB:${displayFilename}: written (hidden,merged)`);
|
||||||
}
|
}
|
||||||
if (needFlush) {
|
if (needFlush) {
|
||||||
await this.extractInternalFileFromDatabase(filename, false);
|
if (await this.extractInternalFileFromDatabase(storeFilePath, false)) {
|
||||||
Logger(`STORAGE --> DB:${filename}: extracted (hidden,merged)`);
|
this._log(`STORAGE --> DB:${displayFilename}: extracted (hidden,merged)`);
|
||||||
|
} else {
|
||||||
|
this._log(`STORAGE --> DB:${displayFilename}: extracted (hidden,merged) Failed`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res(true);
|
res(true);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger("Could not merge conflicted json");
|
this._log("Could not merge conflicted json");
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
res(false);
|
res(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -748,18 +811,19 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean, enableOverwrite?: boolean }) {
|
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean, enableOverwrite?: boolean }) {
|
||||||
const messageFetch = `${opt.enableFetch ? `- Fetch: Use the files stored from other devices. Choose this option if you have already configured hidden file synchronization on those devices and wish to accept their files.\n` : ""}`;
|
const messageFetch = `${opt.enableFetch ? `> - Fetch: Use the files stored from other devices. Choose this option if you have already configured hidden file synchronization on those devices and wish to accept their files.\n` : ""}`;
|
||||||
const messageOverwrite = `${opt.enableOverwrite ? ` - Overwrite: Use the files from this device. Select this option if you want to overwrite the files stored on other devices.\n` : ""}`;
|
const messageOverwrite = `${opt.enableOverwrite ? `> - Overwrite: Use the files from this device. Select this option if you want to overwrite the files stored on other devices.\n` : ""}`;
|
||||||
const messageMerge = `- Merge: Merge the files from this device with those on other devices. Choose this option if you wish to combine files from multiple sources.
|
const messageMerge = `> - Merge: Merge the files from this device with those on other devices. Choose this option if you wish to combine files from multiple sources.
|
||||||
However, please be reminded that merging may cause conflicts if the files are not identical. Additionally, this process may occur within the same folder, potentially breaking your plug-in or theme settings that comprise multiple files.\n`;
|
> However, please be reminded that merging may cause conflicts if the files are not identical. Additionally, this process may occur within the same folder, potentially breaking your plug-in or theme settings that comprise multiple files.\n`;
|
||||||
const message = `Would you like to enable \`Hidden File Synchronization\`?
|
const message = `Would you like to enable **Hidden File Synchronization**?
|
||||||
|
|
||||||
This feature allows you to synchronize all hidden files without any user interaction.
|
|
||||||
To enable this feature, you should choose one of the following options:
|
|
||||||
|
|
||||||
|
> [!DETAILS]-
|
||||||
|
> This feature allows you to synchronize all hidden files without any user interaction.
|
||||||
|
> To enable this feature, you should choose one of the following options:
|
||||||
${messageFetch}${messageOverwrite}${messageMerge}
|
${messageFetch}${messageOverwrite}${messageMerge}
|
||||||
|
|
||||||
Note: Please keep in mind that enabling this feature alongside customisation sync may override certain behaviors.`
|
> [!IMPORTANT]
|
||||||
|
> Please keep in mind that enabling this feature alongside customisation sync may override certain behaviors.`
|
||||||
const CHOICE_FETCH = "Fetch";
|
const CHOICE_FETCH = "Fetch";
|
||||||
const CHOICE_OVERWRITE = "Overwrite";
|
const CHOICE_OVERWRITE = "Overwrite";
|
||||||
const CHOICE_MERGE = "Merge";
|
const CHOICE_MERGE = "Merge";
|
||||||
@@ -788,7 +852,7 @@ Note: Please keep in mind that enabling this feature alongside customisation syn
|
|||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
$allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.syncInternalFiles) {
|
if (this.plugin.settings.syncInternalFiles) {
|
||||||
Logger("Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.", LOG_LEVEL_NOTICE)
|
this._log("Hidden file 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.syncInternalFiles = false;
|
||||||
}
|
}
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -809,7 +873,7 @@ Note: Please keep in mind that enabling this feature alongside customisation syn
|
|||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Logger("Gathering files for enabling Hidden File Sync", LOG_LEVEL_NOTICE);
|
this._log("Gathering files for enabling Hidden File Sync", LOG_LEVEL_NOTICE);
|
||||||
if (mode == "FETCH") {
|
if (mode == "FETCH") {
|
||||||
await this.syncInternalFilesAndDatabase("pullForce", true);
|
await this.syncInternalFilesAndDatabase("pullForce", true);
|
||||||
} else if (mode == "OVERWRITE") {
|
} else if (mode == "OVERWRITE") {
|
||||||
@@ -817,9 +881,10 @@ Note: Please keep in mind that enabling this feature alongside customisation syn
|
|||||||
} else if (mode == "MERGE") {
|
} else if (mode == "MERGE") {
|
||||||
await this.syncInternalFilesAndDatabase("safe", true);
|
await this.syncInternalFilesAndDatabase("safe", true);
|
||||||
}
|
}
|
||||||
|
this.plugin.settings.useAdvancedMode = true;
|
||||||
this.plugin.settings.syncInternalFiles = true;
|
this.plugin.settings.syncInternalFiles = true;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
Logger(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL_NOTICE);
|
this._log(`Done! Restarting the app is strongly recommended!`, LOG_LEVEL_NOTICE);
|
||||||
|
|
||||||
}
|
}
|
||||||
async scanInternalFiles(): Promise<InternalFileInfo[]> {
|
async scanInternalFiles(): Promise<InternalFileInfo[]> {
|
||||||
@@ -867,8 +932,8 @@ Note: Please keep in mind that enabling this feature alongside customisation syn
|
|||||||
try {
|
try {
|
||||||
w = await this.app.vault.adapter.list(path);
|
w = await this.app.vault.adapter.list(path);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Could not traverse(HiddenSync):${path}`, LOG_LEVEL_INFO);
|
this._log(`Could not traverse(HiddenSync):${path}`, LOG_LEVEL_INFO);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const filesSrc = [
|
const filesSrc = [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { Logger } from "octagonal-wheels/common/logger";
|
||||||
import { getPath } from "../common/utils.ts";
|
import { getPath } from "../common/utils.ts";
|
||||||
import { type AnyEntry, type DocumentID, type EntryHasPath, type FilePath, type FilePathWithPrefix } from "../lib/src/common/types.ts";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, type AnyEntry, type DocumentID, type EntryHasPath, type FilePath, type FilePathWithPrefix, type LOG_LEVEL } from "../lib/src/common/types.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||||
|
|
||||||
|
|
||||||
@@ -31,13 +32,21 @@ export abstract class LiveSyncCommands {
|
|||||||
abstract onunload(): void;
|
abstract onunload(): void;
|
||||||
abstract onload(): void | Promise<void>;
|
abstract onload(): void | Promise<void>;
|
||||||
|
|
||||||
$isMainReady() {
|
_isMainReady() {
|
||||||
return this.plugin.$isMainReady();
|
return this.plugin._isMainReady();
|
||||||
}
|
}
|
||||||
$isMainSuspended() {
|
_isMainSuspended() {
|
||||||
return this.plugin.$isMainSuspended();
|
return this.plugin._isMainSuspended();
|
||||||
}
|
}
|
||||||
$isDatabaseReady() {
|
_isDatabaseReady() {
|
||||||
return this.plugin.$isDatabaseReady();
|
return this.plugin._isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
|
if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) {
|
||||||
|
msg = `[${this.constructor.name}]\u{200A} ${msg}`;
|
||||||
|
}
|
||||||
|
// console.log(msg);
|
||||||
|
Logger(msg, level, key);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 92d7b03916...5079b0bf79
40
src/main.ts
40
src/main.ts
@@ -347,11 +347,29 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
async onLiveSyncReady() {
|
async onLiveSyncReady() {
|
||||||
if (!await this.$everyOnLayoutReady()) return;
|
if (!await this.$everyOnLayoutReady()) return;
|
||||||
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
||||||
if (this.settings.suspendFileWatching) {
|
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
||||||
Logger("'Suspend file watching' turned on. Are you sure this is what you intended? Every modification on the vault will be ignored.", LOG_LEVEL_NOTICE);
|
const ANSWER_KEEP = "Keep this plug-in suspended";
|
||||||
}
|
const ANSWER_RESUME = "Resume and restart Obsidian";
|
||||||
if (this.settings.suspendParseReplicationResult) {
|
const message = `Self-hosted LiveSync has been configured to ignore some events. Is this intentional for you?
|
||||||
Logger("'Suspend database reflecting' turned on. Are you sure this is what you intended? Every replicated change will be postponed until disabling this option.", LOG_LEVEL_NOTICE);
|
|
||||||
|
| Type | Status | Note |
|
||||||
|
|:---:|:---:|---|
|
||||||
|
| Storage Events | ${this.settings.suspendFileWatching ? "suspended" : "active"} | Every modification will be ignored |
|
||||||
|
| Database Events | ${this.settings.suspendParseReplicationResult ? "suspended" : "active"} | Every synchronised change will be postponed |
|
||||||
|
|
||||||
|
Do you want to resume them and restart Obsidian?
|
||||||
|
|
||||||
|
> [!DETAILS]-
|
||||||
|
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
|
||||||
|
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
|
||||||
|
`;
|
||||||
|
if (await this.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], { defaultAction: ANSWER_KEEP, title: "Scram Enabled" }) == ANSWER_RESUME) {
|
||||||
|
this.settings.suspendFileWatching = false;
|
||||||
|
this.settings.suspendParseReplicationResult = false;
|
||||||
|
await this.saveSettings();
|
||||||
|
await this.$$scheduleAppReload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const isInitialized = await this.$$initializeDatabase(false, false);
|
const isInitialized = await this.$$initializeDatabase(false, false);
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
@@ -411,7 +429,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
|
|
||||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||||
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
|
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
|
||||||
Logger($f`Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.`, LOG_LEVEL_NOTICE);
|
Logger($f`You have some unread release notes! Please read them once!`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -559,7 +577,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
|
|
||||||
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> { return InterceptiveAny; }
|
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> { return InterceptiveAny; }
|
||||||
|
|
||||||
$$replicateAllToServer(showingNotice: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
$$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
||||||
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
||||||
|
|
||||||
// Remote Governing
|
// Remote Governing
|
||||||
@@ -618,10 +636,10 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
$everyModuleTestMultiDevice(): Promise<boolean> { return InterceptiveEvery; }
|
$everyModuleTestMultiDevice(): Promise<boolean> { return InterceptiveEvery; }
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { throwShouldBeOverridden(); }
|
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { throwShouldBeOverridden(); }
|
||||||
|
|
||||||
$isMainReady(): boolean { return this.isReady; }
|
_isMainReady(): boolean { return this.isReady; }
|
||||||
$isMainSuspended(): boolean { return this.suspended; }
|
_isMainSuspended(): boolean { return this.suspended; }
|
||||||
$isThisModuleEnabled(): boolean { return true; }
|
_isThisModuleEnabled(): boolean { return true; }
|
||||||
$isDatabaseReady(): boolean { return this.localDatabase.isReady; }
|
_isDatabaseReady(): boolean { return this.localDatabase.isReady; }
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> { return InterceptiveAny; }
|
$anyGetAppId(): Promise<string | undefined> { return InterceptiveAny; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,18 +38,18 @@ export abstract class AbstractObsidianModule extends AbstractModule {
|
|||||||
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
||||||
|
|
||||||
|
|
||||||
$isMainReady() {
|
_isMainReady() {
|
||||||
return this.core.$isMainReady();
|
return this.core._isMainReady();
|
||||||
}
|
}
|
||||||
$isMainSuspended() {
|
_isMainSuspended() {
|
||||||
return this.core.$isMainSuspended();
|
return this.core._isMainSuspended();
|
||||||
}
|
}
|
||||||
$isDatabaseReady() {
|
_isDatabaseReady() {
|
||||||
return this.core.$isDatabaseReady();
|
return this.core._isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
//should be overridden
|
//should be overridden
|
||||||
$isThisModuleEnabled() {
|
_isThisModuleEnabled() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,21 +54,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
if (!onlyChunks) {
|
if (!onlyChunks) {
|
||||||
return await this.db.store(readFile);
|
return await this.db.store(readFile);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return await this.db.createChunks(readFile, false, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// I remember that it should be processed naturally. -->
|
|
||||||
|
|
||||||
// // If the file is exist on the database, then it should be updated.
|
|
||||||
// // Check the file is already conflicted or not.
|
|
||||||
// const conflictedRevs = await this.db.getConflictedRevs(file);
|
|
||||||
// if (conflictedRevs.length > 0) {
|
|
||||||
// // If conflicted, then it should be stored as new conflicted file.
|
|
||||||
// const readFile = await this.readFileFromStub(file);
|
|
||||||
// this.db.store(readFile, true);
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//< --
|
|
||||||
|
|
||||||
// entry is exist on the database, check the difference between the file and the entry.
|
// entry is exist on the database, check the difference between the file and the entry.
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async askUsingOptionalFeature(opt: {
|
||||||
|
enableFetch?: boolean;
|
||||||
|
enableOverwrite?: boolean;
|
||||||
|
}) {
|
||||||
|
if (await this.core.confirm.askYesNoDialog("Do you want to enable extra features? If you are new to Self-hosted LiveSync, try the core feature first!", { title: "Enable extra features", defaultOption: "No", timeout: 15 }) == "yes") {
|
||||||
|
await this.core.$allAskUsingOptionalSyncFeature(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async rebuildRemote() {
|
async rebuildRemote() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.core.$allSuspendExtraSync();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
@@ -36,11 +45,11 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.core.$$tryResetRemoteDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.core.$$markRemoteLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.core.$allAskUsingOptionalSyncFeature({ enableOverwrite: true });
|
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.core.$$replicateAllToServer(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.core.$$replicateAllToServer(true, true);
|
||||||
}
|
}
|
||||||
$rebuildRemote(): Promise<void> {
|
$rebuildRemote(): Promise<void> {
|
||||||
return this.rebuildRemote();
|
return this.rebuildRemote();
|
||||||
@@ -59,11 +68,11 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.core.$$tryResetRemoteDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.core.$$markRemoteLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.core.$allAskUsingOptionalSyncFeature({ enableOverwrite: true });
|
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.core.$$replicateAllToServer(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.core.$$replicateAllToServer(true, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +178,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.core.$$replicateAllFromServer(true);
|
||||||
await this.resumeReflectingDatabase();
|
await this.resumeReflectingDatabase();
|
||||||
await this.core.$allAskUsingOptionalSyncFeature({ enableFetch: true });
|
await this.askUsingOptionalFeature({ enableFetch: true });
|
||||||
}
|
}
|
||||||
async fetchLocalWithRebuild() {
|
async fetchLocalWithRebuild() {
|
||||||
return await this.fetchLocal(true);
|
return await this.fetchLocal(true);
|
||||||
|
|||||||
@@ -231,12 +231,17 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isAnyNote(change)) {
|
if (isAnyNote(change)) {
|
||||||
|
const docPath = getPath(change);
|
||||||
|
if (!await this.core.$$isTargetFile(docPath)) {
|
||||||
|
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.databaseQueuedProcessor._isSuspended) {
|
if (this.databaseQueuedProcessor._isSuspended) {
|
||||||
Logger(`Processing scheduled: ${change.path}`, LOG_LEVEL_INFO);
|
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
|
||||||
}
|
}
|
||||||
const size = change.size;
|
const size = change.size;
|
||||||
if (this.core.$$isFileSizeExceeded(size)) {
|
if (this.core.$$isFileSizeExceeded(size)) {
|
||||||
Logger(`Processing ${change.path} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
Logger(`Processing ${docPath} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.databaseQueuedProcessor.enqueue(change);
|
this.databaseQueuedProcessor.enqueue(change);
|
||||||
@@ -258,6 +263,7 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
databaseQueuedProcessor = new QueueProcessor(async (docs: EntryBody[]) => {
|
databaseQueuedProcessor = new QueueProcessor(async (docs: EntryBody[]) => {
|
||||||
const dbDoc = docs[0] as LoadedEntry; // It has no `data`
|
const dbDoc = docs[0] as LoadedEntry; // It has no `data`
|
||||||
const path = getPath(dbDoc);
|
const path = getPath(dbDoc);
|
||||||
|
|
||||||
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
||||||
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
||||||
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, {}, false, true, true);
|
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, {}, false, true, true);
|
||||||
@@ -308,15 +314,17 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateAllToServer(showingNotice: boolean = false): Promise<boolean> {
|
async $$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.isReady) return false;
|
if (!this.core.isReady) return false;
|
||||||
if (!await this.core.$everyBeforeReplicate(showingNotice)) {
|
if (!await this.core.$everyBeforeReplicate(showingNotice)) {
|
||||||
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.core.replicator instanceof LiveSyncCouchDBReplicator) {
|
if (!sendChunksInBulkDisabled) {
|
||||||
if (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { defaultOption: "No", timeout: 20 }) == "yes") {
|
if (this.core.replicator instanceof LiveSyncCouchDBReplicator) {
|
||||||
await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0);
|
if (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { defaultOption: "No", timeout: 20 }) == "yes") {
|
||||||
|
await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
||||||
|
|||||||
@@ -1,39 +1,40 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
||||||
import { delay } from "octagonal-wheels/promises";
|
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
||||||
async $allScanStat(): Promise<boolean> {
|
async $allScanStat(): Promise<boolean> {
|
||||||
this._log(`Checking storage sizes`, LOG_LEVEL_VERBOSE);
|
this._log(`Checking storage sizes`, LOG_LEVEL_VERBOSE);
|
||||||
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
||||||
const message = `Now, Self-hosted LiveSync is able to check the remote storage size on the start-up.
|
const message = `We can set a maximum database capacity warning, **to take action before running out of space on the remote storage**.
|
||||||
|
Do you want to enable this?
|
||||||
|
|
||||||
You can configure the threshold size for your remote storage. This will be different for your server.
|
> [!MORE]-
|
||||||
|
> - 0: Do not warn about storage size.
|
||||||
|
> This is recommended if you have enough space on the remote storage especially you have self-hosted. And you can check the storage size and rebuild manually.
|
||||||
|
> - 800: Warn if the remote storage size exceeds 800MB.
|
||||||
|
> This is recommended if you are using fly.io with 1GB limit or IBM Cloudant.
|
||||||
|
> - 2000: Warn if the remote storage size exceeds 2GB.
|
||||||
|
|
||||||
Please choose the threshold size as you like.
|
If we have reached the limit, we will be asked to enlarge the limit step by step.
|
||||||
|
|
||||||
- 0: Do not warn about storage size.
|
|
||||||
This is recommended if you have enough space on the remote storage especially you have self-hosted. And you can check the storage size and rebuild manually.
|
|
||||||
- 800: Warn if the remote storage size exceeds 800MB.
|
|
||||||
This is recommended if you are using fly.io with 1GB limit or IBM Cloudant.
|
|
||||||
- 2000: Warn if the remote storage size exceeds 2GB.
|
|
||||||
|
|
||||||
And if your actual storage size exceeds the threshold after the setup, you may warned again. But do not worry, you can enlarge the threshold (or rebuild everything to reduce the size).
|
|
||||||
`
|
`
|
||||||
const ANSWER_0 = "Do not warn";
|
const ANSWER_0 = "No, never warn please";
|
||||||
const ANSWER_800 = "800MB";
|
const ANSWER_800 = "800MB (Cloudant, fly.io)";
|
||||||
const ANSWER_2000 = "2GB";
|
const ANSWER_2000 = "2GB (Standard)";
|
||||||
|
const ASK_ME_NEXT_TIME = "Ask me later";
|
||||||
|
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Remote storage size threshold", message, [ANSWER_0, ANSWER_800, ANSWER_2000], ANSWER_800, 40);
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [ANSWER_0, ANSWER_800, ANSWER_2000, ASK_ME_NEXT_TIME], {
|
||||||
|
defaultAction: ASK_ME_NEXT_TIME,
|
||||||
|
title: "Setting up database size notification", timeout: 40
|
||||||
|
});
|
||||||
if (ret == ANSWER_0) {
|
if (ret == ANSWER_0) {
|
||||||
this.settings.notifyThresholdOfRemoteStorageSize = 0;
|
this.settings.notifyThresholdOfRemoteStorageSize = 0;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
} else if (ret == ANSWER_800) {
|
} else if (ret == ANSWER_800) {
|
||||||
this.settings.notifyThresholdOfRemoteStorageSize = 800;
|
this.settings.notifyThresholdOfRemoteStorageSize = 800;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
} else {
|
} else if (ret == ANSWER_2000) {
|
||||||
this.settings.notifyThresholdOfRemoteStorageSize = 2000;
|
this.settings.notifyThresholdOfRemoteStorageSize = 2000;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
}
|
}
|
||||||
@@ -45,28 +46,38 @@ And if your actual storage size exceeds the threshold after the setup, you may w
|
|||||||
if (estimatedSize) {
|
if (estimatedSize) {
|
||||||
const maxSize = this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024;
|
const maxSize = this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024;
|
||||||
if (estimatedSize > maxSize) {
|
if (estimatedSize > maxSize) {
|
||||||
const message = `Remote storage size: ${sizeToHumanReadable(estimatedSize)}. It exceeds the configured value ${sizeToHumanReadable(maxSize)}.
|
const message = `**Your database is getting larger!** But do not worry, we can address it now. The time before running out of space on the remote storage.
|
||||||
This may cause the storage to be full. You should enlarge the remote storage, or rebuild everything to reduce the size. \n
|
|
||||||
**Note:** If you are new to Self-hosted LiveSync, you should enlarge the threshold. \n
|
|
||||||
|
|
||||||
Self-hosted LiveSync will not release the storage automatically even if the file is deleted. This is why they need regular maintenance.\n
|
| Measured size | Configured size |
|
||||||
|
| --- | --- |
|
||||||
|
| ${sizeToHumanReadable(estimatedSize)} | ${sizeToHumanReadable(maxSize)} |
|
||||||
|
|
||||||
If you have enough space on the remote storage, you can enlarge the threshold. Otherwise, you should rebuild everything.\n
|
> [!MORE]-
|
||||||
|
> If you have been using it for many years, there may be unreferenced chunks - that is, garbage - accumulating in the database. Therefore, we recommend rebuilding everything. It will probably become much smaller.
|
||||||
|
>
|
||||||
|
> If the volume of your vault is simply increasing, it is better to rebuild everything after organizing the files. Self-hosted LiveSync does not delete the actual data even if you delete it to speed up the process. It is roughly [documented](https://github.com/vrtmrz/obsidian-livesync/blob/main/docs/tech_info.md).
|
||||||
|
>
|
||||||
|
> If you don't mind the increase, you can increase the notification limit by 100MB. This is the case if you are running it on your own server. However, it is better to rebuild everything from time to time.
|
||||||
|
>
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> If you perform rebuild everything, make sure all devices are synchronised. The plug-in will merge as much as possible, though.
|
||||||
|
|
||||||
However, **Please make sure that all devices have been synchronised**. \n
|
|
||||||
\n`;
|
\n`;
|
||||||
const newMax = ~~(estimatedSize / 1024 / 1024) + 100;
|
const newMax = ~~(estimatedSize / 1024 / 1024) + 100;
|
||||||
const ANSWER_ENLARGE_LIMIT = `Enlarge to ${newMax}MB`;
|
const ANSWER_ENLARGE_LIMIT = `increase to ${newMax}MB`;
|
||||||
const ANSWER_REBUILD = "Rebuild now";
|
const ANSWER_REBUILD = "Rebuild Everything Now";
|
||||||
const ANSWER_IGNORE = "Dismiss";
|
const ANSWER_IGNORE = "Dismiss";
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Remote storage size exceeded", message, [ANSWER_ENLARGE_LIMIT, ANSWER_REBUILD, ANSWER_IGNORE,], ANSWER_IGNORE, 20);
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [ANSWER_ENLARGE_LIMIT, ANSWER_REBUILD, ANSWER_IGNORE,], {
|
||||||
|
defaultAction: ANSWER_IGNORE,
|
||||||
|
title: "Remote storage size exceeded the limit", timeout: 60
|
||||||
|
|
||||||
|
});
|
||||||
if (ret == ANSWER_REBUILD) {
|
if (ret == ANSWER_REBUILD) {
|
||||||
const ret = await this.core.confirm.askYesNoDialog("This may take a bit of a long time. Do you really want to rebuild everything now?", { defaultOption: "No" });
|
const ret = await this.core.confirm.askYesNoDialog("This may take a bit of a long time. Do you really want to rebuild everything now?", { defaultOption: "No" });
|
||||||
if (ret == "yes") {
|
if (ret == "yes") {
|
||||||
this._log(`Receiving all from the server before rebuilding`, LOG_LEVEL_NOTICE);
|
this.core.settings.notifyThresholdOfRemoteStorageSize = -1;
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.saveSettings();
|
||||||
await delay(3000);
|
|
||||||
this._log(`Obsidian will be reloaded to rebuild everything.`, LOG_LEVEL_NOTICE);
|
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
}
|
}
|
||||||
} else if (ret == ANSWER_ENLARGE_LIMIT) {
|
} else if (ret == ANSWER_ENLARGE_LIMIT) {
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
} else if (isRedFlag3Raised) {
|
} else if (isRedFlag3Raised) {
|
||||||
this._log(`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`, LOG_LEVEL_NOTICE);
|
this._log(`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`, LOG_LEVEL_NOTICE);
|
||||||
const makeLocalChunkBeforeSync = ((await this.core.confirm.askYesNoDialog("Do you want to create local chunks before fetching?", { defaultOption: "Yes" })) == "yes");
|
const makeLocalChunkBeforeSync = ((await this.core.confirm.askYesNoDialog(`Do you want to create local chunks before fetching?
|
||||||
|
> [!MORE]-
|
||||||
|
> If creating local chunks before fetching, only the difference between the local and remote will be fetched.
|
||||||
|
|
||||||
|
`, { defaultOption: "Yes", title: "Trick to transfer efficiently" })) == "yes");
|
||||||
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync);
|
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync);
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
if (this.settings.suspendFileWatching) {
|
if (this.settings.suspendFileWatching) {
|
||||||
@@ -87,6 +91,8 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
this.core.$$performRestart();
|
this.core.$$performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this._log("Your content of files will be synchronised gradually. Please wait for the completion.", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Case of FLAGMD_REDFLAG.
|
// Case of FLAGMD_REDFLAG.
|
||||||
|
|||||||
@@ -1,7 +1,29 @@
|
|||||||
import { ButtonComponent } from "obsidian";
|
import { ButtonComponent } from "obsidian";
|
||||||
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts";
|
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts";
|
||||||
|
import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts";
|
||||||
|
import { delay } from "octagonal-wheels/promises";
|
||||||
|
|
||||||
export class InputStringDialog extends Modal {
|
class AutoClosableModal extends Modal {
|
||||||
|
removeEvent: (() => void) | undefined;
|
||||||
|
|
||||||
|
constructor(app: App) {
|
||||||
|
super(app);
|
||||||
|
this.removeEvent = eventHub.on(EVENT_PLUGIN_UNLOADED, async () => {
|
||||||
|
await delay(100);
|
||||||
|
if (!this.removeEvent) return;
|
||||||
|
this.close();
|
||||||
|
this.removeEvent = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose() {
|
||||||
|
if (this.removeEvent) {
|
||||||
|
this.removeEvent();
|
||||||
|
this.removeEvent = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InputStringDialog extends AutoClosableModal {
|
||||||
result: string | false = false;
|
result: string | false = false;
|
||||||
onSubmit: (result: string | false) => void;
|
onSubmit: (result: string | false) => void;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -47,6 +69,7 @@ export class InputStringDialog extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
|
super.onClose();
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
if (this.isManuallyClosed) {
|
if (this.isManuallyClosed) {
|
||||||
@@ -95,7 +118,7 @@ export class PopoverSelectString extends FuzzySuggestModal<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageBox extends Modal {
|
export class MessageBox extends AutoClosableModal {
|
||||||
|
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -144,16 +167,19 @@ export class MessageBox extends Modal {
|
|||||||
onOpen() {
|
onOpen() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
this.titleEl.setText(this.title);
|
this.titleEl.setText(this.title);
|
||||||
contentEl.addEventListener("click", () => {
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = undefined;
|
|
||||||
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const div = contentEl.createDiv();
|
const div = contentEl.createDiv();
|
||||||
|
div.style.userSelect = "text";
|
||||||
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, this.contentMd, div, "/", this.plugin);
|
||||||
const buttonSetting = new Setting(contentEl);
|
const buttonSetting = new Setting(contentEl);
|
||||||
|
const labelWrapper = contentEl.createDiv();
|
||||||
|
labelWrapper.addClass("sls-dialogue-note-wrapper");
|
||||||
|
const labelEl = labelWrapper.createEl("label", { text: "To stop the countdown, tap anywhere on the dialogue" });
|
||||||
|
labelEl.addClass("sls-dialogue-note-countdown");
|
||||||
|
if (!this.timeout || !this.timer) {
|
||||||
|
labelWrapper.empty();
|
||||||
|
labelWrapper.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
buttonSetting.infoEl.style.display = "none";
|
buttonSetting.infoEl.style.display = "none";
|
||||||
buttonSetting.controlEl.style.flexWrap = "wrap";
|
buttonSetting.controlEl.style.flexWrap = "wrap";
|
||||||
if (this.wideButton) {
|
if (this.wideButton) {
|
||||||
@@ -162,6 +188,15 @@ export class MessageBox extends Modal {
|
|||||||
buttonSetting.controlEl.style.justifyContent = "center";
|
buttonSetting.controlEl.style.justifyContent = "center";
|
||||||
buttonSetting.controlEl.style.flexGrow = "1";
|
buttonSetting.controlEl.style.flexGrow = "1";
|
||||||
}
|
}
|
||||||
|
contentEl.addEventListener("click", () => {
|
||||||
|
if (this.timer) {
|
||||||
|
labelWrapper.empty();
|
||||||
|
labelWrapper.style.display = "none";
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = undefined;
|
||||||
|
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
for (const button of this.buttons) {
|
for (const button of this.buttons) {
|
||||||
buttonSetting.addButton((btn) => {
|
buttonSetting.addButton((btn) => {
|
||||||
btn
|
btn
|
||||||
@@ -190,6 +225,7 @@ export class MessageBox extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
|
super.onClose();
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from 'octagonal-wheels/common/logger.js';
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from 'octagonal-wheels/common/logger.js';
|
||||||
import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from '../../lib/src/common/types.js';
|
import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from '../../lib/src/common/types.js';
|
||||||
import { EVENT_REQUEST_OPEN_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI, eventHub } from '../../common/events.ts';
|
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI, eventHub } from '../../common/events.ts';
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
|
const URI_DOC = "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use";
|
||||||
|
|
||||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async migrateDisableBulkSend() {
|
async migrateDisableBulkSend() {
|
||||||
@@ -159,6 +161,63 @@ ___However, to enable either of these changes, both remote and local databases n
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async initialMessage() {
|
||||||
|
const message = `Your device has **not been set up yet**. Let me guide you through the setup process.
|
||||||
|
|
||||||
|
Please keep in mind that every dialogue content can be copied to the clipboard. If you need to refer to it later, you can paste it into a note in Obsidian. You can also translate it into your language using a translation tool.
|
||||||
|
|
||||||
|
First, do you have **Setup URI**?
|
||||||
|
|
||||||
|
Note: If you do not know what it is, please refer to the [documentation](${URI_DOC}).
|
||||||
|
`;
|
||||||
|
|
||||||
|
const USE_SETUP = "Yes, I have";
|
||||||
|
const NEXT = "No, I do not have";
|
||||||
|
|
||||||
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [
|
||||||
|
USE_SETUP, NEXT], {
|
||||||
|
title: "Welcome to Self-hosted LiveSync",
|
||||||
|
defaultAction: USE_SETUP
|
||||||
|
});
|
||||||
|
if (ret === USE_SETUP) {
|
||||||
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (ret == NEXT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async askAgainForSetupURI() {
|
||||||
|
const message = `We strongly recommend that you generate a set-up URI and use it.
|
||||||
|
If you do not have knowledge about it, please refer to the [documentation](${URI_DOC}) (Sorry again, but it is important).
|
||||||
|
|
||||||
|
How do you want to set it up manually?`;
|
||||||
|
|
||||||
|
const USE_MINIMAL = "Take me into the setup wizard";
|
||||||
|
const USE_SETUP = "Set it up all manually";
|
||||||
|
const NEXT = "Remind me at the next launch";
|
||||||
|
|
||||||
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [
|
||||||
|
USE_MINIMAL, USE_SETUP, NEXT], {
|
||||||
|
title: "Recommendation to use Setup URI",
|
||||||
|
defaultAction: USE_MINIMAL
|
||||||
|
});
|
||||||
|
if (ret === USE_MINIMAL) {
|
||||||
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTING_WIZARD);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ret === USE_SETUP) {
|
||||||
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTINGS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (ret == NEXT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async $everyOnFirstInitialize(): Promise<boolean> {
|
async $everyOnFirstInitialize(): Promise<boolean> {
|
||||||
if (!this.localDatabase.isReady) {
|
if (!this.localDatabase.isReady) {
|
||||||
this._log(`Something went wrong! The local database is not ready`, LOG_LEVEL_NOTICE);
|
this._log(`Something went wrong! The local database is not ready`, LOG_LEVEL_NOTICE);
|
||||||
@@ -170,40 +229,11 @@ ___However, to enable either of these changes, both remote and local databases n
|
|||||||
}
|
}
|
||||||
if (!this.settings.isConfigured) {
|
if (!this.settings.isConfigured) {
|
||||||
// Case sensitivity
|
// Case sensitivity
|
||||||
const message = `Hello and welcome to Self-hosted LiveSync.
|
if (!await this.initialMessage() || !await this.askAgainForSetupURI()) {
|
||||||
|
this._log("The setup has been cancelled, Self-hosted LiveSync waiting for your setup!", LOG_LEVEL_NOTICE);
|
||||||
Your device seems to **not be configured yet**. Please finish the setup and synchronise your vaults!
|
return false;
|
||||||
|
|
||||||
Click anywhere to stop counting down.
|
|
||||||
|
|
||||||
## At the first device
|
|
||||||
- With Setup URI -> Use \`Use the copied setup URI\`.
|
|
||||||
If you have configured it automatically, you should have one.
|
|
||||||
- Without Setup URI -> Use \`Setup wizard\` in setting dialogue. **\`Minimal setup\` is recommended**.
|
|
||||||
- What is the Setup URI? -> Do not worry! We have [some docs](https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use) now. Please refer to them once.
|
|
||||||
|
|
||||||
## At the subsequent device
|
|
||||||
- With Setup URI -> Use \`Use the copied setup URI\`.
|
|
||||||
If you do not have it yet, you can copy it on the first device.
|
|
||||||
- Without Setup URI -> Use \`Setup wizard\` in setting dialogue, but **strongly recommends using setup URI**.
|
|
||||||
`
|
|
||||||
const OPEN_SETUP = "Open setting dialog";
|
|
||||||
const USE_SETUP = "Use the copied setup URI";
|
|
||||||
const DISMISS = "Dismiss";
|
|
||||||
|
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Welcome to Self-hosted LiveSync", message, [
|
|
||||||
USE_SETUP, OPEN_SETUP, DISMISS], DISMISS, 40);
|
|
||||||
if (ret === OPEN_SETUP) {
|
|
||||||
try {
|
|
||||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTINGS);
|
|
||||||
} catch (ex) {
|
|
||||||
this._log("Something went wrong on opening setting dialog, please open it manually", LOG_LEVEL_NOTICE);
|
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
||||||
}
|
|
||||||
} else if (ret == USE_SETUP) {
|
|
||||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import { writable } from "svelte/store";
|
|||||||
|
|
||||||
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
__onMissingTranslation(() => { });
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
__onMissingTranslation(() => { });
|
|
||||||
// eslint-disable-next-line no-unused-labels
|
// eslint-disable-next-line no-unused-labels
|
||||||
__onMissingTranslation((key) => {
|
__onMissingTranslation((key) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -106,28 +106,6 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async resolveConflictByNewerEntry(path: FilePathWithPrefix) {
|
|
||||||
// const id = await this.plugin.$$path2id(path);
|
|
||||||
// const doc = await this.localDatabase.getRaw<AnyEntry>(id, { conflicts: true });
|
|
||||||
// // If there is no conflict, return with false.
|
|
||||||
// if (!("_conflicts" in doc) || doc._conflicts === undefined) return false;
|
|
||||||
// if (doc._conflicts.length == 0) return false;
|
|
||||||
// this._log(`Hidden file conflicted:${getPath(doc)}`);
|
|
||||||
// const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
|
|
||||||
// const revA = doc._rev;
|
|
||||||
// const revB = conflicts[0];
|
|
||||||
// const revBDoc = await this.localDatabase.getRaw<EntryDoc>(id, { rev: revB });
|
|
||||||
// // determine which revision should been deleted.
|
|
||||||
// // simply check modified time
|
|
||||||
// const mtimeA = ("mtime" in doc && doc.mtime) || 0;
|
|
||||||
// const mtimeB = ("mtime" in revBDoc && revBDoc.mtime) || 0;
|
|
||||||
// const delRev = mtimeA < mtimeB ? revA : revB;
|
|
||||||
// // delete older one.
|
|
||||||
// await this.localDatabase.removeRevision(id, delRev);
|
|
||||||
// this._log(`Older one has been deleted:${getPath(doc)}`);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
async $allScanStat(): Promise<boolean> {
|
async $allScanStat(): Promise<boolean> {
|
||||||
const notes: { path: string, mtime: number }[] = [];
|
const notes: { path: string, mtime: number }[] = [];
|
||||||
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
const saveData = { ...(settings ? settings : this.settings) } as Partial<ObsidianLiveSyncSettings>;
|
const saveData = { ...(settings ? settings : this.settings) } as Partial<ObsidianLiveSyncSettings>;
|
||||||
delete saveData.encryptedCouchDBConnection;
|
delete saveData.encryptedCouchDBConnection;
|
||||||
delete saveData.encryptedPassphrase;
|
delete saveData.encryptedPassphrase;
|
||||||
|
delete saveData.additionalSuffixOfDatabaseName;
|
||||||
if (!saveData.writeCredentialsForSettingSync && !keepCredential) {
|
if (!saveData.writeCredentialsForSettingSync && !keepCredential) {
|
||||||
delete saveData.couchDB_USER;
|
delete saveData.couchDB_USER;
|
||||||
delete saveData.couchDB_PASSWORD;
|
delete saveData.couchDB_PASSWORD;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
||||||
|
|
||||||
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
@@ -11,6 +11,11 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implem
|
|||||||
this.settingTab = new ObsidianLiveSyncSettingTab(this.app, this.plugin);
|
this.settingTab = new ObsidianLiveSyncSettingTab(this.app, this.plugin);
|
||||||
this.plugin.addSettingTab(this.settingTab);
|
this.plugin.addSettingTab(this.settingTab);
|
||||||
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTINGS, () => this.openSetting());
|
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTINGS, () => this.openSetting());
|
||||||
|
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTING_WIZARD, () => {
|
||||||
|
this.openSetting();
|
||||||
|
void this.settingTab.enableMinimalSetup();
|
||||||
|
});
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { App, PluginSettingTab, MarkdownRenderer, stringifyYaml } from "../../../deps.ts";
|
import { App, PluginSettingTab, MarkdownRenderer, stringifyYaml } from "../../../deps.ts";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, type ConfigPassphraseStore, type RemoteDBSettings, type FilePathWithPrefix, type HashAlgorithm, type DocumentID, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, LOG_LEVEL_INFO, type LoadedEntry, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3_HR, REMOTE_COUCHDB, REMOTE_MINIO, PREFERRED_JOURNAL_SYNC, FLAGMD_REDFLAG, type ConfigLevel, LEVEL_POWER_USER, LEVEL_ADVANCED, LEVEL_EDGE_CASE, type MetaEntry, type FilePath
|
DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, type ConfigPassphraseStore, type RemoteDBSettings, type FilePathWithPrefix, type HashAlgorithm, type DocumentID, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, LOG_LEVEL_INFO, type LoadedEntry, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3_HR, REMOTE_COUCHDB, REMOTE_MINIO, PREFERRED_JOURNAL_SYNC, FLAGMD_REDFLAG, type ConfigLevel, LEVEL_POWER_USER, LEVEL_ADVANCED, LEVEL_EDGE_CASE, type MetaEntry, type FilePath,
|
||||||
} from "../../../lib/src/common/types.ts";
|
} from "../../../lib/src/common/types.ts";
|
||||||
import { createBlob, delay, isDocContentSame, isObjectDifferent, readAsBlob, sizeToHumanReadable } from "../../../lib/src/common/utils.ts";
|
import { createBlob, delay, isDocContentSame, isObjectDifferent, readAsBlob, sizeToHumanReadable } from "../../../lib/src/common/utils.ts";
|
||||||
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
||||||
@@ -9,8 +9,8 @@ import { balanceChunkPurgedDBs, checkSyncInfo, isCloudantURI, purgeUnreferencedC
|
|||||||
import { testCrypt } from "../../../lib/src/encryption/e2ee_v2.ts";
|
import { testCrypt } from "../../../lib/src/encryption/e2ee_v2.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { getPath, requestToCouchDB, scheduleTask } from "../../../common/utils.ts";
|
import { getPath, requestToCouchDB, scheduleTask } from "../../../common/utils.ts";
|
||||||
import { request, TFile } from "obsidian";
|
import { request } from "obsidian";
|
||||||
import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts";
|
import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import MultipleRegExpControl from './MultipleRegExpControl.svelte';
|
import MultipleRegExpControl from './MultipleRegExpControl.svelte';
|
||||||
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
||||||
import { type AllSettingItemKey, type AllStringItemKey, type AllNumericItemKey, type AllBooleanItemKey, type AllSettings, OnDialogSettingsDefault, type OnDialogSettings, getConfName } from "./settingConstants.ts";
|
import { type AllSettingItemKey, type AllStringItemKey, type AllNumericItemKey, type AllBooleanItemKey, type AllSettings, OnDialogSettingsDefault, type OnDialogSettings, getConfName } from "./settingConstants.ts";
|
||||||
@@ -23,6 +23,8 @@ import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts";
|
|||||||
import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_REQUEST_SHOW_HISTORY, eventHub } from "../../../common/events.ts";
|
import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, EVENT_REQUEST_OPEN_SETUP_URI, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_REQUEST_SHOW_HISTORY, eventHub } from "../../../common/events.ts";
|
||||||
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
|
import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
|
||||||
|
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
|
||||||
|
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
|
|
||||||
export type OnUpdateResult = {
|
export type OnUpdateResult = {
|
||||||
visibility?: boolean,
|
visibility?: boolean,
|
||||||
@@ -405,11 +407,51 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screenElements: { [key: string]: HTMLElement[] } = {};
|
||||||
|
changeDisplay(screen: string) {
|
||||||
|
for (const k in this.screenElements) {
|
||||||
|
if (k == screen) {
|
||||||
|
this.screenElements[k].forEach((element) => element.removeClass("setting-collapsed"));
|
||||||
|
} else {
|
||||||
|
this.screenElements[k].forEach((element) => element.addClass("setting-collapsed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.menuEl) {
|
||||||
|
this.menuEl.querySelectorAll(`.sls-setting-label`).forEach((element) => {
|
||||||
|
if (element.hasClass(`c-${screen}`)) {
|
||||||
|
element.addClass("selected");
|
||||||
|
(element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = true;
|
||||||
|
} else {
|
||||||
|
element.removeClass("selected");
|
||||||
|
(element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.selectedScreen = screen;
|
||||||
|
}
|
||||||
|
async enableMinimalSetup() {
|
||||||
|
this.editingSettings.liveSync = false;
|
||||||
|
this.editingSettings.periodicReplication = false;
|
||||||
|
this.editingSettings.syncOnSave = false;
|
||||||
|
this.editingSettings.syncOnEditorSave = false;
|
||||||
|
this.editingSettings.syncOnStart = false;
|
||||||
|
this.editingSettings.syncOnFileOpen = false;
|
||||||
|
this.editingSettings.syncAfterMerge = false;
|
||||||
|
this.plugin.replicator.closeReplication();
|
||||||
|
await this.saveAllDirtySettings();
|
||||||
|
this.containerEl.addClass("isWizard");
|
||||||
|
this.inWizard = true;
|
||||||
|
this.changeDisplay("20")
|
||||||
|
}
|
||||||
|
menuEl?: HTMLElement;
|
||||||
|
|
||||||
display(): void {
|
display(): void {
|
||||||
|
const changeDisplay = this.changeDisplay.bind(this);
|
||||||
const { containerEl } = this;
|
const { containerEl } = this;
|
||||||
this.settingComponents.length = 0;
|
this.settingComponents.length = 0;
|
||||||
this.controlledElementFunc.length = 0;
|
this.controlledElementFunc.length = 0;
|
||||||
this.onSavedHandlers.length = 0;
|
this.onSavedHandlers.length = 0;
|
||||||
|
this.screenElements = {};
|
||||||
if (this._editingSettings == undefined || this.initialSettings == undefined) {
|
if (this._editingSettings == undefined || this.initialSettings == undefined) {
|
||||||
this.reloadAllSettings();
|
this.reloadAllSettings();
|
||||||
}
|
}
|
||||||
@@ -436,26 +478,26 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
setStyle(containerEl, "menu-setting-advanced", () => this.isConfiguredAs("useAdvancedMode", true));
|
setStyle(containerEl, "menu-setting-advanced", () => this.isConfiguredAs("useAdvancedMode", true));
|
||||||
setStyle(containerEl, "menu-setting-edgecase", () => this.isConfiguredAs("useEdgeCaseMode", true));
|
setStyle(containerEl, "menu-setting-edgecase", () => this.isConfiguredAs("useEdgeCaseMode", true));
|
||||||
|
|
||||||
const screenElements: { [key: string]: HTMLElement[] } = {};
|
|
||||||
const addScreenElement = (key: string, element: HTMLElement) => {
|
const addScreenElement = (key: string, element: HTMLElement) => {
|
||||||
if (!(key in screenElements)) {
|
if (!(key in this.screenElements)) {
|
||||||
screenElements[key] = [];
|
this.screenElements[key] = [];
|
||||||
}
|
}
|
||||||
screenElements[key].push(element);
|
this.screenElements[key].push(element);
|
||||||
};
|
};
|
||||||
const menuWrapper = this.createEl(containerEl, "div", { cls: "sls-setting-menu-wrapper" });
|
const menuWrapper = this.createEl(containerEl, "div", { cls: "sls-setting-menu-wrapper" });
|
||||||
|
|
||||||
const w = menuWrapper.createDiv("");
|
if (this.menuEl) {
|
||||||
w.addClass("sls-setting-menu");
|
this.menuEl.remove();
|
||||||
const menuTabs = w.querySelectorAll(".sls-setting-label");
|
}
|
||||||
|
this.menuEl = menuWrapper.createDiv("");
|
||||||
|
this.menuEl.addClass("sls-setting-menu");
|
||||||
|
const menuTabs = this.menuEl.querySelectorAll(".sls-setting-label");
|
||||||
const selectPane = (event: Event) => {
|
const selectPane = (event: Event) => {
|
||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
if (target.tagName == "INPUT") {
|
if (target.tagName == "INPUT") {
|
||||||
const value = target.getAttribute("value");
|
const value = target.getAttribute("value");
|
||||||
if (value && this.selectedScreen != value) {
|
if (value && this.selectedScreen != value) {
|
||||||
changeDisplay(value);
|
changeDisplay(value);
|
||||||
// target.parentElement?.parentElement?.querySelector(".sls-setting-label.selected")?.removeClass("selected");
|
|
||||||
// target.parentElement?.addClass("selected");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,17 +617,19 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
|||||||
}
|
}
|
||||||
setLevelClass(el, level)
|
setLevelClass(el, level)
|
||||||
el.createEl("h3", { text: title, cls: "sls-setting-pane-title" });
|
el.createEl("h3", { text: title, cls: "sls-setting-pane-title" });
|
||||||
w.createEl("label", { cls: `sls-setting-label c-${order} ${wizardHidden ? "wizardHidden" : ""}` }, el => {
|
if (this.menuEl) {
|
||||||
setLevelClass(el, level)
|
this.menuEl.createEl("label", { cls: `sls-setting-label c-${order} ${wizardHidden ? "wizardHidden" : ""}` }, el => {
|
||||||
const inputEl = el.createEl("input", {
|
setLevelClass(el, level)
|
||||||
type: "radio", name: "disp", value: `${order}`, cls: "sls-setting-tab"
|
const inputEl = el.createEl("input", {
|
||||||
} as DomElementInfo);
|
type: "radio", name: "disp", value: `${order}`, cls: "sls-setting-tab"
|
||||||
el.createEl("div", {
|
} as DomElementInfo);
|
||||||
cls: "sls-setting-menu-btn", text: icon, title: title
|
el.createEl("div", {
|
||||||
});
|
cls: "sls-setting-menu-btn", text: icon, title: title
|
||||||
inputEl.addEventListener("change", selectPane);
|
});
|
||||||
inputEl.addEventListener("click", selectPane);
|
inputEl.addEventListener("change", selectPane);
|
||||||
})
|
inputEl.addEventListener("click", selectPane);
|
||||||
|
})
|
||||||
|
}
|
||||||
addScreenElement(`${order}`, el);
|
addScreenElement(`${order}`, el);
|
||||||
const p = Promise.resolve(el)
|
const p = Promise.resolve(el)
|
||||||
// fireAndForget
|
// fireAndForget
|
||||||
@@ -615,31 +659,13 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeDisplay = (screen: string) => {
|
|
||||||
for (const k in screenElements) {
|
|
||||||
if (k == screen) {
|
|
||||||
screenElements[k].forEach((element) => element.removeClass("setting-collapsed"));
|
|
||||||
} else {
|
|
||||||
screenElements[k].forEach((element) => element.addClass("setting-collapsed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.querySelectorAll(`.sls-setting-label`).forEach((element) => {
|
|
||||||
if (element.hasClass(`c-${screen}`)) {
|
|
||||||
element.addClass("selected");
|
|
||||||
(element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = true;
|
|
||||||
} else {
|
|
||||||
element.removeClass("selected");
|
|
||||||
(element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.selectedScreen = screen;
|
|
||||||
};
|
|
||||||
menuTabs.forEach((element) => {
|
menuTabs.forEach((element) => {
|
||||||
const e = element.querySelector(".sls-setting-tab");
|
const e = element.querySelector(".sls-setting-tab");
|
||||||
if (!e) return;
|
if (!e) return;
|
||||||
e.addEventListener("change", (event) => {
|
e.addEventListener("change", (event) => {
|
||||||
menuTabs.forEach((element) => element.removeClass("selected"));
|
menuTabs.forEach((element) => element.removeClass("selected"));
|
||||||
changeDisplay((event.currentTarget as HTMLInputElement).value);
|
this.changeDisplay((event.currentTarget as HTMLInputElement).value);
|
||||||
element.addClass("selected");
|
element.addClass("selected");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -772,18 +798,7 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
|||||||
.setName("Minimal setup")
|
.setName("Minimal setup")
|
||||||
.addButton((text) => {
|
.addButton((text) => {
|
||||||
text.setButtonText("Start").onClick(async () => {
|
text.setButtonText("Start").onClick(async () => {
|
||||||
this.editingSettings.liveSync = false;
|
await this.enableMinimalSetup();
|
||||||
this.editingSettings.periodicReplication = false;
|
|
||||||
this.editingSettings.syncOnSave = false;
|
|
||||||
this.editingSettings.syncOnEditorSave = false;
|
|
||||||
this.editingSettings.syncOnStart = false;
|
|
||||||
this.editingSettings.syncOnFileOpen = false;
|
|
||||||
this.editingSettings.syncAfterMerge = false;
|
|
||||||
this.plugin.replicator.closeReplication();
|
|
||||||
await this.saveAllDirtySettings();
|
|
||||||
containerEl.addClass("isWizard");
|
|
||||||
this.inWizard = true;
|
|
||||||
changeDisplay("0")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
@@ -816,6 +831,8 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
|||||||
text.setButtonText("Discard").onClick(async () => {
|
text.setButtonText("Discard").onClick(async () => {
|
||||||
if (await this.plugin.confirm.askYesNoDialog("Do you really want to discard existing settings and databases?", { defaultOption: "No" }) == "yes") {
|
if (await this.plugin.confirm.askYesNoDialog("Do you really want to discard existing settings and databases?", { defaultOption: "No" }) == "yes") {
|
||||||
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
||||||
|
await this.saveAllDirtySettings();
|
||||||
|
this.plugin.settings = { ...DEFAULT_SETTINGS };
|
||||||
await this.plugin.$$saveSettingData();
|
await this.plugin.$$saveSettingData();
|
||||||
await this.plugin.$$resetLocalDatabase();
|
await this.plugin.$$resetLocalDatabase();
|
||||||
// await this.plugin.initializeDatabase();
|
// await this.plugin.initializeDatabase();
|
||||||
@@ -918,14 +935,193 @@ Store only the settings. **Caution: This may lead to data corruption**; database
|
|||||||
new Setting(paneEl).autoWireToggle("showStatusOnStatusbar");
|
new Setting(paneEl).autoWireToggle("showStatusOnStatusbar");
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, "Logging").then((paneEl) => {
|
void addPanel(paneEl, "Logging").then((paneEl) => {
|
||||||
|
paneEl.addClass("wizardHidden");
|
||||||
|
|
||||||
new Setting(paneEl).autoWireToggle("lessInformationInLog");
|
new Setting(paneEl).autoWireToggle("lessInformationInLog");
|
||||||
|
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
.autoWireToggle("showVerboseLog", { onUpdate: visibleOnly(() => this.isConfiguredAs("lessInformationInLog", false)) });
|
.autoWireToggle("showVerboseLog", { onUpdate: visibleOnly(() => this.isConfiguredAs("lessInformationInLog", false)) });
|
||||||
});
|
});
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setClass("wizardOnly")
|
||||||
|
.addButton((button) => button
|
||||||
|
.setButtonText("Next")
|
||||||
|
.setCta()
|
||||||
|
.onClick(() => {
|
||||||
|
this.changeDisplay("0");
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
|
let checkResultDiv: HTMLDivElement;
|
||||||
|
const checkConfig = async (checkResultDiv: HTMLDivElement | undefined) => {
|
||||||
|
Logger(`Checking database configuration`, LOG_LEVEL_INFO);
|
||||||
|
let isSuccessful = true;
|
||||||
|
const emptyDiv = createDiv();
|
||||||
|
emptyDiv.innerHTML = "<span></span>";
|
||||||
|
checkResultDiv?.replaceChildren(...[emptyDiv]);
|
||||||
|
const addResult = (msg: string, classes?: string[]) => {
|
||||||
|
const tmpDiv = createDiv();
|
||||||
|
tmpDiv.addClass("ob-btn-config-fix");
|
||||||
|
if (classes) {
|
||||||
|
tmpDiv.addClasses(classes);
|
||||||
|
}
|
||||||
|
tmpDiv.innerHTML = `${msg}`;
|
||||||
|
checkResultDiv?.appendChild(tmpDiv);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
|
Logger("This feature cannot be used with IBM Cloudant.", LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const r = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, window.origin);
|
||||||
|
const responseConfig = r.json;
|
||||||
|
|
||||||
|
const addConfigFixButton = (title: string, key: string, value: string) => {
|
||||||
|
if (!checkResultDiv) return;
|
||||||
|
const tmpDiv = createDiv();
|
||||||
|
tmpDiv.addClass("ob-btn-config-fix");
|
||||||
|
tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`;
|
||||||
|
const x = checkResultDiv.appendChild(tmpDiv);
|
||||||
|
x.querySelector("button")?.addEventListener("click", () => {
|
||||||
|
fireAndForget(async () => {
|
||||||
|
Logger(`CouchDB Configuration: ${title} -> Set ${key} to ${value}`)
|
||||||
|
const res = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, undefined, key, value);
|
||||||
|
if (res.status == 200) {
|
||||||
|
Logger(`CouchDB Configuration: ${title} successfully updated`, LOG_LEVEL_NOTICE);
|
||||||
|
checkResultDiv.removeChild(x);
|
||||||
|
await checkConfig(checkResultDiv);
|
||||||
|
} else {
|
||||||
|
Logger(`CouchDB Configuration: ${title} failed`, LOG_LEVEL_NOTICE);
|
||||||
|
Logger(res.text, LOG_LEVEL_VERBOSE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
addResult("---Notice---", ["ob-btn-config-head"]);
|
||||||
|
addResult("If the server configuration is not persistent (e.g., running on docker), the values set from here will also be volatile. Once you are able to connect, please reflect the settings in the server's local.ini.", ["ob-btn-config-info"]);
|
||||||
|
|
||||||
|
addResult("--Config check--", ["ob-btn-config-head"]);
|
||||||
|
|
||||||
|
// Admin check
|
||||||
|
// for database creation and deletion
|
||||||
|
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||||
|
addResult(`⚠ You do not have administrative privileges.`);
|
||||||
|
} else {
|
||||||
|
addResult("✔ You have administrative privileges.");
|
||||||
|
}
|
||||||
|
// HTTP user-authorization check
|
||||||
|
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ chttpd.require_valid_user is wrong.");
|
||||||
|
addConfigFixButton("Set chttpd.require_valid_user = true", "chttpd/require_valid_user", "true");
|
||||||
|
} else {
|
||||||
|
addResult("✔ chttpd.require_valid_user is ok.");
|
||||||
|
}
|
||||||
|
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ chttpd_auth.require_valid_user is wrong.");
|
||||||
|
addConfigFixButton("Set chttpd_auth.require_valid_user = true", "chttpd_auth/require_valid_user", "true");
|
||||||
|
} else {
|
||||||
|
addResult("✔ chttpd_auth.require_valid_user is ok.");
|
||||||
|
}
|
||||||
|
// HTTPD check
|
||||||
|
// Check Authentication header
|
||||||
|
if (!responseConfig?.httpd["WWW-Authenticate"]) {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ httpd.WWW-Authenticate is missing");
|
||||||
|
addConfigFixButton("Set httpd.WWW-Authenticate", "httpd/WWW-Authenticate", 'Basic realm="couchdb"');
|
||||||
|
} else {
|
||||||
|
addResult("✔ httpd.WWW-Authenticate is ok.");
|
||||||
|
}
|
||||||
|
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ httpd.enable_cors is wrong");
|
||||||
|
addConfigFixButton("Set httpd.enable_cors", "httpd/enable_cors", "true");
|
||||||
|
} else {
|
||||||
|
addResult("✔ httpd.enable_cors is ok.");
|
||||||
|
}
|
||||||
|
// If the server is not cloudant, configure request size
|
||||||
|
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
|
// REQUEST SIZE
|
||||||
|
if (Number(responseConfig?.chttpd?.max_http_request_size ?? 0) < 4294967296) {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ chttpd.max_http_request_size is low)");
|
||||||
|
addConfigFixButton("Set chttpd.max_http_request_size", "chttpd/max_http_request_size", "4294967296");
|
||||||
|
} else {
|
||||||
|
addResult("✔ chttpd.max_http_request_size is ok.");
|
||||||
|
}
|
||||||
|
if (Number(responseConfig?.couchdb?.max_document_size ?? 0) < 50000000) {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ couchdb.max_document_size is low)");
|
||||||
|
addConfigFixButton("Set couchdb.max_document_size", "couchdb/max_document_size", "50000000");
|
||||||
|
} else {
|
||||||
|
addResult("✔ couchdb.max_document_size is ok.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// CORS check
|
||||||
|
// checking connectivity for mobile
|
||||||
|
if (responseConfig?.cors?.credentials != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult("❗ cors.credentials is wrong");
|
||||||
|
addConfigFixButton("Set cors.credentials", "cors/credentials", "true");
|
||||||
|
} else {
|
||||||
|
addResult("✔ cors.credentials is ok.");
|
||||||
|
}
|
||||||
|
const ConfiguredOrigins = ((responseConfig?.cors?.origins ?? "") + "").split(",");
|
||||||
|
if (responseConfig?.cors?.origins == "*" || (ConfiguredOrigins.indexOf("app://obsidian.md") !== -1 && ConfiguredOrigins.indexOf("capacitor://localhost") !== -1 && ConfiguredOrigins.indexOf("http://localhost") !== -1)) {
|
||||||
|
addResult("✔ cors.origins is ok.");
|
||||||
|
} else {
|
||||||
|
addResult("❗ cors.origins is wrong");
|
||||||
|
addConfigFixButton("Set cors.origins", "cors/origins", "app://obsidian.md,capacitor://localhost,http://localhost");
|
||||||
|
isSuccessful = false;
|
||||||
|
}
|
||||||
|
addResult("--Connection check--", ["ob-btn-config-head"]);
|
||||||
|
addResult(`Current origin:${window.location.origin}`);
|
||||||
|
|
||||||
|
// Request header check
|
||||||
|
const origins = [
|
||||||
|
"app://obsidian.md",
|
||||||
|
"capacitor://localhost",
|
||||||
|
"http://localhost"];
|
||||||
|
for (const org of origins) {
|
||||||
|
const rr = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, org);
|
||||||
|
const responseHeaders = Object.fromEntries(Object.entries(rr.headers)
|
||||||
|
.map((e) => {
|
||||||
|
e[0] = `${e[0]}`.toLowerCase();
|
||||||
|
return e;
|
||||||
|
}));
|
||||||
|
addResult(`Origin check:${org}`);
|
||||||
|
if (responseHeaders["access-control-allow-credentials"] != "true") {
|
||||||
|
addResult("❗ CORS is not allowing credentials");
|
||||||
|
isSuccessful = false;
|
||||||
|
} else {
|
||||||
|
addResult("✔ CORS credentials OK");
|
||||||
|
}
|
||||||
|
if (responseHeaders["access-control-allow-origin"] != org) {
|
||||||
|
addResult(`⚠ CORS Origin is unmatched:${origin}->${responseHeaders["access-control-allow-origin"]}`);
|
||||||
|
} else {
|
||||||
|
addResult("✔ CORS origin OK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"]);
|
||||||
|
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
||||||
|
} catch (ex: any) {
|
||||||
|
if (ex?.status == 401) {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult(`❗ Access forbidden.`);
|
||||||
|
addResult(`We could not continue the test.`);
|
||||||
|
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
||||||
|
} else {
|
||||||
|
Logger(`Checking configuration failed`, LOG_LEVEL_NOTICE);
|
||||||
|
Logger(ex);
|
||||||
|
isSuccessful = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isSuccessful;
|
||||||
|
};
|
||||||
|
|
||||||
void addPane(containerEl, "Remote Configuration", "🛰️", 0, false).then((paneEl) => {
|
void addPane(containerEl, "Remote Configuration", "🛰️", 0, false).then((paneEl) => {
|
||||||
void addPanel(paneEl, "Remote Server").then((paneEl) => {
|
void addPanel(paneEl, "Remote Server").then((paneEl) => {
|
||||||
// const containerRemoteDatabaseEl = containerEl.createDiv();
|
// const containerRemoteDatabaseEl = containerEl.createDiv();
|
||||||
@@ -1039,164 +1235,9 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
.setButtonText("Check")
|
.setButtonText("Check")
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
const checkConfig = async () => {
|
await checkConfig(checkResultDiv);
|
||||||
Logger(`Checking database configuration`, LOG_LEVEL_INFO);
|
|
||||||
|
|
||||||
const emptyDiv = createDiv();
|
|
||||||
emptyDiv.innerHTML = "<span></span>";
|
|
||||||
checkResultDiv.replaceChildren(...[emptyDiv]);
|
|
||||||
const addResult = (msg: string, classes?: string[]) => {
|
|
||||||
const tmpDiv = createDiv();
|
|
||||||
tmpDiv.addClass("ob-btn-config-fix");
|
|
||||||
if (classes) {
|
|
||||||
tmpDiv.addClasses(classes);
|
|
||||||
}
|
|
||||||
tmpDiv.innerHTML = `${msg}`;
|
|
||||||
checkResultDiv.appendChild(tmpDiv);
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (isCloudantURI(this.editingSettings.couchDB_URI)) {
|
|
||||||
Logger("This feature cannot be used with IBM Cloudant.", LOG_LEVEL_NOTICE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const r = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, window.origin);
|
|
||||||
const responseConfig = r.json;
|
|
||||||
|
|
||||||
const addConfigFixButton = (title: string, key: string, value: string) => {
|
|
||||||
const tmpDiv = createDiv();
|
|
||||||
tmpDiv.addClass("ob-btn-config-fix");
|
|
||||||
tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`;
|
|
||||||
const x = checkResultDiv.appendChild(tmpDiv);
|
|
||||||
x.querySelector("button")?.addEventListener("click", () => {
|
|
||||||
fireAndForget(async () => {
|
|
||||||
Logger(`CouchDB Configuration: ${title} -> Set ${key} to ${value}`)
|
|
||||||
const res = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, undefined, key, value);
|
|
||||||
if (res.status == 200) {
|
|
||||||
Logger(`CouchDB Configuration: ${title} successfully updated`, LOG_LEVEL_NOTICE);
|
|
||||||
checkResultDiv.removeChild(x);
|
|
||||||
await checkConfig();
|
|
||||||
} else {
|
|
||||||
Logger(`CouchDB Configuration: ${title} failed`, LOG_LEVEL_NOTICE);
|
|
||||||
Logger(res.text, LOG_LEVEL_VERBOSE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
addResult("---Notice---", ["ob-btn-config-head"]);
|
|
||||||
addResult("If the server configuration is not persistent (e.g., running on docker), the values set from here will also be volatile. Once you are able to connect, please reflect the settings in the server's local.ini.", ["ob-btn-config-info"]);
|
|
||||||
|
|
||||||
addResult("--Config check--", ["ob-btn-config-head"]);
|
|
||||||
|
|
||||||
// Admin check
|
|
||||||
// for database creation and deletion
|
|
||||||
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
|
||||||
addResult(`⚠ You do not have administrative privileges.`);
|
|
||||||
} else {
|
|
||||||
addResult("✔ You have administrative privileges.");
|
|
||||||
}
|
|
||||||
// HTTP user-authorization check
|
|
||||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
|
||||||
addResult("❗ chttpd.require_valid_user is wrong.");
|
|
||||||
addConfigFixButton("Set chttpd.require_valid_user = true", "chttpd/require_valid_user", "true");
|
|
||||||
} else {
|
|
||||||
addResult("✔ chttpd.require_valid_user is ok.");
|
|
||||||
}
|
|
||||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
|
||||||
addResult("❗ chttpd_auth.require_valid_user is wrong.");
|
|
||||||
addConfigFixButton("Set chttpd_auth.require_valid_user = true", "chttpd_auth/require_valid_user", "true");
|
|
||||||
} else {
|
|
||||||
addResult("✔ chttpd_auth.require_valid_user is ok.");
|
|
||||||
}
|
|
||||||
// HTTPD check
|
|
||||||
// Check Authentication header
|
|
||||||
if (!responseConfig?.httpd["WWW-Authenticate"]) {
|
|
||||||
addResult("❗ httpd.WWW-Authenticate is missing");
|
|
||||||
addConfigFixButton("Set httpd.WWW-Authenticate", "httpd/WWW-Authenticate", 'Basic realm="couchdb"');
|
|
||||||
} else {
|
|
||||||
addResult("✔ httpd.WWW-Authenticate is ok.");
|
|
||||||
}
|
|
||||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
|
||||||
addResult("❗ httpd.enable_cors is wrong");
|
|
||||||
addConfigFixButton("Set httpd.enable_cors", "httpd/enable_cors", "true");
|
|
||||||
} else {
|
|
||||||
addResult("✔ httpd.enable_cors is ok.");
|
|
||||||
}
|
|
||||||
// If the server is not cloudant, configure request size
|
|
||||||
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
|
||||||
// REQUEST SIZE
|
|
||||||
if (Number(responseConfig?.chttpd?.max_http_request_size ?? 0) < 4294967296) {
|
|
||||||
addResult("❗ chttpd.max_http_request_size is low)");
|
|
||||||
addConfigFixButton("Set chttpd.max_http_request_size", "chttpd/max_http_request_size", "4294967296");
|
|
||||||
} else {
|
|
||||||
addResult("✔ chttpd.max_http_request_size is ok.");
|
|
||||||
}
|
|
||||||
if (Number(responseConfig?.couchdb?.max_document_size ?? 0) < 50000000) {
|
|
||||||
addResult("❗ couchdb.max_document_size is low)");
|
|
||||||
addConfigFixButton("Set couchdb.max_document_size", "couchdb/max_document_size", "50000000");
|
|
||||||
} else {
|
|
||||||
addResult("✔ couchdb.max_document_size is ok.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// CORS check
|
|
||||||
// checking connectivity for mobile
|
|
||||||
if (responseConfig?.cors?.credentials != "true") {
|
|
||||||
addResult("❗ cors.credentials is wrong");
|
|
||||||
addConfigFixButton("Set cors.credentials", "cors/credentials", "true");
|
|
||||||
} else {
|
|
||||||
addResult("✔ cors.credentials is ok.");
|
|
||||||
}
|
|
||||||
const ConfiguredOrigins = ((responseConfig?.cors?.origins ?? "") + "").split(",");
|
|
||||||
if (responseConfig?.cors?.origins == "*" || (ConfiguredOrigins.indexOf("app://obsidian.md") !== -1 && ConfiguredOrigins.indexOf("capacitor://localhost") !== -1 && ConfiguredOrigins.indexOf("http://localhost") !== -1)) {
|
|
||||||
addResult("✔ cors.origins is ok.");
|
|
||||||
} else {
|
|
||||||
addResult("❗ cors.origins is wrong");
|
|
||||||
addConfigFixButton("Set cors.origins", "cors/origins", "app://obsidian.md,capacitor://localhost,http://localhost");
|
|
||||||
}
|
|
||||||
addResult("--Connection check--", ["ob-btn-config-head"]);
|
|
||||||
addResult(`Current origin:${window.location.origin}`);
|
|
||||||
|
|
||||||
// Request header check
|
|
||||||
const origins = [
|
|
||||||
"app://obsidian.md",
|
|
||||||
"capacitor://localhost",
|
|
||||||
"http://localhost"];
|
|
||||||
for (const org of origins) {
|
|
||||||
const rr = await requestToCouchDB(this.editingSettings.couchDB_URI, this.editingSettings.couchDB_USER, this.editingSettings.couchDB_PASSWORD, org);
|
|
||||||
const responseHeaders = Object.fromEntries(Object.entries(rr.headers)
|
|
||||||
.map((e) => {
|
|
||||||
e[0] = `${e[0]}`.toLowerCase();
|
|
||||||
return e;
|
|
||||||
}));
|
|
||||||
addResult(`Origin check:${org}`);
|
|
||||||
if (responseHeaders["access-control-allow-credentials"] != "true") {
|
|
||||||
addResult("❗ CORS is not allowing credentials");
|
|
||||||
} else {
|
|
||||||
addResult("✔ CORS credentials OK");
|
|
||||||
}
|
|
||||||
if (responseHeaders["access-control-allow-origin"] != org) {
|
|
||||||
addResult(`❗ CORS Origin is unmatched:${origin}->${responseHeaders["access-control-allow-origin"]}`);
|
|
||||||
} else {
|
|
||||||
addResult("✔ CORS origin OK");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"]);
|
|
||||||
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
|
||||||
} catch (ex: any) {
|
|
||||||
if (ex?.status == 401) {
|
|
||||||
addResult(`❗ Access forbidden.`);
|
|
||||||
addResult(`We could not continue the test.`);
|
|
||||||
Logger(`Checking configuration done`, LOG_LEVEL_INFO);
|
|
||||||
} else {
|
|
||||||
Logger(`Checking configuration failed`, LOG_LEVEL_NOTICE);
|
|
||||||
Logger(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await checkConfig();
|
|
||||||
}));
|
}));
|
||||||
const checkResultDiv = this.createEl(paneEl, "div", {
|
checkResultDiv = this.createEl(paneEl, "div", {
|
||||||
text: "",
|
text: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1245,10 +1286,26 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
.setButtonText("Next")
|
.setButtonText("Next")
|
||||||
.setCta()
|
.setCta()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(() => {
|
.onClick(async () => {
|
||||||
|
if (!await checkConfig(checkResultDiv)) {
|
||||||
|
if (await this.plugin.confirm.askYesNoDialog("The configuration check has failed. Do you want to continue anyway?", { defaultOption: "No", title: "Remote Configuration Check Failed" }) == "no") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const isEncryptionFullyEnabled = !this.editingSettings.encrypt || !this.editingSettings.usePathObfuscation;
|
||||||
|
if (isEncryptionFullyEnabled) {
|
||||||
|
if (await this.plugin.confirm.askYesNoDialog("Enabling End-to-End Encryption and Path Obfuscation is strongly recommended. Do you surely want to continue without encryption?", { defaultOption: "No", title: "Encryption is not enabled" }) == "no") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!this.editingSettings.encrypt) {
|
if (!this.editingSettings.encrypt) {
|
||||||
this.editingSettings.passphrase = "";
|
this.editingSettings.passphrase = "";
|
||||||
}
|
}
|
||||||
|
if (!await isPassphraseValid()) {
|
||||||
|
if (await this.plugin.confirm.askYesNoDialog("End-to-End encryption seems to have trouble. Do you surely want to continue with the current settings?", { defaultOption: "No", title: "Encryption has some trouble" }) == "no") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isCloudantURI(this.editingSettings.couchDB_URI)) {
|
if (isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
this.editingSettings = { ...this.editingSettings, ...PREFERRED_SETTING_CLOUDANT };
|
this.editingSettings = { ...this.editingSettings, ...PREFERRED_SETTING_CLOUDANT };
|
||||||
} else if (this.editingSettings.remoteType == REMOTE_MINIO) {
|
} else if (this.editingSettings.remoteType == REMOTE_MINIO) {
|
||||||
@@ -1279,7 +1336,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.createEl(paneEl, "div", {
|
this.createEl(paneEl, "div", {
|
||||||
text: `Please select any preset to complete the wizard.`,
|
text: `Please select and apply any preset item to complete the wizard.`,
|
||||||
cls: "wizardOnly"
|
cls: "wizardOnly"
|
||||||
}).addClasses(["op-warn-info"]);
|
}).addClasses(["op-warn-info"]);
|
||||||
|
|
||||||
@@ -1362,9 +1419,12 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
await this.plugin.realizeSettingSyncMode();
|
await this.plugin.realizeSettingSyncMode();
|
||||||
await rebuildDB("localOnly");
|
await rebuildDB("localOnly");
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
Logger("All done! Please set up subsequent devices with 'Copy current settings as a new setup URI' and 'Use the copied setup URI'.", LOG_LEVEL_NOTICE);
|
if (await this.plugin.confirm.askYesNoDialog(
|
||||||
// await this.plugin.addOnSetup.command_copySetupURI();
|
"All done!, do you want to generate a setup URI to set up other devices?",
|
||||||
eventHub.emitEvent(EVENT_REQUEST_COPY_SETUP_URI);
|
{ defaultOption: "Yes", title: "Congratulations!" }
|
||||||
|
) == "yes") {
|
||||||
|
eventHub.emitEvent(EVENT_REQUEST_COPY_SETUP_URI);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isNeedRebuildLocal() || isNeedRebuildRemote()) {
|
if (isNeedRebuildLocal() || isNeedRebuildRemote()) {
|
||||||
await confirmRebuild();
|
await confirmRebuild();
|
||||||
@@ -1669,6 +1729,17 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
const enableOnlyOnPluginSyncIsNotEnabled = enableOnly(() => this.isConfiguredAs("usePluginSync", false));
|
const enableOnlyOnPluginSyncIsNotEnabled = enableOnly(() => this.isConfiguredAs("usePluginSync", false));
|
||||||
const visibleOnlyOnPluginSyncEnabled = visibleOnly(() => this.isConfiguredAs("usePluginSync", true));
|
const visibleOnlyOnPluginSyncEnabled = visibleOnly(() => this.isConfiguredAs("usePluginSync", true));
|
||||||
|
|
||||||
|
this.createEl(paneEl, "div", {
|
||||||
|
text: "Please set device name to identify this device. This name should be unique among your devices. While not configured, we cannot enable this feature.",
|
||||||
|
cls: "op-warn"
|
||||||
|
}, c => {
|
||||||
|
}, visibleOnly(() => this.isConfiguredAs("deviceAndVaultName", "")));
|
||||||
|
this.createEl(paneEl, "div", {
|
||||||
|
text: "We cannot change the device name while this feature is enabled. Please disable this feature to change the device name.",
|
||||||
|
cls: "op-warn-info"
|
||||||
|
}, c => {
|
||||||
|
}, visibleOnly(() => this.isConfiguredAs("usePluginSync", true)));
|
||||||
|
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
.autoWireText("deviceAndVaultName", {
|
.autoWireText("deviceAndVaultName", {
|
||||||
placeHolder: "desktop", onUpdate: enableOnlyOnPluginSyncIsNotEnabled
|
placeHolder: "desktop", onUpdate: enableOnlyOnPluginSyncIsNotEnabled
|
||||||
@@ -1714,7 +1785,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
void addPane(containerEl, "Hatch", "🧰", 50, false).then((paneEl) => {
|
void addPane(containerEl, "Hatch", "🧰", 50, true).then((paneEl) => {
|
||||||
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
||||||
// hatchWarn.addClass("op-warn-info");
|
// hatchWarn.addClass("op-warn-info");
|
||||||
void addPanel(paneEl, "Reporting Issue").then((paneEl) => {
|
void addPanel(paneEl, "Reporting Issue").then((paneEl) => {
|
||||||
@@ -1798,11 +1869,12 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
|
|
||||||
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
||||||
|
|
||||||
const addResult = (path: string, file: TFile | false, fileOnDB: LoadedEntry | false) => {
|
const addResult = async (path: string, file: FilePathWithPrefix | false, fileOnDB: LoadedEntry | false) => {
|
||||||
|
const storageFileStat = file ? await this.plugin.storageAccess.statHidden(file) : null;
|
||||||
resultArea.appendChild(this.createEl(resultArea, "div", {}, el => {
|
resultArea.appendChild(this.createEl(resultArea, "div", {}, el => {
|
||||||
el.appendChild(this.createEl(el, "h6", { text: path }));
|
el.appendChild(this.createEl(el, "h6", { text: path }));
|
||||||
el.appendChild(this.createEl(el, "div", {}, infoGroupEl => {
|
el.appendChild(this.createEl(el, "div", {}, infoGroupEl => {
|
||||||
infoGroupEl.appendChild(this.createEl(infoGroupEl, "div", { text: `Storage : Modified: ${!file ? `Missing:` : `${new Date(file.stat.mtime).toLocaleString()}, Size:${file.stat.size}`}` }))
|
infoGroupEl.appendChild(this.createEl(infoGroupEl, "div", { text: `Storage : Modified: ${!storageFileStat ? `Missing:` : `${new Date(storageFileStat.mtime).toLocaleString()}, Size:${storageFileStat.size}`}` }))
|
||||||
infoGroupEl.appendChild(this.createEl(infoGroupEl, "div", { text: `Database: Modified: ${!fileOnDB ? `Missing:` : `${new Date(fileOnDB.mtime).toLocaleString()}, Size:${fileOnDB.size}`}` }))
|
infoGroupEl.appendChild(this.createEl(infoGroupEl, "div", { text: `Database: Modified: ${!fileOnDB ? `Missing:` : `${new Date(fileOnDB.mtime).toLocaleString()}, Size:${fileOnDB.size}`}` }))
|
||||||
}));
|
}));
|
||||||
if (fileOnDB && file) {
|
if (fileOnDB && file) {
|
||||||
@@ -1817,20 +1889,24 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
if (file) {
|
if (file) {
|
||||||
el.appendChild(this.createEl(el, "button", { text: "Storage -> Database" }, buttonEl => {
|
el.appendChild(this.createEl(el, "button", { text: "Storage -> Database" }, buttonEl => {
|
||||||
buttonEl.onClickEvent(async () => {
|
buttonEl.onClickEvent(async () => {
|
||||||
// const file = this.plugin.storageAccess.getFileStub(path);
|
if (file.startsWith(".")) {
|
||||||
// if (!file) {
|
const addOn = this.plugin.getAddOn<HiddenFileSync>(HiddenFileSync.name);
|
||||||
// Logger(`File not found: ${path}`, LOG_LEVEL_NOTICE);
|
if (addOn) {
|
||||||
// return;
|
const file = (await addOn.scanInternalFiles()).find(e => e.path == path);
|
||||||
// }
|
if (!file) {
|
||||||
// const content = await this.plugin.storageAccess.readStubContent(file);
|
Logger(`Failed to find the file in the internal files: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
// if (!content) {
|
return;
|
||||||
// Logger(`Content cannot be read: ${path}`, LOG_LEVEL_NOTICE);
|
}
|
||||||
// return;
|
if (!await addOn.storeInternalFileToDatabase(file, true)) {
|
||||||
// }
|
Logger(`Failed to store the file to the database (Hidden file): ${file}`, LOG_LEVEL_NOTICE);
|
||||||
// this.plugin.databaseFileAccess.store(content, true);
|
return;
|
||||||
if (!await this.plugin.fileHandler.storeFileToDB(file.path as FilePath, true)) {
|
}
|
||||||
Logger(`Failed to store the file to the database: ${file.path}`, LOG_LEVEL_NOTICE);
|
}
|
||||||
return;
|
} else {
|
||||||
|
if (!await this.plugin.fileHandler.storeFileToDB(file as FilePath, true)) {
|
||||||
|
Logger(`Failed to store the file to the database: ${file}`, LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
el.remove();
|
el.remove();
|
||||||
})
|
})
|
||||||
@@ -1839,10 +1915,20 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
if (fileOnDB) {
|
if (fileOnDB) {
|
||||||
el.appendChild(this.createEl(el, "button", { text: "Database -> Storage" }, buttonEl => {
|
el.appendChild(this.createEl(el, "button", { text: "Database -> Storage" }, buttonEl => {
|
||||||
buttonEl.onClickEvent(async () => {
|
buttonEl.onClickEvent(async () => {
|
||||||
// this.plugin.pullFile(this.plugin.getPath(fileOnDB), undefined, true, undefined, false);
|
if (fileOnDB.path.startsWith(ICHeader)) {
|
||||||
if (!await this.plugin.fileHandler.dbToStorage(fileOnDB as MetaEntry, null, true)) {
|
const addOn = this.plugin.getAddOn<HiddenFileSync>(HiddenFileSync.name);
|
||||||
Logger(`Failed to store the file to the storage: ${fileOnDB.path}`, LOG_LEVEL_NOTICE);
|
if (addOn) {
|
||||||
return;
|
if (!await addOn.extractInternalFileFromDatabase(path as FilePath, true)) {
|
||||||
|
Logger(`Failed to store the file to the database (Hidden file): ${file}`, LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (!await this.plugin.fileHandler.dbToStorage(fileOnDB as MetaEntry, null, true)) {
|
||||||
|
Logger(`Failed to store the file to the storage: ${fileOnDB.path}`, LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
el.remove();
|
el.remove();
|
||||||
})
|
})
|
||||||
@@ -1852,14 +1938,14 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkBetweenStorageAndDatabase = async (file: TFile, fileOnDB: LoadedEntry) => {
|
const checkBetweenStorageAndDatabase = async (file: FilePathWithPrefix, fileOnDB: LoadedEntry) => {
|
||||||
const dataContent = readAsBlob(fileOnDB);
|
const dataContent = readAsBlob(fileOnDB);
|
||||||
const content = createBlob(await this.plugin.storageAccess.readFileAuto(file.path))
|
const content = createBlob(await this.plugin.storageAccess.readHiddenFileBinary(file))
|
||||||
if (await isDocContentSame(content, dataContent)) {
|
if (await isDocContentSame(content, dataContent)) {
|
||||||
Logger(`Compare: SAME: ${file.path}`)
|
Logger(`Compare: SAME: ${file}`)
|
||||||
} else {
|
} else {
|
||||||
Logger(`Compare: CONTENT IS NOT MATCHED! ${file.path}`, LOG_LEVEL_NOTICE);
|
Logger(`Compare: CONTENT IS NOT MATCHED! ${file}`, LOG_LEVEL_NOTICE);
|
||||||
addResult(file.path, file, fileOnDB)
|
void addResult(file, file, fileOnDB)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
@@ -1887,17 +1973,26 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.setCta()
|
.setCta()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
|
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
|
||||||
|
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns
|
||||||
|
.replace(/\n| /g, "")
|
||||||
|
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
||||||
this.plugin.localDatabase.hashCaches.clear();
|
this.plugin.localDatabase.hashCaches.clear();
|
||||||
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
|
Logger("Start verifying all files", LOG_LEVEL_NOTICE, "verify");
|
||||||
const files = this.app.vault.getFiles();
|
const files = await this.plugin.storageAccess.getFilesIncludeHidden("/", undefined, ignorePatterns)
|
||||||
const documents = [] as FilePathWithPrefix[];
|
const documents = [] as FilePath[];
|
||||||
|
|
||||||
const adn = this.plugin.localDatabase.findAllNormalDocs()
|
const adn = this.plugin.localDatabase.findAllDocs()
|
||||||
for await (const i of adn) documents.push(getPath(i));
|
for await (const i of adn) {
|
||||||
|
const path = getPath(i);
|
||||||
|
if (path.startsWith(ICXHeader)) continue;
|
||||||
|
if (path.startsWith(PSCHeader)) continue;
|
||||||
|
documents.push(stripAllPrefixes(path));
|
||||||
|
}
|
||||||
const allPaths = [
|
const allPaths = [
|
||||||
...new Set([
|
...new Set([
|
||||||
...documents,
|
...documents,
|
||||||
...files.map(e => e.path as FilePathWithPrefix)])];
|
...files])];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const incProc = () => {
|
const incProc = () => {
|
||||||
i++;
|
i++;
|
||||||
@@ -1909,27 +2004,29 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
if (shouldBeIgnored(path)) {
|
if (shouldBeIgnored(path)) {
|
||||||
return incProc();
|
return incProc();
|
||||||
}
|
}
|
||||||
const abstractFile = this.plugin.storageAccess.getFileStub(path);
|
const stat = await this.plugin.storageAccess.isExistsIncludeHidden(path) ? await this.plugin.storageAccess.statHidden(path) : false;
|
||||||
const fileOnStorage = abstractFile instanceof TFile ? abstractFile : false;
|
const fileOnStorage = stat != null ? stat : false;
|
||||||
if (!await this.plugin.$$isTargetFile(path)) return incProc();
|
if (!await this.plugin.$$isTargetFile(path)) return incProc();
|
||||||
const releaser = await semaphore.acquire(1)
|
const releaser = await semaphore.acquire(1)
|
||||||
if (fileOnStorage && this.plugin.$$isFileSizeExceeded(fileOnStorage.stat.size)) return incProc();
|
if (fileOnStorage && this.plugin.$$isFileSizeExceeded(fileOnStorage.size)) return incProc();
|
||||||
try {
|
try {
|
||||||
const fileOnDB = await this.plugin.localDatabase.getDBEntry(path);
|
const isHiddenFile = path.startsWith(".");
|
||||||
|
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
|
||||||
|
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
|
||||||
if (fileOnDB && this.plugin.$$isFileSizeExceeded(fileOnDB.size)) return incProc();
|
if (fileOnDB && this.plugin.$$isFileSizeExceeded(fileOnDB.size)) return incProc();
|
||||||
|
|
||||||
if (!fileOnDB && fileOnStorage) {
|
if (!fileOnDB && fileOnStorage) {
|
||||||
Logger(`Compare: Not found on the local database: ${path}`, LOG_LEVEL_NOTICE);
|
Logger(`Compare: Not found on the local database: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
addResult(path, fileOnStorage, false)
|
void addResult(path, path, false)
|
||||||
return incProc();
|
return incProc();
|
||||||
}
|
}
|
||||||
if (fileOnDB && !fileOnStorage) {
|
if (fileOnDB && !fileOnStorage) {
|
||||||
Logger(`Compare: Not found on the storage: ${path}`, LOG_LEVEL_NOTICE);
|
Logger(`Compare: Not found on the storage: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
addResult(path, false, fileOnDB)
|
void addResult(path, false, fileOnDB)
|
||||||
return incProc();
|
return incProc();
|
||||||
}
|
}
|
||||||
if (fileOnStorage && fileOnDB) {
|
if (fileOnStorage && fileOnDB) {
|
||||||
await checkBetweenStorageAndDatabase(fileOnStorage, fileOnDB)
|
await checkBetweenStorageAndDatabase(path, fileOnDB)
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Logger(`Error while processing ${path}`, LOG_LEVEL_NOTICE);
|
Logger(`Error while processing ${path}`, LOG_LEVEL_NOTICE);
|
||||||
@@ -2269,7 +2366,7 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
const isRemoteLocked = () => this.plugin?.replicator?.remoteLocked;
|
const isRemoteLocked = () => this.plugin?.replicator?.remoteLocked;
|
||||||
// if (this.plugin?.replicator?.remoteLockedAndDeviceNotAccepted) {
|
// if (this.plugin?.replicator?.remoteLockedAndDeviceNotAccepted) {
|
||||||
this.createEl(paneEl, "div", {
|
this.createEl(paneEl, "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 the local database, and press 'Mark this device as resolved'. ",
|
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 the local database, and press 'Mark this device as resolved'. This warning kept showing until confirming the device is resolved by the replication.",
|
||||||
cls: "op-warn"
|
cls: "op-warn"
|
||||||
}, c => {
|
}, c => {
|
||||||
this.createEl(c, "button", {
|
this.createEl(c, "button", {
|
||||||
@@ -2284,7 +2381,7 @@ ${stringifyYaml(pluginConfig)}`;
|
|||||||
})
|
})
|
||||||
}, visibleOnly(isRemoteLockedAndDeviceNotAccepted));
|
}, visibleOnly(isRemoteLockedAndDeviceNotAccepted));
|
||||||
this.createEl(paneEl, "div", {
|
this.createEl(paneEl, "div", {
|
||||||
text: "To prevent unwanted vault corruption, the remote database has been locked for synchronization. (This device is marked 'resolved') When all your devices are marked 'resolved', unlock the database.",
|
text: "To prevent unwanted vault corruption, the remote database has been locked for synchronization. (This device is marked 'resolved') When all your devices are marked 'resolved', unlock the database. This warning kept showing until confirming the device is resolved by the replication",
|
||||||
cls: "op-warn"
|
cls: "op-warn"
|
||||||
}, c => this.createEl(c, "button", {
|
}, c => this.createEl(c, "button", {
|
||||||
text: "I'm ready, unlock the database", cls: "mod-warning"
|
text: "I'm ready, unlock the database", cls: "mod-warning"
|
||||||
|
|||||||
75
updates.md
75
updates.md
@@ -23,6 +23,81 @@ Thank you, and I hope your troubles will be resolved!
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.24.0.dev-rc7
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Verifying files between the local database and storage is now working correctly.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- We can verify and resolve also the hidden files now.
|
||||||
|
|
||||||
|
## 0.24.0.dev-rc6
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- We can resolve the conflict of the JSON file correctly now.
|
||||||
|
- This would be the final Release Candidate.
|
||||||
|
|
||||||
|
## 0.24.0.dev-rc5
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- A note relating to device names has been added to Customisation Sync on the setting dialogue.
|
||||||
|
- Logs of Hidden File Sync and Customisation Sync have been prefixed with the respective feature names.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Hidden file sync is now working correctly.
|
||||||
|
- Customisation Sync is now working correctly together with hidden file sync
|
||||||
|
- No longer database suffix is stored in the setting sharing markdown.
|
||||||
|
|
||||||
|
## 0.24.0.dev-rc4
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- The welcome message is now more simple to encourage the use of the Setup-URI.
|
||||||
|
- And the secondary message is also simpler to guide users to Minimal Setup.
|
||||||
|
- But Setup-URI will be recommended again, due to its importance.
|
||||||
|
- These dialogues contain a link to the documentation which can be clicked.
|
||||||
|
- The minimal setup is more minimal now. And, the setup is more user-friendly.
|
||||||
|
- Now the Configuration of the remote database is checked more robust, but we can ignore the warning and proceed with the setup.
|
||||||
|
- Before we are asked about each feature, we are asked if we want to use optional features in the first place.
|
||||||
|
- This is to prevent the user from being overwhelmed by the features.
|
||||||
|
- And made it clear that it is not recommended for new users.
|
||||||
|
- Many messages have been improved for better understanding.
|
||||||
|
- Ridiculous messages have been (carefully) refined.
|
||||||
|
- Dialogues are more informative and friendly.
|
||||||
|
- A lot of messages have been mostly rewritten, leveraging Markdown.
|
||||||
|
- Especially auto-closing dialogues are now explicitly labelled: `To stop the countdown, tap anywhere on the dialogue`.
|
||||||
|
- Now if the is plugin configured to ignore some events, we will get a chance to fix it, in addition to the warning.
|
||||||
|
- And why that has happened is also explained in the dialogue.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- While restarting the plug-in, the shown dialogues will be automatically closed to avoid unexpected behaviour.
|
||||||
|
- Replicated documents that the local device has configured to ignore are now correctly ignored.
|
||||||
|
- The chunks of the document on the local device during the first transfer will be created correctly.
|
||||||
|
- And why we should create them is now explained in the dialogue.
|
||||||
|
- If optional features have been enabled in the wizard, `Enable advanced features` will be toggled correctly.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Some default settings have been changed for easier new user experience.
|
||||||
|
- Preventing the meaningless migration of the settings.
|
||||||
|
|
||||||
|
### Tidied
|
||||||
|
|
||||||
|
- Commented-out codes have been gradually removed.
|
||||||
|
|
||||||
|
## 0.24.0.dev-rc3
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- No longer Missing Translation Warning is shown in the console.
|
||||||
|
- Fixed the issue where some functions were not working properly (`_` started functions).
|
||||||
|
|
||||||
## 0.24.0.dev-rc2
|
## 0.24.0.dev-rc2
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Reference in New Issue
Block a user