mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-24 08:11:35 +00:00
Compare commits
33 Commits
0.25.12
...
0.25.21.be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51dc44bfb0 | ||
|
|
7c4f2bf78a | ||
|
|
67c9b4cf06 | ||
|
|
4808876968 | ||
|
|
cccff21ecc | ||
|
|
d8415a97e5 | ||
|
|
85e9aa2978 | ||
|
|
b4eb0e4868 | ||
|
|
3ea348f468 | ||
|
|
81362816d6 | ||
|
|
d6efe4510f | ||
|
|
ca5a7ae18c | ||
|
|
a27652ac34 | ||
|
|
29b89efc47 | ||
|
|
ef3eef2d08 | ||
|
|
ffbbe32e36 | ||
|
|
0a5371cdee | ||
|
|
466bb142e2 | ||
|
|
d394a4ce7f | ||
|
|
71ce76e502 | ||
|
|
ae7a7dd456 | ||
|
|
4048186bb5 | ||
|
|
2b94fd9139 | ||
|
|
ec72ece86d | ||
|
|
e394a994c5 | ||
|
|
aa23b6a39a | ||
|
|
58e328a591 | ||
|
|
1730c39d70 | ||
|
|
dfeac201a2 | ||
|
|
b42152db5e | ||
|
|
171cfc0a38 | ||
|
|
d2787bdb6a | ||
|
|
44b022f003 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,6 +9,7 @@ package-lock.json
|
|||||||
# build
|
# build
|
||||||
main.js
|
main.js
|
||||||
main_org.js
|
main_org.js
|
||||||
|
main_org_*.js
|
||||||
*.js.map
|
*.js.map
|
||||||
meta.json
|
meta.json
|
||||||
meta-*.json
|
meta-*.json
|
||||||
@@ -17,3 +18,6 @@ meta-*.json
|
|||||||
# obsidian
|
# obsidian
|
||||||
data.json
|
data.json
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Additionally, it supports peer-to-peer synchronisation using WebRTC now (experim
|
|||||||
- Use open-source solutions for the server.
|
- Use open-source solutions for the server.
|
||||||
- Compatible solutions are supported.
|
- Compatible solutions are supported.
|
||||||
- Support end-to-end encryption.
|
- Support end-to-end encryption.
|
||||||
- Synchronise settings, snippets, themes, and plug-ins via [Customisation Sync (Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync).
|
- Synchronise settings, snippets, themes, and plug-ins via [Customisation Sync (Beta)](docs/settings.md#6-customization-sync-advanced) or [Hidden File Sync](docs/settings.md#7-hidden-files-advanced).
|
||||||
- Enable WebRTC peer-to-peer synchronisation without requiring a `host` (Experimental).
|
- Enable WebRTC peer-to-peer synchronisation without requiring a `host` (Experimental).
|
||||||
- This feature is still in the experimental stage. Please exercise caution when using it.
|
- This feature is still in the experimental stage. Please exercise caution when using it.
|
||||||
- WebRTC is a peer-to-peer synchronisation method, so **at least one device must be online to synchronise**.
|
- WebRTC is a peer-to-peer synchronisation method, so **at least one device must be online to synchronise**.
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ If it results like the following:
|
|||||||
|
|
||||||
Your CouchDB has been initialised successfully. If you want this manually, please read the script.
|
Your CouchDB has been initialised successfully. If you want this manually, please read the script.
|
||||||
|
|
||||||
|
If you are using Docker Compose and the above command does not work or displays `ERROR: Hostname missing`, you can try running the following command, replacing the placeholders with your own values:
|
||||||
|
```
|
||||||
|
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> bash
|
||||||
|
```
|
||||||
|
|
||||||
## 3. Expose CouchDB to the Internet
|
## 3. Expose CouchDB to the Internet
|
||||||
|
|
||||||
- You can skip this instruction if you using only in intranet and only with desktop devices.
|
- You can skip this instruction if you using only in intranet and only with desktop devices.
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + "");
|
|||||||
const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
|
const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
|
||||||
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
||||||
|
|
||||||
|
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
|
||||||
|
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length);
|
||||||
|
if (!prod) {
|
||||||
|
if (PATH_TEST_INSTALL) {
|
||||||
|
console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
|
||||||
|
} else {
|
||||||
|
console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Production build");
|
||||||
|
}
|
||||||
|
|
||||||
const moduleAliasPlugin = {
|
const moduleAliasPlugin = {
|
||||||
name: "module-alias",
|
name: "module-alias",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
@@ -95,6 +107,21 @@ const plugins = [
|
|||||||
} else {
|
} else {
|
||||||
fs.copyFileSync("./main_org.js", "./main.js");
|
fs.copyFileSync("./main_org.js", "./main.js");
|
||||||
}
|
}
|
||||||
|
if (PATH_TEST_INSTALL) {
|
||||||
|
for (const installPath of PATH_TEST_INSTALL) {
|
||||||
|
const realPath = path.resolve(installPath);
|
||||||
|
console.log(`Copying built files to ${realPath}`);
|
||||||
|
if (!fs.existsSync(realPath)) {
|
||||||
|
console.warn(`Test install path ${installPath} does not exist`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const manifestX = JSON.parse(fs.readFileSync("./manifest.json") + "");
|
||||||
|
manifestX.version = manifestJson.version + "." + Date.now();
|
||||||
|
fs.writeFileSync(path.join(installPath, "manifest.json"), JSON.stringify(manifestX, null, 2));
|
||||||
|
fs.copyFileSync("./main.js", path.join(installPath, "main.js"));
|
||||||
|
fs.copyFileSync("./styles.css", path.join(installPath, "styles.css"));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
1
example.env
Normal file
1
example.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PATHS_TEST_INSTALL=your-vault-plugin-path:and-another-path
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.2",
|
"version": "0.25.21.beta2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.12",
|
"version": "0.25.20",
|
||||||
"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",
|
||||||
|
|||||||
1240
package-lock.json
generated
1240
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.12",
|
"version": "0.25.21.beta2",
|
||||||
"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",
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"postbakei18n": "prettier --config ./.prettierrc ./src/lib/src/common/messages/*.ts --write --log-level error",
|
"postbakei18n": "prettier --config ./.prettierrc ./src/lib/src/common/messages/*.ts --write --log-level error",
|
||||||
"posti18n:yaml2json": "npm run prettyjson",
|
"posti18n:yaml2json": "npm run prettyjson",
|
||||||
"predev": "npm run bakei18n",
|
"predev": "npm run bakei18n",
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node --env-file=.env esbuild.config.mjs",
|
||||||
"prebuild": "npm run bakei18n",
|
"prebuild": "npm run bakei18n",
|
||||||
"build": "node esbuild.config.mjs production",
|
"build": "node esbuild.config.mjs production",
|
||||||
"buildDev": "node esbuild.config.mjs dev",
|
"buildDev": "node esbuild.config.mjs dev",
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
"@typescript-eslint/parser": "8.25.0",
|
"@typescript-eslint/parser": "8.25.0",
|
||||||
"builtin-modules": "5.0.0",
|
"builtin-modules": "5.0.0",
|
||||||
"esbuild": "0.25.0",
|
"esbuild": "0.25.0",
|
||||||
|
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||||
"esbuild-svelte": "^0.9.0",
|
"esbuild-svelte": "^0.9.0",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"pouchdb-utils": "^9.0.0",
|
"pouchdb-utils": "^9.0.0",
|
||||||
"prettier": "3.5.2",
|
"prettier": "3.5.2",
|
||||||
"svelte": "5.28.6",
|
"svelte": "5.28.6",
|
||||||
|
"svelte-check": "^4.1.7",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"terser": "^5.39.0",
|
"terser": "^5.39.0",
|
||||||
"transform-pouch": "^2.0.0",
|
"transform-pouch": "^2.0.0",
|
||||||
@@ -88,14 +90,12 @@
|
|||||||
"@smithy/protocol-http": "^5.1.0",
|
"@smithy/protocol-http": "^5.1.0",
|
||||||
"@smithy/querystring-builder": "^4.0.2",
|
"@smithy/querystring-builder": "^4.0.2",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.2",
|
||||||
"octagonal-wheels": "^0.1.37",
|
"octagonal-wheels": "^0.1.41",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"svelte-check": "^4.1.7",
|
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"trystero": "^0.21.5",
|
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatab
|
|||||||
}
|
}
|
||||||
const storeKey = dbKey;
|
const storeKey = dbKey;
|
||||||
const dbPromise = openDB(dbKey, 1, {
|
const dbPromise = openDB(dbKey, 1, {
|
||||||
upgrade(db) {
|
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
|
||||||
db.createObjectStore(storeKey);
|
return db.createObjectStore(storeKey);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const db = await dbPromise;
|
const db = await dbPromise;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PersistentMap } from "../lib/src/dataobject/PersistentMap.ts";
|
import { PersistentMap } from "octagonal-wheels/dataobject/PersistentMap";
|
||||||
|
|
||||||
export let sameChangePairs: PersistentMap<number[]>;
|
export let sameChangePairs: PersistentMap<number[]>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { type PluginManifest, TFile } from "../deps.ts";
|
import { type PluginManifest, TFile } from "../deps.ts";
|
||||||
import {
|
import { type DatabaseEntry, type EntryBody, type FilePath } from "../lib/src/common/types.ts";
|
||||||
type DatabaseEntry,
|
export type { CacheData, FileEventItem } from "../lib/src/common/types.ts";
|
||||||
type EntryBody,
|
|
||||||
type FilePath,
|
|
||||||
type UXFileInfoStub,
|
|
||||||
type UXInternalFileInfoStub,
|
|
||||||
} from "../lib/src/common/types.ts";
|
|
||||||
|
|
||||||
export interface PluginDataEntry extends DatabaseEntry {
|
export interface PluginDataEntry extends DatabaseEntry {
|
||||||
deviceVaultName: string;
|
deviceVaultName: string;
|
||||||
@@ -54,23 +49,6 @@ export type queueItem = {
|
|||||||
warned?: boolean;
|
warned?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CacheData = string | ArrayBuffer;
|
|
||||||
export type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "INTERNAL";
|
|
||||||
export type FileEventArgs = {
|
|
||||||
file: UXFileInfoStub | UXInternalFileInfoStub;
|
|
||||||
cache?: CacheData;
|
|
||||||
oldPath?: string;
|
|
||||||
ctx?: any;
|
|
||||||
};
|
|
||||||
export type FileEventItem = {
|
|
||||||
type: FileEventType;
|
|
||||||
args: FileEventArgs;
|
|
||||||
key: string;
|
|
||||||
skipBatchWait?: boolean;
|
|
||||||
cancelled?: boolean;
|
|
||||||
batched?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hidden items (Now means `chunk`)
|
// Hidden items (Now means `chunk`)
|
||||||
export const CHeader = "h:";
|
export const CHeader = "h:";
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels
|
|||||||
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts";
|
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts";
|
||||||
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
||||||
|
|
||||||
export { scheduleTask, cancelTask, cancelAllTasks } from "../lib/src/concurrency/task.ts";
|
export { scheduleTask, cancelTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||||
|
|
||||||
// For backward compatibility, using the path for determining id.
|
// For backward compatibility, using the path for determining id.
|
||||||
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
||||||
@@ -189,7 +189,7 @@ export class PeriodicProcessor {
|
|||||||
() =>
|
() =>
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.process();
|
await this.process();
|
||||||
if (this._plugin.$$isUnloaded()) {
|
if (this._plugin.services?.appLifecycle?.hasUnloaded()) {
|
||||||
this.disable();
|
this.disable();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
} from "../../lib/src/common/utils.ts";
|
} from "../../lib/src/common/utils.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 "octagonal-wheels/concurrency/lock";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import {
|
import {
|
||||||
@@ -62,20 +62,29 @@ import {
|
|||||||
scheduleTask,
|
scheduleTask,
|
||||||
} from "../../common/utils.ts";
|
} from "../../common/utils.ts";
|
||||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||||
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
|
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
|
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
|
||||||
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
|
||||||
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
||||||
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
const d = "\u200b";
|
const d = "\u200b";
|
||||||
const d2 = "\n";
|
const d2 = "\n";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface OPTIONAL_SYNC_FEATURES {
|
||||||
|
DISABLE: "DISABLE";
|
||||||
|
CUSTOMIZE: "CUSTOMIZE";
|
||||||
|
DISABLE_CUSTOM: "DISABLE_CUSTOM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function serialize(data: PluginDataEx): string {
|
function serialize(data: PluginDataEx): string {
|
||||||
// For higher performance, create custom plug-in data strings.
|
// For higher performance, create custom plug-in data strings.
|
||||||
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
|
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
|
||||||
@@ -384,7 +393,7 @@ export type PluginDataEx = {
|
|||||||
mtime: number;
|
mtime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
export class ConfigSync extends LiveSyncCommands {
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
pluginScanningCount.onChanged((e) => {
|
pluginScanningCount.onChanged((e) => {
|
||||||
@@ -402,7 +411,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +420,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) {
|
||||||
@@ -482,8 +491,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
// Idea non-filter option?
|
// Idea non-filter option?
|
||||||
return this.getFileCategory(filePath) != "";
|
return this.getFileCategory(filePath) != "";
|
||||||
}
|
}
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
private async _everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
if (!this._isThisModuleEnabled()) return true;
|
if (!this.isThisModuleEnabled()) return true;
|
||||||
try {
|
try {
|
||||||
this._log("Scanning customizations...");
|
this._log("Scanning customizations...");
|
||||||
await this.scanAllConfigFiles(showNotice);
|
await this.scanAllConfigFiles(showNotice);
|
||||||
@@ -494,16 +503,16 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -517,9 +526,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$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) {
|
||||||
@@ -633,7 +642,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
).startPipeline();
|
).startPipeline();
|
||||||
|
|
||||||
filenameToUnifiedKey(path: string, termOverRide?: string) {
|
filenameToUnifiedKey(path: string, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
const category = this.getFileCategory(path);
|
const category = this.getFileCategory(path);
|
||||||
const name =
|
const name =
|
||||||
category == "CONFIG" || category == "SNIPPET"
|
category == "CONFIG" || category == "SNIPPET"
|
||||||
@@ -645,7 +654,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filenameWithUnifiedKey(path: string, termOverRide?: string) {
|
filenameWithUnifiedKey(path: string, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
const category = this.getFileCategory(path);
|
const category = this.getFileCategory(path);
|
||||||
const name =
|
const name =
|
||||||
category == "CONFIG" || category == "SNIPPET" ? path.split("/").slice(-1)[0] : path.split("/").slice(-2)[0];
|
category == "CONFIG" || category == "SNIPPET" ? path.split("/").slice(-1)[0] : path.split("/").slice(-2)[0];
|
||||||
@@ -654,7 +663,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unifiedKeyPrefixOfTerminal(termOverRide?: string) {
|
unifiedKeyPrefixOfTerminal(termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
return `${ICXHeader}${term}/` as FilePathWithPrefix;
|
return `${ICXHeader}${term}/` as FilePathWithPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +840,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
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}`);
|
||||||
this._log(`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.services.path.path2id(v2Path);
|
||||||
// const buf =
|
// const buf =
|
||||||
|
|
||||||
const data = createBlob([DUMMY_HEAD, DUMMY_END, ...getDocDataAsArray(f.data)]);
|
const data = createBlob([DUMMY_HEAD, DUMMY_END, ...getDocDataAsArray(f.data)]);
|
||||||
@@ -861,7 +870,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -999,7 +1008,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
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.
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
||||||
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
|
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
|
||||||
} else {
|
} else {
|
||||||
const files = data.files;
|
const files = data.files;
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
@@ -1042,7 +1051,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
||||||
}
|
}
|
||||||
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
||||||
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
|
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -1114,7 +1123,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.category == "CONFIG") {
|
} else if (data.category == "CONFIG") {
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -1157,15 +1166,15 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
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 false;
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
await this.updatePluginList(
|
await this.updatePluginList(
|
||||||
false,
|
false,
|
||||||
(docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry)
|
(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) => {
|
||||||
@@ -1205,11 +1214,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -1345,7 +1354,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.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -1488,14 +1497,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
|
||||||
return await this.watchVaultRawEventsAsync(path);
|
return await this.watchVaultRawEventsAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchVaultRawEventsAsync(path: FilePath) {
|
async watchVaultRawEventsAsync(path: FilePath) {
|
||||||
if (!this._isMainReady) return false;
|
if (!this._isMainReady) return false;
|
||||||
if (this._isMainSuspended()) return false;
|
if (this._isMainSuspended()) return false;
|
||||||
if (!this._isThisModuleEnabled()) return false;
|
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.
|
||||||
@@ -1535,7 +1544,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
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;
|
||||||
this._log("Scanning customizing files.", logLevel, "scan-all-config");
|
this._log("Scanning customizing files.", logLevel, "scan-all-config");
|
||||||
const term = this.plugin.$$getDeviceAndVaultName();
|
const term = this.services.setting.getDeviceAndVaultName();
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -1673,11 +1682,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return filenames as FilePath[];
|
return filenames as FilePath[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
private async _allAskUsingOptionalSyncFeature(opt: {
|
||||||
await this._askHiddenFileConfiguration(opt);
|
enableFetch?: boolean;
|
||||||
|
enableOverwrite?: boolean;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
await this.__askHiddenFileConfiguration(opt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private 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**?
|
||||||
|
|
||||||
> [!DETAILS]-
|
> [!DETAILS]-
|
||||||
@@ -1707,7 +1719,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
||||||
if (isPluginMetadata(path)) {
|
if (isPluginMetadata(path)) {
|
||||||
return Promise.resolve("newer");
|
return Promise.resolve("newer");
|
||||||
}
|
}
|
||||||
@@ -1717,7 +1729,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
private _allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
||||||
this._log(
|
this._log(
|
||||||
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
||||||
@@ -1729,10 +1741,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyConfigureOptionalSyncFeature(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
|
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
await this.configureHiddenFileSync(mode);
|
await this.configureHiddenFileSync(mode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
async configureHiddenFileSync(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
|
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
if (mode == "DISABLE") {
|
if (mode == "DISABLE") {
|
||||||
this.plugin.settings.usePluginSync = false;
|
this.plugin.settings.usePluginSync = false;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -1740,7 +1753,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode == "CUSTOMIZE") {
|
if (mode == "CUSTOMIZE") {
|
||||||
if (!this.plugin.$$getDeviceAndVaultName()) {
|
if (!this.services.setting.getDeviceAndVaultName()) {
|
||||||
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
|
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
if (Platform.isAndroidApp) {
|
if (Platform.isAndroidApp) {
|
||||||
@@ -1764,7 +1777,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
name = name + Math.random().toString(36).slice(-4);
|
name = name + Math.random().toString(36).slice(-4);
|
||||||
}
|
}
|
||||||
this.plugin.$$setDeviceAndVaultName(name);
|
this.services.setting.setDeviceAndVaultName(name);
|
||||||
}
|
}
|
||||||
this.plugin.settings.usePluginSync = true;
|
this.plugin.settings.usePluginSync = true;
|
||||||
this.plugin.settings.useAdvancedMode = true;
|
this.plugin.settings.useAdvancedMode = true;
|
||||||
@@ -1789,4 +1802,17 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
|
||||||
|
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
|
||||||
|
services.replication.handleProcessVirtualDocuments(this._anyModuleParsedReplicationResultItem.bind(this));
|
||||||
|
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
|
||||||
|
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||||
|
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
export let plugin: ObsidianLiveSyncPlugin;
|
export let plugin: ObsidianLiveSyncPlugin;
|
||||||
|
|
||||||
$: hideNotApplicable = false;
|
$: hideNotApplicable = false;
|
||||||
$: thisTerm = plugin.$$getDeviceAndVaultName();
|
$: thisTerm = plugin.services.setting.getDeviceAndVaultName();
|
||||||
|
|
||||||
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
|
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
|
||||||
if (!addOn) {
|
if (!addOn) {
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
await requestUpdate();
|
await requestUpdate();
|
||||||
}
|
}
|
||||||
async function replicate() {
|
async function replicate() {
|
||||||
await plugin.$$replicate(true);
|
await plugin.services.replication.replicate(true);
|
||||||
}
|
}
|
||||||
function selectAllNewest(selectMode: boolean) {
|
function selectAllNewest(selectMode: boolean) {
|
||||||
selectNewestPulse++;
|
selectNewestPulse++;
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
plugin.settings.pluginSyncExtendedSetting[key].files = files;
|
plugin.settings.pluginSyncExtendedSetting[key].files = files;
|
||||||
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
|
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
|
||||||
}
|
}
|
||||||
plugin.$$saveSettingData();
|
plugin.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
function getIcon(mode: SYNC_MODE) {
|
function getIcon(mode: SYNC_MODE) {
|
||||||
if (mode in ICONS) {
|
if (mode in ICONS) {
|
||||||
|
|||||||
@@ -45,18 +45,26 @@ import {
|
|||||||
BASE_IS_NEW,
|
BASE_IS_NEW,
|
||||||
EVEN,
|
EVEN,
|
||||||
} from "../../common/utils.ts";
|
} from "../../common/utils.ts";
|
||||||
import { serialized, skipIfDuplicated } from "../../lib/src/concurrency/lock.ts";
|
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
|
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
|
||||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface OPTIONAL_SYNC_FEATURES {
|
||||||
|
FETCH: "FETCH";
|
||||||
|
OVERWRITE: "OVERWRITE";
|
||||||
|
MERGE: "MERGE";
|
||||||
|
DISABLE: "DISABLE";
|
||||||
|
DISABLE_HIDDEN: "DISABLE_HIDDEN";
|
||||||
|
}
|
||||||
|
}
|
||||||
function getComparingMTime(
|
function getComparingMTime(
|
||||||
doc: (MetaEntry | LoadedEntry | false) | UXFileInfo | UXStat | null | undefined,
|
doc: (MetaEntry | LoadedEntry | false) | UXFileInfo | UXStat | null | undefined,
|
||||||
includeDeleted = false
|
includeDeleted = false
|
||||||
@@ -72,14 +80,14 @@ function getComparingMTime(
|
|||||||
return doc.mtime ?? 0;
|
return doc.mtime ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
|
export class HiddenFileSync extends LiveSyncCommands {
|
||||||
_isThisModuleEnabled() {
|
isThisModuleEnabled() {
|
||||||
return this.plugin.settings.syncInternalFiles;
|
return this.plugin.settings.syncInternalFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
|
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
|
||||||
this.plugin,
|
this.plugin,
|
||||||
async () => this._isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
|
async () => this.isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
|
||||||
);
|
);
|
||||||
|
|
||||||
get kvDB() {
|
get kvDB() {
|
||||||
@@ -132,14 +140,18 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
this.updateSettingCache();
|
this.updateSettingCache();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
// We cannot initialise autosaveCache because kvDB is not ready yet
|
||||||
|
// async _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
|
// this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
||||||
|
// this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
||||||
|
// this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
private async _everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
||||||
this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
||||||
this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
||||||
return true;
|
if (this.isThisModuleEnabled()) {
|
||||||
}
|
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
|
||||||
if (this._isThisModuleEnabled()) {
|
|
||||||
if (this._fileInfoLastProcessed.size == 0 && this._fileInfoLastProcessed.size == 0) {
|
if (this._fileInfoLastProcessed.size == 0 && this._fileInfoLastProcessed.size == 0) {
|
||||||
this._log(`No cache found. Performing startup scan.`, LOG_LEVEL_VERBOSE);
|
this._log(`No cache found. Performing startup scan.`, LOG_LEVEL_VERBOSE);
|
||||||
await this.performStartupScan(true);
|
await this.performStartupScan(true);
|
||||||
@@ -149,9 +161,9 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showNotice: boolean) {
|
async _everyBeforeReplicate(showNotice: boolean) {
|
||||||
if (
|
if (
|
||||||
this._isThisModuleEnabled() &&
|
this.isThisModuleEnabled() &&
|
||||||
this._isDatabaseReady() &&
|
this._isDatabaseReady() &&
|
||||||
this.settings.syncInternalFilesBeforeReplication &&
|
this.settings.syncInternalFilesBeforeReplication &&
|
||||||
!this.settings.watchInternalFileChanges
|
!this.settings.watchInternalFileChanges
|
||||||
@@ -161,7 +173,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
this.updateSettingCache();
|
this.updateSettingCache();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -188,7 +200,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
isReady() {
|
isReady() {
|
||||||
if (!this._isMainReady) return false;
|
if (!this._isMainReady) return false;
|
||||||
if (this._isMainSuspended()) return false;
|
if (this._isMainSuspended()) return false;
|
||||||
if (!this._isThisModuleEnabled()) return false;
|
if (!this.isThisModuleEnabled()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
shouldSkipFile = [] as FilePathWithPrefixLC[];
|
shouldSkipFile = [] as FilePathWithPrefixLC[];
|
||||||
@@ -197,26 +209,26 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
await this.applyOfflineChanges(showNotice);
|
await this.applyOfflineChanges(showNotice);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnResumeProcess(): Promise<boolean> {
|
async _everyOnResumeProcess(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this._isMainSuspended()) return true;
|
if (this._isMainSuspended()) return true;
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
await this.performStartupScan(false);
|
await this.performStartupScan(false);
|
||||||
}
|
}
|
||||||
this.periodicInternalFileScanProcessor.enable(
|
this.periodicInternalFileScanProcessor.enable(
|
||||||
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
||||||
? this.settings.syncInternalFilesInterval * 1000
|
? this.settings.syncInternalFilesInterval * 1000
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
_everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this._isMainSuspended()) return Promise.resolve(true);
|
if (this._isMainSuspended()) return Promise.resolve(true);
|
||||||
if (!this.plugin.$$isReady()) return Promise.resolve(true);
|
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
|
||||||
this.periodicInternalFileScanProcessor.enable(
|
this.periodicInternalFileScanProcessor.enable(
|
||||||
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
||||||
? this.settings.syncInternalFilesInterval * 1000
|
? this.settings.syncInternalFilesInterval * 1000
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
@@ -227,13 +239,14 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
|
||||||
if (this.isReady()) {
|
if (this.isReady()) {
|
||||||
return await this.trackStorageFileModification(path);
|
return (await this.trackStorageFileModification(path)) || false;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
||||||
if (isInternalMetadata(path)) {
|
if (isInternalMetadata(path)) {
|
||||||
this.queueConflictCheck(path);
|
this.queueConflictCheck(path);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -241,12 +254,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
async _anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean> {
|
||||||
if (isInternalMetadata(doc._id)) {
|
if (isInternalMetadata(doc._id)) {
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
//system file
|
//system file
|
||||||
const filename = getPath(doc);
|
const filename = getPath(doc);
|
||||||
if (await this.plugin.$$isTargetFile(filename)) {
|
if (await this.services.vault.isTargetFile(filename)) {
|
||||||
// this.procInternalFile(filename);
|
// this.procInternalFile(filename);
|
||||||
await this.processReplicationResult(doc);
|
await this.processReplicationResult(doc);
|
||||||
return true;
|
return true;
|
||||||
@@ -1091,14 +1104,14 @@ Offline Changed files: ${files.length}`;
|
|||||||
|
|
||||||
// If something changes left, notify for reloading Obsidian.
|
// If something changes left, notify for reloading Obsidian.
|
||||||
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
|
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
|
||||||
if (!this.plugin.$$isReloadingScheduled()) {
|
if (!this.services.appLifecycle.isReloadingScheduled()) {
|
||||||
this.plugin.confirm.askInPopup(
|
this.plugin.confirm.askInPopup(
|
||||||
`updated-any-hidden`,
|
`updated-any-hidden`,
|
||||||
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
|
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
|
||||||
(anchor) => {
|
(anchor) => {
|
||||||
anchor.text = "HERE";
|
anchor.text = "HERE";
|
||||||
anchor.addEventListener("click", () => {
|
anchor.addEventListener("click", () => {
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1318,7 +1331,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
|
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
|
||||||
const storeFilePath = stripAllPrefixes(file.path as FilePath);
|
const storeFilePath = stripAllPrefixes(file.path as FilePath);
|
||||||
const storageFilePath = file.path;
|
const storageFilePath = file.path;
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
@@ -1372,7 +1385,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
const displayFileName = filenameSrc;
|
const displayFileName = filenameSrc;
|
||||||
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
const mtime = new Date().getTime();
|
const mtime = new Date().getTime();
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
@@ -1432,7 +1445,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
includeDeletion = true
|
includeDeletion = true
|
||||||
) {
|
) {
|
||||||
const prefixedFileName = addPrefix(storageFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storageFilePath, ICHeader);
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
@@ -1479,7 +1492,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
const deleted = metaOnDB.deleted || metaOnDB._deleted || false;
|
const deleted = metaOnDB.deleted || metaOnDB._deleted || false;
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
const result = await this._deleteFile(storageFilePath);
|
const result = await this.__deleteFile(storageFilePath);
|
||||||
if (result == "OK") {
|
if (result == "OK") {
|
||||||
this.updateLastProcessedDeletion(storageFilePath, metaOnDB);
|
this.updateLastProcessedDeletion(storageFilePath, metaOnDB);
|
||||||
return true;
|
return true;
|
||||||
@@ -1493,7 +1506,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
if (fileOnDB === false) {
|
if (fileOnDB === false) {
|
||||||
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
||||||
}
|
}
|
||||||
const resultStat = await this._writeFile(storageFilePath, fileOnDB, force);
|
const resultStat = await this.__writeFile(storageFilePath, fileOnDB, force);
|
||||||
if (resultStat) {
|
if (resultStat) {
|
||||||
this.updateLastProcessed(storageFilePath, metaOnDB, resultStat);
|
this.updateLastProcessed(storageFilePath, metaOnDB, resultStat);
|
||||||
this.queueNotification(storageFilePath);
|
this.queueNotification(storageFilePath);
|
||||||
@@ -1526,7 +1539,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
|
async __writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
|
||||||
try {
|
try {
|
||||||
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
|
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
|
||||||
const isExist = statBefore != null;
|
const isExist = statBefore != null;
|
||||||
@@ -1565,7 +1578,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
|
async __deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
|
||||||
const result = await this.__removeFile(storageFilePath);
|
const result = await this.__removeFile(storageFilePath);
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
this._log(`STORAGE <x- DB: ${storageFilePath}: deleting (hidden) Failed`);
|
this._log(`STORAGE <x- DB: ${storageFilePath}: deleting (hidden) Failed`);
|
||||||
@@ -1582,11 +1595,11 @@ Offline Changed files: ${files.length}`;
|
|||||||
|
|
||||||
// <-- Database To Storage Functions
|
// <-- Database To Storage Functions
|
||||||
|
|
||||||
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private async _allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
||||||
await this._askHiddenFileConfiguration(opt);
|
await this.__askHiddenFileConfiguration(opt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private 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.
|
||||||
@@ -1632,7 +1645,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
private _allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.syncInternalFiles) {
|
if (this.plugin.settings.syncInternalFiles) {
|
||||||
this._log(
|
this._log(
|
||||||
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
||||||
@@ -1644,11 +1657,12 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --> Configuration handling
|
// --> Configuration handling
|
||||||
async $anyConfigureOptionalSyncFeature(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
|
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
await this.configureHiddenFileSync(mode);
|
await this.configureHiddenFileSync(mode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async configureHiddenFileSync(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
|
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
if (
|
if (
|
||||||
mode != "FETCH" &&
|
mode != "FETCH" &&
|
||||||
mode != "OVERWRITE" &&
|
mode != "OVERWRITE" &&
|
||||||
@@ -1718,7 +1732,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
const result: InternalFileInfo[] = [];
|
const result: InternalFileInfo[] = [];
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
const w = await f;
|
const w = await f;
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(w.path)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(w.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const mtime = w.stat?.mtime ?? 0;
|
const mtime = w.stat?.mtime ?? 0;
|
||||||
@@ -1756,7 +1770,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
|
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
L1: for (const v of w.folders) {
|
L1: for (const v of w.folders) {
|
||||||
@@ -1765,13 +1779,10 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (ignoreFilter && ignoreFilter.some((e) => e.test(v))) {
|
||||||
ignoreFilter &&
|
|
||||||
ignoreFilter.some((e) => (e.pattern.startsWith("/") || e.pattern.startsWith("\\/")) && e.test(v))
|
|
||||||
) {
|
|
||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(v)) {
|
||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
|
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
|
||||||
@@ -1780,4 +1791,20 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// <-- Local Storage SubFunctions
|
// <-- Local Storage SubFunctions
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// No longer needed on initialisation
|
||||||
|
// services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
|
||||||
|
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
|
||||||
|
services.replication.handleProcessOptionalSynchroniseResult(this._anyProcessOptionalSyncFiles.bind(this));
|
||||||
|
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
|
||||||
|
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||||
|
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import {
|
|||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
type AnyEntry,
|
type AnyEntry,
|
||||||
type DocumentID,
|
type DocumentID,
|
||||||
type EntryHasPath,
|
|
||||||
type FilePath,
|
type FilePath,
|
||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type LOG_LEVEL,
|
type LOG_LEVEL,
|
||||||
} from "../lib/src/common/types.ts";
|
} from "../lib/src/common/types.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||||
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
||||||
|
import type { LiveSyncCore } from "../main.ts";
|
||||||
|
import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts";
|
||||||
|
|
||||||
let noticeIndex = 0;
|
let noticeIndex = 0;
|
||||||
export abstract class LiveSyncCommands {
|
export abstract class LiveSyncCommands {
|
||||||
@@ -25,12 +26,15 @@ export abstract class LiveSyncCommands {
|
|||||||
get localDatabase() {
|
get localDatabase() {
|
||||||
return this.plugin.localDatabase;
|
return this.plugin.localDatabase;
|
||||||
}
|
}
|
||||||
|
get services() {
|
||||||
id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
return this.plugin.services;
|
||||||
return this.plugin.$$id2path(id, entry, stripPrefix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
||||||
|
// return this.plugin.$$id2path(id, entry, stripPrefix);
|
||||||
|
// }
|
||||||
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
return await this.plugin.$$path2id(filename, prefix);
|
return await this.services.path.path2id(filename, prefix);
|
||||||
}
|
}
|
||||||
getPath(entry: AnyEntry): FilePathWithPrefix {
|
getPath(entry: AnyEntry): FilePathWithPrefix {
|
||||||
return getPath(entry);
|
return getPath(entry);
|
||||||
@@ -38,18 +42,20 @@ export abstract class LiveSyncCommands {
|
|||||||
|
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.onBindFunction(plugin, plugin.services);
|
||||||
|
__$checkInstanceBinding(this);
|
||||||
}
|
}
|
||||||
abstract onunload(): void;
|
abstract onunload(): void;
|
||||||
abstract onload(): void | Promise<void>;
|
abstract onload(): void | Promise<void>;
|
||||||
|
|
||||||
_isMainReady() {
|
_isMainReady() {
|
||||||
return this.plugin.$$isReady();
|
return this.plugin.services.appLifecycle.isReady();
|
||||||
}
|
}
|
||||||
_isMainSuspended() {
|
_isMainSuspended() {
|
||||||
return this.plugin.$$isSuspended();
|
return this.services.appLifecycle.isSuspended();
|
||||||
}
|
}
|
||||||
_isDatabaseReady() {
|
_isDatabaseReady() {
|
||||||
return this.plugin.$$isDatabaseReady();
|
return this.services.database.isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
@@ -89,4 +95,8 @@ export abstract class LiveSyncCommands {
|
|||||||
_debug = (msg: any, key?: string) => {
|
_debug = (msg: any, key?: string) => {
|
||||||
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// Override if needed.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
type MetaEntry,
|
type MetaEntry,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands";
|
import { LiveSyncCommands } from "../LiveSyncCommands";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||||
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||||
@@ -22,10 +21,7 @@ type NoteDocumentID = DocumentID;
|
|||||||
type Rev = string;
|
type Rev = string;
|
||||||
|
|
||||||
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
|
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
|
||||||
export class LocalDatabaseMaintenance extends LiveSyncCommands implements IObsidianModule {
|
export class LocalDatabaseMaintenance extends LiveSyncCommands {
|
||||||
$everyOnload(): Promise<boolean> {
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
onunload(): void {
|
onunload(): void {
|
||||||
// NO OP.
|
// NO OP.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
|
||||||
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
||||||
import {
|
import {
|
||||||
AutoAccepting,
|
AutoAccepting,
|
||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
|
P2P_DEFAULT_SETTINGS,
|
||||||
REMOTE_P2P,
|
REMOTE_P2P,
|
||||||
type EntryDoc,
|
type EntryDoc,
|
||||||
type P2PSyncSetting,
|
type P2PSyncSetting,
|
||||||
type RemoteDBSettings,
|
type RemoteDBSettings,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
import {
|
||||||
|
LiveSyncTrysteroReplicator,
|
||||||
|
setReplicatorFunc,
|
||||||
|
} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
||||||
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
||||||
import { Logger } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||||
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
||||||
import {
|
import {
|
||||||
P2PReplicatorMixIn,
|
addP2PEventHandlers,
|
||||||
|
closeP2PReplicator,
|
||||||
|
openP2PReplicator,
|
||||||
|
P2PLogCollector,
|
||||||
removeP2PReplicatorInstance,
|
removeP2PReplicatorInstance,
|
||||||
type P2PReplicatorBase,
|
type P2PReplicatorBase,
|
||||||
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
@@ -24,8 +30,10 @@ import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
|||||||
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
|
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
|
||||||
|
|
||||||
class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase {
|
export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase, CommandShim {
|
||||||
storeP2PStatusLine = reactiveSource("");
|
storeP2PStatusLine = reactiveSource("");
|
||||||
|
|
||||||
getSettings(): P2PSyncSetting {
|
getSettings(): P2PSyncSetting {
|
||||||
@@ -49,47 +57,127 @@ class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicator
|
|||||||
|
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
|
setReplicatorFunc(() => this._replicatorInstance);
|
||||||
|
addP2PEventHandlers(this);
|
||||||
|
this.afterConstructor();
|
||||||
|
// onBindFunction is called in super class
|
||||||
|
// this.onBindFunction(plugin, plugin.services);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
|
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
|
||||||
// console.log("Processing Replicated Docs", docs);
|
// console.log("Processing Replicated Docs", docs);
|
||||||
return await this.plugin.$$parseReplicationResult(docs as PouchDB.Core.ExistingDocument<EntryDoc>[]);
|
return await this.services.replication.parseSynchroniseResult(
|
||||||
}
|
docs as PouchDB.Core.ExistingDocument<EntryDoc>[]
|
||||||
onunload(): void {
|
);
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
onload(): void | Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
||||||
this._simpleStore = this.plugin.$$getSimpleStore("p2p-sync");
|
|
||||||
return Promise.resolve(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class P2PReplicator
|
|
||||||
extends P2PReplicatorMixIn(P2PReplicatorCommandBase)
|
|
||||||
implements IObsidianModule, CommandShim
|
|
||||||
{
|
|
||||||
storeP2PStatusLine = reactiveSource("");
|
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return undefined!;
|
||||||
}
|
}
|
||||||
override getPlatform(): string {
|
_replicatorInstance?: TrysteroReplicator;
|
||||||
|
p2pLogCollector = new P2PLogCollector();
|
||||||
|
|
||||||
|
afterConstructor() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open() {
|
||||||
|
await openP2PReplicator(this);
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
await closeP2PReplicator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(key: string) {
|
||||||
|
const vaultName = this.services.vault.getVaultName();
|
||||||
|
const dbKey = `${vaultName}-${key}`;
|
||||||
|
return localStorage.getItem(dbKey);
|
||||||
|
}
|
||||||
|
setConfig(key: string, value: string) {
|
||||||
|
const vaultName = this.services.vault.getVaultName();
|
||||||
|
const dbKey = `${vaultName}-${key}`;
|
||||||
|
localStorage.setItem(dbKey, value);
|
||||||
|
}
|
||||||
|
enableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.enableBroadcastChanges();
|
||||||
|
}
|
||||||
|
disableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.disableBroadcastChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._simpleStore = this.services.database.openSimpleStore("p2p-sync");
|
||||||
|
return Promise.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialiseP2PReplicator(): Promise<TrysteroReplicator> {
|
||||||
|
await this.init();
|
||||||
|
try {
|
||||||
|
if (this._replicatorInstance) {
|
||||||
|
await this._replicatorInstance.close();
|
||||||
|
this._replicatorInstance = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.settings.P2P_AppID) {
|
||||||
|
this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID;
|
||||||
|
}
|
||||||
|
const getInitialDeviceName = () => this.getConfig("p2p_device_name") || this.services.vault.getVaultName();
|
||||||
|
|
||||||
|
const getSettings = () => this.settings;
|
||||||
|
const store = () => this.simpleStore();
|
||||||
|
const getDB = () => this.getDB();
|
||||||
|
|
||||||
|
const getConfirm = () => this.confirm;
|
||||||
|
const getPlatform = () => this.getPlatform();
|
||||||
|
const env = {
|
||||||
|
get db() {
|
||||||
|
return getDB();
|
||||||
|
},
|
||||||
|
get confirm() {
|
||||||
|
return getConfirm();
|
||||||
|
},
|
||||||
|
get deviceName() {
|
||||||
|
return getInitialDeviceName();
|
||||||
|
},
|
||||||
|
get platform() {
|
||||||
|
return getPlatform();
|
||||||
|
},
|
||||||
|
get settings() {
|
||||||
|
return getSettings();
|
||||||
|
},
|
||||||
|
processReplicatedDocs: async (docs: EntryDoc[]): Promise<void> => {
|
||||||
|
await this.handleReplicatedDocuments(docs);
|
||||||
|
// No op. This is a client and does not need to process the docs
|
||||||
|
},
|
||||||
|
get simpleStore() {
|
||||||
|
return store();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this._replicatorInstance = new TrysteroReplicator(env);
|
||||||
|
return this._replicatorInstance;
|
||||||
|
} catch (e) {
|
||||||
|
this._log(
|
||||||
|
e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
|
this._log(e, LOG_LEVEL_VERBOSE);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getPlatform(): string {
|
||||||
return getPlatformName();
|
return getPlatformName();
|
||||||
}
|
}
|
||||||
|
|
||||||
override onunload(): void {
|
onunload(): void {
|
||||||
removeP2PReplicatorInstance();
|
removeP2PReplicatorInstance();
|
||||||
void this.close();
|
void this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
override onload(): void | Promise<void> {
|
onload(): void | Promise<void> {
|
||||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
||||||
void this.openPane();
|
void this.openPane();
|
||||||
});
|
});
|
||||||
@@ -97,12 +185,12 @@ export class P2PReplicator
|
|||||||
this.storeP2PStatusLine.value = line.value;
|
this.storeP2PStatusLine.value = line.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $everyOnInitializeDatabase(): Promise<boolean> {
|
async _everyOnInitializeDatabase(): Promise<boolean> {
|
||||||
await this.initialiseP2PReplicator();
|
await this.initialiseP2PReplicator();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allSuspendExtraSync() {
|
private async _allSuspendExtraSync() {
|
||||||
this.plugin.settings.P2P_Enabled = false;
|
this.plugin.settings.P2P_Enabled = false;
|
||||||
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
||||||
this.plugin.settings.P2P_AutoBroadcast = false;
|
this.plugin.settings.P2P_AutoBroadcast = false;
|
||||||
@@ -112,15 +200,15 @@ export class P2PReplicator
|
|||||||
return await Promise.resolve(true);
|
return await Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnLoadStart() {
|
// async $everyOnLoadStart() {
|
||||||
return await Promise.resolve();
|
// return await Promise.resolve();
|
||||||
}
|
// }
|
||||||
|
|
||||||
async openPane() {
|
async openPane() {
|
||||||
await this.plugin.$$showView(VIEW_TYPE_P2P);
|
await this.services.API.showWindow(VIEW_TYPE_P2P);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnloadStart(): Promise<boolean> {
|
async _everyOnloadStart(): Promise<boolean> {
|
||||||
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
||||||
this.plugin.addCommand({
|
this.plugin.addCommand({
|
||||||
id: "open-p2p-replicator",
|
id: "open-p2p-replicator",
|
||||||
@@ -170,10 +258,26 @@ export class P2PReplicator
|
|||||||
|
|
||||||
return await Promise.resolve(true);
|
return await Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
||||||
setTimeout(() => void this.open(), 100);
|
setTimeout(() => void this.open(), 100);
|
||||||
}
|
}
|
||||||
|
const rep = this._replicatorInstance;
|
||||||
|
rep?.allowReconnection();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
_everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
|
const rep = this._replicatorInstance;
|
||||||
|
rep?.disconnectFromServer();
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
const initialSettings = { ...plugin.settings };
|
const initialSettings = { ...plugin.settings };
|
||||||
|
|
||||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||||
// const vaultName = plugin.$$getVaultName();
|
// const vaultName = service.vault.getVaultName();
|
||||||
// const dbKey = `${vaultName}-p2p-device-name`;
|
// const dbKey = `${vaultName}-p2p-device-name`;
|
||||||
|
|
||||||
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
|
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.services.vault.getVaultName();
|
||||||
let deviceName = $state<string>(initialDeviceName);
|
let deviceName = $state<string>(initialDeviceName);
|
||||||
|
|
||||||
let eP2PEnabled = $state<boolean>(initialSettings.P2P_Enabled);
|
let eP2PEnabled = $state<boolean>(initialSettings.P2P_Enabled);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ And you can also drop the local database to rebuild from the remote device.`,
|
|||||||
if (yn === DROP) {
|
if (yn === DROP) {
|
||||||
await this.plugin.rebuilder.scheduleFetch();
|
await this.plugin.rebuilder.scheduleFetch();
|
||||||
} else {
|
} else {
|
||||||
await this.plugin.$$scheduleAppReload();
|
this.plugin.services.appLifecycle.scheduleRestart();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: f21001fcb2...6c2a72d9cc
794
src/main.ts
794
src/main.ts
@@ -1,26 +1,10 @@
|
|||||||
import { Plugin } from "./deps";
|
import { Plugin } from "./deps";
|
||||||
import {
|
import {
|
||||||
type EntryDoc,
|
type EntryDoc,
|
||||||
type LoadedEntry,
|
|
||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
type LOG_LEVEL,
|
|
||||||
type diff_result,
|
|
||||||
type DatabaseConnectingStatus,
|
type DatabaseConnectingStatus,
|
||||||
type EntryHasPath,
|
|
||||||
type DocumentID,
|
|
||||||
type FilePathWithPrefix,
|
|
||||||
type FilePath,
|
|
||||||
LOG_LEVEL_INFO,
|
|
||||||
type HasSettings,
|
type HasSettings,
|
||||||
type MetaEntry,
|
|
||||||
type UXFileInfoStub,
|
|
||||||
type MISSING_OR_ERROR,
|
|
||||||
type AUTO_MERGED,
|
|
||||||
type RemoteDBSettings,
|
|
||||||
type TweakValues,
|
|
||||||
type CouchDBCredentials,
|
|
||||||
} from "./lib/src/common/types.ts";
|
} from "./lib/src/common/types.ts";
|
||||||
import { type FileEventItem } from "./common/types.ts";
|
|
||||||
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
||||||
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import {
|
import {
|
||||||
@@ -31,11 +15,10 @@ import { type KeyValueDatabase } from "./lib/src/interfaces/KeyValueDatabase.ts"
|
|||||||
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
||||||
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
|
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
|
||||||
import { reactiveSource, type ReactiveValue } from "./lib/src/dataobject/reactive.js";
|
import { reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
|
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
|
||||||
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
|
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
|
||||||
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
|
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
|
||||||
import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js";
|
|
||||||
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
|
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
|
||||||
|
|
||||||
import { ModuleDev } from "./modules/extras/ModuleDev.ts";
|
import { ModuleDev } from "./modules/extras/ModuleDev.ts";
|
||||||
@@ -59,8 +42,7 @@ import { ModuleDatabaseFileAccess } from "./modules/core/ModuleDatabaseFileAcces
|
|||||||
import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts";
|
import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts";
|
||||||
import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts";
|
import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts";
|
||||||
import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts";
|
import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts";
|
||||||
import { injectModules, type AbstractModule } from "./modules/AbstractModule.ts";
|
import { type AbstractModule } from "./modules/AbstractModule.ts";
|
||||||
import type { ICoreModule } from "./modules/ModuleTypes.ts";
|
|
||||||
import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts";
|
import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts";
|
||||||
import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts";
|
import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts";
|
||||||
import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts";
|
import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts";
|
||||||
@@ -85,13 +67,16 @@ import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleE
|
|||||||
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||||
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
|
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
|
||||||
import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts";
|
import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts";
|
||||||
|
import { ObsidianServiceHub } from "./modules/services/ObsidianServices.ts";
|
||||||
|
import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts";
|
||||||
|
|
||||||
|
// function throwShouldBeOverridden(): never {
|
||||||
|
// throw new Error("This function should be overridden by the module.");
|
||||||
|
// }
|
||||||
|
// const InterceptiveAll = Promise.resolve(true);
|
||||||
|
// const InterceptiveEvery = Promise.resolve(true);
|
||||||
|
// const InterceptiveAny = Promise.resolve(undefined);
|
||||||
|
|
||||||
function throwShouldBeOverridden(): never {
|
|
||||||
throw new Error("This function should be overridden by the module.");
|
|
||||||
}
|
|
||||||
const InterceptiveAll = Promise.resolve(true);
|
|
||||||
const InterceptiveEvery = Promise.resolve(true);
|
|
||||||
const InterceptiveAny = Promise.resolve(undefined);
|
|
||||||
/**
|
/**
|
||||||
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
||||||
* Please refer to the module's source code to understand the function.
|
* Please refer to the module's source code to understand the function.
|
||||||
@@ -101,6 +86,13 @@ const InterceptiveAny = Promise.resolve(undefined);
|
|||||||
* $any : Process all modules until the first success.
|
* $any : Process all modules until the first success.
|
||||||
* $ : Other interceptive points. You should manually assign the module
|
* $ : Other interceptive points. You should manually assign the module
|
||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
|
*
|
||||||
|
* No longer used! See AppLifecycleService in Services.ts.
|
||||||
|
* For a while, just commented out some previously used code. (sorry, some are deleted...)
|
||||||
|
* 'Convention over configuration' was a lie for me. At least, very lack of refactor-ability.
|
||||||
|
*
|
||||||
|
* Still some modules are separated, and connected by `ThroughHole` class.
|
||||||
|
* However, it is not a good design. I am going to manage the modules in a more explicit way.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin
|
export default class ObsidianLiveSyncPlugin
|
||||||
@@ -112,6 +104,18 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
LiveSyncCouchDBReplicatorEnv,
|
LiveSyncCouchDBReplicatorEnv,
|
||||||
HasSettings<ObsidianLiveSyncSettings>
|
HasSettings<ObsidianLiveSyncSettings>
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The service hub for managing all services.
|
||||||
|
*/
|
||||||
|
_services: InjectableServiceHub = new ObsidianServiceHub();
|
||||||
|
get services() {
|
||||||
|
return this._services;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Bind functions to the service hub (for migration purpose).
|
||||||
|
*/
|
||||||
|
// bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub);
|
||||||
|
|
||||||
// --> Module System
|
// --> Module System
|
||||||
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
||||||
for (const addon of this.addOns) {
|
for (const addon of this.addOns) {
|
||||||
@@ -173,41 +177,9 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
new ModuleReplicateTest(this, this),
|
new ModuleReplicateTest(this, this),
|
||||||
new ModuleIntegratedTest(this, this),
|
new ModuleIntegratedTest(this, this),
|
||||||
] as (IObsidianModule | AbstractModule)[];
|
] as (IObsidianModule | AbstractModule)[];
|
||||||
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
// injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
||||||
// <-- Module System
|
// <-- Module System
|
||||||
|
|
||||||
$$isSuspended(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$setSuspended(value: boolean): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isDatabaseReady(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getDeviceAndVaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$setDeviceAndVaultName(name: string): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$isReady(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$markIsReady(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$resetIsReady(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are plugged by the modules.
|
// Following are plugged by the modules.
|
||||||
|
|
||||||
settings!: ObsidianLiveSyncSettings;
|
settings!: ObsidianLiveSyncSettings;
|
||||||
@@ -229,30 +201,6 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
return this.settings;
|
return this.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$customFetchHandler(): ObsHttpHandler {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getLastPostFailedBySize(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isStorageInsensitive(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$shouldCheckCaseInsensitive(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isUnloaded(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
requestCount = reactiveSource(0);
|
requestCount = reactiveSource(0);
|
||||||
responseCount = reactiveSource(0);
|
responseCount = reactiveSource(0);
|
||||||
totalQueued = reactiveSource(0);
|
totalQueued = reactiveSource(0);
|
||||||
@@ -277,101 +225,6 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
syncStatus: "CLOSED" as DatabaseConnectingStatus,
|
syncStatus: "CLOSED" as DatabaseConnectingStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
$$isReloadingScheduled(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$getReplicator(): LiveSyncAbstractReplicator {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$connectRemoteCouchDB(
|
|
||||||
uri: string,
|
|
||||||
auth: CouchDBCredentials,
|
|
||||||
disableRequestURI: boolean,
|
|
||||||
passphrase: string | false,
|
|
||||||
useDynamicIterationCount: boolean,
|
|
||||||
performSetup: boolean,
|
|
||||||
skipInfo: boolean,
|
|
||||||
compression: boolean,
|
|
||||||
customHeaders: Record<string, string>,
|
|
||||||
useRequestAPI: boolean,
|
|
||||||
getPBKDF2Salt: () => Promise<Uint8Array>
|
|
||||||
): Promise<
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
db: PouchDB.Database<EntryDoc>;
|
|
||||||
info: PouchDB.Core.DatabaseInfo;
|
|
||||||
}
|
|
||||||
> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isMobile(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$vaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --> Path
|
|
||||||
|
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <-- Path
|
|
||||||
|
|
||||||
// --> Path conversion
|
|
||||||
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <!-- Path conversion
|
|
||||||
|
|
||||||
// --> Database
|
|
||||||
$$createPouchDBInstance<T extends object>(
|
|
||||||
name?: string,
|
|
||||||
options?: PouchDB.Configuration.DatabaseConfiguration
|
|
||||||
): PouchDB.Database<T> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$allOnDBUnload(db: LiveSyncLocalDB): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$allOnDBClose(db: LiveSyncLocalDB): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// <!-- Database
|
|
||||||
|
|
||||||
$anyNewReplicator(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
|
||||||
return InterceptiveEvery;
|
|
||||||
}
|
|
||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
|
||||||
return InterceptiveEvery;
|
|
||||||
}
|
|
||||||
|
|
||||||
// end interfaces
|
|
||||||
|
|
||||||
$$getVaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getSimpleStore<T>(kind: string): SimpleStore<T> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
// trench!: Trench;
|
|
||||||
|
|
||||||
// --> Events
|
// --> Events
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -412,331 +265,404 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
// $everyOnLayoutReady(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onLayoutReady
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
$everyOnFirstInitialize(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnFirstInitialize(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onFirstInitialize
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
// Some Module should call this function to start the plugin.
|
// Some Module should call this function to start the plugin.
|
||||||
$$onLiveSyncReady(): Promise<false | undefined> {
|
// $$onLiveSyncReady(): Promise<false | undefined> {
|
||||||
throwShouldBeOverridden();
|
// //TODO: AppLifecycleService.onLiveSyncReady
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$wireUpEvents(): void {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$wireUpEvents(): void {
|
||||||
}
|
// //TODO: AppLifecycleService.wireUpEvents
|
||||||
$$onLiveSyncLoad(): Promise<void> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$onLiveSyncLoad(): Promise<void> {
|
||||||
|
// //TODO: AppLifecycleService.onLoad
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$onLiveSyncUnload(): Promise<void> {
|
// $$onLiveSyncUnload(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// //TODO: AppLifecycleService.onAppUnload
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$allScanStat(): Promise<boolean> {
|
// $allScanStat(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// //TODO: AppLifecycleService.scanStartupIssues
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnloadStart(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onInitialise
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
// $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onApplyStartupLoaded
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
// $everyOnload(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onLoaded
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
// $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// //TODO: FileProcessingService.processFileEvent
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> {
|
// $allStartOnUnload(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// //TODO: AppLifecycleService.onBeforeUnload
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$allOnUnload(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveAll;
|
// $allOnUnload(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onUnload
|
||||||
|
// return InterceptiveAll;
|
||||||
|
// }
|
||||||
|
|
||||||
$$openDatabase(): Promise<boolean> {
|
// $$openDatabase(): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseService.openDatabase
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$realizeSettingSyncMode(): Promise<void> {
|
// $$realizeSettingSyncMode(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.realiseSetting
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$performRestart() {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$performRestart() {
|
||||||
}
|
// // AppLifecycleService.performRestart
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$clearUsedPassphrase(): void {
|
// $$clearUsedPassphrase(): void {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.clearUsedPassphrase
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
// $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.decryptSettings
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
}
|
// // SettingService.adjustSettings
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$loadSettings(): Promise<void> {
|
// $$loadSettings(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.loadSettings
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$saveDeviceAndVaultName(): void {
|
// $$saveDeviceAndVaultName(): void {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.saveDeviceAndVaultName
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$saveSettingData(): Promise<void> {
|
// $$saveSettingData(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.saveSettingData
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
// $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // FileProcessingService.processOptionalFileEvent
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyCommitPendingFileEvent(): Promise<boolean> {
|
// $everyCommitPendingFileEvent(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// // FileProcessingService.commitPendingFileEvent
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
// ->
|
// ->
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
|
// $anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
|
||||||
return InterceptiveAny;
|
// return InterceptiveAny;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
// $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.queueCheckForConflictIfOpen
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
// $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.queueCheckForConflict
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$waitForAllConflictProcessed(): Promise<boolean> {
|
// $$waitForAllConflictProcessed(): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.ensureAllConflictProcessed
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
//<-- Conflict Check
|
//<-- Conflict Check
|
||||||
|
|
||||||
$anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
// $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processOptionalSyncFile
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
|
// $anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processReplicatedDocument
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
//---> Sync
|
//---> Sync
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
// $$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.parseSynchroniseResult
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
|
// $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processVirtualDocument
|
||||||
}
|
// return InterceptiveAny;
|
||||||
$everyBeforeRealizeSetting(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyBeforeRealizeSetting(): Promise<boolean> {
|
||||||
}
|
// // SettingEventManager.beforeRealiseSetting
|
||||||
$everyAfterRealizeSetting(): Promise<boolean> {
|
// return InterceptiveEvery;
|
||||||
return InterceptiveEvery;
|
// }
|
||||||
}
|
// $everyAfterRealizeSetting(): Promise<boolean> {
|
||||||
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
// // SettingEventManager.onSettingRealised
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
|
// $everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
|
// // SettingEventManager.onRealiseSetting
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
// $everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// // AppLifecycleService.onSuspending
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
$everyOnResumeProcess(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnResumeProcess(): Promise<boolean> {
|
||||||
}
|
// // AppLifecycleService.onResuming
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
// return InterceptiveEvery;
|
||||||
return InterceptiveEvery;
|
// }
|
||||||
}
|
// $everyAfterResumeProcess(): Promise<boolean> {
|
||||||
|
// // AppLifecycleService.onResumed
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
// $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
||||||
throwShouldBeOverridden();
|
// //TODO:TweakValueService.fetchRemotePreferred
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
|
||||||
}
|
// //TODO:TweakValueService.checkAndAskResolvingMismatched
|
||||||
$$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
||||||
|
// //TODO:TweakValueService.askResolvingMismatched
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$checkAndAskUseRemoteConfiguration(
|
// $$checkAndAskUseRemoteConfiguration(
|
||||||
settings: RemoteDBSettings
|
// settings: RemoteDBSettings
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
throwShouldBeOverridden();
|
// // TweakValueService.checkAndAskUseRemoteConfiguration
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$askUseRemoteConfiguration(
|
// $$askUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings,
|
// trialSetting: RemoteDBSettings,
|
||||||
preferred: TweakValues
|
// preferred: TweakValues
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
throwShouldBeOverridden();
|
// // TweakValueService.askUseRemoteConfiguration
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
}
|
// // ReplicationService.beforeReplicate
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
// $$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.isReplicationReady
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
// $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.replicate
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
}
|
// // ReplicationService.replicateByEvent
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
|
// $everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseEventService.onDatabaseInitialised
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$initializeDatabase(
|
// $$initializeDatabase(
|
||||||
showingNotice: boolean = false,
|
// showingNotice: boolean = false,
|
||||||
reopenDatabase = true,
|
// reopenDatabase = true,
|
||||||
ignoreSuspending: boolean = false
|
// ignoreSuspending: boolean = false
|
||||||
): Promise<boolean> {
|
// ): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseEventService.initializeDatabase
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
// $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.checkConnectionFailure
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$$replicateAllToServer(
|
// $$replicateAllToServer(
|
||||||
showingNotice: boolean = false,
|
// showingNotice: boolean = false,
|
||||||
sendChunksInBulkDisabled: boolean = false
|
// sendChunksInBulkDisabled: boolean = false
|
||||||
): Promise<boolean> {
|
// ): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.replicateAllToRemote
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
||||||
}
|
// // RemoteService.replicateAllFromRemote
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// Remote Governing
|
// Remote Governing
|
||||||
$$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
// $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markLocked;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$markRemoteUnlocked(): Promise<void> {
|
// $$markRemoteUnlocked(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markUnlocked;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$markRemoteResolved(): Promise<void> {
|
// $$markRemoteResolved(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markResolved;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// <-- Remote Governing
|
// <-- Remote Governing
|
||||||
|
|
||||||
$$isFileSizeExceeded(size: number): boolean {
|
// $$isFileSizeExceeded(size: number): boolean {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isFileSizeTooLarge
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
|
// $$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.scanVault
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyResolveConflictByUI(
|
// $anyResolveConflictByUI(
|
||||||
filename: FilePathWithPrefix,
|
// filename: FilePathWithPrefix,
|
||||||
conflictCheckResult: diff_result
|
// conflictCheckResult: diff_result
|
||||||
): Promise<boolean | undefined> {
|
// ): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ConflictService.resolveConflictByUserInteraction
|
||||||
}
|
// return InterceptiveAny;
|
||||||
$$resolveConflictByDeletingRev(
|
// }
|
||||||
path: FilePathWithPrefix,
|
// $$resolveConflictByDeletingRev(
|
||||||
deleteRevision: string,
|
// path: FilePathWithPrefix,
|
||||||
subTitle = ""
|
// deleteRevision: string,
|
||||||
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
// subTitle = ""
|
||||||
throwShouldBeOverridden();
|
// ): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
||||||
}
|
// // ConflictService.resolveByDeletingRevision
|
||||||
$$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
$anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
// // ConflictService.resolveConflict
|
||||||
throwShouldBeOverridden();
|
// throwShouldBeOverridden();
|
||||||
}
|
// }
|
||||||
|
// $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
||||||
|
// // ConflictService.resolveByNewest
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$resetLocalDatabase(): Promise<void> {
|
// $$resetLocalDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseService.resetDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$tryResetRemoteDatabase(): Promise<void> {
|
// $$tryResetRemoteDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.tryResetDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$tryCreateRemoteDatabase(): Promise<void> {
|
// $$tryCreateRemoteDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.tryCreateDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
// $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isIgnoredByIgnoreFiles
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
|
// $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isTargetFile
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$askReload(message?: string) {
|
// $$askReload(message?: string) {
|
||||||
throwShouldBeOverridden();
|
// // AppLifecycleService.askRestart
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$scheduleAppReload() {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$scheduleAppReload() {
|
||||||
}
|
// // AppLifecycleService.scheduleRestart
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
//--- Setup
|
//--- Setup
|
||||||
$allSuspendAllSync(): Promise<boolean> {
|
// $allSuspendAllSync(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// // SettingEventManager.suspendAllSync
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveAll;
|
// $allSuspendExtraSync(): Promise<boolean> {
|
||||||
}
|
// // SettingEventManager.suspendExtraSync
|
||||||
|
// return InterceptiveAll;
|
||||||
|
// }
|
||||||
|
|
||||||
$allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
// $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // SettingEventManager.suggestOptionalFeatures
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
|
||||||
}
|
// // SettingEventManager.enableOptionalFeature
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$showView(viewType: string): Promise<void> {
|
// $$showView(viewType: string): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // UIManager.showWindow //
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
||||||
$everyModuleTest(): Promise<boolean> {
|
// $everyModuleTest(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
$everyModuleTestMultiDevice(): Promise<boolean> {
|
// $everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
// $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
||||||
throwShouldBeOverridden();
|
// throwShouldBeOverridden();
|
||||||
}
|
// }
|
||||||
|
|
||||||
_isThisModuleEnabled(): boolean {
|
// _isThisModuleEnabled(): boolean {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> {
|
// $anyGetAppId(): Promise<string | undefined> {
|
||||||
return InterceptiveAny;
|
// // APIService.getAppId
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
// Plug-in's overrideable functions
|
// Plug-in's overrideable functions
|
||||||
onload() {
|
onload() {
|
||||||
void this.$$onLiveSyncLoad();
|
void this.services.appLifecycle.onLoad();
|
||||||
}
|
}
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
return void this.$$onLiveSyncUnload();
|
return void this.services.appLifecycle.onAppUnload();
|
||||||
}
|
}
|
||||||
// <-- Plug-in's overrideable functions
|
// <-- Plug-in's overrideable functions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||||
import type { LOG_LEVEL } from "../lib/src/common/types";
|
import type { LOG_LEVEL } from "../lib/src/common/types";
|
||||||
import type { LiveSyncCore } from "../main";
|
import type { LiveSyncCore } from "../main";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { __$checkInstanceBinding } from "../lib/src/dev/checks";
|
||||||
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
// import { unique } from "octagonal-wheels/collection";
|
||||||
import type {
|
// import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
||||||
ICoreModuleBase,
|
// import type {
|
||||||
AllInjectableProps,
|
// ICoreModuleBase,
|
||||||
AllExecuteProps,
|
// AllInjectableProps,
|
||||||
EveryExecuteProps,
|
// AllExecuteProps,
|
||||||
AnyExecuteProps,
|
// EveryExecuteProps,
|
||||||
ICoreModule,
|
// AnyExecuteProps,
|
||||||
} from "./ModuleTypes";
|
// ICoreModule,
|
||||||
|
// } from "./ModuleTypes";
|
||||||
|
|
||||||
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
// function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
||||||
return key.startsWith("$");
|
// return key.startsWith("$");
|
||||||
}
|
// }
|
||||||
|
|
||||||
function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
// function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
||||||
return key.startsWith("$$");
|
// return key.startsWith("$$");
|
||||||
}
|
// }
|
||||||
|
|
||||||
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
// function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
||||||
return key.startsWith("$all");
|
// return key.startsWith("$all");
|
||||||
}
|
// }
|
||||||
function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
|
// function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
|
||||||
return key.startsWith("$every");
|
// return key.startsWith("$every");
|
||||||
}
|
// }
|
||||||
function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
// function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
||||||
return key.startsWith("$any");
|
// return key.startsWith("$any");
|
||||||
}
|
// }
|
||||||
/**
|
/**
|
||||||
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
||||||
* Please refer to the module's source code to understand the function.
|
* Please refer to the module's source code to understand the function.
|
||||||
@@ -39,100 +40,100 @@ function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
|||||||
* $ : Other interceptive points. You should manually assign the module
|
* $ : Other interceptive points. You should manually assign the module
|
||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
*/
|
*/
|
||||||
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
// export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
||||||
const allKeys = unique([
|
// const allKeys = unique([
|
||||||
...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
// ...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
||||||
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
// ...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
||||||
]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
// ]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
||||||
const moduleMap = new Map<string, IObsidianModule[]>();
|
// const moduleMap = new Map<string, IObsidianModule[]>();
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
for (const key of allKeys) {
|
// for (const key of allKeys) {
|
||||||
if (isOverridableKey(key)) {
|
// if (isOverridableKey(key)) {
|
||||||
if (key in module) {
|
// if (key in module) {
|
||||||
const list = moduleMap.get(key) || [];
|
// const list = moduleMap.get(key) || [];
|
||||||
if (typeof module[key] === "function") {
|
// if (typeof module[key] === "function") {
|
||||||
module[key] = module[key].bind(module) as any;
|
// module[key] = module[key].bind(module) as any;
|
||||||
}
|
// }
|
||||||
list.push(module);
|
// list.push(module);
|
||||||
moduleMap.set(key, list);
|
// moduleMap.set(key, list);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
// Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
||||||
for (const key of allKeys) {
|
// for (const key of allKeys) {
|
||||||
const modules = moduleMap.get(key) || [];
|
// const modules = moduleMap.get(key) || [];
|
||||||
if (isInjectableKey(key)) {
|
// if (isInjectableKey(key)) {
|
||||||
if (modules.length == 0) {
|
// if (modules.length == 0) {
|
||||||
throw new Error(`No module injected for ${key}. This is a fatal error.`);
|
// throw new Error(`No module injected for ${key}. This is a fatal error.`);
|
||||||
}
|
// }
|
||||||
target[key] = modules[0][key]! as any;
|
// target[key] = modules[0][key]! as any;
|
||||||
Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
} else if (isAllExecuteKey(key)) {
|
// } else if (isAllExecuteKey(key)) {
|
||||||
const modules = moduleMap.get(key) || [];
|
// const modules = moduleMap.get(key) || [];
|
||||||
target[key] = async (...args: any) => {
|
// target[key] = async (...args: any) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
await module[key]!(...args);
|
// await module[key]!(...args);
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else if (isEveryExecuteKey(key)) {
|
// } else if (isEveryExecuteKey(key)) {
|
||||||
target[key] = async (...args: any) => {
|
// target[key] = async (...args: any) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore:2556
|
// //@ts-ignore:2556
|
||||||
const ret = await module[key]!(...args);
|
// const ret = await module[key]!(...args);
|
||||||
if (ret !== undefined && !ret) {
|
// if (ret !== undefined && !ret) {
|
||||||
// Failed then return that falsy value.
|
// // Failed then return that falsy value.
|
||||||
return ret;
|
// return ret;
|
||||||
}
|
// }
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
|
// Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else if (isAnyExecuteKey(key)) {
|
// } else if (isAnyExecuteKey(key)) {
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
target[key] = async (...args: any[]) => {
|
// target[key] = async (...args: any[]) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore:2556
|
// //@ts-ignore:2556
|
||||||
const ret = await module[key](...args);
|
// const ret = await module[key](...args);
|
||||||
// If truly value returned, then return that value.
|
// // If truly value returned, then return that value.
|
||||||
if (ret) {
|
// if (ret) {
|
||||||
return ret;
|
// return ret;
|
||||||
}
|
// }
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
|
// Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return false;
|
// return false;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
// Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export abstract class AbstractModule {
|
export abstract class AbstractModule {
|
||||||
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
@@ -153,14 +154,18 @@ export abstract class AbstractModule {
|
|||||||
this.core.settings = value;
|
this.core.settings = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// Override if needed.
|
||||||
|
}
|
||||||
constructor(public core: LiveSyncCore) {
|
constructor(public core: LiveSyncCore) {
|
||||||
|
this.onBindFunction(core, core.services);
|
||||||
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
|
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
|
||||||
|
__$checkInstanceBinding(this);
|
||||||
}
|
}
|
||||||
saveSettings = this.core.saveSettings.bind(this.core);
|
saveSettings = this.core.saveSettings.bind(this.core);
|
||||||
|
|
||||||
// abstract $everyTest(): Promise<boolean>;
|
|
||||||
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
|
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
|
||||||
this.core.$$addTestResult(`${this.constructor.name}`, key, value, summary, message);
|
this.services.test.addTestResult(`${this.constructor.name}`, key, value, summary, message);
|
||||||
}
|
}
|
||||||
testDone(result: boolean = true) {
|
testDone(result: boolean = true) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
@@ -185,4 +190,8 @@ export abstract class AbstractModule {
|
|||||||
}
|
}
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get services() {
|
||||||
|
return this.core._services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,18 +37,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.$$isReady();
|
return this.services.appLifecycle.isReady();
|
||||||
}
|
}
|
||||||
_isMainSuspended() {
|
isMainSuspended() {
|
||||||
return this.core.$$isSuspended();
|
return this.services.appLifecycle.isSuspended();
|
||||||
}
|
}
|
||||||
_isDatabaseReady() {
|
isDatabaseReady() {
|
||||||
return this.core.$$isDatabaseReady();
|
return this.services.database.isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
//should be overridden
|
//should be overridden
|
||||||
_isThisModuleEnabled() {
|
isThisModuleEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import type {
|
|||||||
DocumentID,
|
DocumentID,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
||||||
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
|
|
||||||
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
||||||
import {
|
import {
|
||||||
createBlob,
|
createBlob,
|
||||||
@@ -30,14 +29,15 @@ import {
|
|||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { ICHeader } from "../../common/types.ts";
|
import { ICHeader } from "../../common/types.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
|
export class ModuleDatabaseFileAccess extends AbstractModule implements DatabaseFileAccess {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.databaseFileAccess = this;
|
this.core.databaseFileAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyModuleTest(): Promise<boolean> {
|
private async _everyModuleTest(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
|
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
|
||||||
// Before test, we need to delete completely.
|
// Before test, we need to delete completely.
|
||||||
@@ -75,7 +75,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
|
|
||||||
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
||||||
const path = getStoragePathFromUXFileInfo(file);
|
const path = getStoragePathFromUXFileInfo(file);
|
||||||
if (!(await this.core.$$isTargetFile(path))) {
|
if (!(await this.services.vault.isTargetFile(path))) {
|
||||||
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -102,11 +102,11 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
||||||
return await this._store(file, force, skipCheck, true);
|
return await this.__store(file, force, skipCheck, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
||||||
return await this._store(file, force, skipCheck, false);
|
return await this.__store(file, force, skipCheck, false);
|
||||||
}
|
}
|
||||||
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
|
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
|
||||||
const blob = createTextBlob(content);
|
const blob = createTextBlob(content);
|
||||||
@@ -124,10 +124,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
body: blob,
|
body: blob,
|
||||||
isInternal,
|
isInternal,
|
||||||
};
|
};
|
||||||
return await this._store(dummyUXFileInfo, true, false, false);
|
return await this.__store(dummyUXFileInfo, true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _store(
|
private async __store(
|
||||||
file: UXFileInfo,
|
file: UXFileInfo,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
skipCheck?: boolean,
|
skipCheck?: boolean,
|
||||||
@@ -177,7 +177,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const idMain = await this.core.$$path2id(fullPath);
|
const idMain = await this.services.path.path2id(fullPath);
|
||||||
|
|
||||||
const id = (idPrefix + idMain) as DocumentID;
|
const id = (idPrefix + idMain) as DocumentID;
|
||||||
const d: SavingEntry = {
|
const d: SavingEntry = {
|
||||||
@@ -345,4 +345,8 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
eventHub.emitEvent(EVENT_FILE_SAVED);
|
eventHub.emitEvent(EVENT_FILE_SAVED);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.test.handleTest(this._everyModuleTest.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ import {
|
|||||||
} from "../../common/utils";
|
} from "../../common/utils";
|
||||||
import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils";
|
import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils";
|
||||||
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
export class ModuleFileHandler extends AbstractModule {
|
||||||
get db() {
|
get db() {
|
||||||
return this.core.databaseFileAccess;
|
return this.core.databaseFileAccess;
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return this.core.storageAccess;
|
return this.core.storageAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.core.fileHandler = this;
|
this.core.fileHandler = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
|
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
onlyChunks: boolean = false
|
onlyChunks: boolean = false
|
||||||
): Promise<boolean | undefined> {
|
): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -94,10 +94,14 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
let readFile: UXFileInfo | undefined = undefined;
|
let readFile: UXFileInfo | undefined = undefined;
|
||||||
if (!shouldApplied) {
|
if (!shouldApplied) {
|
||||||
readFile = await this.readFileFromStub(file);
|
readFile = await this.readFileFromStub(file);
|
||||||
|
if (!readFile) {
|
||||||
|
this._log(`File ${file.path} is not exist on the storage`, LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
|
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
|
||||||
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
||||||
// So, mark the changes are same.
|
// So, mark the changes are same.
|
||||||
markChangesAreSame(file, file.stat.mtime, entry.mtime);
|
markChangesAreSame(readFile, readFile.stat.mtime, entry.mtime);
|
||||||
} else {
|
} else {
|
||||||
shouldApplied = true;
|
shouldApplied = true;
|
||||||
}
|
}
|
||||||
@@ -125,7 +129,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean | undefined> {
|
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -222,7 +226,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
// NO OP
|
// NO OP
|
||||||
} else {
|
} else {
|
||||||
// If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved).
|
// If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved).
|
||||||
await this.core.$$queueConflictCheckIfOpen(path);
|
await this.services.conflict.queueCheckForIfOpen(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,11 +317,11 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
private async _anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean> {
|
||||||
const eventItem = item.args;
|
const eventItem = item.args;
|
||||||
const type = item.type;
|
const type = item.type;
|
||||||
const path = eventItem.file.path;
|
const path = eventItem.file.path;
|
||||||
if (!(await this.core.$$isTargetFile(path))) {
|
if (!(await this.services.vault.isTargetFile(path))) {
|
||||||
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -343,9 +347,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean | undefined> {
|
async _anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean> {
|
||||||
return await serialized(entry.path, async () => {
|
return await serialized(entry.path, async () => {
|
||||||
if (!(await this.core.$$isTargetFile(entry.path))) {
|
if (!(await this.services.vault.isTargetFile(entry.path))) {
|
||||||
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -362,7 +366,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this._log(
|
this._log(
|
||||||
`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`,
|
`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Started...`,
|
||||||
LOG_LEVEL_VERBOSE
|
LOG_LEVEL_VERBOSE
|
||||||
);
|
);
|
||||||
// Before writing (or skipped ), merging dialogue should be cancelled.
|
// Before writing (or skipped ), merging dialogue should be cancelled.
|
||||||
@@ -391,7 +395,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
};
|
};
|
||||||
const total = filesStorageSrc.length;
|
const total = filesStorageSrc.length;
|
||||||
const procAllChunks = filesStorageSrc.map(async (file) => {
|
const procAllChunks = filesStorageSrc.map(async (file) => {
|
||||||
if (!(await this.core.$$isTargetFile(file))) {
|
if (!(await this.services.vault.isTargetFile(file))) {
|
||||||
incProcessed();
|
incProcessed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -416,4 +420,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
"chunkCreation"
|
"chunkCreation"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.fileProcessing.handleProcessFileEvent(this._anyHandlerProcessesFileEvent.bind(this));
|
||||||
|
services.replication.handleProcessSynchroniseResult(this._anyProcessReplicatedDoc.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import { $msg } from "../../lib/src/common/i18n";
|
|||||||
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import { initializeStores } from "../../common/stores.ts";
|
import { initializeStores } from "../../common/stores.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts";
|
import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICoreModule {
|
export class ModuleLocalDatabaseObsidian extends AbstractModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async $$openDatabase(): Promise<boolean> {
|
private async _openDatabase(): Promise<boolean> {
|
||||||
if (this.localDatabase != null) {
|
if (this.localDatabase != null) {
|
||||||
await this.localDatabase.close();
|
await this.localDatabase.close();
|
||||||
}
|
}
|
||||||
const vaultName = this.core.$$getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
this._log($msg("moduleLocalDatabase.logWaitingForReady"));
|
this._log($msg("moduleLocalDatabase.logWaitingForReady"));
|
||||||
const getDB = () => this.core.localDatabase.localDatabase;
|
const getDB = () => this.core.localDatabase.localDatabase;
|
||||||
const getSettings = () => this.core.settings;
|
const getSettings = () => this.core.settings;
|
||||||
@@ -22,8 +22,9 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
|
|||||||
return getDB();
|
return getDB();
|
||||||
},
|
},
|
||||||
getActiveReplicator: () => this.core.replicator,
|
getActiveReplicator: () => this.core.replicator,
|
||||||
id2path: this.core.$$id2path.bind(this.core),
|
id2path: this.services.path.id2path,
|
||||||
path2id: this.core.$$path2id.bind(this.core),
|
// path2id: this.core.$$path2id.bind(this.core),
|
||||||
|
path2id: this.services.path.path2id,
|
||||||
get settings() {
|
get settings() {
|
||||||
return getSettings();
|
return getSettings();
|
||||||
},
|
},
|
||||||
@@ -34,7 +35,12 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
|
|||||||
return await this.localDatabase.initializeDatabase();
|
return await this.localDatabase.initializeDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isDatabaseReady(): boolean {
|
_isDatabaseReady(): boolean {
|
||||||
return this.localDatabase != null && this.localDatabase.isReady;
|
return this.localDatabase != null && this.localDatabase.isReady;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.database.handleIsDatabaseReady(this._isDatabaseReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.database.handleOpenDatabase(this._openDatabase.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import { PeriodicProcessor } from "../../common/utils";
|
import { PeriodicProcessor } from "../../common/utils";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
|
|
||||||
export class ModulePeriodicProcess extends AbstractModule implements ICoreModule {
|
export class ModulePeriodicProcess extends AbstractModule {
|
||||||
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.core.$$replicate());
|
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate());
|
||||||
|
|
||||||
_disablePeriodic() {
|
disablePeriodic() {
|
||||||
this.periodicSyncProcessor?.disable();
|
this.periodicSyncProcessor?.disable();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
_resumePeriodic() {
|
resumePeriodic() {
|
||||||
this.periodicSyncProcessor.enable(
|
this.periodicSyncProcessor.enable(
|
||||||
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
|
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
|
||||||
);
|
);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnUnload() {
|
private _allOnUnload() {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyBeforeRealizeSetting(): Promise<boolean> {
|
private _everyBeforeRealizeSetting(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
private _everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
private _everyAfterResumeProcess(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this.resumePeriodic();
|
||||||
}
|
}
|
||||||
$everyAfterRealizeSetting(): Promise<boolean> {
|
private _everyAfterRealizeSetting(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this.resumePeriodic();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnUnload(this._allOnUnload.bind(this));
|
||||||
|
services.setting.handleBeforeRealiseSetting(this._everyBeforeRealizeSetting.bind(this));
|
||||||
|
services.setting.handleSettingRealised(this._everyAfterRealizeSetting.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
export class ModulePouchDB extends AbstractModule {
|
||||||
$$createPouchDBInstance<T extends object>(
|
_createPouchDBInstance<T extends object>(
|
||||||
name?: string,
|
name?: string,
|
||||||
options?: PouchDB.Configuration.DatabaseConfiguration
|
options?: PouchDB.Configuration.DatabaseConfiguration
|
||||||
): PouchDB.Database<T> {
|
): PouchDB.Database<T> {
|
||||||
@@ -16,4 +16,7 @@ export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return new PouchDB(name, optionPass);
|
return new PouchDB(name, optionPass);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.database.handleCreatePouchDBInstance(this._createPouchDBInstance.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import {
|
|||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
||||||
import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts";
|
import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts";
|
||||||
import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts";
|
import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebuilder {
|
export class ModuleRebuilder extends AbstractModule implements Rebuilder {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.rebuilder = this;
|
this.core.rebuilder = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -43,47 +43,47 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
|
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
|
||||||
)) == "yes"
|
)) == "yes"
|
||||||
) {
|
) {
|
||||||
await this.core.$allAskUsingOptionalSyncFeature(opt);
|
await this.services.setting.suggestOptionalFeatures(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async rebuildRemote() {
|
async rebuildRemote() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
|
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.services.remote.tryResetDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.services.remote.replicateAllToRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true, true);
|
await this.services.remote.replicateAllToRemote(true, true);
|
||||||
}
|
}
|
||||||
$rebuildRemote(): Promise<void> {
|
$rebuildRemote(): Promise<void> {
|
||||||
return this.rebuildRemote();
|
return this.rebuildRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
async rebuildEverything() {
|
async rebuildEverything() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$initializeDatabase(true, true, true);
|
await this.services.databaseEvents.initialiseDatabase(true, true, true);
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.services.remote.tryResetDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
// We do not have any other devices' data, so we do not need to ask for overwriting.
|
// We do not have any other devices' data, so we do not need to ask for overwriting.
|
||||||
await this.askUsingOptionalFeature({ enableOverwrite: false });
|
await this.askUsingOptionalFeature({ enableOverwrite: false });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.services.remote.replicateAllToRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true, true);
|
await this.services.remote.replicateAllToRemote(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rebuildEverything(): Promise<void> {
|
$rebuildEverything(): Promise<void> {
|
||||||
@@ -101,7 +101,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE);
|
this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
async scheduleFetch(): Promise<void> {
|
async scheduleFetch(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -110,20 +110,20 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE);
|
this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$tryResetRemoteDatabase(): Promise<void> {
|
private async _tryResetRemoteDatabase(): Promise<void> {
|
||||||
await this.core.replicator.tryResetRemoteDatabase(this.settings);
|
await this.core.replicator.tryResetRemoteDatabase(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$tryCreateRemoteDatabase(): Promise<void> {
|
private async _tryCreateRemoteDatabase(): Promise<void> {
|
||||||
await this.core.replicator.tryCreateRemoteDatabase(this.settings);
|
await this.core.replicator.tryCreateRemoteDatabase(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$resetLocalDatabase(): Promise<void> {
|
private async _resetLocalDatabase(): Promise<boolean> {
|
||||||
this.core.storageAccess.clearTouched();
|
this.core.storageAccess.clearTouched();
|
||||||
await this.localDatabase.resetDatabase();
|
return await this.localDatabase.resetDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
async suspendAllSync() {
|
async suspendAllSync() {
|
||||||
@@ -134,7 +134,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this.core.settings.syncOnStart = false;
|
this.core.settings.syncOnStart = false;
|
||||||
this.core.settings.syncOnFileOpen = false;
|
this.core.settings.syncOnFileOpen = false;
|
||||||
this.core.settings.syncAfterMerge = false;
|
this.core.settings.syncAfterMerge = false;
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
}
|
}
|
||||||
async suspendReflectingDatabase() {
|
async suspendReflectingDatabase() {
|
||||||
if (this.core.settings.doNotSuspendOnFetching) return;
|
if (this.core.settings.doNotSuspendOnFetching) return;
|
||||||
@@ -153,8 +153,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE);
|
this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE);
|
||||||
this.core.settings.suspendParseReplicationResult = false;
|
this.core.settings.suspendParseReplicationResult = false;
|
||||||
this.core.settings.suspendFileWatching = false;
|
this.core.settings.suspendFileWatching = false;
|
||||||
await this.core.$$performFullScan(true);
|
await this.services.vault.scanVault(true);
|
||||||
await this.core.$everyBeforeReplicate(false); //TODO: Check actual need of this.
|
await this.services.replication.onBeforeReplicate(false); //TODO: Check actual need of this.
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
}
|
}
|
||||||
async askUseNewAdapter() {
|
async askUseNewAdapter() {
|
||||||
@@ -177,28 +177,28 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
|
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
await this.suspendReflectingDatabase();
|
await this.suspendReflectingDatabase();
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$openDatabase();
|
await this.services.database.openDatabase();
|
||||||
// this.core.isReady = true;
|
// this.core.isReady = true;
|
||||||
this.core.$$markIsReady();
|
this.services.appLifecycle.markIsReady();
|
||||||
if (makeLocalChunkBeforeSync) {
|
if (makeLocalChunkBeforeSync) {
|
||||||
await this.core.fileHandler.createAllChunks(true);
|
await this.core.fileHandler.createAllChunks(true);
|
||||||
} else if (!preventMakeLocalFilesBeforeSync) {
|
} else if (!preventMakeLocalFilesBeforeSync) {
|
||||||
await this.core.$$initializeDatabase(true, true, true);
|
await this.services.databaseEvents.initialiseDatabase(true, true, true);
|
||||||
} else {
|
} else {
|
||||||
// Do not create local file entries before sync (Means use remote information)
|
// Do not create local file entries before sync (Means use remote information)
|
||||||
}
|
}
|
||||||
await this.core.$$markRemoteResolved();
|
await this.services.remote.markResolved();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.services.remote.replicateAllFromRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.services.remote.replicateAllFromRemote(true);
|
||||||
await this.resumeReflectingDatabase();
|
await this.resumeReflectingDatabase();
|
||||||
await this.askUsingOptionalFeature({ enableFetch: true });
|
await this.askUsingOptionalFeature({ enableFetch: true });
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
return await this.fetchLocal(true);
|
return await this.fetchLocal(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allSuspendAllSync(): Promise<boolean> {
|
private async _allSuspendAllSync(): Promise<boolean> {
|
||||||
await this.suspendAllSync();
|
await this.suspendAllSync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -214,11 +214,11 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
async resetLocalDatabase() {
|
async resetLocalDatabase() {
|
||||||
if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") {
|
if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") {
|
||||||
// Discard the non-suffixed database
|
// Discard the non-suffixed database
|
||||||
await this.core.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
}
|
}
|
||||||
const suffix = (await this.core.$anyGetAppId()) || "";
|
const suffix = this.services.API.getAppID() || "";
|
||||||
this.core.settings.additionalSuffixOfDatabaseName = suffix;
|
this.core.settings.additionalSuffixOfDatabaseName = suffix;
|
||||||
await this.core.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
eventHub.emitEvent(EVENT_DATABASE_REBUILT);
|
eventHub.emitEvent(EVENT_DATABASE_REBUILT);
|
||||||
}
|
}
|
||||||
async fetchRemoteChunks() {
|
async fetchRemoteChunks() {
|
||||||
@@ -228,10 +228,10 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this.core.settings.remoteType == REMOTE_COUCHDB
|
this.core.settings.remoteType == REMOTE_COUCHDB
|
||||||
) {
|
) {
|
||||||
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
||||||
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
|
const replicator = this.services.replicator.getActiveReplicator() as LiveSyncCouchDBReplicator;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
this.settings,
|
this.settings,
|
||||||
this.core.$$isMobile(),
|
this.services.API.isMobile(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
@@ -254,8 +254,15 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
"resolveAllConflictedFilesByNewerOnes"
|
"resolveAllConflictedFilesByNewerOnes"
|
||||||
);
|
);
|
||||||
await this.core.$anyResolveConflictByNewest(file);
|
await this.services.conflict.resolveByNewest(file);
|
||||||
}
|
}
|
||||||
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.database.handleResetDatabase(this._resetLocalDatabase.bind(this));
|
||||||
|
services.remote.handleTryResetDatabase(this._tryResetRemoteDatabase.bind(this));
|
||||||
|
services.remote.handleTryCreateDatabase(this._tryCreateRemoteDatabase.bind(this));
|
||||||
|
services.setting.handleSuspendAllSync(this._allSuspendAllSync.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
|
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
|
||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
|
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
|
||||||
@@ -34,17 +33,18 @@ import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveS
|
|||||||
|
|
||||||
import { $msg } from "../../lib/src/common/i18n";
|
import { $msg } from "../../lib/src/common/i18n";
|
||||||
import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler";
|
import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
|
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
|
||||||
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
|
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
|
||||||
|
|
||||||
export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
export class ModuleReplicator extends AbstractModule {
|
||||||
_replicatorType?: RemoteType;
|
_replicatorType?: RemoteType;
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
||||||
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
|
if (this.settings.syncOnSave && !this.core.services.appLifecycle.isSuspended()) {
|
||||||
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent());
|
scheduleTask("perform-replicate-after-save", 250, () => this.services.replication.replicateByEvent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
|
||||||
@@ -57,7 +57,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setReplicator() {
|
async setReplicator() {
|
||||||
const replicator = await this.core.$anyNewReplicator();
|
const replicator = await this.services.replicator.getNewReplicator();
|
||||||
if (!replicator) {
|
if (!replicator) {
|
||||||
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -74,24 +74,28 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$getReplicator(): LiveSyncAbstractReplicator {
|
_getReplicator(): LiveSyncAbstractReplicator {
|
||||||
return this.core.replicator;
|
return this.core.replicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.setReplicator();
|
return this.setReplicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.setReplicator();
|
return this.setReplicator();
|
||||||
}
|
}
|
||||||
async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise<boolean> {
|
async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise<boolean> {
|
||||||
// Checking salt
|
// Checking salt
|
||||||
const replicator = this.core.$$getReplicator();
|
const replicator = this.services.replicator.getActiveReplicator();
|
||||||
|
if (!replicator) {
|
||||||
|
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
|
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
// Checking salt
|
// Checking salt
|
||||||
if (!this.core.managers.networkManager.isOnline) {
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
@@ -106,7 +110,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
private async _replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
try {
|
try {
|
||||||
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
|
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
|
||||||
return await this.$$_replicate(showMessage);
|
return await this.$$_replicate(showMessage);
|
||||||
@@ -143,11 +147,11 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
await this.core.rebuilder.$performRebuildDB("localOnly");
|
await this.core.rebuilder.$performRebuildDB("localOnly");
|
||||||
}
|
}
|
||||||
if (ret == CHOICE_CLEAN) {
|
if (ret == CHOICE_CLEAN) {
|
||||||
const replicator = this.core.$$getReplicator();
|
const replicator = this.services.replicator.getActiveReplicator();
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
this.settings,
|
this.settings,
|
||||||
this.core.$$isMobile(),
|
this.services.API.isMobile(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
@@ -162,7 +166,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
||||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||||
this.localDatabase.clearCaches();
|
this.localDatabase.clearCaches();
|
||||||
await this.core.$$getReplicator().markRemoteResolved(this.settings);
|
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
|
||||||
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
} else {
|
} else {
|
||||||
Logger(
|
Logger(
|
||||||
@@ -174,8 +178,8 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
async _canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) {
|
if (!this.services.appLifecycle.isReady()) {
|
||||||
Logger(`Not ready`);
|
Logger(`Not ready`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -190,7 +194,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await this.core.$everyCommitPendingFileEvent())) {
|
if (!(await this.services.fileProcessing.commitPendingFileEvents())) {
|
||||||
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -199,7 +203,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
|
if (!(await this.services.replication.onBeforeReplicate(showMessage))) {
|
||||||
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -207,14 +211,14 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
const checkBeforeReplicate = await this.$$canReplicate(showMessage);
|
const checkBeforeReplicate = await this.services.replication.isReplicationReady(showMessage);
|
||||||
if (!checkBeforeReplicate) return false;
|
if (!checkBeforeReplicate) return false;
|
||||||
|
|
||||||
//<-- Here could be an module.
|
//<-- Here could be an module.
|
||||||
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) {
|
if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) {
|
||||||
await this.core.$$askResolvingMismatchedTweaks(this.core.replicator.preferredTweakValue);
|
await this.services.tweakValue.askResolvingMismatched(this.core.replicator.preferredTweakValue);
|
||||||
} else {
|
} else {
|
||||||
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
||||||
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
||||||
@@ -236,7 +240,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
if (ret == CHOICE_FETCH) {
|
if (ret == CHOICE_FETCH) {
|
||||||
this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE);
|
this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE);
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return;
|
||||||
} else if (ret == CHOICE_UNLOCK) {
|
} else if (ret == CHOICE_UNLOCK) {
|
||||||
await this.core.replicator.markRemoteResolved(this.settings);
|
await this.core.replicator.markRemoteResolved(this.settings);
|
||||||
@@ -250,16 +254,16 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateByEvent(): Promise<boolean | void> {
|
private async _replicateByEvent(): Promise<boolean | void> {
|
||||||
const least = this.settings.syncMinimumInterval;
|
const least = this.settings.syncMinimumInterval;
|
||||||
if (least > 0) {
|
if (least > 0) {
|
||||||
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
|
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
|
||||||
return await this.$$replicate();
|
return await this.services.replication.replicate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return await shareRunningResult(`replication`, () => this.core.$$replicate());
|
return await shareRunningResult(`replication`, () => this.services.replication.replicate());
|
||||||
}
|
}
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
_parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
||||||
this.replicationResultProcessor.suspend();
|
this.replicationResultProcessor.suspend();
|
||||||
}
|
}
|
||||||
@@ -336,7 +340,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
this.localDatabase.onNewLeaf(change as EntryLeaf);
|
this.localDatabase.onNewLeaf(change as EntryLeaf);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await this.core.$anyModuleParsedReplicationResultItem(change)) return;
|
if (await this.services.replication.processVirtualDocument(change)) return;
|
||||||
// any addon needs this item?
|
// any addon needs this item?
|
||||||
// for (const proc of this.core.addOns) {
|
// for (const proc of this.core.addOns) {
|
||||||
// if (await proc.parseReplicationResultItem(change)) {
|
// if (await proc.parseReplicationResultItem(change)) {
|
||||||
@@ -361,7 +365,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
if (isAnyNote(change)) {
|
if (isAnyNote(change)) {
|
||||||
const docPath = getPath(change);
|
const docPath = getPath(change);
|
||||||
if (!(await this.core.$$isTargetFile(docPath))) {
|
if (!(await this.services.vault.isTargetFile(docPath))) {
|
||||||
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -369,7 +373,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
Logger(`Processing scheduled: ${docPath}`, 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.services.vault.isFileSizeTooLarge(size)) {
|
||||||
Logger(
|
Logger(
|
||||||
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
|
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
@@ -413,7 +417,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.core.$anyProcessOptionalSyncFiles(dbDoc)) {
|
if (await this.services.replication.processOptionalSynchroniseResult(dbDoc)) {
|
||||||
// Already processed
|
// Already processed
|
||||||
} else if (isValidPath(getPath(doc))) {
|
} else if (isValidPath(getPath(doc))) {
|
||||||
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
||||||
@@ -440,7 +444,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
storageApplyingProcessor = new QueueProcessor(
|
storageApplyingProcessor = new QueueProcessor(
|
||||||
async (docs: MetaEntry[]) => {
|
async (docs: MetaEntry[]) => {
|
||||||
const entry = docs[0];
|
const entry = docs[0];
|
||||||
await this.core.$anyProcessReplicatedDoc(entry);
|
await this.services.replication.processSynchroniseResult(entry);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -458,17 +462,17 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
})
|
})
|
||||||
.startPipeline();
|
.startPipeline();
|
||||||
|
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
_everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
this.core.replicator.closeReplication();
|
this.core.replicator?.closeReplication();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateAllToServer(
|
private async _replicateAllToServer(
|
||||||
showingNotice: boolean = false,
|
showingNotice: boolean = false,
|
||||||
sendChunksInBulkDisabled: boolean = false
|
sendChunksInBulkDisabled: boolean = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return false;
|
if (!this.services.appLifecycle.isReady()) return false;
|
||||||
if (!(await this.core.$everyBeforeReplicate(showingNotice))) {
|
if (!(await this.services.replication.onBeforeReplicate(showingNotice))) {
|
||||||
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -486,16 +490,31 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
||||||
if (ret) return true;
|
if (ret) return true;
|
||||||
const checkResult = await this.core.$anyAfterConnectCheckFailed();
|
const checkResult = await this.services.replication.checkConnectionFailure();
|
||||||
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllToServer(showingNotice);
|
if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice);
|
||||||
return !checkResult;
|
return !checkResult;
|
||||||
}
|
}
|
||||||
async $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
async _replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return false;
|
if (!this.services.appLifecycle.isReady()) return false;
|
||||||
const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice);
|
const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice);
|
||||||
if (ret) return true;
|
if (ret) return true;
|
||||||
const checkResult = await this.core.$anyAfterConnectCheckFailed();
|
const checkResult = await this.services.replication.checkConnectionFailure();
|
||||||
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllFromServer(showingNotice);
|
if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice);
|
||||||
return !checkResult;
|
return !checkResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetActiveReplicator(this._getReplicator.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.replication.handleParseSynchroniseResult(this._parseReplicationResult.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.replication.handleIsReplicationReady(this._canReplicate.bind(this));
|
||||||
|
services.replication.handleReplicate(this._replicate.bind(this));
|
||||||
|
services.replication.handleReplicateByEvent(this._replicateByEvent.bind(this));
|
||||||
|
services.remote.handleReplicateAllToRemote(this._replicateAllToServer.bind(this));
|
||||||
|
services.remote.handleReplicateAllFromRemote(this._replicateAllFromServer.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/c
|
|||||||
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorCouchDB extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
||||||
if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) {
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (!this.core.$$isSuspended) return Promise.resolve(true);
|
if (!this.services.appLifecycle.isSuspended()) return Promise.resolve(true);
|
||||||
if (!this.core.$$isReady) return Promise.resolve(true);
|
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
|
||||||
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
||||||
const LiveSyncEnabled = this.settings.liveSync;
|
const LiveSyncEnabled = this.settings.liveSync;
|
||||||
const continuous = LiveSyncEnabled;
|
const continuous = LiveSyncEnabled;
|
||||||
@@ -27,7 +27,7 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
|
|||||||
// And note that we do not open the conflict detection dialogue directly during this process.
|
// And note that we do not open the conflict detection dialogue directly during this process.
|
||||||
// This should be raised explicitly if needed.
|
// This should be raised explicitly if needed.
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
const canReplicate = await this.core.$$canReplicate(false);
|
const canReplicate = await this.services.replication.isReplicationReady(false);
|
||||||
if (!canReplicate) return;
|
if (!canReplicate) return;
|
||||||
void this.core.replicator.openReplication(this.settings, continuous, false, false);
|
void this.core.replicator.openReplication(this.settings, continuous, false, false);
|
||||||
});
|
});
|
||||||
@@ -36,4 +36,8 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
|
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||||
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
|
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
|
|
||||||
export class ModuleReplicatorMinIO extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorMinIO extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_MINIO) {
|
if (settings.remoteType == REMOTE_MINIO) {
|
||||||
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
|
import { REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorP2P extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
return Promise.resolve(new LiveSyncTrysteroReplicator(this.core));
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.core));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (this.settings.remoteType == REMOTE_P2P) {
|
if (this.settings.remoteType == REMOTE_P2P) {
|
||||||
// // If LiveSync enabled, open replication
|
// // If LiveSync enabled, open replication
|
||||||
// if (this.settings.liveSync) {
|
// if (this.settings.liveSync) {
|
||||||
@@ -27,4 +27,8 @@ export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ import {
|
|||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
|
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
import { isDirty } from "../../lib/src/common/utils";
|
import { isDirty } from "../../lib/src/common/utils";
|
||||||
export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
export class ModuleTargetFilter extends AbstractModule {
|
||||||
reloadIgnoreFiles() {
|
reloadIgnoreFiles() {
|
||||||
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
|
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
||||||
this.reloadIgnoreFiles();
|
this.reloadIgnoreFiles();
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
_id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
||||||
const tempId = id2path(id, entry);
|
const tempId = id2path(id, entry);
|
||||||
if (stripPrefix && isInternalMetadata(tempId)) {
|
if (stripPrefix && isInternalMetadata(tempId)) {
|
||||||
const out = stripInternalMetadataPrefix(tempId);
|
const out = stripInternalMetadataPrefix(tempId);
|
||||||
@@ -43,7 +43,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return tempId;
|
return tempId;
|
||||||
}
|
}
|
||||||
async $$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
async _path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
const destPath = addPrefix(filename, prefix ?? "");
|
const destPath = addPrefix(filename, prefix ?? "");
|
||||||
return await path2id(
|
return await path2id(
|
||||||
destPath,
|
destPath,
|
||||||
@@ -52,7 +52,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isFileSizeExceeded(size: number) {
|
private _isFileSizeExceeded(size: number) {
|
||||||
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
||||||
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
||||||
return true;
|
return true;
|
||||||
@@ -61,7 +61,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void {
|
_markFileListPossiblyChanged(): void {
|
||||||
this.totalFileEventCount++;
|
this.totalFileEventCount++;
|
||||||
}
|
}
|
||||||
totalFileEventCount = 0;
|
totalFileEventCount = 0;
|
||||||
@@ -72,7 +72,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
private async _isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
||||||
const fileCount = useMemo<Record<string, number>>(
|
const fileCount = useMemo<Record<string, number>>(
|
||||||
{
|
{
|
||||||
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
||||||
@@ -109,7 +109,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
const filepath = getStoragePathFromUXFileInfo(file);
|
const filepath = getStoragePathFromUXFileInfo(file);
|
||||||
const lc = filepath.toLowerCase();
|
const lc = filepath.toLowerCase();
|
||||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
if (this.services.setting.shouldCheckCaseInsensitively()) {
|
||||||
if (lc in fileCount && fileCount[lc] > 1) {
|
if (lc in fileCount && fileCount[lc] > 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
// We must reload ignore files due to the its change.
|
// We must reload ignore files due to the its change.
|
||||||
await this.readIgnoreFile(filepath);
|
await this.readIgnoreFile(filepath);
|
||||||
}
|
}
|
||||||
if (await this.core.$$isIgnoredByIgnoreFiles(file)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return await this.readIgnoreFile(path);
|
return await this.readIgnoreFile(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
private async _isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
||||||
if (!this.settings.useIgnoreFiles) {
|
if (!this.settings.useIgnoreFiles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -164,4 +164,14 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.vault.handleMarkFileListPossiblyChanged(this._markFileListPossiblyChanged.bind(this));
|
||||||
|
services.path.handleId2Path(this._id2path.bind(this));
|
||||||
|
services.path.handlePath2Id(this._path2id.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.vault.handleIsFileSizeTooLarge(this._isFileSizeExceeded.bind(this));
|
||||||
|
services.vault.handleIsIgnoredByIgnoreFile(this._isIgnoredByIgnoreFiles.bind(this));
|
||||||
|
services.vault.handleIsTargetFile(this._isTargetFile.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
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 type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
export class ModuleCheckRemoteSize extends AbstractModule {
|
||||||
async $allScanStat(): Promise<boolean> {
|
async _allScanStat(): Promise<boolean> {
|
||||||
if (this.core.managers.networkManager.isOnline === false) {
|
if (this.core.managers.networkManager.isOnline === false) {
|
||||||
this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO);
|
this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO);
|
||||||
return true;
|
return true;
|
||||||
@@ -109,4 +109,7 @@ export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,36 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
|
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleConflictChecker extends AbstractModule implements ICoreModule {
|
export class ModuleConflictChecker extends AbstractModule {
|
||||||
async $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
const path = file;
|
const path = file;
|
||||||
if (this.settings.checkConflictOnlyOnOpen) {
|
if (this.settings.checkConflictOnlyOnOpen) {
|
||||||
const af = this.core.$$getActiveFilePath();
|
const af = this.services.vault.getActiveFilePath();
|
||||||
if (af && af != path) {
|
if (af && af != path) {
|
||||||
this._log(`${file} is conflicted, merging process has been postponed.`, LOG_LEVEL_NOTICE);
|
this._log(`${file} is conflicted, merging process has been postponed.`, LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.core.$$queueConflictCheck(path);
|
await this.services.conflict.queueCheckFor(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
async _queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
||||||
const optionalConflictResult = await this.core.$anyGetOptionalConflictCheckMethod(file);
|
const optionalConflictResult = await this.services.conflict.getOptionalConflictCheckMethod(file);
|
||||||
if (optionalConflictResult == true) {
|
if (optionalConflictResult == true) {
|
||||||
// The conflict has been resolved by another process.
|
// The conflict has been resolved by another process.
|
||||||
return;
|
return;
|
||||||
} else if (optionalConflictResult === "newer") {
|
} else if (optionalConflictResult === "newer") {
|
||||||
// The conflict should be resolved by the newer entry.
|
// The conflict should be resolved by the newer entry.
|
||||||
await this.core.$anyResolveConflictByNewest(file);
|
await this.services.conflict.resolveByNewest(file);
|
||||||
} else {
|
} else {
|
||||||
this.conflictCheckQueue.enqueue(file);
|
this.conflictCheckQueue.enqueue(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$$waitForAllConflictProcessed(): Promise<boolean> {
|
_waitForAllConflictProcessed(): Promise<boolean> {
|
||||||
return this.conflictResolveQueue.waitForAllProcessed();
|
return this.conflictResolveQueue.waitForAllProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
|
|||||||
conflictResolveQueue = new QueueProcessor(
|
conflictResolveQueue = new QueueProcessor(
|
||||||
async (filenames: FilePathWithPrefix[]) => {
|
async (filenames: FilePathWithPrefix[]) => {
|
||||||
const filename = filenames[0];
|
const filename = filenames[0];
|
||||||
return await this.core.$$resolveConflict(filename);
|
return await this.services.conflict.resolve(filename);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suspended: false,
|
suspended: false,
|
||||||
@@ -73,4 +74,9 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
|
|||||||
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
|
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.conflict.handleQueueCheckForIfOpen(this._queueConflictCheckIfOpen.bind(this));
|
||||||
|
services.conflict.handleQueueCheckFor(this._queueConflictCheck.bind(this));
|
||||||
|
services.conflict.handleEnsureAllProcessed(this._waitForAllConflictProcessed.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ import {
|
|||||||
} from "../../common/utils";
|
} from "../../common/utils";
|
||||||
import diff_match_patch from "diff-match-patch";
|
import diff_match_patch from "diff-match-patch";
|
||||||
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
@@ -29,8 +30,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleConflictResolver extends AbstractModule implements ICoreModule {
|
export class ModuleConflictResolver extends AbstractModule {
|
||||||
async $$resolveConflictByDeletingRev(
|
private async _resolveConflictByDeletingRev(
|
||||||
path: FilePathWithPrefix,
|
path: FilePathWithPrefix,
|
||||||
deleteRevision: string,
|
deleteRevision: string,
|
||||||
subTitle = ""
|
subTitle = ""
|
||||||
@@ -82,7 +83,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, ret.conflictedRev, "Sensible");
|
return await this.services.conflict.resolveByDeletingRevision(path, ret.conflictedRev, "Sensible");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rightRev, leftLeaf, rightLeaf } = ret;
|
const { rightRev, leftLeaf, rightLeaf } = ret;
|
||||||
@@ -95,7 +96,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
if (rightLeaf == false) {
|
if (rightLeaf == false) {
|
||||||
// Conflicted item could not load, delete this.
|
// Conflicted item could not load, delete this.
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, rightRev, "MISSING OLD REV");
|
return await this.services.conflict.resolveByDeletingRevision(path, rightRev, "MISSING OLD REV");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSame = leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted;
|
const isSame = leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted;
|
||||||
@@ -115,7 +116,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
]
|
]
|
||||||
.filter((e) => e.trim())
|
.filter((e) => e.trim())
|
||||||
.join(",");
|
.join(",");
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
return await this.services.conflict.resolveByDeletingRevision(path, loser.rev, subTitle);
|
||||||
}
|
}
|
||||||
// make diff.
|
// make diff.
|
||||||
const dmp = new diff_match_patch();
|
const dmp = new diff_match_patch();
|
||||||
@@ -129,7 +130,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
private async _resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
// const filename = filenames[0];
|
// const filename = filenames[0];
|
||||||
return await serialized(`conflict-resolve:${filename}`, async () => {
|
return await serialized(`conflict-resolve:${filename}`, async () => {
|
||||||
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
||||||
@@ -144,16 +145,16 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
if (conflictCheckResult === AUTO_MERGED) {
|
if (conflictCheckResult === AUTO_MERGED) {
|
||||||
//auto resolved, but need check again;
|
//auto resolved, but need check again;
|
||||||
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
|
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
|
||||||
//Wait for the running replication, if not running replication, run it once.
|
//Wait for the running replication, if not running replication, run it once.
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
this._log("[conflict] Automatically merged, but we have to check it again");
|
this._log("[conflict] Automatically merged, but we have to check it again");
|
||||||
await this.core.$$queueConflictCheck(filename);
|
await this.services.conflict.queueCheckFor(filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings.showMergeDialogOnlyOnActive) {
|
if (this.settings.showMergeDialogOnlyOnActive) {
|
||||||
const af = this.core.$$getActiveFilePath();
|
const af = this.services.vault.getActiveFilePath();
|
||||||
if (af && af != filename) {
|
if (af && af != filename) {
|
||||||
this._log(
|
this._log(
|
||||||
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
||||||
@@ -164,11 +165,11 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
this._log("[conflict] Manual merge required!");
|
this._log("[conflict] Manual merge required!");
|
||||||
eventHub.emitEvent("conflict-cancelled", filename);
|
eventHub.emitEvent("conflict-cancelled", filename);
|
||||||
await this.core.$anyResolveConflictByUI(filename, conflictCheckResult);
|
await this.services.conflict.resolveByUserInteraction(filename, conflictCheckResult);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
private async _anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
||||||
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
|
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
|
||||||
if (currentRev == false) {
|
if (currentRev == false) {
|
||||||
this._log(`Could not get current revision of ${filename}`);
|
this._log(`Could not get current revision of ${filename}`);
|
||||||
@@ -206,8 +207,14 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
this._log(
|
this._log(
|
||||||
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
|
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
|
||||||
);
|
);
|
||||||
await this.core.$$resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST");
|
await this.services.conflict.resolveByDeletingRevision(filename, mTimeAndRev[i][1], "NEWEST");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.conflict.handleResolveByDeletingRevision(this._resolveConflictByDeletingRev.bind(this));
|
||||||
|
services.conflict.handleResolve(this._resolveConflict.bind(this));
|
||||||
|
services.conflict.handleResolveByNewest(this._anyResolveConflictByNewest.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
export class ModuleRedFlag extends AbstractModule {
|
||||||
async isFlagFileExist(path: string) {
|
async isFlagFileExist(path: string) {
|
||||||
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
||||||
if (redflag) {
|
if (redflag) {
|
||||||
@@ -48,7 +48,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
await this.deleteFlagFile(FLAGMD_REDFLAG3);
|
await this.deleteFlagFile(FLAGMD_REDFLAG3);
|
||||||
await this.deleteFlagFile(FLAGMD_REDFLAG3_HR);
|
await this.deleteFlagFile(FLAGMD_REDFLAG3_HR);
|
||||||
}
|
}
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const isRedFlagRaised = await this.isRedFlagRaised();
|
const isRedFlagRaised = await this.isRedFlagRaised();
|
||||||
const isRedFlag2Raised = await this.isRedFlag2Raised();
|
const isRedFlag2Raised = await this.isRedFlag2Raised();
|
||||||
@@ -63,7 +63,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
)) !== "yes"
|
)) !== "yes"
|
||||||
) {
|
) {
|
||||||
await this.deleteRedFlag2();
|
await this.deleteRedFlag2();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,13 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
})) !== "yes"
|
})) !== "yes"
|
||||||
) {
|
) {
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.settings.batchSave = false;
|
this.settings.batchSave = false;
|
||||||
await this.core.$allSuspendAllSync();
|
await this.services.setting.suspendAllSync();
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.settings.suspendFileWatching = true;
|
this.settings.suspendFileWatching = true;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
if (isRedFlag2Raised) {
|
if (isRedFlag2Raised) {
|
||||||
@@ -99,7 +99,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
) {
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (isRedFlag3Raised) {
|
} else if (isRedFlag3Raised) {
|
||||||
@@ -148,7 +148,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
if (fetchRemote === optionFetchRemoteConf) {
|
if (fetchRemote === optionFetchRemoteConf) {
|
||||||
this._log("Fetching remote configuration", LOG_LEVEL_NOTICE);
|
this._log("Fetching remote configuration", LOG_LEVEL_NOTICE);
|
||||||
const newSettings = JSON.parse(JSON.stringify(this.core.settings)) as ObsidianLiveSyncSettings;
|
const newSettings = JSON.parse(JSON.stringify(this.core.settings)) as ObsidianLiveSyncSettings;
|
||||||
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
|
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
|
||||||
if (remoteConfig) {
|
if (remoteConfig) {
|
||||||
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
||||||
const mergedSettings = {
|
const mergedSettings = {
|
||||||
@@ -174,7 +174,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
) {
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -198,4 +198,8 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
|
|
||||||
export class ModuleRemoteGovernor extends AbstractModule implements ICoreModule {
|
export class ModuleRemoteGovernor extends AbstractModule {
|
||||||
async $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
private async _markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean);
|
return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$markRemoteUnlocked(): Promise<void> {
|
private async _markRemoteUnlocked(): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteLocked(this.settings, false, false);
|
return await this.core.replicator.markRemoteLocked(this.settings, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$markRemoteResolved(): Promise<void> {
|
private async _markRemoteResolved(): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteResolved(this.settings);
|
return await this.core.replicator.markRemoteResolved(this.settings);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.remote.handleMarkLocked(this._markRemoteLocked.bind(this));
|
||||||
|
services.remote.handleMarkUnlocked(this._markRemoteUnlocked.bind(this));
|
||||||
|
services.remote.handleMarkResolved(this._markRemoteResolved.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,22 @@ import {
|
|||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleResolvingMismatchedTweaks extends AbstractModule implements ICoreModule {
|
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||||
async $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
||||||
const preferred = this.core.replicator.preferredTweakValue;
|
const preferred = this.core.replicator.preferredTweakValue;
|
||||||
if (!preferred) return false;
|
if (!preferred) return false;
|
||||||
const ret = await this.core.$$askResolvingMismatchedTweaks(preferred);
|
const ret = await this.services.tweakValue.askResolvingMismatched(preferred);
|
||||||
if (ret == "OK") return false;
|
if (ret == "OK") return false;
|
||||||
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
|
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
|
||||||
if (ret == "IGNORE") return true;
|
if (ret == "IGNORE") return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$checkAndAskResolvingMismatchedTweaks(
|
async _checkAndAskResolvingMismatchedTweaks(
|
||||||
preferred: Partial<TweakValues>
|
preferred: Partial<TweakValues>
|
||||||
): Promise<[TweakValues | boolean, boolean]> {
|
): Promise<[TweakValues | boolean, boolean]> {
|
||||||
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
||||||
@@ -127,7 +128,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return CHOICES[retKey];
|
return CHOICES[retKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
async _askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
||||||
if (!this.core.replicator.tweakSettingsMismatched) {
|
if (!this.core.replicator.tweakSettingsMismatched) {
|
||||||
return "OK";
|
return "OK";
|
||||||
}
|
}
|
||||||
@@ -137,7 +138,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
}
|
}
|
||||||
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
||||||
|
|
||||||
const [conf, rebuildRequired] = await this.core.$$checkAndAskResolvingMismatchedTweaks(preferred);
|
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(preferred);
|
||||||
if (!conf) return "IGNORE";
|
if (!conf) return "IGNORE";
|
||||||
|
|
||||||
if (conf === true) {
|
if (conf === true) {
|
||||||
@@ -154,7 +155,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
if (conf) {
|
if (conf) {
|
||||||
this.settings = { ...this.settings, ...conf };
|
this.settings = { ...this.settings, ...conf };
|
||||||
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
if (rebuildRequired) {
|
if (rebuildRequired) {
|
||||||
await this.core.rebuilder.$fetchLocal();
|
await this.core.rebuilder.$fetchLocal();
|
||||||
}
|
}
|
||||||
@@ -164,8 +165,12 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return "IGNORE";
|
return "IGNORE";
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
||||||
const replicator = await this.core.$anyNewReplicator();
|
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
||||||
|
if (!replicator) {
|
||||||
|
this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (await replicator.tryConnectRemote(trialSetting)) {
|
if (await replicator.tryConnectRemote(trialSetting)) {
|
||||||
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
@@ -178,17 +183,17 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$checkAndAskUseRemoteConfiguration(
|
async _checkAndAskUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings
|
trialSetting: RemoteDBSettings
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
const preferred = await this.core.$$fetchRemotePreferredTweakValues(trialSetting);
|
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
return await this.$$askUseRemoteConfiguration(trialSetting, preferred);
|
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
|
||||||
}
|
}
|
||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$askUseRemoteConfiguration(
|
async _askUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings,
|
trialSetting: RemoteDBSettings,
|
||||||
preferred: TweakValues
|
preferred: TweakValues
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
@@ -278,4 +283,13 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
}
|
}
|
||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.tweakValue.handleFetchRemotePreferred(this._fetchRemotePreferredTweakValues.bind(this));
|
||||||
|
services.tweakValue.handleCheckAndAskResolvingMismatched(this._checkAndAskResolvingMismatchedTweaks.bind(this));
|
||||||
|
services.tweakValue.handleAskResolvingMismatched(this._askResolvingMismatchedTweaks.bind(this));
|
||||||
|
services.tweakValue.handleCheckAndAskUseRemoteConfiguration(this._checkAndAskUseRemoteConfiguration.bind(this));
|
||||||
|
services.tweakValue.handleAskUseRemoteConfiguration(this._askUseRemoteConfiguration.bind(this));
|
||||||
|
services.replication.handleCheckConnectionFailure(this._anyAfterConnectCheckFailed.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TFile, TFolder, type ListedFiles } from "obsidian";
|
import { TFile, TFolder, type ListedFiles } from "obsidian";
|
||||||
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import type {
|
import type {
|
||||||
FilePath,
|
FilePath,
|
||||||
@@ -15,43 +15,72 @@ import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/uti
|
|||||||
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
||||||
import type { StorageAccess } from "../interfaces/StorageAccess";
|
import type { StorageAccess } from "../interfaces/StorageAccess";
|
||||||
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
|
||||||
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
const fileLockPrefix = "file-lock:";
|
||||||
|
|
||||||
|
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements StorageAccess {
|
||||||
|
processingFiles: Set<FilePathWithPrefix> = new Set();
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return this.processingFiles.has(path);
|
||||||
|
}
|
||||||
vaultAccess!: SerializedFileAccess;
|
vaultAccess!: SerializedFileAccess;
|
||||||
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core);
|
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core, this);
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.storageAccess = this;
|
this.core.storageAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnFirstInitialize(): Promise<boolean> {
|
_everyOnFirstInitialize(): Promise<boolean> {
|
||||||
this.vaultManager.beginWatch();
|
this.vaultManager.beginWatch();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnUnload(): Promise<boolean> {
|
|
||||||
// this.vaultManager.
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// $$flushFileEventQueue(): void {
|
// $$flushFileEventQueue(): void {
|
||||||
// this.vaultManager.flushQueue();
|
// this.vaultManager.flushQueue();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$everyCommitPendingFileEvent(): Promise<boolean> {
|
_everyCommitPendingFileEvent(): Promise<boolean> {
|
||||||
this.vaultManager.flushQueue();
|
this.vaultManager.flushQueue();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin);
|
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isStorageInsensitive(): boolean {
|
_isStorageInsensitive(): boolean {
|
||||||
return this.vaultAccess.isStorageInsensitive();
|
return this.vaultAccess.isStorageInsensitive();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$shouldCheckCaseInsensitive(): boolean {
|
_shouldCheckCaseInsensitive(): boolean {
|
||||||
if (this.$$isStorageInsensitive()) return false;
|
if (this.services.vault.isStorageInsensitive()) return false;
|
||||||
return !this.settings.handleFilenameCaseSensitive;
|
return !this.settings.handleFilenameCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +222,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false> {
|
async readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false> {
|
||||||
const file = this.vaultAccess.getAbstractFileByPath(stub.path);
|
const file = this.vaultAccess.getAbstractFileByPath(stub.path);
|
||||||
if (!(file instanceof TFile)) {
|
if (!(file instanceof TFile)) {
|
||||||
@@ -202,6 +232,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
const data = await this.vaultAccess.vaultReadAuto(file);
|
const data = await this.vaultAccess.vaultReadAuto(file);
|
||||||
return {
|
return {
|
||||||
...stub,
|
...stub,
|
||||||
|
...TFileToUXFileInfoStub(file),
|
||||||
body: createBlob(data),
|
body: createBlob(data),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -245,7 +276,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
|
if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +289,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
|
if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(v)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// OK, deep dive!
|
// OK, deep dive!
|
||||||
@@ -314,9 +345,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async _deleteVaultItem(file: TFile | TFolder) {
|
async __deleteVaultItem(file: TFile | TFolder) {
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) return;
|
if (!(await this.services.vault.isTargetFile(file.path))) return;
|
||||||
}
|
}
|
||||||
const dir = file.parent;
|
const dir = file.parent;
|
||||||
if (this.settings.trashInsteadDelete) {
|
if (this.settings.trashInsteadDelete) {
|
||||||
@@ -332,7 +363,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
this._log(
|
this._log(
|
||||||
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
|
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
|
||||||
);
|
);
|
||||||
await this._deleteVaultItem(dir);
|
await this.__deleteVaultItem(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +374,19 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
const file = this.vaultAccess.getAbstractFileByPath(path);
|
const file = this.vaultAccess.getAbstractFileByPath(path);
|
||||||
if (file === null) return;
|
if (file === null) return;
|
||||||
if (file instanceof TFile || file instanceof TFolder) {
|
if (file instanceof TFile || file instanceof TFolder) {
|
||||||
return await this._deleteVaultItem(file);
|
return await this.__deleteVaultItem(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
||||||
|
super(plugin, core);
|
||||||
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.vault.handleIsStorageInsensitive(this._isStorageInsensitive.bind(this));
|
||||||
|
services.setting.handleShouldCheckCaseInsensitively(this._shouldCheckCaseInsensitive.bind(this));
|
||||||
|
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.fileProcessing.handleCommitPendingFileEvents(this._everyCommitPendingFileEvent.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ModuleInputUIObsidian.ts
|
// ModuleInputUIObsidian.ts
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
||||||
import {
|
import {
|
||||||
@@ -13,12 +13,13 @@ import { Notice } from "../../deps.ts";
|
|||||||
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
||||||
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
|
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
// This module cannot be a common module because it depends on Obsidian's API.
|
// This module cannot be a common module because it depends on Obsidian's API.
|
||||||
// However, we have to make compatible one for other platform.
|
// However, we have to make compatible one for other platform.
|
||||||
|
|
||||||
export class ModuleInputUIObsidian extends AbstractObsidianModule implements IObsidianModule, Confirm {
|
export class ModuleInputUIObsidian extends AbstractObsidianModule implements Confirm {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.confirm = this;
|
this.core.confirm = this;
|
||||||
setConfirmInstance(this);
|
setConfirmInstance(this);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -110,4 +111,8 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
): Promise<(typeof buttons)[number] | false> {
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
||||||
import { serialized } from "../../../lib/src/concurrency/lock.ts";
|
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
||||||
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
||||||
import type { InternalFileInfo } from "../../../common/types.ts";
|
import type { InternalFileInfo } from "../../../common/types.ts";
|
||||||
import { markChangesAreSame } from "../../../common/utils.ts";
|
import { markChangesAreSame } from "../../../common/utils.ts";
|
||||||
import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
|
|
||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
|
||||||
return `fl:${typeof file == "string" ? file : file.path}`;
|
|
||||||
}
|
|
||||||
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
return arr.buffer;
|
return arr.buffer;
|
||||||
@@ -21,60 +16,55 @@ function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<Arr
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// function isFile(file: TFile | TFolder | string | UXFileInfo): boolean {
|
|
||||||
// file instanceof TFile;
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function processReadFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
async function processWriteFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SerializedFileAccess {
|
export class SerializedFileAccess {
|
||||||
app: App;
|
app: App;
|
||||||
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
||||||
constructor(app: App, plugin: (typeof this)["plugin"]) {
|
storageAccess: StorageAccess;
|
||||||
|
constructor(app: App, plugin: SerializedFileAccess["plugin"], storageAccess: StorageAccess) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryAdapterStat(file: TFile | string) {
|
async tryAdapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, async () => {
|
return await this.storageAccess.processReadFile(path as FilePath, async () => {
|
||||||
if (!(await this.app.vault.adapter.exists(path))) return null;
|
if (!(await this.app.vault.adapter.exists(path))) return null;
|
||||||
return this.app.vault.adapter.stat(path);
|
return this.app.vault.adapter.stat(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async adapterStat(file: TFile | string) {
|
async adapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.stat(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.stat(path));
|
||||||
}
|
}
|
||||||
async adapterExists(file: TFile | string) {
|
async adapterExists(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.exists(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.exists(path));
|
||||||
}
|
}
|
||||||
async adapterRemove(file: TFile | string) {
|
async adapterRemove(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.remove(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.remove(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterRead(file: TFile | string) {
|
async adapterRead(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
}
|
}
|
||||||
async adapterReadBinary(file: TFile | string) {
|
async adapterReadBinary(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterReadAuto(file: TFile | string) {
|
async adapterReadAuto(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterWrite(
|
async adapterWrite(
|
||||||
@@ -84,35 +74,39 @@ export class SerializedFileAccess {
|
|||||||
) {
|
) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.write(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, () =>
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultCacheRead(file: TFile) {
|
async vaultCacheRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.cachedRead(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.cachedRead(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultRead(file: TFile) {
|
async vaultRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.read(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.read(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadBinary(file: TFile) {
|
async vaultReadBinary(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadAuto(file: TFile) {
|
async vaultReadAuto(file: TFile) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.read(file));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.read(file));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.read(file);
|
const oldData = await this.app.vault.read(file);
|
||||||
if (data === oldData) {
|
if (data === oldData) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -122,7 +116,7 @@ export class SerializedFileAccess {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.readBinary(file);
|
const oldData = await this.app.vault.readBinary(file);
|
||||||
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -139,9 +133,13 @@ export class SerializedFileAccess {
|
|||||||
options?: DataWriteOptions
|
options?: DataWriteOptions
|
||||||
): Promise<TFile> {
|
): Promise<TFile> {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(path, () => this.app.vault.create(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.create(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(path, () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.createBinary(path, toArrayBuffer(data), options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +152,14 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: TFile | TFolder, force = false) {
|
async delete(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.delete(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.delete(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async trash(file: TFile | TFolder, force = false) {
|
async trash(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.trash(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isStorageInsensitive(): boolean {
|
isStorageInsensitive(): boolean {
|
||||||
|
|||||||
@@ -7,24 +7,26 @@ import {
|
|||||||
LOG_LEVEL_INFO,
|
LOG_LEVEL_INFO,
|
||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
LOG_LEVEL_VERBOSE,
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type FileEventType,
|
||||||
type FilePath,
|
type FilePath,
|
||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type UXFileInfoStub,
|
type UXFileInfoStub,
|
||||||
type UXInternalFileInfoStub,
|
type UXInternalFileInfoStub,
|
||||||
} from "../../../lib/src/common/types.ts";
|
} from "../../../lib/src/common/types.ts";
|
||||||
import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
|
import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
|
||||||
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
|
import { type FileEventItem } from "../../../common/types.ts";
|
||||||
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
|
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import {
|
import {
|
||||||
finishAllWaitingForTimeout,
|
finishAllWaitingForTimeout,
|
||||||
finishWaitingForTimeout,
|
finishWaitingForTimeout,
|
||||||
isWaitingForTimeout,
|
isWaitingForTimeout,
|
||||||
waitForTimeout,
|
waitForTimeout,
|
||||||
} from "../../../lib/src/concurrency/task.ts";
|
} from "octagonal-wheels/concurrency/task";
|
||||||
import { Semaphore } from "../../../lib/src/concurrency/semaphore.ts";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import type { LiveSyncCore } from "../../../main.ts";
|
import type { LiveSyncCore } from "../../../main.ts";
|
||||||
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
||||||
|
|
||||||
export type FileEvent = {
|
export type FileEvent = {
|
||||||
@@ -46,6 +48,10 @@ export abstract class StorageEventManager {
|
|||||||
export class StorageEventManagerObsidian extends StorageEventManager {
|
export class StorageEventManagerObsidian extends StorageEventManager {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
core: LiveSyncCore;
|
core: LiveSyncCore;
|
||||||
|
storageAccess: StorageAccess;
|
||||||
|
get services() {
|
||||||
|
return this.core.services;
|
||||||
|
}
|
||||||
|
|
||||||
get shouldBatchSave() {
|
get shouldBatchSave() {
|
||||||
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
||||||
@@ -56,8 +62,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
get batchSaveMaximumDelay(): number {
|
get batchSaveMaximumDelay(): number {
|
||||||
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
||||||
}
|
}
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccess: StorageAccess) {
|
||||||
super();
|
super();
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
@@ -88,6 +95,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
const file = info?.file as TFile;
|
const file = info?.file as TFile;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.isWaiting(file.path as FilePath)) {
|
if (!this.isWaiting(file.path as FilePath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,22 +113,35 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
|
|
||||||
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file, true);
|
const fileInfo = TFileToUXFileInfoStub(file, true);
|
||||||
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||||
|
// vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename').
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue(
|
void this.appendQueue(
|
||||||
@@ -145,13 +169,17 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
// Watch raw events (Internal API)
|
// Watch raw events (Internal API)
|
||||||
watchVaultRawEvents(path: FilePath) {
|
watchVaultRawEvents(path: FilePath) {
|
||||||
|
if (this.storageAccess.isFileProcessing(path)) {
|
||||||
|
// Logger(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Only for internal files.
|
// Only for internal files.
|
||||||
if (!this.plugin.settings) return;
|
if (!this.plugin.settings) return;
|
||||||
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
||||||
if (this.plugin.settings.useIgnoreFiles) {
|
if (this.plugin.settings.useIgnoreFiles) {
|
||||||
// If it is one of ignore files, refresh the cached one.
|
// If it is one of ignore files, refresh the cached one.
|
||||||
// (Calling$$isTargetFile will refresh the cache)
|
// (Calling$$isTargetFile will refresh the cache)
|
||||||
void this.plugin.$$isTargetFile(path).then(() => this._watchVaultRawEvents(path));
|
void this.services.vault.isTargetFile(path).then(() => this._watchVaultRawEvents(path));
|
||||||
} else {
|
} else {
|
||||||
this._watchVaultRawEvents(path);
|
this._watchVaultRawEvents(path);
|
||||||
}
|
}
|
||||||
@@ -185,7 +213,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
async appendQueue(params: FileEvent[], ctx?: any) {
|
async appendQueue(params: FileEvent[], ctx?: any) {
|
||||||
if (!this.core.settings.isConfigured) return;
|
if (!this.core.settings.isConfigured) return;
|
||||||
if (this.core.settings.suspendFileWatching) return;
|
if (this.core.settings.suspendFileWatching) return;
|
||||||
this.core.$$markFileListPossiblyChanged();
|
this.core.services.vault.markFileListPossiblyChanged();
|
||||||
// Flag up to be reload
|
// Flag up to be reload
|
||||||
const processFiles = new Set<FilePath>();
|
const processFiles = new Set<FilePath>();
|
||||||
for (const param of params) {
|
for (const param of params) {
|
||||||
@@ -198,7 +226,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
const oldPath = param.oldPath;
|
const oldPath = param.oldPath;
|
||||||
if (type !== "INTERNAL") {
|
if (type !== "INTERNAL") {
|
||||||
const size = (file as UXFileInfoStub).stat.size;
|
const size = (file as UXFileInfoStub).stat.size;
|
||||||
if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
|
if (this.services.vault.isFileSizeTooLarge(size) && (type == "CREATE" || type == "CHANGED")) {
|
||||||
Logger(
|
Logger(
|
||||||
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
|
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
@@ -207,7 +235,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (file instanceof TFolder) continue;
|
if (file instanceof TFolder) continue;
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) continue;
|
// TODO: Confirm why only the TFolder skipping
|
||||||
|
// Possibly following line is needed...
|
||||||
|
// if (file?.isFolder) continue;
|
||||||
|
if (!(await this.services.vault.isTargetFile(file.path))) continue;
|
||||||
|
|
||||||
// Stop cache using to prevent the corruption;
|
// Stop cache using to prevent the corruption;
|
||||||
// let cache: null | string | ArrayBuffer;
|
// let cache: null | string | ArrayBuffer;
|
||||||
@@ -264,7 +295,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
concurrentProcessing = Semaphore(5);
|
concurrentProcessing = Semaphore(5);
|
||||||
waitedSince = new Map<FilePath | FilePathWithPrefix, number>();
|
waitedSince = new Map<FilePath | FilePathWithPrefix, number>();
|
||||||
async startStandingBy(filename: FilePath) {
|
async startStandingBy(filename: FilePath) {
|
||||||
// If waited, cancel previous waiting.
|
// If waited, no need to start again (looping inside the function)
|
||||||
await skipIfDuplicated(`storage-event-manager-${filename}`, async () => {
|
await skipIfDuplicated(`storage-event-manager-${filename}`, async () => {
|
||||||
Logger(`Processing ${filename}: Starting`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Starting`, LOG_LEVEL_DEBUG);
|
||||||
const release = await this.concurrentProcessing.acquire();
|
const release = await this.concurrentProcessing.acquire();
|
||||||
@@ -284,6 +315,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
const type = target.type;
|
const type = target.type;
|
||||||
|
// If already cancelled by other operation, skip this.
|
||||||
if (target.cancelled) {
|
if (target.cancelled) {
|
||||||
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
|
||||||
this.cancelStandingBy(target);
|
this.cancelStandingBy(target);
|
||||||
@@ -384,12 +416,12 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
const lockKey = `handleFile:${file.path}`;
|
const lockKey = `handleFile:${file.path}`;
|
||||||
return await serialized(lockKey, async () => {
|
return await serialized(lockKey, async () => {
|
||||||
if (queue.type == "INTERNAL" || file.isInternal) {
|
if (queue.type == "INTERNAL" || file.isInternal) {
|
||||||
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
|
await this.core.services.fileProcessing.processOptionalFileEvent(file.path as unknown as FilePath);
|
||||||
} else {
|
} else {
|
||||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||||
const last = Number((await this.core.kvDB.get(key)) || 0);
|
const last = Number((await this.core.kvDB.get(key)) || 0);
|
||||||
if (queue.type == "DELETE") {
|
if (queue.type == "DELETE") {
|
||||||
await this.core.$anyHandlerProcessesFileEvent(queue);
|
await this.core.services.fileProcessing.processFileEvent(queue);
|
||||||
} else {
|
} else {
|
||||||
if (file.stat.mtime == last) {
|
if (file.stat.mtime == last) {
|
||||||
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -397,7 +429,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
// this.cancelRelativeEvent(queue);
|
// this.cancelRelativeEvent(queue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$anyHandlerProcessesFileEvent(queue))) {
|
if (!(await this.core.services.fileProcessing.processFileEvent(queue))) {
|
||||||
Logger(
|
Logger(
|
||||||
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
|
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
|
||||||
LOG_LEVEL_INFO
|
LOG_LEVEL_INFO
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import {
|
|||||||
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
||||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { withConcurrency } from "octagonal-wheels/iterable/map";
|
import { withConcurrency } from "octagonal-wheels/iterable/map";
|
||||||
export class ModuleInitializerFile extends AbstractModule implements ICoreModule {
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
async $$performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<void> {
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
export class ModuleInitializerFile extends AbstractModule {
|
||||||
|
private async _performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<boolean> {
|
||||||
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
||||||
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
|
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
|
||||||
// synchronize all files between database and storage.
|
// synchronize all files between database and storage.
|
||||||
@@ -32,7 +33,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
"syncAll"
|
"syncAll"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ignoreSuspending && this.settings.suspendFileWatching) {
|
if (!ignoreSuspending && this.settings.suspendFileWatching) {
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
@@ -42,7 +43,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
"syncAll"
|
"syncAll"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
@@ -59,7 +60,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
const _filesStorage = [] as typeof filesStorageSrc;
|
const _filesStorage = [] as typeof filesStorageSrc;
|
||||||
|
|
||||||
for (const f of filesStorageSrc) {
|
for (const f of filesStorageSrc) {
|
||||||
if (await this.core.$$isTargetFile(f.path, f != filesStorageSrc[0])) {
|
if (await this.services.vault.isTargetFile(f.path, f != filesStorageSrc[0])) {
|
||||||
_filesStorage.push(f);
|
_filesStorage.push(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
);
|
);
|
||||||
const path = getPath(doc);
|
const path = getPath(doc);
|
||||||
|
|
||||||
if (isValidPath(path) && (await this.core.$$isTargetFile(path, true))) {
|
if (isValidPath(path) && (await this.services.vault.isTargetFile(path, true))) {
|
||||||
if (!isMetaEntry(doc)) {
|
if (!isMetaEntry(doc)) {
|
||||||
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
||||||
continue;
|
continue;
|
||||||
@@ -133,7 +134,6 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
|
|
||||||
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
||||||
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
||||||
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
||||||
@@ -192,7 +192,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
||||||
// Exists in storage but not in database.
|
// Exists in storage but not in database.
|
||||||
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
||||||
@@ -208,7 +208,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
// Exists in database but not in storage.
|
// Exists in database but not in storage.
|
||||||
const path = getPath(w) ?? e;
|
const path = getPath(w) ?? e;
|
||||||
if (w && !(w.deleted || w._deleted)) {
|
if (w && !(w.deleted || w._deleted)) {
|
||||||
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(w.size)) {
|
||||||
// Prevent applying the conflicted state to the storage.
|
// Prevent applying the conflicted state to the storage.
|
||||||
if (w._conflicts?.length ?? 0 > 0) {
|
if (w._conflicts?.length ?? 0 > 0) {
|
||||||
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped (x)`, LOG_LEVEL_INFO);
|
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped (x)`, LOG_LEVEL_INFO);
|
||||||
@@ -250,7 +250,10 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
if (
|
||||||
|
!this.services.vault.isFileSizeTooLarge(file.stat.size) &&
|
||||||
|
!this.services.vault.isFileSizeTooLarge(doc.size)
|
||||||
|
) {
|
||||||
await this.syncFileBetweenDBandStorage(file, doc);
|
await this.syncFileBetweenDBandStorage(file, doc);
|
||||||
} else {
|
} else {
|
||||||
this._log(
|
this._log(
|
||||||
@@ -271,6 +274,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
||||||
@@ -289,7 +293,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
const compareResult = compareFileFreshness(file, doc);
|
const compareResult = compareFileFreshness(file, doc);
|
||||||
switch (compareResult) {
|
switch (compareResult) {
|
||||||
case BASE_IS_NEW:
|
case BASE_IS_NEW:
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
|
||||||
this._log("STORAGE -> DB :" + file.path);
|
this._log("STORAGE -> DB :" + file.path);
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
} else {
|
} else {
|
||||||
@@ -300,7 +304,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TARGET_IS_NEW:
|
case TARGET_IS_NEW:
|
||||||
if (!this.core.$$isFileSizeExceeded(doc.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(doc.size)) {
|
||||||
this._log("STORAGE <- DB :" + file.path);
|
this._log("STORAGE <- DB :" + file.path);
|
||||||
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
||||||
eventHub.emitEvent("event-file-changed", {
|
eventHub.emitEvent("event-file-changed", {
|
||||||
@@ -365,27 +369,31 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`Checking expired file history done`);
|
this._log(`Checking expired file history done`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$initializeDatabase(
|
private async _initializeDatabase(
|
||||||
showingNotice: boolean = false,
|
showingNotice: boolean = false,
|
||||||
reopenDatabase = true,
|
reopenDatabase = true,
|
||||||
ignoreSuspending: boolean = false
|
ignoreSuspending: boolean = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
this.core.$$resetIsReady();
|
this.services.appLifecycle.resetIsReady();
|
||||||
if (!reopenDatabase || (await this.core.$$openDatabase())) {
|
if (!reopenDatabase || (await this.services.database.openDatabase())) {
|
||||||
if (this.localDatabase.isReady) {
|
if (this.localDatabase.isReady) {
|
||||||
await this.core.$$performFullScan(showingNotice, ignoreSuspending);
|
await this.services.vault.scanVault(showingNotice, ignoreSuspending);
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyOnDatabaseInitialized(showingNotice))) {
|
if (!(await this.services.databaseEvents.onDatabaseInitialised(showingNotice))) {
|
||||||
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
|
this._log(`Initializing database has been failed on some module!`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.core.$$markIsReady();
|
this.services.appLifecycle.markIsReady();
|
||||||
// run queued event once.
|
// run queued event once.
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.core.$$resetIsReady();
|
this.services.appLifecycle.resetIsReady();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.databaseEvents.handleInitialiseDatabase(this._initializeDatabase.bind(this));
|
||||||
|
services.vault.handleScanVault(this._performFullScan.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { OpenKeyValueDatabase } from "../../common/KeyValueDB.ts";
|
|||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
export class ModuleKeyValueDB extends AbstractModule {
|
||||||
tryCloseKvDB() {
|
tryCloseKvDB() {
|
||||||
try {
|
try {
|
||||||
this.core.kvDB?.close();
|
this.core.kvDB?.close();
|
||||||
@@ -22,7 +22,7 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
this.tryCloseKvDB();
|
this.tryCloseKvDB();
|
||||||
await delay(10);
|
await delay(10);
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
|
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
await delay(100);
|
await delay(100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -33,21 +33,23 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$allOnDBUnload(db: LiveSyncLocalDB): void {
|
_onDBUnload(db: LiveSyncLocalDB) {
|
||||||
if (this.core.kvDB) this.core.kvDB.close();
|
if (this.core.kvDB) this.core.kvDB.close();
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnDBClose(db: LiveSyncLocalDB): void {
|
_onDBClose(db: LiveSyncLocalDB) {
|
||||||
if (this.core.kvDB) this.core.kvDB.close();
|
if (this.core.kvDB) this.core.kvDB.close();
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private async _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!(await this.openKeyValueDB())) {
|
if (!(await this.openKeyValueDB())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.core.simpleStore = this.core.$$getSimpleStore<any>("os");
|
this.core.simpleStore = this.services.database.openSimpleStore<any>("os");
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$$getSimpleStore<T>(kind: string) {
|
_getSimpleStore<T>(kind: string) {
|
||||||
const prefix = `${kind}-`;
|
const prefix = `${kind}-`;
|
||||||
return {
|
return {
|
||||||
get: async (key: string): Promise<T> => {
|
get: async (key: string): Promise<T> => {
|
||||||
@@ -75,18 +77,18 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.openKeyValueDB();
|
return this.openKeyValueDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
async _everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const kvDBKey = "queued-files";
|
const kvDBKey = "queued-files";
|
||||||
await this.core.kvDB.del(kvDBKey);
|
await this.core.kvDB.del(kvDBKey);
|
||||||
// localStorage.removeItem(lsKey);
|
// localStorage.removeItem(lsKey);
|
||||||
await this.core.kvDB.destroy();
|
await this.core.kvDB.destroy();
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
|
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
|
||||||
await delay(100);
|
await delay(100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.core.kvDB = undefined!;
|
this.core.kvDB = undefined!;
|
||||||
@@ -96,4 +98,12 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.databaseEvents.handleOnUnloadDatabase(this._onDBUnload.bind(this));
|
||||||
|
services.databaseEvents.handleOnCloseDatabase(this._onDBClose.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
|
||||||
|
services.database.handleOpenSimpleStore(this._getSimpleStore.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,24 @@ import {
|
|||||||
eventHub,
|
eventHub,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts";
|
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts";
|
||||||
import { getPath, isValidPath } from "../../common/utils.ts";
|
import { getPath, isValidPath } from "../../common/utils.ts";
|
||||||
import { isMetaEntry } from "../../lib/src/common/types.ts";
|
import { isMetaEntry } from "../../lib/src/common/types.ts";
|
||||||
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
||||||
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
type ErrorInfo = {
|
||||||
|
path: string;
|
||||||
|
recordedSize: number;
|
||||||
|
actualSize: number;
|
||||||
|
storageSize: number;
|
||||||
|
contentMatched: boolean;
|
||||||
|
isConflicted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ModuleMigration extends AbstractModule {
|
||||||
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
||||||
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
||||||
this.core,
|
this.core,
|
||||||
@@ -36,11 +45,11 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
if (!skipRebuild) {
|
if (!skipRebuild) {
|
||||||
if (shouldRebuild) {
|
if (shouldRebuild) {
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
} else if (shouldRebuildLocal) {
|
} else if (shouldRebuildLocal) {
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,14 +121,15 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._log("Checking for incomplete documents...", LOG_LEVEL_NOTICE, "check-incomplete");
|
this._log("Checking for incomplete documents...", LOG_LEVEL_NOTICE, "check-incomplete");
|
||||||
const errorFiles = [];
|
|
||||||
|
const errorFiles = [] as ErrorInfo[];
|
||||||
for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) {
|
for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) {
|
||||||
const path = getPath(metaDoc);
|
const path = getPath(metaDoc);
|
||||||
|
|
||||||
if (!isValidPath(path)) {
|
if (!isValidPath(path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$$isTargetFile(path, true))) {
|
if (!(await this.services.vault.isTargetFile(path, true))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isMetaEntry(metaDoc)) {
|
if (!isMetaEntry(metaDoc)) {
|
||||||
@@ -133,17 +143,38 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
if (isDeletedEntry(doc)) {
|
if (isDeletedEntry(doc)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const storageFileContent = await this.core.storageAccess.readHiddenFileBinary(path);
|
const isConflicted = metaDoc?._conflicts && metaDoc._conflicts.length > 0;
|
||||||
|
|
||||||
|
let storageFileContent;
|
||||||
|
try {
|
||||||
|
storageFileContent = await this.core.storageAccess.readHiddenFileBinary(path);
|
||||||
|
} catch (e) {
|
||||||
|
Logger(`Failed to read file ${path}: Possibly unprocessed or missing`);
|
||||||
|
Logger(e, LOG_LEVEL_VERBOSE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// const storageFileBlob = createBlob(storageFileContent);
|
// const storageFileBlob = createBlob(storageFileContent);
|
||||||
const sizeOnStorage = storageFileContent.byteLength;
|
const sizeOnStorage = storageFileContent.byteLength;
|
||||||
const recordedSize = doc.size;
|
const recordedSize = doc.size;
|
||||||
const docBlob = readAsBlob(doc);
|
const docBlob = readAsBlob(doc);
|
||||||
const actualSize = docBlob.size;
|
const actualSize = docBlob.size;
|
||||||
if (recordedSize !== actualSize || sizeOnStorage !== actualSize || sizeOnStorage !== recordedSize) {
|
if (
|
||||||
|
recordedSize !== actualSize ||
|
||||||
|
sizeOnStorage !== actualSize ||
|
||||||
|
sizeOnStorage !== recordedSize ||
|
||||||
|
isConflicted
|
||||||
|
) {
|
||||||
const contentMatched = await isDocContentSame(doc.data, storageFileContent);
|
const contentMatched = await isDocContentSame(doc.data, storageFileContent);
|
||||||
errorFiles.push({ path, recordedSize, actualSize, storageSize: sizeOnStorage, contentMatched });
|
errorFiles.push({
|
||||||
|
path,
|
||||||
|
recordedSize,
|
||||||
|
actualSize,
|
||||||
|
storageSize: sizeOnStorage,
|
||||||
|
contentMatched,
|
||||||
|
isConflicted,
|
||||||
|
});
|
||||||
Logger(
|
Logger(
|
||||||
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"}`
|
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"} ${isConflicted ? "Conflicted" : "Not Conflicted"}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,24 +198,23 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
// Probably restored by the user by resolving A or B on other device, We should overwrite the storage
|
// Probably restored by the user by resolving A or B on other device, We should overwrite the storage
|
||||||
// Also do not fix it automatically. It should be overwritten by replication.
|
// Also do not fix it automatically. It should be overwritten by replication.
|
||||||
const recoverable = errorFiles.filter((e) => {
|
const recoverable = errorFiles.filter((e) => {
|
||||||
return e.recordedSize === e.storageSize;
|
return e.recordedSize === e.storageSize && !e.isConflicted;
|
||||||
});
|
});
|
||||||
const unrecoverable = errorFiles.filter((e) => {
|
const unrecoverable = errorFiles.filter((e) => {
|
||||||
return e.recordedSize !== e.storageSize;
|
return e.recordedSize !== e.storageSize || e.isConflicted;
|
||||||
});
|
});
|
||||||
|
const fileInfo = (e: (typeof errorFiles)[0]) => {
|
||||||
|
return `${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize}) ${e.isConflicted ? "(Conflicted)" : ""}`;
|
||||||
|
};
|
||||||
const messageUnrecoverable =
|
const messageUnrecoverable =
|
||||||
unrecoverable.length > 0
|
unrecoverable.length > 0
|
||||||
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
||||||
filesNotRecoverable: unrecoverable
|
filesNotRecoverable: unrecoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
.map((e) => `- ${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize})`)
|
|
||||||
.join("\n"),
|
|
||||||
})
|
})
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const message = $msg("moduleMigration.fix0256.message", {
|
const message = $msg("moduleMigration.fix0256.message", {
|
||||||
files: recoverable
|
files: recoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
.map((e) => `- ${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize})`)
|
|
||||||
.join("\n"),
|
|
||||||
messageUnrecoverable,
|
messageUnrecoverable,
|
||||||
});
|
});
|
||||||
const CHECK_IT_LATER = $msg("moduleMigration.fix0256.buttons.checkItLater");
|
const CHECK_IT_LATER = $msg("moduleMigration.fix0256.buttons.checkItLater");
|
||||||
@@ -227,9 +257,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
// Check local database for compromised chunks
|
// Check local database for compromised chunks
|
||||||
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
|
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
|
||||||
const remote = this.core.$$getReplicator();
|
const remote = this.services.replicator.getActiveReplicator();
|
||||||
const remoteCompromised = this.core.managers.networkManager.isOnline
|
const remoteCompromised = this.core.managers.networkManager.isOnline
|
||||||
? await remote.countCompromisedChunks()
|
? await remote?.countCompromisedChunks()
|
||||||
: 0;
|
: 0;
|
||||||
if (localCompromised === false) {
|
if (localCompromised === false) {
|
||||||
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
|
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
|
||||||
@@ -263,12 +293,12 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
if (result === REBUILD) {
|
if (result === REBUILD) {
|
||||||
// Rebuild the database
|
// Rebuild the database
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
} else if (result === FETCH) {
|
} else if (result === FETCH) {
|
||||||
// Fetch the latest data from remote
|
// Fetch the latest data from remote
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// User chose to dismiss the issue
|
// User chose to dismiss the issue
|
||||||
@@ -277,7 +307,7 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnFirstInitialize(): Promise<boolean> {
|
async _everyOnFirstInitialize(): Promise<boolean> {
|
||||||
if (!this.localDatabase.isReady) {
|
if (!this.localDatabase.isReady) {
|
||||||
this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -307,7 +337,7 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
_everyOnLayoutReady(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
|
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
|
||||||
await this.migrateUsingDoctor(false, reason, true);
|
await this.migrateUsingDoctor(false, reason, true);
|
||||||
});
|
});
|
||||||
@@ -316,4 +346,9 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
|
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
|
||||||
import { type CouchDBCredentials, type EntryDoc, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts";
|
||||||
import { getPathFromTFile } from "../../common/utils.ts";
|
import { getPathFromTFile } from "../../common/utils.ts";
|
||||||
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
|
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
|
||||||
@@ -11,6 +11,7 @@ import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
|
|||||||
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
|
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
|
||||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
|
||||||
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
|
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
setNoticeClass(Notice);
|
setNoticeClass(Notice);
|
||||||
|
|
||||||
@@ -19,21 +20,21 @@ async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Prom
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianAPI extends AbstractObsidianModule {
|
||||||
_customHandler!: ObsHttpHandler;
|
_customHandler!: ObsHttpHandler;
|
||||||
|
|
||||||
_authHeader = new AuthorizationHeaderGenerator();
|
_authHeader = new AuthorizationHeaderGenerator();
|
||||||
|
|
||||||
last_successful_post = false;
|
last_successful_post = false;
|
||||||
$$customFetchHandler(): ObsHttpHandler {
|
_customFetchHandler(): ObsHttpHandler {
|
||||||
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
|
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
|
||||||
return this._customHandler;
|
return this._customHandler;
|
||||||
}
|
}
|
||||||
$$getLastPostFailedBySize(): boolean {
|
_getLastPostFailedBySize(): boolean {
|
||||||
return !this.last_successful_post;
|
return !this.last_successful_post;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
|
async __fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
|
||||||
const body = opts?.body as string;
|
const body = opts?.body as string;
|
||||||
|
|
||||||
const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
|
const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
|
||||||
@@ -68,7 +69,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
const body = opts?.body as string;
|
const body = opts?.body as string;
|
||||||
const size = body ? ` (${body.length})` : "";
|
const size = body ? ` (${body.length})` : "";
|
||||||
try {
|
try {
|
||||||
const r = await this._fetchByAPI(url, authHeader, opts);
|
const r = await this.__fetchByAPI(url, authHeader, opts);
|
||||||
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
||||||
if (method == "POST" || method == "PUT") {
|
if (method == "POST" || method == "PUT") {
|
||||||
this.last_successful_post = r.status - (r.status % 100) == 200;
|
this.last_successful_post = r.status - (r.status % 100) == 200;
|
||||||
@@ -90,7 +91,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$connectRemoteCouchDB(
|
async _connectRemoteCouchDB(
|
||||||
uri: string,
|
uri: string,
|
||||||
auth: CouchDBCredentials,
|
auth: CouchDBCredentials,
|
||||||
disableRequestURI: boolean,
|
disableRequestURI: boolean,
|
||||||
@@ -148,7 +149,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
try {
|
try {
|
||||||
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
||||||
const response: Response = await (useRequestAPI
|
const response: Response = await (useRequestAPI
|
||||||
? this._fetchByAPI(url.toString(), authHeader, { ...opts, headers })
|
? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers })
|
||||||
: fetch(url, { ...opts, headers }));
|
: fetch(url, { ...opts, headers }));
|
||||||
if (method == "POST" || method == "PUT") {
|
if (method == "POST" || method == "PUT") {
|
||||||
this.last_successful_post = response.ok;
|
this.last_successful_post = response.ok;
|
||||||
@@ -252,21 +253,21 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isMobile(): boolean {
|
_isMobile(): boolean {
|
||||||
//@ts-ignore : internal API
|
//@ts-ignore : internal API
|
||||||
return this.app.isMobile;
|
return this.app.isMobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$vaultName(): string {
|
_vaultName(): string {
|
||||||
return this.app.vault.getName();
|
return this.app.vault.getName();
|
||||||
}
|
}
|
||||||
$$getVaultName(): string {
|
_getVaultName(): string {
|
||||||
return (
|
return (
|
||||||
this.core.$$vaultName() +
|
this.services.vault.vaultName() +
|
||||||
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
|
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
_getActiveFilePath(): FilePath | undefined {
|
||||||
const file = this.app.workspace.getActiveFile();
|
const file = this.app.workspace.getActiveFile();
|
||||||
if (file) {
|
if (file) {
|
||||||
return getPathFromTFile(file);
|
return getPathFromTFile(file);
|
||||||
@@ -274,7 +275,18 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> {
|
_anyGetAppId(): string {
|
||||||
return Promise.resolve(`${"appId" in this.app ? this.app.appId : ""}`);
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
services.API.handleGetCustomFetchHandler(this._customFetchHandler.bind(this));
|
||||||
|
services.API.handleIsLastPostFailedDueToPayloadSize(this._getLastPostFailedBySize.bind(this));
|
||||||
|
services.remote.handleConnect(this._connectRemoteCouchDB.bind(this));
|
||||||
|
services.API.handleIsMobile(this._isMobile.bind(this));
|
||||||
|
services.vault.handleGetVaultName(this._getVaultName.bind(this));
|
||||||
|
services.vault.handleVaultName(this._vaultName.bind(this));
|
||||||
|
services.vault.handleGetActiveFilePath(this._getActiveFilePath.bind(this));
|
||||||
|
services.API.handleGetAppID(this._anyGetAppId.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
|
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
@@ -12,9 +12,10 @@ import {
|
|||||||
hiddenFilesEventCount,
|
hiddenFilesEventCount,
|
||||||
hiddenFilesProcessingCount,
|
hiddenFilesProcessingCount,
|
||||||
} from "../../lib/src/mock_and_interop/stores.ts";
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleObsidianEvents extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
||||||
this.plugin.registerEvent(
|
this.plugin.registerEvent(
|
||||||
this.app.vault.on("rename", (file, oldPath) => {
|
this.app.vault.on("rename", (file, oldPath) => {
|
||||||
@@ -30,11 +31,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$performRestart(): void {
|
private _performRestart(): void {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
_performAppReload() {
|
__performAppReload() {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
this.app.commands.executeCommandById("app:reload");
|
this.app.commands.executeCommandById("app:reload");
|
||||||
}
|
}
|
||||||
@@ -49,14 +50,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
this.initialCallback = save;
|
this.initialCallback = save;
|
||||||
saveCommandDefinition.callback = () => {
|
saveCommandDefinition.callback = () => {
|
||||||
scheduleTask("syncOnEditorSave", 250, () => {
|
scheduleTask("syncOnEditorSave", 250, () => {
|
||||||
if (this.core.$$isUnloaded()) {
|
if (this.services.appLifecycle.hasUnloaded()) {
|
||||||
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
|
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
|
||||||
saveCommandDefinition.callback = this.initialCallback;
|
saveCommandDefinition.callback = this.initialCallback;
|
||||||
this.initialCallback = undefined;
|
this.initialCallback = undefined;
|
||||||
} else {
|
} else {
|
||||||
if (this.settings.syncOnEditorSave) {
|
if (this.settings.syncOnEditorSave) {
|
||||||
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
|
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
|
||||||
fireAndForget(() => this.core.$$replicateByEvent());
|
fireAndForget(() => this.services.replication.replicateByEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -106,14 +107,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
||||||
if (navigator.onLine && this.localDatabase.needScanning) {
|
if (navigator.onLine && this.localDatabase.needScanning) {
|
||||||
this.localDatabase.needScanning = false;
|
this.localDatabase.needScanning = false;
|
||||||
await this.core.$$performFullScan();
|
await this.services.vault.scanVault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchWindowVisibilityAsync() {
|
async watchWindowVisibilityAsync() {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
|
|
||||||
if (this.isLastHidden && !this.hasFocus) {
|
if (this.isLastHidden && !this.hasFocus) {
|
||||||
// NO OP while non-focused after made hidden;
|
// NO OP while non-focused after made hidden;
|
||||||
@@ -126,22 +127,22 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
this.isLastHidden = isHidden;
|
this.isLastHidden = isHidden;
|
||||||
|
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
await this.core.$everyBeforeSuspendProcess();
|
await this.services.appLifecycle.onSuspending();
|
||||||
} else {
|
} else {
|
||||||
// suspend all temporary.
|
// suspend all temporary.
|
||||||
if (this.core.$$isSuspended()) return;
|
if (this.services.appLifecycle.isSuspended()) return;
|
||||||
if (!this.hasFocus) return;
|
if (!this.hasFocus) return;
|
||||||
await this.core.$everyOnResumeProcess();
|
await this.services.appLifecycle.onResuming();
|
||||||
await this.core.$everyAfterResumeProcess();
|
await this.services.appLifecycle.onResumed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watchWorkspaceOpen(file: TFile | null) {
|
watchWorkspaceOpen(file: TFile | null) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
|
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
|
||||||
}
|
}
|
||||||
@@ -149,25 +150,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
async watchWorkspaceOpenAsync(file: TFile) {
|
async watchWorkspaceOpenAsync(file: TFile) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) {
|
if (this.settings.syncOnFileOpen && !this.services.appLifecycle.isSuspended()) {
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
|
await this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
_everyOnLayoutReady(): Promise<boolean> {
|
||||||
this.swapSaveCommand();
|
this.swapSaveCommand();
|
||||||
this.registerWatchEvents();
|
this.registerWatchEvents();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$askReload(message?: string) {
|
private _askReload(message?: string) {
|
||||||
if (this.core.$$isReloadingScheduled()) {
|
if (this.services.appLifecycle.isReloadingScheduled()) {
|
||||||
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -181,13 +182,13 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
{ defaultAction: RETRY_LATER }
|
{ defaultAction: RETRY_LATER }
|
||||||
);
|
);
|
||||||
if (ret == RESTART_NOW) {
|
if (ret == RESTART_NOW) {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
} else if (ret == RESTART_AFTER_STABLE) {
|
} else if (ret == RESTART_AFTER_STABLE) {
|
||||||
this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$$scheduleAppReload() {
|
private _scheduleAppReload() {
|
||||||
if (!this.core._totalProcessingCount) {
|
if (!this.core._totalProcessingCount) {
|
||||||
const __tick = reactiveSource(0);
|
const __tick = reactiveSource(0);
|
||||||
this.core._totalProcessingCount = reactive(() => {
|
this.core._totalProcessingCount = reactive(() => {
|
||||||
@@ -224,7 +225,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
this.core._totalProcessingCount.onChanged((e) => {
|
this.core._totalProcessingCount.onChanged((e) => {
|
||||||
if (e.value == 0) {
|
if (e.value == 0) {
|
||||||
if (stableCheck-- <= 0) {
|
if (stableCheck-- <= 0) {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
}
|
}
|
||||||
this._log(
|
this._log(
|
||||||
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
|
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
|
||||||
@@ -237,4 +238,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handlePerformRestart(this._performRestart.bind(this));
|
||||||
|
services.appLifecycle.handleAskRestart(this._askReload.bind(this));
|
||||||
|
services.appLifecycle.handleScheduleRestart(this._scheduleAppReload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
|
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
|
||||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianMenu extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
// UI
|
// UI
|
||||||
addIcon(
|
addIcon(
|
||||||
"replicate",
|
"replicate",
|
||||||
@@ -18,21 +19,21 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.addRibbonIcon("replicate", $msg("moduleObsidianMenu.replicate"), async () => {
|
this.addRibbonIcon("replicate", $msg("moduleObsidianMenu.replicate"), async () => {
|
||||||
await this.core.$$replicate(true);
|
await this.services.replication.replicate(true);
|
||||||
}).addClass("livesync-ribbon-replicate");
|
}).addClass("livesync-ribbon-replicate");
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-replicate",
|
id: "livesync-replicate",
|
||||||
name: "Replicate now",
|
name: "Replicate now",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-dump",
|
id: "livesync-dump",
|
||||||
name: "Dump information of this doc ",
|
name: "Dump information of this doc ",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const file = this.core.$$getActiveFilePath();
|
const file = this.services.vault.getActiveFilePath();
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
|
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
|
||||||
},
|
},
|
||||||
@@ -43,7 +44,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
|
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
|
||||||
const file = view.file;
|
const file = view.file;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
void this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
|
void this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,23 +59,23 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
this.settings.liveSync = true;
|
this.settings.liveSync = true;
|
||||||
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
|
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-suspendall",
|
id: "livesync-suspendall",
|
||||||
name: "Toggle All Sync.",
|
name: "Toggle All Sync.",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
if (this.core.$$isSuspended()) {
|
if (this.services.appLifecycle.isSuspended()) {
|
||||||
this.core.$$setSuspended(false);
|
this.services.appLifecycle.setSuspended(false);
|
||||||
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
|
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
this.core.$$setSuspended(true);
|
this.services.appLifecycle.setSuspended(true);
|
||||||
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
|
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
id: "livesync-scan-files",
|
id: "livesync-scan-files",
|
||||||
name: "Scan storage and database again",
|
name: "Scan storage and database again",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$$performFullScan(true);
|
await this.services.vault.scanVault(true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
id: "livesync-runbatch",
|
id: "livesync-runbatch",
|
||||||
name: "Run pended batch processes",
|
name: "Run pended batch processes",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,12 +105,15 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
private __onWorkspaceReady() {
|
||||||
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
|
void this.services.appLifecycle.onReady();
|
||||||
|
}
|
||||||
|
private _everyOnload(): Promise<boolean> {
|
||||||
|
this.app.workspace.onLayoutReady(this.__onWorkspaceReady.bind(this));
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$showView(viewType: string) {
|
private async _showView(viewType: string) {
|
||||||
const leaves = this.app.workspace.getLeavesOfType(viewType);
|
const leaves = this.app.workspace.getLeavesOfType(viewType);
|
||||||
if (leaves.length == 0) {
|
if (leaves.length == 0) {
|
||||||
await this.app.workspace.getLeaf(true).setViewState({
|
await this.app.workspace.getLeaf(true).setViewState({
|
||||||
@@ -126,4 +130,9 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
await this.app.workspace.revealLeaf(leaves[0]);
|
await this.app.workspace.revealLeaf(leaves[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.API.handleShowWindow(this._showView.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
|
|
||||||
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleExtraSyncObsidian extends AbstractObsidianModule {
|
||||||
deviceAndVaultName: string = "";
|
deviceAndVaultName: string = "";
|
||||||
|
|
||||||
$$getDeviceAndVaultName(): string {
|
_getDeviceAndVaultName(): string {
|
||||||
return this.deviceAndVaultName;
|
return this.deviceAndVaultName;
|
||||||
}
|
}
|
||||||
$$setDeviceAndVaultName(name: string): void {
|
_setDeviceAndVaultName(name: string): void {
|
||||||
this.deviceAndVaultName = name;
|
this.deviceAndVaultName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.setting.handleGetDeviceAndVaultName(this._getDeviceAndVaultName.bind(this));
|
||||||
|
services.setting.handleSetDeviceAndVaultName(this._setDeviceAndVaultName.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { __onMissingTranslation } from "../../lib/src/common/i18n";
|
import { __onMissingTranslation } from "../../lib/src/common/i18n";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { eventHub } from "../../common/events";
|
import { eventHub } from "../../common/events";
|
||||||
import { enableTestFunction } from "./devUtil/testUtils.ts";
|
import { enableTestFunction } from "./devUtil/testUtils.ts";
|
||||||
import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleDev extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
__onMissingTranslation(() => {});
|
__onMissingTranslation(() => {});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
||||||
__onMissingTranslation((key) => {
|
__onMissingTranslation((key) => {
|
||||||
@@ -92,12 +93,12 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
id: "view-test",
|
id: "view-test",
|
||||||
name: "Open Test dialogue",
|
name: "Open Test dialogue",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_TEST);
|
void this.services.API.showWindow(VIEW_TYPE_TEST);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
||||||
// void this.core.$$showView(VIEW_TYPE_TEST);
|
// void this.core.$$showView(VIEW_TYPE_TEST);
|
||||||
@@ -121,7 +122,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (w) {
|
if (w) {
|
||||||
const id = await this.core.$$path2id(filename as FilePathWithPrefix);
|
const id = await this.services.path.path2id(filename as FilePathWithPrefix);
|
||||||
const f = await this.core.localDatabase.getRaw(id);
|
const f = await this.core.localDatabase.getRaw(id);
|
||||||
console.log(f);
|
console.log(f);
|
||||||
console.log(f._rev);
|
console.log(f._rev);
|
||||||
@@ -139,14 +140,14 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
testResults = writable<[boolean, string, string][]>([]);
|
testResults = writable<[boolean, string, string][]>([]);
|
||||||
// testResults: string[] = [];
|
// testResults: string[] = [];
|
||||||
|
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
private _addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
||||||
const logLine = `${name}: ${key} ${summary ?? ""}`;
|
const logLine = `${name}: ${key} ${summary ?? ""}`;
|
||||||
this.testResults.update((results) => {
|
this.testResults.update((results) => {
|
||||||
results.push([result, logLine, message ?? ""]);
|
results.push([result, logLine, message ?? ""]);
|
||||||
return results;
|
return results;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$everyModuleTest(): Promise<boolean> {
|
private _everyModuleTest(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// this.core.$$addTestResult("DevModule", "Test", true);
|
// this.core.$$addTestResult("DevModule", "Test", true);
|
||||||
// return Promise.resolve(true);
|
// return Promise.resolve(true);
|
||||||
@@ -155,4 +156,11 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
// this.addTestResult("Test of test3", true);
|
// this.addTestResult("Test of test3", true);
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.test.handleTest(this._everyModuleTest.bind(this));
|
||||||
|
services.test.handleAddTestResult(this._addTestResult.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
import { delay } from "octagonal-wheels/promises";
|
||||||
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
|
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
|
||||||
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule";
|
||||||
|
|
||||||
export class ModuleIntegratedTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleIntegratedTest extends AbstractObsidianModule {
|
||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
||||||
await delay(100);
|
await delay(100);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
@@ -45,7 +45,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
async __orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
||||||
if (!(await this._test(key, proc))) {
|
if (!(await this._test(key, proc))) {
|
||||||
throw new Error(`${key}`);
|
throw new Error(`${key}`);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
tryReplicate() {
|
tryReplicate() {
|
||||||
if (!this.settings.liveSync) {
|
if (!this.settings.liveSync) {
|
||||||
return shareRunningResult("replicate-test", async () => {
|
return shareRunningResult("replicate-test", async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,13 +64,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
return await this.core.storageAccess.readHiddenFileText(file);
|
return await this.core.storageAccess.readHiddenFileText(file);
|
||||||
}
|
}
|
||||||
async _proceed(no: number, title: string): Promise<boolean> {
|
async __proceed(no: number, title: string): Promise<boolean> {
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
||||||
const stepContent = `Step ${no}`;
|
const stepContent = `Step ${no}`;
|
||||||
await this.core.$anyResolveConflictByNewest(stepFile);
|
await this.services.conflict.resolveByNewest(stepFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
||||||
await this._orDie(`Wait for acknowledge ${no}`, async () => {
|
await this.__orDie(`Wait for acknowledge ${no}`, async () => {
|
||||||
if (
|
if (
|
||||||
!(await this.waitWithReplicating(async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
||||||
@@ -81,13 +81,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _join(no: number, title: string): Promise<boolean> {
|
async __join(no: number, title: string): Promise<boolean> {
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
||||||
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
|
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
|
||||||
const stepContent = `Step ${no}`;
|
const stepContent = `Step ${no}`;
|
||||||
|
|
||||||
await this._orDie(`Wait for step ${no} (${title})`, async () => {
|
await this.__orDie(`Wait for step ${no} (${title})`, async () => {
|
||||||
if (
|
if (
|
||||||
!(await this.waitWithReplicating(async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepFile, stepContent);
|
return await this.storageContentIsEqual(stepFile, stepContent);
|
||||||
@@ -96,7 +96,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
await this.core.$anyResolveConflictByNewest(stepAckFile);
|
await this.services.conflict.resolveByNewest(stepAckFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
||||||
await this.tryReplicate();
|
await this.tryReplicate();
|
||||||
return true;
|
return true;
|
||||||
@@ -116,16 +116,16 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
check: () => Promise<boolean>;
|
check: () => Promise<boolean>;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
if (isGameChanger) {
|
if (isGameChanger) {
|
||||||
await this._proceed(step, title);
|
await this.__proceed(step, title);
|
||||||
try {
|
try {
|
||||||
await proc();
|
await proc();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log(`Error: ${e}`);
|
this._log(`Error: ${e}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await this._orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
return await this.__orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
||||||
} else {
|
} else {
|
||||||
return await this._join(step, title);
|
return await this.__join(step, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// // see scenario.md
|
// // see scenario.md
|
||||||
@@ -151,7 +151,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
||||||
);
|
);
|
||||||
if (isLeader) {
|
if (isLeader) {
|
||||||
await this._proceed(0, "start");
|
await this.__proceed(0, "start");
|
||||||
}
|
}
|
||||||
await this.tryReplicate();
|
await this.tryReplicate();
|
||||||
|
|
||||||
@@ -424,9 +424,9 @@ Line4:D`;
|
|||||||
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyModuleTestMultiDevice(): Promise<boolean> {
|
async _everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
const isLeader = this.core.$$vaultName().indexOf("recv") === -1;
|
const isLeader = this.core.services.vault.vaultName().indexOf("recv") === -1;
|
||||||
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
|
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
|
||||||
try {
|
try {
|
||||||
this._log(`Starting Test`);
|
this._log(`Starting Test`);
|
||||||
@@ -440,4 +440,7 @@ Line4:D`;
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
import { delay } from "octagonal-wheels/promises";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
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 { eventHub } from "../../common/events";
|
import { eventHub } from "../../common/events";
|
||||||
import { getWebCrypto } from "../../lib/src/mods.ts";
|
import { getWebCrypto } from "../../lib/src/mods.ts";
|
||||||
@@ -8,6 +8,7 @@ import { parseYaml, requestUrl, stringifyYaml } from "obsidian";
|
|||||||
import type { FilePath } from "../../lib/src/common/types.ts";
|
import type { FilePath } from "../../lib/src/common/types.ts";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { getFileRegExp } from "../../lib/src/common/utils.ts";
|
import { getFileRegExp } from "../../lib/src/common/utils.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
@@ -15,12 +16,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleReplicateTest extends AbstractObsidianModule {
|
||||||
testRootPath = "_test/";
|
testRootPath = "_test/";
|
||||||
testInfoPath = "_testinfo/";
|
testInfoPath = "_testinfo/";
|
||||||
|
|
||||||
get isLeader() {
|
get isLeader() {
|
||||||
return this.core.$$getVaultName().indexOf("dev") >= 0 && this.core.$$vaultName().indexOf("recv") < 0;
|
return (
|
||||||
|
this.services.vault.getVaultName().indexOf("dev") >= 0 &&
|
||||||
|
this.services.vault.vaultName().indexOf("recv") < 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get nameByKind() {
|
get nameByKind() {
|
||||||
@@ -52,24 +56,24 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
async dumpList() {
|
async dumpList() {
|
||||||
if (this.settings.syncInternalFiles) {
|
if (this.settings.syncInternalFiles) {
|
||||||
this._log("Write file list (Include Hidden)");
|
this._log("Write file list (Include Hidden)");
|
||||||
await this._dumpFileListIncludeHidden("files.md");
|
await this.__dumpFileListIncludeHidden("files.md");
|
||||||
} else {
|
} else {
|
||||||
this._log("Write file list");
|
this._log("Write file list");
|
||||||
await this._dumpFileList("files.md");
|
await this.__dumpFileList("files.md");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
await this.dumpList();
|
await this.dumpList();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "dump-file-structure-normal",
|
id: "dump-file-structure-normal",
|
||||||
name: `Dump Structure (Normal)`,
|
name: `Dump Structure (Normal)`,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this._dumpFileList("files.md").finally(() => {
|
void this.__dumpFileList("files.md").finally(() => {
|
||||||
void this.refreshSyncStatus();
|
void this.refreshSyncStatus();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -79,7 +83,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
name: "Dump Structure (Include Hidden)",
|
name: "Dump Structure (Include Hidden)",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const d = "files.md";
|
const d = "files.md";
|
||||||
void this._dumpFileListIncludeHidden(d);
|
void this.__dumpFileListIncludeHidden(d);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
@@ -160,7 +164,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dumpFileList(outFile?: string) {
|
async __dumpFileList(outFile?: string) {
|
||||||
if (!this.core || !this.core.storageAccess) {
|
if (!this.core || !this.core.storageAccess) {
|
||||||
this._log("No storage access", LOG_LEVEL_INFO);
|
this._log("No storage access", LOG_LEVEL_INFO);
|
||||||
return;
|
return;
|
||||||
@@ -169,7 +173,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
const webcrypto = await getWebCrypto();
|
const webcrypto = await getWebCrypto();
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) {
|
if (!(await this.services.vault.isTargetFile(file.path))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (file.path.startsWith(this.testInfoPath)) continue;
|
if (file.path.startsWith(this.testInfoPath)) continue;
|
||||||
@@ -200,7 +204,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
|
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dumpFileListIncludeHidden(outFile?: string) {
|
async __dumpFileListIncludeHidden(outFile?: string) {
|
||||||
const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
|
const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
|
||||||
const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
|
const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
|
||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
@@ -316,7 +320,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually1() {
|
async testConflictedManually1() {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve!
|
const commonFile = `Resolve!
|
||||||
*****, the amazing chocolatier!!`;
|
*****, the amazing chocolatier!!`;
|
||||||
@@ -325,8 +329,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
@@ -356,12 +360,12 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
return (
|
return (
|
||||||
(await this.__assertStorageContent(
|
(await this.__assertStorageContent(
|
||||||
(this.testRootPath + "wonka.md") as FilePath,
|
(this.testRootPath + "wonka.md") as FilePath,
|
||||||
@@ -379,7 +383,7 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually2() {
|
async testConflictedManually2() {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve To concatenate
|
const commonFile = `Resolve To concatenate
|
||||||
ABCDEFG`;
|
ABCDEFG`;
|
||||||
@@ -388,8 +392,8 @@ ABCDEFG`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
@@ -420,12 +424,12 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
return (
|
return (
|
||||||
(await this.__assertStorageContent(
|
(await this.__assertStorageContent(
|
||||||
(this.testRootPath + "concat.md") as FilePath,
|
(this.testRootPath + "concat.md") as FilePath,
|
||||||
@@ -457,8 +461,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
||||||
}
|
}
|
||||||
await delay(100);
|
await delay(100);
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
||||||
@@ -487,8 +491,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
||||||
@@ -496,8 +500,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
const mergedDoc = `Tasks!
|
const mergedDoc = `Tasks!
|
||||||
- [ ] Task 1
|
- [ ] Task 1
|
||||||
- [v] Task 2
|
- [v] Task 2
|
||||||
@@ -511,7 +515,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
|
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
|
||||||
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
||||||
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (!(await this.testConflictAutomatic())) {
|
if (!(await this.testConflictAutomatic())) {
|
||||||
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
||||||
@@ -569,11 +573,16 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
// return results;
|
// return results;
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
async $everyModuleTestMultiDevice(): Promise<boolean> {
|
private async _everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// this.core.$$addTestResult("DevModule", "Test", true);
|
// this.core.$$addTestResult("DevModule", "Test", true);
|
||||||
// return Promise.resolve(true);
|
// return Promise.resolve(true);
|
||||||
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,14 +57,14 @@
|
|||||||
function moduleMultiDeviceTest() {
|
function moduleMultiDeviceTest() {
|
||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
plugin.$everyModuleTestMultiDevice().finally(() => {
|
plugin.services.test.testMultiDevice().finally(() => {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function moduleSingleDeviceTest() {
|
function moduleSingleDeviceTest() {
|
||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
plugin.$everyModuleTest().finally(() => {
|
plugin.services.test.test().finally(() => {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
try {
|
try {
|
||||||
await plugin.$everyModuleTest();
|
await plugin.services.test.test();
|
||||||
await plugin.$everyModuleTestMultiDevice();
|
await plugin.services.test.testMultiDevice();
|
||||||
} finally {
|
} finally {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fireAndForget } from "../../../lib/src/common/utils.ts";
|
import { fireAndForget } from "../../../lib/src/common/utils.ts";
|
||||||
import { serialized } from "../../../lib/src/concurrency/lock.ts";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
|
||||||
let plugin: ObsidianLiveSyncPlugin;
|
let plugin: ObsidianLiveSyncPlugin;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Trench } from "../../../lib/src/memory/memutil.ts";
|
import { Trench } from "octagonal-wheels/memory/memutil";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
type MeasureResult = [times: number, spent: number];
|
type MeasureResult = [times: number, spent: number];
|
||||||
type NamedMeasureResult = [name: string, result: MeasureResult];
|
type NamedMeasureResult = [name: string, result: MeasureResult];
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ function readDocument(w: LoadedEntry) {
|
|||||||
}
|
}
|
||||||
export class DocumentHistoryModal extends Modal {
|
export class DocumentHistoryModal extends Modal {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
|
get services() {
|
||||||
|
return this.plugin.services;
|
||||||
|
}
|
||||||
range!: HTMLInputElement;
|
range!: HTMLInputElement;
|
||||||
contentView!: HTMLDivElement;
|
contentView!: HTMLDivElement;
|
||||||
info!: HTMLDivElement;
|
info!: HTMLDivElement;
|
||||||
@@ -74,7 +77,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.initialRev = revision;
|
this.initialRev = revision;
|
||||||
if (!file && id) {
|
if (!file && id) {
|
||||||
this.file = this.plugin.$$id2path(id);
|
this.file = this.services.path.id2path(id);
|
||||||
}
|
}
|
||||||
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
|
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
|
||||||
this.showDiff = true;
|
this.showDiff = true;
|
||||||
@@ -83,7 +86,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
|
|
||||||
async loadFile(initialRev?: string) {
|
async loadFile(initialRev?: string) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
this.id = await this.plugin.$$path2id(this.file);
|
this.id = await this.services.path.path2id(this.file);
|
||||||
}
|
}
|
||||||
const db = this.plugin.localDatabase;
|
const db = this.plugin.localDatabase;
|
||||||
try {
|
try {
|
||||||
@@ -126,7 +129,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
this.BlobURLs.delete(key);
|
this.BlobURLs.delete(key);
|
||||||
}
|
}
|
||||||
generateBlobURL(key: string, data: Uint8Array) {
|
generateBlobURL(key: string, data: Uint8Array<ArrayBuffer>) {
|
||||||
this.revokeURL(key);
|
this.revokeURL(key);
|
||||||
const v = URL.createObjectURL(new Blob([data], { endings: "transparent", type: "application/octet-stream" }));
|
const v = URL.createObjectURL(new Blob([data], { endings: "transparent", type: "application/octet-stream" }));
|
||||||
this.BlobURLs.set(key, v);
|
this.BlobURLs.set(key, v);
|
||||||
@@ -175,7 +178,10 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
result = result.replace(/\n/g, "<br>");
|
result = result.replace(/\n/g, "<br>");
|
||||||
} else if (isImage(this.file)) {
|
} else if (isImage(this.file)) {
|
||||||
const src = this.generateBlobURL("base", w1data);
|
const src = this.generateBlobURL("base", w1data);
|
||||||
const overlay = this.generateBlobURL("overlay", readDocument(w2) as Uint8Array);
|
const overlay = this.generateBlobURL(
|
||||||
|
"overlay",
|
||||||
|
readDocument(w2) as Uint8Array<ArrayBuffer>
|
||||||
|
);
|
||||||
result = `<div class='ls-imgdiff-wrap'>
|
result = `<div class='ls-imgdiff-wrap'>
|
||||||
<div class='overlay'>
|
<div class='overlay'>
|
||||||
<img class='img-base' src="${src}">
|
<img class='img-base' src="${src}">
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
title: string = "Conflicting changes";
|
title: string = "Conflicting changes";
|
||||||
|
|
||||||
pluginPickMode: boolean = false;
|
pluginPickMode: boolean = false;
|
||||||
localName: string = "Use Base";
|
localName: string = "Base";
|
||||||
remoteName: string = "Use Conflicted";
|
remoteName: string = "Conflicted";
|
||||||
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
||||||
|
|
||||||
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
||||||
@@ -36,8 +36,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
this.pluginPickMode = pluginPickMode || false;
|
this.pluginPickMode = pluginPickMode || false;
|
||||||
if (this.pluginPickMode) {
|
if (this.pluginPickMode) {
|
||||||
this.title = "Pick a version";
|
this.title = "Pick a version";
|
||||||
this.remoteName = `Use ${remoteName || "Remote"}`;
|
this.remoteName = `${remoteName || "Remote"}`;
|
||||||
this.localName = "Use Local";
|
this.localName = "Local";
|
||||||
}
|
}
|
||||||
// Send cancel signal for the previous merge dialogue
|
// Send cancel signal for the previous merge dialogue
|
||||||
// if not there, simply be ignored.
|
// if not there, simply be ignored.
|
||||||
@@ -93,12 +93,13 @@ export class ConflictResolveModal extends Modal {
|
|||||||
const date2 =
|
const date2 =
|
||||||
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
||||||
div2.innerHTML = `
|
div2.innerHTML = `
|
||||||
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
|
<span class='deleted'><span class='conflict-dev-name'>${this.localName}</span>: ${date1}</span><br>
|
||||||
|
<span class='added'><span class='conflict-dev-name'>${this.remoteName}</span>: ${date2}</span><br>
|
||||||
`;
|
`;
|
||||||
contentEl.createEl("button", { text: this.localName }, (e) =>
|
contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
contentEl.createEl("button", { text: this.remoteName }, (e) =>
|
contentEl.createEl("button", { text: `Use ${this.remoteName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
if (!this.pluginPickMode) {
|
if (!this.pluginPickMode) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||||
import { reactive, type ReactiveInstance } from "../../../lib/src/dataobject/reactive";
|
import { reactive, type ReactiveInstance } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { Logger } from "../../../lib/src/common/logger";
|
import { Logger } from "../../../lib/src/common/logger";
|
||||||
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
||||||
|
|
||||||
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-global-history",
|
id: "livesync-global-history",
|
||||||
name: "Show vault history",
|
name: "Show vault history",
|
||||||
@@ -17,6 +17,9 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
showGlobalHistory() {
|
showGlobalHistory() {
|
||||||
void this.core.$$showView(VIEW_TYPE_GLOBAL_HISTORY);
|
void this.services.API.showWindow(VIEW_TYPE_GLOBAL_HISTORY);
|
||||||
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import {
|
|||||||
type diff_result,
|
type diff_result,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { serialized } from "../../lib/src/concurrency/lock.ts";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-conflictcheck",
|
id: "livesync-conflictcheck",
|
||||||
name: "Pick a file to resolve conflict",
|
name: "Pick a file to resolve conflict",
|
||||||
@@ -34,7 +35,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
|
async _anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
|
||||||
// UI for resolving conflicts should one-by-one.
|
// UI for resolving conflicts should one-by-one.
|
||||||
return await serialized(`conflict-resolve-ui`, async () => {
|
return await serialized(`conflict-resolve-ui`, async () => {
|
||||||
this._log("Merge:open conflict dialog", LOG_LEVEL_VERBOSE);
|
this._log("Merge:open conflict dialog", LOG_LEVEL_VERBOSE);
|
||||||
@@ -68,7 +69,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
||||||
if (
|
if (
|
||||||
(await this.core.$$resolveConflictByDeletingRev(filename, delRev, "UI Concatenated")) ==
|
(await this.services.conflict.resolveByDeletingRevision(filename, delRev, "UI Concatenated")) ==
|
||||||
MISSING_OR_ERROR
|
MISSING_OR_ERROR
|
||||||
) {
|
) {
|
||||||
this._log(
|
this._log(
|
||||||
@@ -80,7 +81,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
} else if (typeof toDelete === "string") {
|
} else if (typeof toDelete === "string") {
|
||||||
// Select one of the conflicted revision to delete.
|
// Select one of the conflicted revision to delete.
|
||||||
if (
|
if (
|
||||||
(await this.core.$$resolveConflictByDeletingRev(filename, toDelete, "UI Selected")) ==
|
(await this.services.conflict.resolveByDeletingRevision(filename, toDelete, "UI Selected")) ==
|
||||||
MISSING_OR_ERROR
|
MISSING_OR_ERROR
|
||||||
) {
|
) {
|
||||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
||||||
@@ -93,11 +94,11 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
// In here, some merge has been processed.
|
// In here, some merge has been processed.
|
||||||
// So we have to run replication if configured.
|
// So we have to run replication if configured.
|
||||||
// TODO: Make this is as a event request
|
// TODO: Make this is as a event request
|
||||||
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
|
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
// And, check it again.
|
// And, check it again.
|
||||||
await this.core.$$queueConflictCheck(filename);
|
await this.services.conflict.queueCheckFor(filename);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -120,14 +121,14 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
||||||
if (target) {
|
if (target) {
|
||||||
const targetItem = notes.find((e) => e.dispPath == target)!;
|
const targetItem = notes.find((e) => e.dispPath == target)!;
|
||||||
await this.core.$$queueConflictCheck(targetItem.path);
|
await this.services.conflict.queueCheckFor(targetItem.path);
|
||||||
await this.core.$$waitForAllConflictProcessed();
|
await this.services.conflict.ensureAllProcessed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
@@ -157,4 +158,9 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.conflict.handleResolveByUserInteraction(this._anyResolveConflictByUI.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
} from "../../lib/src/mock_and_interop/stores.ts";
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import { eventHub } from "../../lib/src/hub/hub.ts";
|
import { eventHub } from "../../lib/src/hub/hub.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { addIcon, normalizePath, Notice } from "../../deps.ts";
|
import { addIcon, normalizePath, Notice } from "../../deps.ts";
|
||||||
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
@@ -28,6 +28,7 @@ import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
|||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
// This module cannot be a core module because it depends on the Obsidian UI.
|
// This module cannot be a core module because it depends on the Obsidian UI.
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ const recentLogProcessor = new QueueProcessor(
|
|||||||
|
|
||||||
const showDebugLog = false;
|
const showDebugLog = false;
|
||||||
export const MARK_DONE = "\u{2009}\u{2009}";
|
export const MARK_DONE = "\u{2009}\u{2009}";
|
||||||
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleLog extends AbstractObsidianModule {
|
||||||
registerView = this.plugin.registerView.bind(this.plugin);
|
registerView = this.plugin.registerView.bind(this.plugin);
|
||||||
|
|
||||||
statusBar?: HTMLElement;
|
statusBar?: HTMLElement;
|
||||||
@@ -178,7 +179,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
});
|
});
|
||||||
|
|
||||||
const statusBarLabels = reactive(() => {
|
const statusBarLabels = reactive(() => {
|
||||||
const scheduleMessage = this.core.$$isReloadingScheduled()
|
const scheduleMessage = this.services.appLifecycle.isReloadingScheduled()
|
||||||
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
|
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
|
||||||
: "";
|
: "";
|
||||||
const { message } = statusLineLabel();
|
const { message } = statusLineLabel();
|
||||||
@@ -199,7 +200,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
||||||
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
||||||
|
|
||||||
@@ -219,15 +220,15 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const thisFile = this.app.workspace.getActiveFile();
|
const thisFile = this.app.workspace.getActiveFile();
|
||||||
if (!thisFile) return "";
|
if (!thisFile) return "";
|
||||||
// Case Sensitivity
|
// Case Sensitivity
|
||||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
if (this.services.setting.shouldCheckCaseInsensitively()) {
|
||||||
const f = this.core.storageAccess
|
const f = this.core.storageAccess
|
||||||
.getFiles()
|
.getFiles()
|
||||||
.map((e) => e.path)
|
.map((e) => e.path)
|
||||||
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
|
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
|
||||||
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
||||||
}
|
}
|
||||||
if (!(await this.core.$$isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
|
if (!(await this.services.vault.isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
|
||||||
if (this.core.$$isFileSizeExceeded(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
if (this.services.vault.isFileSizeTooLarge(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
async setFileStatus() {
|
async setFileStatus() {
|
||||||
@@ -287,14 +288,14 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> {
|
private _allStartOnUnload(): Promise<boolean> {
|
||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.remove();
|
this.statusDiv.remove();
|
||||||
}
|
}
|
||||||
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
addIcon(
|
addIcon(
|
||||||
"view-log",
|
"view-log",
|
||||||
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
|
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
|
||||||
@@ -303,23 +304,23 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
</g>`
|
</g>`
|
||||||
);
|
);
|
||||||
this.addRibbonIcon("view-log", $msg("moduleLog.showLog"), () => {
|
this.addRibbonIcon("view-log", $msg("moduleLog.showLog"), () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
||||||
}).addClass("livesync-ribbon-showlog");
|
}).addClass("livesync-ribbon-showlog");
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "view-log",
|
id: "view-log",
|
||||||
name: "Show log",
|
name: "Show log",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
logStore
|
logStore
|
||||||
.pipeTo(
|
.pipeTo(
|
||||||
new QueueProcessor((logs) => logs.forEach((e) => this.core.$$addLog(e.message, e.level, e.key)), {
|
new QueueProcessor((logs) => logs.forEach((e) => this.__addLog(e.message, e.level, e.key)), {
|
||||||
suspended: false,
|
suspended: false,
|
||||||
batchSize: 20,
|
batchSize: 20,
|
||||||
concurrentLimit: 1,
|
concurrentLimit: 1,
|
||||||
@@ -366,17 +367,17 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
__addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
||||||
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (level < LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
if (level <= LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const vaultName = this.core.$$getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timestamp = now.toLocaleString();
|
const timestamp = now.toLocaleString();
|
||||||
const messageContent =
|
const messageContent =
|
||||||
@@ -437,4 +438,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.appLifecycle.handleOnBeforeUnload(this._allStartOnUnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import { type TFile } from "obsidian";
|
|||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts";
|
import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts";
|
||||||
import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
|
import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
|
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
|
||||||
import { getPath } from "../../common/utils.ts";
|
import { getPath } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
|
|
||||||
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-history",
|
id: "livesync-history",
|
||||||
name: "Show history",
|
name: "Show history",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const file = this.core.$$getActiveFilePath();
|
const file = this.services.vault.getActiveFilePath();
|
||||||
if (file) this.showHistory(file, undefined);
|
if (file) this.showHistory(file, undefined);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -51,4 +51,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
|
|||||||
this.showHistory(targetId.path, targetId.id);
|
this.showHistory(targetId.path, targetId.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
import {
|
import {
|
||||||
@@ -16,8 +16,9 @@ import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
|||||||
import { getLanguage } from "obsidian";
|
import { getLanguage } from "obsidian";
|
||||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
||||||
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
||||||
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
export class ModuleObsidianSettings extends AbstractObsidianModule {
|
||||||
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
let isChanged = false;
|
let isChanged = false;
|
||||||
if (this.settings.displayLanguage == "") {
|
if (this.settings.displayLanguage == "") {
|
||||||
const obsidianLanguage = getLanguage();
|
const obsidianLanguage = getLanguage();
|
||||||
@@ -32,7 +33,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
} else if (this.settings.displayLanguage == "") {
|
} else if (this.settings.displayLanguage == "") {
|
||||||
this.settings.displayLanguage = "def";
|
this.settings.displayLanguage = "def";
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
@@ -46,7 +47,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
this.settings.displayLanguage = "def";
|
this.settings.displayLanguage = "def";
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
}
|
}
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -61,13 +62,13 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return methodFunc();
|
return methodFunc();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$saveDeviceAndVaultName(): void {
|
_saveDeviceAndVaultName(): void {
|
||||||
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
|
||||||
localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || "");
|
localStorage.setItem(lsKey, this.services.setting.getDeviceAndVaultName() || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
usedPassphrase = "";
|
usedPassphrase = "";
|
||||||
$$clearUsedPassphrase(): void {
|
private _clearUsedPassphrase(): void {
|
||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +107,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return `${"appId" in this.app ? this.app.appId : ""}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$saveSettingData() {
|
async _saveSettingData() {
|
||||||
this.core.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
settings.deviceAndVaultName = "";
|
settings.deviceAndVaultName = "";
|
||||||
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
||||||
@@ -140,6 +141,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
jwtSub: settings.jwtSub,
|
jwtSub: settings.jwtSub,
|
||||||
useRequestAPI: settings.useRequestAPI,
|
useRequestAPI: settings.useRequestAPI,
|
||||||
bucketPrefix: settings.bucketPrefix,
|
bucketPrefix: settings.bucketPrefix,
|
||||||
|
forcePathStyle: settings.forcePathStyle,
|
||||||
};
|
};
|
||||||
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
||||||
JSON.stringify(connectionSetting),
|
JSON.stringify(connectionSetting),
|
||||||
@@ -173,7 +175,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
async _decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
const passphrase = await this.getPassphrase(settings);
|
const passphrase = await this.getPassphrase(settings);
|
||||||
if (passphrase === false) {
|
if (passphrase === false) {
|
||||||
this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT);
|
this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT);
|
||||||
@@ -233,7 +235,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
* @param settings
|
* @param settings
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
_adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
// Adjust settings as needed
|
// Adjust settings as needed
|
||||||
|
|
||||||
// Delete this feature to avoid problems on mobile.
|
// Delete this feature to avoid problems on mobile.
|
||||||
@@ -263,7 +265,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return Promise.resolve(settings);
|
return Promise.resolve(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$loadSettings(): Promise<void> {
|
async _loadSettings(): Promise<void> {
|
||||||
const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings;
|
const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings;
|
||||||
|
|
||||||
if (typeof settings.isConfigured == "undefined") {
|
if (typeof settings.isConfigured == "undefined") {
|
||||||
@@ -276,17 +278,17 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.settings = await this.core.$$decryptSettings(settings);
|
this.settings = await this.services.setting.decryptSettings(settings);
|
||||||
|
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
|
|
||||||
await this.core.$$adjustSettings(this.settings);
|
await this.services.setting.adjustSettings(this.settings);
|
||||||
|
|
||||||
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
|
||||||
if (this.settings.deviceAndVaultName != "") {
|
if (this.settings.deviceAndVaultName != "") {
|
||||||
if (!localStorage.getItem(lsKey)) {
|
if (!localStorage.getItem(lsKey)) {
|
||||||
this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName);
|
this.services.setting.setDeviceAndVaultName(this.settings.deviceAndVaultName);
|
||||||
this.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
this.settings.deviceAndVaultName = "";
|
this.settings.deviceAndVaultName = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,8 +299,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
);
|
);
|
||||||
this.settings.customChunkSize = 0;
|
this.settings.customChunkSize = 0;
|
||||||
}
|
}
|
||||||
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
this.services.setting.setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
||||||
if (this.core.$$getDeviceAndVaultName() == "") {
|
if (this.services.setting.getDeviceAndVaultName() == "") {
|
||||||
if (this.settings.usePluginSync) {
|
if (this.settings.usePluginSync) {
|
||||||
this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE);
|
this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE);
|
||||||
this.settings.usePluginSync = false;
|
this.settings.usePluginSync = false;
|
||||||
@@ -308,4 +310,14 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
||||||
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.setting.handleClearUsedPassphrase(this._clearUsedPassphrase.bind(this));
|
||||||
|
services.setting.handleDecryptSettings(this._decryptSettings.bind(this));
|
||||||
|
services.setting.handleAdjustSettings(this._adjustSettings.bind(this));
|
||||||
|
services.setting.handleLoadSettings(this._loadSettings.bind(this));
|
||||||
|
services.setting.handleSaveDeviceAndVaultName(this._saveDeviceAndVaultName.bind(this));
|
||||||
|
services.setting.handleSaveSettingData(this._saveSettingData.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { isObjectDifferent } from "octagonal-wheels/object";
|
import { isObjectDifferent } from "octagonal-wheels/object";
|
||||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
@@ -8,8 +8,8 @@ import { parseYaml, stringifyYaml } from "../../deps";
|
|||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
||||||
const SETTING_FOOTER = "\n````";
|
const SETTING_FOOTER = "\n````";
|
||||||
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-export-config",
|
id: "livesync-export-config",
|
||||||
name: "Write setting markdown manually",
|
name: "Write setting markdown manually",
|
||||||
@@ -18,7 +18,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
return this.settings.settingSyncFile != "";
|
return this.settings.settingSyncFile != "";
|
||||||
}
|
}
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -160,7 +160,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
result == APPLY_AND_FETCH
|
result == APPLY_AND_FETCH
|
||||||
) {
|
) {
|
||||||
this.core.settings = settingToApply;
|
this.core.settings = settingToApply;
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
if (result == APPLY_ONLY) {
|
if (result == APPLY_ONLY) {
|
||||||
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -171,7 +171,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
if (result == APPLY_AND_FETCH) {
|
if (result == APPLY_AND_FETCH) {
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -242,4 +242,7 @@ We can perform a command in this file.
|
|||||||
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, 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 {
|
||||||
settingTab!: ObsidianLiveSyncSettingTab;
|
settingTab!: ObsidianLiveSyncSettingTab;
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
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());
|
||||||
@@ -29,4 +29,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implem
|
|||||||
get appId() {
|
get appId() {
|
||||||
return `${"appId" in this.app ? this.app.appId : ""}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ import {
|
|||||||
EVENT_REQUEST_SHOW_SETUP_QR,
|
EVENT_REQUEST_SHOW_SETUP_QR,
|
||||||
eventHub,
|
eventHub,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { decodeAnyArray, encodeAnyArray } from "../../common/utils.ts";
|
import { decodeAnyArray, encodeAnyArray } from "../../common/utils.ts";
|
||||||
import qrcode from "qrcode-generator";
|
import qrcode from "qrcode-generator";
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
||||||
import { encryptString, decryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
import { encryptString, decryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
||||||
if (conf.settings) {
|
if (conf.settings) {
|
||||||
await this.setupWizard(conf.settings);
|
await this.setupWizard(conf.settings);
|
||||||
@@ -182,7 +183,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings;
|
const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings;
|
||||||
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
|
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
|
||||||
if (remoteConfig) {
|
if (remoteConfig) {
|
||||||
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
||||||
const resultSettings = {
|
const resultSettings = {
|
||||||
@@ -282,16 +283,16 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
);
|
);
|
||||||
if (setupType == setupJustImport) {
|
if (setupType == setupJustImport) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
} else if (setupType == setupAsNew) {
|
} else if (setupType == setupAsNew) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
await this.core.rebuilder.$fetchLocal();
|
await this.core.rebuilder.$fetchLocal();
|
||||||
} else if (setupType == setupAsMerge) {
|
} else if (setupType == setupAsMerge) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
await this.core.rebuilder.$fetchLocal(true);
|
await this.core.rebuilder.$fetchLocal(true);
|
||||||
} else if (setupType == setupAgain) {
|
} else if (setupType == setupAgain) {
|
||||||
@@ -308,7 +309,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.rebuilder.$rebuildEverything();
|
await this.core.rebuilder.$rebuildEverything();
|
||||||
} else {
|
} else {
|
||||||
// Explicitly cancel the operation or the dialog was closed.
|
// Explicitly cancel the operation or the dialog was closed.
|
||||||
@@ -345,4 +346,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/
|
|||||||
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts";
|
import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts";
|
||||||
import { testCrypt } from "../../../lib/src/encryption/e2ee_v2.ts";
|
import { testCrypt } from "octagonal-wheels/encryption/encryption";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { scheduleTask } from "../../../common/utils.ts";
|
import { scheduleTask } from "../../../common/utils.ts";
|
||||||
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
||||||
@@ -86,6 +86,9 @@ export function createStub(name: string, key: string, value: string, panel: stri
|
|||||||
|
|
||||||
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
|
get services() {
|
||||||
|
return this.plugin.services;
|
||||||
|
}
|
||||||
selectedScreen = "";
|
selectedScreen = "";
|
||||||
|
|
||||||
_editingSettings?: AllSettings;
|
_editingSettings?: AllSettings;
|
||||||
@@ -139,8 +142,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
return await Promise.resolve();
|
return await Promise.resolve();
|
||||||
}
|
}
|
||||||
if (key == "deviceAndVaultName") {
|
if (key == "deviceAndVaultName") {
|
||||||
this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
|
this.services.setting.setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
|
||||||
this.plugin.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
return await Promise.resolve();
|
return await Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +213,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
const ret = { ...OnDialogSettingsDefault };
|
const ret = { ...OnDialogSettingsDefault };
|
||||||
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
|
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
|
||||||
ret.preset = "";
|
ret.preset = "";
|
||||||
ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName();
|
ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
computeAllLocalSettings(): Partial<OnDialogSettings> {
|
computeAllLocalSettings(): Partial<OnDialogSettings> {
|
||||||
@@ -295,7 +298,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
|
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
|
||||||
const trialSetting = { ...this.editingSettings, ...settingOverride };
|
const trialSetting = { ...this.editingSettings, ...settingOverride };
|
||||||
const replicator = await this.plugin.$anyNewReplicator(trialSetting);
|
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
||||||
|
if (!replicator) {
|
||||||
|
Logger("No replicator available for the current settings.", LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await replicator.tryConnectRemote(trialSetting);
|
await replicator.tryConnectRemote(trialSetting);
|
||||||
const status = await replicator.getRemoteStatus(trialSetting);
|
const status = await replicator.getRemoteStatus(trialSetting);
|
||||||
if (status) {
|
if (status) {
|
||||||
@@ -546,10 +553,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
const settingForCheck: RemoteDBSettings = {
|
const settingForCheck: RemoteDBSettings = {
|
||||||
...this.editingSettings,
|
...this.editingSettings,
|
||||||
};
|
};
|
||||||
const replicator = this.plugin.$anyNewReplicator(settingForCheck);
|
const replicator = this.services.replicator.getNewReplicator(settingForCheck);
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
|
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
|
||||||
|
|
||||||
const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.$$isMobile(), true);
|
const db = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
|
settingForCheck,
|
||||||
|
this.services.API.isMobile(),
|
||||||
|
true
|
||||||
|
);
|
||||||
if (typeof db === "string") {
|
if (typeof db === "string") {
|
||||||
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -588,8 +599,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.editingSettings.passphrase = "";
|
this.editingSettings.passphrase = "";
|
||||||
}
|
}
|
||||||
this.applyAllSettings();
|
this.applyAllSettings();
|
||||||
await this.plugin.$allSuspendAllSync();
|
await this.services.setting.suspendAllSync();
|
||||||
await this.plugin.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.reloadAllSettings();
|
this.reloadAllSettings();
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
Logger($msg("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
|
Logger($msg("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
|
||||||
@@ -638,12 +649,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
await this.applyAllSettings();
|
await this.applyAllSettings();
|
||||||
if (result == OPTION_FETCH) {
|
if (result == OPTION_FETCH) {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// await rebuildDB("localOnly");
|
// await rebuildDB("localOnly");
|
||||||
} else if (result == OPTION_REBUILD_BOTH) {
|
} else if (result == OPTION_REBUILD_BOTH) {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
} else if (result == OPTION_ONLY_SETTING) {
|
} else if (result == OPTION_ONLY_SETTING) {
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -859,26 +870,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMinioJournalSyncClient() {
|
getMinioJournalSyncClient() {
|
||||||
const id = this.plugin.settings.accessKey;
|
return new JournalSyncMinio(this.plugin.settings, this.plugin.simpleStore, this.plugin);
|
||||||
const key = this.plugin.settings.secretKey;
|
|
||||||
const bucket = this.plugin.settings.bucket;
|
|
||||||
const prefix = this.plugin.settings.bucketPrefix;
|
|
||||||
const region = this.plugin.settings.region;
|
|
||||||
const endpoint = this.plugin.settings.endpoint;
|
|
||||||
const useCustomRequestHandler = this.plugin.settings.useCustomRequestHandler;
|
|
||||||
const customHeaders = this.plugin.settings.bucketCustomHeaders;
|
|
||||||
return new JournalSyncMinio(
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
endpoint,
|
|
||||||
bucket,
|
|
||||||
prefix,
|
|
||||||
this.plugin.simpleStore,
|
|
||||||
this.plugin,
|
|
||||||
useCustomRequestHandler,
|
|
||||||
region,
|
|
||||||
customHeaders
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
async resetRemoteBucket() {
|
async resetRemoteBucket() {
|
||||||
const minioJournal = this.getMinioJournalSyncClient();
|
const minioJournal = this.getMinioJournalSyncClient();
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
}
|
}
|
||||||
const obsidianInfo = {
|
const obsidianInfo = {
|
||||||
navigator: navigator.userAgent,
|
navigator: navigator.userAgent,
|
||||||
fileSystem: this.plugin.$$isStorageInsensitive() ? "insensitive" : "sensitive",
|
fileSystem: this.plugin.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive",
|
||||||
};
|
};
|
||||||
const msgConfig = `# ---- Obsidian info ----
|
const msgConfig = `# ---- Obsidian info ----
|
||||||
${stringifyYaml(obsidianInfo)}
|
${stringifyYaml(obsidianInfo)}
|
||||||
@@ -182,10 +182,10 @@ ${stringifyYaml({
|
|||||||
|
|
||||||
void addPanel(paneEl, "Scram Switches").then((paneEl) => {
|
void addPanel(paneEl, "Scram Switches").then((paneEl) => {
|
||||||
new Setting(paneEl).autoWireToggle("suspendFileWatching");
|
new Setting(paneEl).autoWireToggle("suspendFileWatching");
|
||||||
this.addOnSaved("suspendFileWatching", () => this.plugin.$$askReload());
|
this.addOnSaved("suspendFileWatching", () => this.services.appLifecycle.askRestart());
|
||||||
|
|
||||||
new Setting(paneEl).autoWireToggle("suspendParseReplicationResult");
|
new Setting(paneEl).autoWireToggle("suspendParseReplicationResult");
|
||||||
this.addOnSaved("suspendParseReplicationResult", () => this.plugin.$$askReload());
|
this.addOnSaved("suspendParseReplicationResult", () => this.services.appLifecycle.askRestart());
|
||||||
});
|
});
|
||||||
|
|
||||||
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
||||||
@@ -384,15 +384,16 @@ ${stringifyYaml({
|
|||||||
? await this.plugin.storageAccess.statHidden(path)
|
? await this.plugin.storageAccess.statHidden(path)
|
||||||
: false;
|
: false;
|
||||||
const fileOnStorage = stat != null ? stat : false;
|
const fileOnStorage = stat != null ? stat : false;
|
||||||
if (!(await this.plugin.$$isTargetFile(path))) return incProc();
|
if (!(await this.services.vault.isTargetFile(path))) return incProc();
|
||||||
const releaser = await semaphore.acquire(1);
|
const releaser = await semaphore.acquire(1);
|
||||||
if (fileOnStorage && this.plugin.$$isFileSizeExceeded(fileOnStorage.size))
|
if (fileOnStorage && this.services.vault.isFileSizeTooLarge(fileOnStorage.size))
|
||||||
return incProc();
|
return incProc();
|
||||||
try {
|
try {
|
||||||
const isHiddenFile = path.startsWith(".");
|
const isHiddenFile = path.startsWith(".");
|
||||||
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
|
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
|
||||||
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
|
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
|
||||||
if (fileOnDB && this.plugin.$$isFileSizeExceeded(fileOnDB.size)) return incProc();
|
if (fileOnDB && this.services.vault.isFileSizeTooLarge(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);
|
||||||
@@ -436,7 +437,7 @@ ${stringifyYaml({
|
|||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
for await (const docName of this.plugin.localDatabase.findAllDocNames()) {
|
for await (const docName of this.plugin.localDatabase.findAllDocNames()) {
|
||||||
if (!docName.startsWith("f:")) {
|
if (!docName.startsWith("f:")) {
|
||||||
const idEncoded = await this.plugin.$$path2id(docName as FilePathWithPrefix);
|
const idEncoded = await this.services.path.path2id(docName as FilePathWithPrefix);
|
||||||
const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID);
|
const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID);
|
||||||
if (!doc) continue;
|
if (!doc) continue;
|
||||||
if (doc.type != "newnote" && doc.type != "plain") {
|
if (doc.type != "newnote" && doc.type != "plain") {
|
||||||
@@ -477,7 +478,7 @@ ${stringifyYaml({
|
|||||||
if ((await this.plugin.localDatabase.putRaw(doc)).ok) {
|
if ((await this.plugin.localDatabase.putRaw(doc)).ok) {
|
||||||
Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE);
|
Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.plugin.$$queueConflictCheckIfOpen(docName as FilePathWithPrefix);
|
await this.services.conflict.queueCheckForIfOpen(docName as FilePathWithPrefix);
|
||||||
} else {
|
} else {
|
||||||
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ret, LOG_LEVEL_VERBOSE);
|
Logger(ret, LOG_LEVEL_VERBOSE);
|
||||||
@@ -512,7 +513,7 @@ ${stringifyYaml({
|
|||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
this.editingSettings.isConfigured = false;
|
this.editingSettings.isConfigured = false;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function paneMaintenance(
|
|||||||
(e) => {
|
(e) => {
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.plugin.$$markRemoteResolved();
|
await this.services.remote.markResolved();
|
||||||
this.display();
|
this.display();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@ export function paneMaintenance(
|
|||||||
(e) => {
|
(e) => {
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.plugin.$$markRemoteUnlocked();
|
await this.services.remote.markUnlocked();
|
||||||
this.display();
|
this.display();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -78,7 +78,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.setWarning()
|
.setWarning()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addOnUpdate(this.onlyOnCouchDBOrMinIO);
|
.addOnUpdate(this.onlyOnCouchDBOrMinIO);
|
||||||
@@ -93,7 +93,7 @@ export function paneMaintenance(
|
|||||||
.setWarning()
|
.setWarning()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -255,7 +255,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
@@ -294,7 +294,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
@@ -405,8 +405,8 @@ export function paneMaintenance(
|
|||||||
.setWarning()
|
.setWarning()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
await this.plugin.$$initializeDatabase();
|
await this.services.databaseEvents.initialiseDatabase();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
|||||||
|
|
||||||
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
|
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
|
||||||
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
|
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
|
||||||
await this.plugin.$$initializeDatabase();
|
await this.services.databaseEvents.initialiseDatabase();
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireDropDown("hashAlg", {
|
new Setting(paneEl).autoWireDropDown("hashAlg", {
|
||||||
|
|||||||
@@ -101,6 +101,23 @@ export function paneRemoteConfig(
|
|||||||
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
||||||
|
|
||||||
|
const serverBanner = r.headers["server"] ?? r.headers["Server"] ?? "unknown";
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.serverVersion", { info: serverBanner }));
|
||||||
|
const versionMatch = serverBanner.match(/CouchDB(\/([0-9.]+))?/);
|
||||||
|
const versionStr = versionMatch ? versionMatch[2] : "0.0.0";
|
||||||
|
const versionParts = `${versionStr}.0.0.0`.split(".");
|
||||||
|
// Compare version string with the target version.
|
||||||
|
// version must be a string like "3.2.1" or "3.10.2", and must be two or three parts.
|
||||||
|
function isGreaterThanOrEqual(version: string) {
|
||||||
|
const targetParts = version.split(".");
|
||||||
|
for (let i = 0; i < targetParts.length; i++) {
|
||||||
|
// compare as number if possible (so 3.10 > 3.2, 3.10.1b > 3.10.1a)
|
||||||
|
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
|
||||||
|
if (result > 0) return true;
|
||||||
|
if (result < 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Admin check
|
// Admin check
|
||||||
// for database creation and deletion
|
// for database creation and deletion
|
||||||
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||||
@@ -108,28 +125,31 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
||||||
}
|
}
|
||||||
// HTTP user-authorization check
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
// HTTP user-authorization check
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
isSuccessful = false;
|
||||||
addConfigFixButton(
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
addConfigFixButton(
|
||||||
"chttpd/require_valid_user",
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
||||||
"true"
|
"chttpd/require_valid_user",
|
||||||
);
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||||
}
|
isSuccessful = false;
|
||||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
||||||
isSuccessful = false;
|
addConfigFixButton(
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
||||||
addConfigFixButton(
|
"chttpd_auth/require_valid_user",
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
"true"
|
||||||
"chttpd_auth/require_valid_user",
|
);
|
||||||
"true"
|
} else {
|
||||||
);
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
||||||
} else {
|
}
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
|
||||||
}
|
}
|
||||||
// HTTPD check
|
// HTTPD check
|
||||||
// Check Authentication header
|
// Check Authentication header
|
||||||
@@ -144,12 +164,26 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
||||||
}
|
}
|
||||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.enable_cors != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
isSuccessful = false;
|
||||||
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"));
|
||||||
|
addConfigFixButton(
|
||||||
|
$msg("obsidianLiveSyncSettingTab.msgEnableCorsChttpd"),
|
||||||
|
"chttpd/enable_cors",
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCorsChttpd"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
||||||
|
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the server is not cloudant, configure request size
|
// If the server is not cloudant, configure request size
|
||||||
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
@@ -287,280 +321,281 @@ export function paneRemoteConfig(
|
|||||||
},
|
},
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, "Peer-to-Peer", undefined, this.onlyOnOnlyP2P).then((paneEl) => {
|
});
|
||||||
const syncWarnP2P = this.createEl(paneEl, "div", {
|
|
||||||
text: "",
|
void addPanel(paneEl, "Peer-to-Peer", undefined, this.onlyOnOnlyP2P).then((paneEl) => {
|
||||||
});
|
const syncWarnP2P = this.createEl(paneEl, "div", {
|
||||||
const p2pMessage = `This feature is a Work In Progress, and configurable on \`P2P Replicator\` Pane.
|
text: "",
|
||||||
|
});
|
||||||
|
const p2pMessage = `This feature is a Work In Progress, and configurable on \`P2P Replicator\` Pane.
|
||||||
The pane also can be launched by \`P2P Replicator\` command from the Command Palette.
|
The pane also can be launched by \`P2P Replicator\` command from the Command Palette.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
void MarkdownRenderer.render(this.plugin.app, p2pMessage, syncWarnP2P, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, p2pMessage, syncWarnP2P, "/", this.plugin);
|
||||||
syncWarnP2P.addClass("op-warn-info");
|
syncWarnP2P.addClass("op-warn-info");
|
||||||
new Setting(paneEl).setName("Apply Settings").setClass("wizardHidden").addApplyButton(["remoteType"]);
|
new Setting(paneEl).setName("Apply Settings").setClass("wizardHidden").addApplyButton(["remoteType"]);
|
||||||
// .addOnUpdate(onlyOnMinIO);
|
// .addOnUpdate(onlyOnMinIO);
|
||||||
// new Setting(paneEl).addButton((button) =>
|
// new Setting(paneEl).addButton((button) =>
|
||||||
// button
|
// button
|
||||||
// .setButtonText("Open P2P Replicator")
|
// .setButtonText("Open P2P Replicator")
|
||||||
// .onClick(() => {
|
// .onClick(() => {
|
||||||
// const addOn = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
// const addOn = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
||||||
// void addOn?.openPane();
|
// void addOn?.openPane();
|
||||||
// this.closeSetting();
|
// this.closeSetting();
|
||||||
// })
|
// })
|
||||||
// );
|
// );
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), undefined, this.onlyOnMinIO).then(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), undefined, this.onlyOnMinIO).then(
|
||||||
(paneEl) => {
|
(paneEl) => {
|
||||||
const syncWarnMinio = this.createEl(paneEl, "div", {
|
const syncWarnMinio = this.createEl(paneEl, "div", {
|
||||||
text: "",
|
text: "",
|
||||||
});
|
});
|
||||||
const ObjectStorageMessage = $msg("obsidianLiveSyncSettingTab.msgObjectStorageWarning");
|
const ObjectStorageMessage = $msg("obsidianLiveSyncSettingTab.msgObjectStorageWarning");
|
||||||
|
|
||||||
void MarkdownRenderer.render(this.plugin.app, ObjectStorageMessage, syncWarnMinio, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, ObjectStorageMessage, syncWarnMinio, "/", this.plugin);
|
||||||
syncWarnMinio.addClass("op-warn-info");
|
syncWarnMinio.addClass("op-warn-info");
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("endpoint", { holdValue: true });
|
new Setting(paneEl).autoWireText("endpoint", { holdValue: true });
|
||||||
new Setting(paneEl).autoWireText("accessKey", { holdValue: true });
|
new Setting(paneEl).autoWireToggle("forcePathStyle", { holdValue: true });
|
||||||
|
new Setting(paneEl).autoWireText("accessKey", { holdValue: true });
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("secretKey", {
|
new Setting(paneEl).autoWireText("secretKey", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
isPassword: true,
|
isPassword: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("region", { holdValue: true });
|
new Setting(paneEl).autoWireText("region", { holdValue: true });
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("bucket", { holdValue: true });
|
new Setting(paneEl).autoWireText("bucket", { holdValue: true });
|
||||||
new Setting(paneEl).autoWireText("bucketPrefix", {
|
new Setting(paneEl).autoWireText("bucketPrefix", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
placeHolder: "vaultname/",
|
placeHolder: "vaultname/",
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireToggle("useCustomRequestHandler", { holdValue: true });
|
new Setting(paneEl).autoWireToggle("useCustomRequestHandler", { holdValue: true });
|
||||||
new Setting(paneEl).autoWireTextArea("bucketCustomHeaders", {
|
new Setting(paneEl).autoWireTextArea("bucketCustomHeaders", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
placeHolder: "x-custom-header: value\n x-custom-header2: value2",
|
placeHolder: "x-custom-header: value\n x-custom-header2: value2",
|
||||||
});
|
});
|
||||||
new Setting(paneEl).setName($msg("obsidianLiveSyncSettingTab.nameTestConnection")).addButton((button) =>
|
new Setting(paneEl).setName($msg("obsidianLiveSyncSettingTab.nameTestConnection")).addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.testConnection(this.editingSettings);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||||
|
.setClass("wizardHidden")
|
||||||
|
.addApplyButton([
|
||||||
|
"remoteType",
|
||||||
|
"endpoint",
|
||||||
|
"region",
|
||||||
|
"accessKey",
|
||||||
|
"secretKey",
|
||||||
|
"bucket",
|
||||||
|
"useCustomRequestHandler",
|
||||||
|
"bucketCustomHeaders",
|
||||||
|
"bucketPrefix",
|
||||||
|
])
|
||||||
|
.addOnUpdate(this.onlyOnMinIO);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, this.onlyOnCouchDB).then(
|
||||||
|
(paneEl) => {
|
||||||
|
if (this.services.API.isMobile()) {
|
||||||
|
this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSWarning"),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||||
|
).addClass("op-warn");
|
||||||
|
} else {
|
||||||
|
this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSInfo"),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||||
|
).addClass("op-warn-info");
|
||||||
|
}
|
||||||
|
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_URI", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireToggle("useJWT", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_USER", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => !this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_PASSWORD", {
|
||||||
|
holdValue: true,
|
||||||
|
isPassword: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => !this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const algorithms = {
|
||||||
|
["HS256"]: "HS256",
|
||||||
|
["HS512"]: "HS512",
|
||||||
|
["ES256"]: "ES256",
|
||||||
|
["ES512"]: "ES512",
|
||||||
|
} as const;
|
||||||
|
new Setting(paneEl).autoWireDropDown("jwtAlgorithm", {
|
||||||
|
options: algorithms,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireTextArea("jwtKey", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let generatedKeyDivEl: HTMLDivElement;
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setDesc("Generate ES256 Keypair for testing")
|
||||||
|
.addButton((button) =>
|
||||||
|
button.setButtonText("Generate").onClick(async () => {
|
||||||
|
const crypto = await getWebCrypto();
|
||||||
|
const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [
|
||||||
|
"sign",
|
||||||
|
"verify",
|
||||||
|
]);
|
||||||
|
const pubKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
||||||
|
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||||
|
const encodedPublicKey = await arrayBufferToBase64Single(pubKey);
|
||||||
|
const encodedPrivateKey = await arrayBufferToBase64Single(privateKey);
|
||||||
|
|
||||||
|
const privateKeyPem = `> -----BEGIN PRIVATE KEY-----\n> ${encodedPrivateKey}\n> -----END PRIVATE KEY-----`;
|
||||||
|
const publicKeyPem = `> -----BEGIN PUBLIC KEY-----\\n${encodedPublicKey}\\n-----END PUBLIC KEY-----`;
|
||||||
|
|
||||||
|
const title = $msg("Setting.GenerateKeyPair.Title");
|
||||||
|
const msg = $msg("Setting.GenerateKeyPair.Desc", {
|
||||||
|
public_key: publicKeyPem,
|
||||||
|
private_key: privateKeyPem,
|
||||||
|
});
|
||||||
|
await MarkdownRenderer.render(
|
||||||
|
this.plugin.app,
|
||||||
|
"## " + title + "\n\n" + msg,
|
||||||
|
generatedKeyDivEl,
|
||||||
|
"/",
|
||||||
|
this.plugin
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addOnUpdate(
|
||||||
|
combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
generatedKeyDivEl = this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{ text: "" },
|
||||||
|
(el) => {},
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(paneEl).autoWireText("jwtKid", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("jwtSub", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireNumeric("jwtExpDuration", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_DBNAME", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true });
|
||||||
|
new Setting(paneEl).autoWireToggle("useRequestAPI", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
|
||||||
|
.setClass("wizardHidden")
|
||||||
|
.setDesc($msg("obsidianLiveSyncSettingTab.descTestDatabaseConnection"))
|
||||||
|
.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.testConnection(this.editingSettings);
|
await this.testConnection();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.addApplyButton([
|
|
||||||
"remoteType",
|
|
||||||
"endpoint",
|
|
||||||
"region",
|
|
||||||
"accessKey",
|
|
||||||
"secretKey",
|
|
||||||
"bucket",
|
|
||||||
"useCustomRequestHandler",
|
|
||||||
"bucketCustomHeaders",
|
|
||||||
"bucketPrefix",
|
|
||||||
])
|
|
||||||
.addOnUpdate(this.onlyOnMinIO);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, this.onlyOnCouchDB).then(
|
new Setting(paneEl)
|
||||||
(paneEl) => {
|
.setName($msg("obsidianLiveSyncSettingTab.nameValidateDatabaseConfig"))
|
||||||
if (this.plugin.$$isMobile()) {
|
.setDesc($msg("obsidianLiveSyncSettingTab.descValidateDatabaseConfig"))
|
||||||
this.createEl(
|
.addButton((button) =>
|
||||||
paneEl,
|
button
|
||||||
"div",
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnCheck"))
|
||||||
{
|
.setDisabled(false)
|
||||||
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSWarning"),
|
.onClick(async () => {
|
||||||
},
|
await checkConfig(checkResultDiv);
|
||||||
undefined,
|
|
||||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
|
||||||
).addClass("op-warn");
|
|
||||||
} else {
|
|
||||||
this.createEl(
|
|
||||||
paneEl,
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSInfo"),
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
|
||||||
).addClass("op-warn-info");
|
|
||||||
}
|
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_URI", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireToggle("useJWT", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_USER", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => !this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_PASSWORD", {
|
|
||||||
holdValue: true,
|
|
||||||
isPassword: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => !this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
const algorithms = {
|
|
||||||
["HS256"]: "HS256",
|
|
||||||
["HS512"]: "HS512",
|
|
||||||
["ES256"]: "ES256",
|
|
||||||
["ES512"]: "ES512",
|
|
||||||
} as const;
|
|
||||||
new Setting(paneEl).autoWireDropDown("jwtAlgorithm", {
|
|
||||||
options: algorithms,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireTextArea("jwtKey", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
let generatedKeyDivEl: HTMLDivElement;
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setDesc("Generate ES256 Keypair for testing")
|
|
||||||
.addButton((button) =>
|
|
||||||
button.setButtonText("Generate").onClick(async () => {
|
|
||||||
const crypto = await getWebCrypto();
|
|
||||||
const keyPair = await crypto.subtle.generateKey(
|
|
||||||
{ name: "ECDSA", namedCurve: "P-256" },
|
|
||||||
true,
|
|
||||||
["sign", "verify"]
|
|
||||||
);
|
|
||||||
const pubKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
||||||
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
|
||||||
const encodedPublicKey = await arrayBufferToBase64Single(pubKey);
|
|
||||||
const encodedPrivateKey = await arrayBufferToBase64Single(privateKey);
|
|
||||||
|
|
||||||
const privateKeyPem = `> -----BEGIN PRIVATE KEY-----\n> ${encodedPrivateKey}\n> -----END PRIVATE KEY-----`;
|
|
||||||
const publicKeyPem = `> -----BEGIN PUBLIC KEY-----\\n${encodedPublicKey}\\n-----END PUBLIC KEY-----`;
|
|
||||||
|
|
||||||
const title = $msg("Setting.GenerateKeyPair.Title");
|
|
||||||
const msg = $msg("Setting.GenerateKeyPair.Desc", {
|
|
||||||
public_key: publicKeyPem,
|
|
||||||
private_key: privateKeyPem,
|
|
||||||
});
|
|
||||||
await MarkdownRenderer.render(
|
|
||||||
this.plugin.app,
|
|
||||||
"## " + title + "\n\n" + msg,
|
|
||||||
generatedKeyDivEl,
|
|
||||||
"/",
|
|
||||||
this.plugin
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.addOnUpdate(
|
|
||||||
combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
generatedKeyDivEl = this.createEl(
|
|
||||||
paneEl,
|
|
||||||
"div",
|
|
||||||
{ text: "" },
|
|
||||||
(el) => {},
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
);
|
);
|
||||||
|
checkResultDiv = this.createEl(paneEl, "div", {
|
||||||
|
text: "",
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("jwtKid", {
|
new Setting(paneEl)
|
||||||
holdValue: true,
|
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||||
onUpdate: combineOnUpdate(
|
.setClass("wizardHidden")
|
||||||
this.enableOnlySyncDisabled,
|
.addApplyButton([
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
"remoteType",
|
||||||
),
|
"couchDB_URI",
|
||||||
});
|
"couchDB_USER",
|
||||||
new Setting(paneEl).autoWireText("jwtSub", {
|
"couchDB_PASSWORD",
|
||||||
holdValue: true,
|
"couchDB_DBNAME",
|
||||||
onUpdate: combineOnUpdate(
|
"jwtAlgorithm",
|
||||||
this.enableOnlySyncDisabled,
|
"jwtExpDuration",
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
"jwtKey",
|
||||||
),
|
"jwtSub",
|
||||||
});
|
"jwtKid",
|
||||||
new Setting(paneEl).autoWireNumeric("jwtExpDuration", {
|
"useJWT",
|
||||||
holdValue: true,
|
"couchDB_CustomHeaders",
|
||||||
onUpdate: combineOnUpdate(
|
"useRequestAPI",
|
||||||
this.enableOnlySyncDisabled,
|
])
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
.addOnUpdate(this.onlyOnCouchDB);
|
||||||
),
|
}
|
||||||
});
|
);
|
||||||
new Setting(paneEl).autoWireText("couchDB_DBNAME", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true });
|
|
||||||
new Setting(paneEl).autoWireToggle("useRequestAPI", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.setDesc($msg("obsidianLiveSyncSettingTab.descTestDatabaseConnection"))
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
|
||||||
.setDisabled(false)
|
|
||||||
.onClick(async () => {
|
|
||||||
await this.testConnection();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameValidateDatabaseConfig"))
|
|
||||||
.setDesc($msg("obsidianLiveSyncSettingTab.descValidateDatabaseConfig"))
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnCheck"))
|
|
||||||
.setDisabled(false)
|
|
||||||
.onClick(async () => {
|
|
||||||
await checkConfig(checkResultDiv);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkResultDiv = this.createEl(paneEl, "div", {
|
|
||||||
text: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.addApplyButton([
|
|
||||||
"remoteType",
|
|
||||||
"couchDB_URI",
|
|
||||||
"couchDB_USER",
|
|
||||||
"couchDB_PASSWORD",
|
|
||||||
"couchDB_DBNAME",
|
|
||||||
"jwtAlgorithm",
|
|
||||||
"jwtExpDuration",
|
|
||||||
"jwtKey",
|
|
||||||
"jwtSub",
|
|
||||||
"jwtKid",
|
|
||||||
"useJWT",
|
|
||||||
"couchDB_CustomHeaders",
|
|
||||||
"useRequestAPI",
|
|
||||||
])
|
|
||||||
.addOnUpdate(this.onlyOnCouchDB);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => {}, this.onlyOnCouchDB).then(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => {}, this.onlyOnCouchDB).then(
|
||||||
(paneEl) => {
|
(paneEl) => {
|
||||||
paneEl.addClass("wizardHidden");
|
paneEl.addClass("wizardHidden");
|
||||||
@@ -595,7 +630,8 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||||
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
|
const newTweaks =
|
||||||
|
await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
|
||||||
if (newTweaks.result !== false) {
|
if (newTweaks.result !== false) {
|
||||||
if (this.inWizard) {
|
if (this.inWizard) {
|
||||||
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
||||||
@@ -613,15 +649,15 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
}
|
}
|
||||||
)) == "no"
|
)) == "no"
|
||||||
) {
|
) {
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
await this.plugin.rebuilder.scheduleFetch();
|
await this.plugin.rebuilder.scheduleFetch();
|
||||||
await this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -692,7 +728,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
)) == "yes"
|
)) == "yes"
|
||||||
) {
|
) {
|
||||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||||
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
|
const newTweaks = await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
|
||||||
if (newTweaks.result !== false) {
|
if (newTweaks.result !== false) {
|
||||||
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function paneSetup(
|
|||||||
text.setButtonText($msg("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
|
text.setButtonText($msg("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -91,10 +91,10 @@ export function paneSetup(
|
|||||||
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.settings = { ...DEFAULT_SETTINGS };
|
this.plugin.settings = { ...DEFAULT_SETTINGS };
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
await this.plugin.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
// await this.plugin.initializeDatabase();
|
// await this.plugin.initializeDatabase();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setWarning();
|
.setWarning();
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export function paneSyncSettings(
|
|||||||
if (!this.editingSettings.isConfigured) {
|
if (!this.editingSettings.isConfigured) {
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
await this.rebuildDB("localOnly");
|
await this.rebuildDB("localOnly");
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
if (
|
if (
|
||||||
@@ -124,13 +124,13 @@ export function paneSyncSettings(
|
|||||||
await this.confirmRebuild();
|
await this.confirmRebuild();
|
||||||
} else {
|
} else {
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -169,7 +169,7 @@ export function paneSyncSettings(
|
|||||||
}
|
}
|
||||||
await this.saveSettings(["liveSync", "periodicReplication"]);
|
await this.saveSettings(["liveSync", "periodicReplication"]);
|
||||||
|
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
@@ -289,21 +289,21 @@ export function paneSyncSettings(
|
|||||||
button.setButtonText("Merge").onClick(async () => {
|
button.setButtonText("Merge").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("MERGE");
|
await this.services.setting.enableOptionalFeature("MERGE");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.addButton((button) => {
|
.addButton((button) => {
|
||||||
button.setButtonText("Fetch").onClick(async () => {
|
button.setButtonText("Fetch").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("FETCH");
|
await this.services.setting.enableOptionalFeature("FETCH");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.addButton((button) => {
|
.addButton((button) => {
|
||||||
button.setButtonText("Overwrite").onClick(async () => {
|
button.setButtonText("Overwrite").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("OVERWRITE");
|
await this.services.setting.enableOptionalFeature("OVERWRITE");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import type {
|
|||||||
import type { CustomRegExp } from "../../lib/src/common/utils";
|
import type { CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
|
||||||
export interface StorageAccess {
|
export interface StorageAccess {
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean;
|
||||||
|
|
||||||
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
||||||
|
|
||||||
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import { versionNumberString2Number } from "../../lib/src/string_and_binary/conv
|
|||||||
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||||
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
|
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
export class ModuleLiveSyncMain extends AbstractModule {
|
||||||
async $$onLiveSyncReady() {
|
async _onLiveSyncReady() {
|
||||||
if (!(await this.core.$everyOnLayoutReady())) return;
|
if (!(await this.core.services.appLifecycle.onLayoutReady())) return false;
|
||||||
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
||||||
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
||||||
const ANSWER_KEEP = $msg("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
|
const ANSWER_KEEP = $msg("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
|
||||||
@@ -36,46 +37,49 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
this.settings.suspendParseReplicationResult = false;
|
this.settings.suspendParseReplicationResult = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
await this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const isInitialized = await this.core.$$initializeDatabase(false, false);
|
const isInitialized = await this.services.databaseEvents.initialiseDatabase(false, false);
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
//TODO:stop all sync.
|
//TODO:stop all sync.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyOnFirstInitialize())) return;
|
if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false;
|
||||||
await this.core.$$realizeSettingSyncMode();
|
// await this.core.$$realizeSettingSyncMode();
|
||||||
|
await this.services.setting.onRealiseSetting();
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
|
||||||
if (!(await this.core.$allScanStat())) {
|
if (!(await this.services.appLifecycle.onScanningStartupIssues())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
this._log($msg("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$wireUpEvents(): void {
|
_wireUpEvents() {
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
this.localDatabase.settings = settings;
|
this.localDatabase.settings = settings;
|
||||||
setLang(settings.displayLanguage);
|
setLang(settings.displayLanguage);
|
||||||
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
fireAndForget(() => this.core.$$realizeSettingSyncMode());
|
fireAndForget(() => this.core.services.setting.onRealiseSetting());
|
||||||
});
|
});
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncLoad(): Promise<void> {
|
async _onLiveSyncLoad(): Promise<boolean> {
|
||||||
this.$$wireUpEvents();
|
await this.services.appLifecycle.onWireUpEvents();
|
||||||
// debugger;
|
// debugger;
|
||||||
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
||||||
this._log($msg("moduleLiveSyncMain.logLoadingPlugin"));
|
this._log($msg("moduleLiveSyncMain.logLoadingPlugin"));
|
||||||
if (!(await this.core.$everyOnloadStart())) {
|
if (!(await this.services.appLifecycle.onInitialise())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// this.addUIs();
|
// this.addUIs();
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -84,12 +88,12 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
||||||
|
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
||||||
await this.core.$$loadSettings();
|
await this.services.setting.loadSettings();
|
||||||
if (!(await this.core.$everyOnloadAfterLoadSettings())) {
|
if (!(await this.services.appLifecycle.onSettingLoaded())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-ver" + this.services.vault.getVaultName();
|
||||||
const last_version = localStorage.getItem(lsKey);
|
const last_version = localStorage.getItem(lsKey);
|
||||||
|
|
||||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||||
@@ -113,22 +117,23 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
localStorage.setItem(lsKey, `${VER}`);
|
localStorage.setItem(lsKey, `${VER}`);
|
||||||
await this.core.$$openDatabase();
|
await this.services.database.openDatabase();
|
||||||
this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
|
// this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
|
||||||
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
|
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
|
||||||
// this.$$replicate = this.$$replicate.bind(this);
|
// this.$$replicate = this.$$replicate.bind(this);
|
||||||
this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
// this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
||||||
await this.core.$everyOnload();
|
await this.core.services.appLifecycle.onLoaded();
|
||||||
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncUnload(): Promise<void> {
|
async _onLiveSyncUnload(): Promise<void> {
|
||||||
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
|
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
|
||||||
await this.core.$allStartOnUnload();
|
await this.services.appLifecycle.onBeforeUnload();
|
||||||
cancelAllPeriodicTask();
|
cancelAllPeriodicTask();
|
||||||
cancelAllTasks();
|
cancelAllTasks();
|
||||||
stopAllRunningProcessors();
|
stopAllRunningProcessors();
|
||||||
await this.core.$allOnUnload();
|
await this.services.appLifecycle.onUnload();
|
||||||
this._unloaded = true;
|
this._unloaded = true;
|
||||||
for (const addOn of this.core.addOns) {
|
for (const addOn of this.core.addOns) {
|
||||||
addOn.onunload();
|
addOn.onunload();
|
||||||
@@ -143,49 +148,68 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
eventHub.emitEvent(EVENT_PLATFORM_UNLOADED);
|
eventHub.emitEvent(EVENT_PLATFORM_UNLOADED);
|
||||||
eventHub.offAll();
|
eventHub.offAll();
|
||||||
this._log($msg("moduleLiveSyncMain.logUnloadingPlugin"));
|
this._log($msg("moduleLiveSyncMain.logUnloadingPlugin"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$realizeSettingSyncMode(): Promise<void> {
|
private async _realizeSettingSyncMode(): Promise<void> {
|
||||||
await this.core.$everyBeforeSuspendProcess();
|
await this.services.appLifecycle.onSuspending();
|
||||||
await this.core.$everyBeforeRealizeSetting();
|
await this.services.setting.onBeforeRealiseSetting();
|
||||||
this.localDatabase.refreshSettings();
|
this.localDatabase.refreshSettings();
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
await this.core.$everyRealizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
// disable all sync temporary.
|
// disable all sync temporary.
|
||||||
if (this.core.$$isSuspended()) return;
|
if (this.services.appLifecycle.isSuspended()) return;
|
||||||
await this.core.$everyOnResumeProcess();
|
await this.services.appLifecycle.onResuming();
|
||||||
await this.core.$everyAfterResumeProcess();
|
await this.services.appLifecycle.onResumed();
|
||||||
await this.core.$everyAfterRealizeSetting();
|
await this.services.setting.onSettingRealised();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isReloadingScheduled(): boolean {
|
_isReloadingScheduled(): boolean {
|
||||||
return this.core._totalProcessingCount !== undefined;
|
return this.core._totalProcessingCount !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
isReady = false;
|
isReady = false;
|
||||||
|
|
||||||
$$isReady(): boolean {
|
_isReady(): boolean {
|
||||||
return this.isReady;
|
return this.isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markIsReady(): void {
|
_markIsReady(): void {
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$resetIsReady(): void {
|
_resetIsReady(): void {
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_suspended = false;
|
_suspended = false;
|
||||||
$$isSuspended(): boolean {
|
_isSuspended(): boolean {
|
||||||
return this._suspended || !this.settings?.isConfigured;
|
return this._suspended || !this.settings?.isConfigured;
|
||||||
}
|
}
|
||||||
$$setSuspended(value: boolean) {
|
|
||||||
|
_setSuspended(value: boolean) {
|
||||||
this._suspended = value;
|
this._suspended = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_unloaded = false;
|
_unloaded = false;
|
||||||
$$isUnloaded(): boolean {
|
_isUnloaded(): boolean {
|
||||||
return this._unloaded;
|
return this._unloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleIsSuspended(this._isSuspended.bind(this));
|
||||||
|
services.appLifecycle.handleSetSuspended(this._setSuspended.bind(this));
|
||||||
|
services.appLifecycle.handleIsReady(this._isReady.bind(this));
|
||||||
|
services.appLifecycle.handleMarkIsReady(this._markIsReady.bind(this));
|
||||||
|
services.appLifecycle.handleResetIsReady(this._resetIsReady.bind(this));
|
||||||
|
services.appLifecycle.handleHasUnloaded(this._isUnloaded.bind(this));
|
||||||
|
services.appLifecycle.handleIsReloadingScheduled(this._isReloadingScheduled.bind(this));
|
||||||
|
services.appLifecycle.handleOnReady(this._onLiveSyncReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnWireUpEvents(this._wireUpEvents.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoad(this._onLiveSyncLoad.bind(this));
|
||||||
|
services.appLifecycle.handleOnAppUnload(this._onLiveSyncUnload.bind(this));
|
||||||
|
services.setting.handleRealiseSetting(this._realizeSettingSyncMode.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/modules/services/ObsidianServices.ts
Normal file
76
src/modules/services/ObsidianServices.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
InjectableAPIService,
|
||||||
|
InjectableAppLifecycleService,
|
||||||
|
InjectableConflictService,
|
||||||
|
InjectableDatabaseService,
|
||||||
|
InjectableFileProcessingService,
|
||||||
|
InjectablePathService,
|
||||||
|
InjectableRemoteService,
|
||||||
|
InjectableReplicationService,
|
||||||
|
InjectableReplicatorService,
|
||||||
|
InjectableSettingService,
|
||||||
|
InjectableTestService,
|
||||||
|
InjectableTweakValueService,
|
||||||
|
InjectableVaultService,
|
||||||
|
} from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
||||||
|
// This is a migration step.
|
||||||
|
|
||||||
|
export class ObsidianAPIService extends InjectableAPIService {}
|
||||||
|
export class ObsidianPathService extends InjectablePathService {}
|
||||||
|
export class ObsidianDatabaseService extends InjectableDatabaseService {}
|
||||||
|
|
||||||
|
// InjectableReplicatorService
|
||||||
|
export class ObsidianReplicatorService extends InjectableReplicatorService {}
|
||||||
|
// InjectableFileProcessingService
|
||||||
|
export class ObsidianFileProcessingService extends InjectableFileProcessingService {}
|
||||||
|
// InjectableReplicationService
|
||||||
|
export class ObsidianReplicationService extends InjectableReplicationService {}
|
||||||
|
// InjectableRemoteService
|
||||||
|
export class ObsidianRemoteService extends InjectableRemoteService {}
|
||||||
|
// InjectableConflictService
|
||||||
|
export class ObsidianConflictService extends InjectableConflictService {}
|
||||||
|
// InjectableAppLifecycleService
|
||||||
|
export class ObsidianAppLifecycleService extends InjectableAppLifecycleService {}
|
||||||
|
// InjectableSettingService
|
||||||
|
export class ObsidianSettingService extends InjectableSettingService {}
|
||||||
|
// InjectableTweakValueService
|
||||||
|
export class ObsidianTweakValueService extends InjectableTweakValueService {}
|
||||||
|
// InjectableVaultService
|
||||||
|
export class ObsidianVaultService extends InjectableVaultService {}
|
||||||
|
// InjectableTestService
|
||||||
|
export class ObsidianTestService extends InjectableTestService {}
|
||||||
|
|
||||||
|
// InjectableServiceHub
|
||||||
|
|
||||||
|
export class ObsidianServiceHub extends InjectableServiceHub {
|
||||||
|
protected _api: ObsidianAPIService = new ObsidianAPIService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _path: ObsidianPathService = new ObsidianPathService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _database: ObsidianDatabaseService = new ObsidianDatabaseService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _replicator: ObsidianReplicatorService = new ObsidianReplicatorService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _fileProcessing: ObsidianFileProcessingService = new ObsidianFileProcessingService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _replication: ObsidianReplicationService = new ObsidianReplicationService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _remote: ObsidianRemoteService = new ObsidianRemoteService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _conflict: ObsidianConflictService = new ObsidianConflictService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _appLifecycle: ObsidianAppLifecycleService = new ObsidianAppLifecycleService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _setting: ObsidianSettingService = new ObsidianSettingService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _tweakValue: ObsidianTweakValueService = new ObsidianTweakValueService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _vault: ObsidianVaultService = new ObsidianVaultService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _test: ObsidianTestService = new ObsidianTestService(this._serviceBackend, this._throughHole);
|
||||||
|
}
|
||||||
27
styles.css
27
styles.css
@@ -12,6 +12,11 @@
|
|||||||
background-color: var(--text-muted);
|
background-color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conflict-dev-name {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.op-scrollable {
|
.op-scrollable {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
/* min-height: 280px; */
|
/* min-height: 280px; */
|
||||||
@@ -105,10 +110,10 @@
|
|||||||
div.sls-setting-menu-btn {
|
div.sls-setting-menu-btn {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 8px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 12px;
|
margin-right: 2px;
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
outline: none;
|
outline: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -135,9 +140,9 @@ div.sls-setting-menu-btn {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: rgba(var(--background-primary), 0.3);
|
backdrop-filter: blur(15px);
|
||||||
backdrop-filter: blur(4px);
|
padding: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,13 +437,11 @@ div.workspace-leaf-content[data-type=bases] .livesync-status {
|
|||||||
|
|
||||||
.sls-setting-panel-title {
|
.sls-setting-panel-title {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
}
|
font-size: medium;
|
||||||
|
top: 2.5em;
|
||||||
.sls-setting-panel-title {
|
background-color: var(--background-secondary-alt);
|
||||||
top: 2em;
|
border-radius: 10px;
|
||||||
background-color: rgba(var(--background-primary), 0.3);
|
padding: 0.5em 1.0em;
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
border-radius: 30%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-dialogue-note-wrapper {
|
.sls-dialogue-note-wrapper {
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ const terserOption = {
|
|||||||
evaluate: true,
|
evaluate: true,
|
||||||
dead_code: true,
|
dead_code: true,
|
||||||
// directives: true,
|
// directives: true,
|
||||||
inline: 3,
|
inline: false,
|
||||||
join_vars: true,
|
join_vars: true,
|
||||||
loops: true,
|
loops: true,
|
||||||
passes: 4,
|
passes: 4,
|
||||||
reduce_vars: true,
|
reduce_vars: true,
|
||||||
reduce_funcs: true,
|
reduce_funcs: false,
|
||||||
arrows: true,
|
arrows: true,
|
||||||
collapse_vars: true,
|
collapse_vars: true,
|
||||||
comparisons: true,
|
comparisons: true,
|
||||||
|
|||||||
178
updates.md
178
updates.md
@@ -1,75 +1,6 @@
|
|||||||
## 0.25.12
|
## 0.25
|
||||||
|
|
||||||
29th August, 2025
|
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue with automatic synchronisation starting (#702).
|
|
||||||
|
|
||||||
## 0.25.11
|
|
||||||
|
|
||||||
28th August, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Automatic translation detection on the first launch now works correctly (#630).
|
|
||||||
- No errors are shown during synchronisations in offline (if not explicitly requested) (#699).
|
|
||||||
- Missing some checking during automatic-synchronisation now works correctly.
|
|
||||||
|
|
||||||
## 0.25.10
|
|
||||||
|
|
||||||
26th August, 2025
|
|
||||||
|
|
||||||
### New experimental feature
|
|
||||||
|
|
||||||
- We can perform Garbage Collection (Beta2) without rebuilding the entire database, and also fetch the database.
|
|
||||||
- Note that this feature is very experimental and should be used with caution.
|
|
||||||
- This feature requires disabling `Fetch chunks on demand`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Resetting the bucket now properly clears all uploaded files.
|
|
||||||
|
|
||||||
### Refactored
|
|
||||||
|
|
||||||
- Some files have been moved to better reflect their purpose and improve maintainability.
|
|
||||||
- The extensive LiveSyncLocalDB has been split into separate files for each role.
|
|
||||||
|
|
||||||
## 0.25.9
|
|
||||||
|
|
||||||
20th August, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- CORS Checking messages now use replacements.
|
|
||||||
- Configuring CORS setting via the UI now respects the existing rules.
|
|
||||||
- Now startup-checking works correctly again, performs migration check serially and then it will also fix starting LiveSync or start-up sync. (#696)
|
|
||||||
- Statusline in editor now supported 'Bases'.
|
|
||||||
|
|
||||||
## 0.25.8
|
|
||||||
|
|
||||||
18th August, 2025
|
|
||||||
|
|
||||||
### New feature
|
|
||||||
|
|
||||||
- Insecure chunk detection has been implemented.
|
|
||||||
- A notification dialogue will be shown if any insecure chunks are detected; these may have been created by v0.25.6 due to its issue. If this dialogue appears, please ensure you rebuild the database after backing it up.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Unexpected `Failed to obtain PBKDF2 salt` or similar errors during bucket-synchronisation no longer occur.
|
|
||||||
- Unexpected long delays for chunk-missing documents when using bucket-synchronisation have been resolved.
|
|
||||||
- Fetched remote chunks are now properly stored in the local database if `Fetch chunks on demand` is enabled.
|
|
||||||
- The 'fetch' dialogue's message has been refined.
|
|
||||||
- No longer overwriting any corrupted documents to the storage on boot-sequence.
|
|
||||||
|
|
||||||
### Refactored
|
|
||||||
|
|
||||||
- Type errors have been corrected.
|
|
||||||
|
|
||||||
## 0.25.0
|
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
|
||||||
|
|
||||||
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
||||||
|
|
||||||
@@ -77,5 +8,110 @@ I have now rewritten the E2EE code to be more robust and easier to understand. I
|
|||||||
|
|
||||||
As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution.
|
As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution.
|
||||||
|
|
||||||
|
## 0.25.21.beta2
|
||||||
|
|
||||||
|
8th October, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed wrong event type bindings (which caused some events not to be handled correctly).
|
||||||
|
- Fixed detected a timing issue in StorageEventManager
|
||||||
|
- When multiple events for the same file are fired in quick succession, metadata has been kept older information. This induces unexpected wrong notifications and write prevention.
|
||||||
|
|
||||||
|
## 0.25.21.beta1
|
||||||
|
|
||||||
|
6th October, 2025
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Event handling now does not rely on 'convention over configuration'.
|
||||||
|
- Services.ts now have a proper event handler registration system.
|
||||||
|
|
||||||
|
## 0.25.20
|
||||||
|
|
||||||
|
26th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710).
|
||||||
|
- Just using the fetched chunk temporarily.
|
||||||
|
- Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712).
|
||||||
|
- It no longer detects files that the plug-in has modified.
|
||||||
|
- It may reduce unnecessary file comparisons and unexpected file states.
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now checking the remote database configuration respecting the CouchDB version (#714).
|
||||||
|
|
||||||
|
## 0.25.19
|
||||||
|
|
||||||
|
18th September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now encoding/decoding for chunk data and encryption/decryption are performed in native functions (if they were available).
|
||||||
|
- This uses Uint8Array.fromBase64 and Uint8Array.toBase64, which are natively available in iOS 18.2+ and Android with Chrome 140+.
|
||||||
|
- In Android, WebView is by default updated with Chrome, so it should be available in most cases.
|
||||||
|
- Note that this is not available in Desktop yet (due to being based on Electron). We are staying tuned for future updates.
|
||||||
|
- This realised by an external(?) package [octagonal-wheels](https://github.com/vrtmrz/octagonal-wheels). Therefore, this update only updates the dependency.
|
||||||
|
|
||||||
|
## 0.25.18
|
||||||
|
|
||||||
|
17th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Property encryption detection now works correctly (On Self-hosted LiveSync, it was not broken, but as a library, it was not working correctly).
|
||||||
|
- Initialising the chunk splitter is now surely performed.
|
||||||
|
- DirectFileManipulator now works fine (as a library)
|
||||||
|
- Old `DirectFileManipulatorV1` is now removed.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Removed some unnecessary intermediate files.
|
||||||
|
|
||||||
|
## 0.25.17
|
||||||
|
|
||||||
|
16th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- No longer information-level logs have produced during toggling `Show only notifications` in the settings (#708).
|
||||||
|
- Ignoring filters for Hidden file sync now works correctly (#709).
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Removed some unnecessary intermediate files.
|
||||||
|
|
||||||
|
## 0.25.16
|
||||||
|
|
||||||
|
4th September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Improved connectivity for P2P connections
|
||||||
|
- The connection to the signalling server can now be disconnected while in the background or when explicitly disconnected.
|
||||||
|
- These features use a patch that has not been incorporated upstream.
|
||||||
|
- This patch is available at [vrtmrz/trystero](https://github.com/vrtmrz/trystero).
|
||||||
|
|
||||||
|
## 0.25.15
|
||||||
|
|
||||||
|
3rd September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now we can configure `forcePathStyle` for bucket synchronisation (#707).
|
||||||
|
|
||||||
|
## 0.25.14
|
||||||
|
|
||||||
|
2nd September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Opening IndexedDB handling has been ensured.
|
||||||
|
- Migration check of corrupted files detection has been fixed.
|
||||||
|
- Now informs us about conflicted files as non-recoverable, but noted so.
|
||||||
|
- No longer errors on not-found files.
|
||||||
|
|
||||||
Older notes are in
|
Older notes are in
|
||||||
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
# 0.25
|
||||||
|
|
||||||
## 0.25.0
|
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
|
||||||
|
|
||||||
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
||||||
|
|
||||||
@@ -11,6 +10,82 @@ As a result, this is the first time in a while that forward compatibility has be
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.25.13
|
||||||
|
|
||||||
|
1st September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Conflict resolving dialogue now properly displays the changeset name instead of A or B (#691).
|
||||||
|
|
||||||
|
## 0.25.12
|
||||||
|
|
||||||
|
29th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue with automatic synchronisation starting (#702).
|
||||||
|
|
||||||
|
## 0.25.11
|
||||||
|
|
||||||
|
28th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Automatic translation detection on the first launch now works correctly (#630).
|
||||||
|
- No errors are shown during synchronisations in offline (if not explicitly requested) (#699).
|
||||||
|
- Missing some checking during automatic-synchronisation now works correctly.
|
||||||
|
|
||||||
|
## 0.25.10
|
||||||
|
|
||||||
|
26th August, 2025
|
||||||
|
|
||||||
|
### New experimental feature
|
||||||
|
|
||||||
|
- We can perform Garbage Collection (Beta2) without rebuilding the entire database, and also fetch the database.
|
||||||
|
- Note that this feature is very experimental and should be used with caution.
|
||||||
|
- This feature requires disabling `Fetch chunks on demand`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Resetting the bucket now properly clears all uploaded files.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Some files have been moved to better reflect their purpose and improve maintainability.
|
||||||
|
- The extensive LiveSyncLocalDB has been split into separate files for each role.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Unexpected `Failed to obtain PBKDF2 salt` or similar errors during bucket-synchronisation no longer occur.
|
||||||
|
- Unexpected long delays for chunk-missing documents when using bucket-synchronisation have been resolved.
|
||||||
|
- Fetched remote chunks are now properly stored in the local database if `Fetch chunks on demand` is enabled.
|
||||||
|
- The 'fetch' dialogue's message has been refined.
|
||||||
|
- No longer overwriting any corrupted documents to the storage on boot-sequence.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Type errors have been corrected.
|
||||||
|
|
||||||
|
## 0.25.9
|
||||||
|
|
||||||
|
20th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- CORS Checking messages now use replacements.
|
||||||
|
- Configuring CORS setting via the UI now respects the existing rules.
|
||||||
|
- Now startup-checking works correctly again, performs migration check serially and then it will also fix starting LiveSync or start-up sync. (#696)
|
||||||
|
- Statusline in editor now supported 'Bases'.
|
||||||
|
|
||||||
|
## 0.25.8
|
||||||
|
|
||||||
|
18th August, 2025
|
||||||
|
|
||||||
|
### New feature
|
||||||
|
|
||||||
|
- Insecure chunk detection has been implemented.
|
||||||
|
- A notification dialogue will be shown if any insecure chunks are detected; these may have been created by v0.25.6 due to its issue. If this dialogue appears, please ensure you rebuild the database after backing it up.
|
||||||
|
|
||||||
## 0.25.7
|
## 0.25.7
|
||||||
|
|
||||||
@@ -49,7 +124,6 @@ In next version, insecure chunk detection will be implemented.
|
|||||||
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
||||||
- Removed unused code.
|
- Removed unused code.
|
||||||
|
|
||||||
|
|
||||||
## ~~0.25.5~~ 0.25.6
|
## ~~0.25.5~~ 0.25.6
|
||||||
|
|
||||||
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
||||||
@@ -150,7 +224,6 @@ The PBKDF2Salt will be referred to as the `Security Seed`, and it is used to der
|
|||||||
- The Set-up URI is now encrypted with a new encryption algorithm (mostly the same as `V2`).
|
- The Set-up URI is now encrypted with a new encryption algorithm (mostly the same as `V2`).
|
||||||
- The new Set-up URI is not compatible with version 0.24.x or earlier.
|
- The new Set-up URI is not compatible with version 0.24.x or earlier.
|
||||||
|
|
||||||
|
|
||||||
## 0.25.0
|
## 0.25.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -368,7 +441,6 @@ However, just to whisper, this is tremendously fast.
|
|||||||
- Dependent libraries have been updated to the latest version.
|
- Dependent libraries have been updated to the latest version.
|
||||||
- Some build processes have been separated to `pre` and `post` processes.
|
- Some build processes have been separated to `pre` and `post` processes.
|
||||||
|
|
||||||
|
|
||||||
## 0.24.25
|
## 0.24.25
|
||||||
|
|
||||||
22nd April, 2025
|
22nd April, 2025
|
||||||
@@ -446,8 +518,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- No longer conflicted files are handled in the boot-up process. No more
|
- No longer conflicted files are handled in the boot-up process. No more
|
||||||
unexpected overwriting.
|
unexpected overwriting.
|
||||||
- It ignores `Always overwrite with a newer file`, and always be prevented for
|
- It ignores `Always overwrite with a newer file`, and always be prevented for
|
||||||
the safety. Please pick it manually or open the file.
|
the safety. Please pick it manually or open the file.
|
||||||
- Some log messages on conflict resolution has been corrected.
|
- Some log messages on conflict resolution has been corrected.
|
||||||
- Automatic merge notifications, displayed on the grounds of `same`, have been
|
- Automatic merge notifications, displayed on the grounds of `same`, have been
|
||||||
degraded to logs.
|
degraded to logs.
|
||||||
@@ -456,8 +528,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- Now we can fetch the remote database with keeping local files completely
|
- Now we can fetch the remote database with keeping local files completely
|
||||||
intact.
|
intact.
|
||||||
- In new option, all files are stored into the local database before the
|
- In new option, all files are stored into the local database before the
|
||||||
fetching, and will be merged automatically or detected as conflicts.
|
fetching, and will be merged automatically or detected as conflicts.
|
||||||
- The dialogue presenting options when performing `Fetch` are now more
|
- The dialogue presenting options when performing `Fetch` are now more
|
||||||
informative.
|
informative.
|
||||||
|
|
||||||
@@ -479,8 +551,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- **NOW INDEED AND ACTUALLY** `Compute revisions for chunks` are backed into
|
- **NOW INDEED AND ACTUALLY** `Compute revisions for chunks` are backed into
|
||||||
enabled again. it is necessary for garbage collection of chunks.
|
enabled again. it is necessary for garbage collection of chunks.
|
||||||
- As far as existing users are concerned, this will not automatically change,
|
- As far as existing users are concerned, this will not automatically change,
|
||||||
but the Doctor will inform us.
|
but the Doctor will inform us.
|
||||||
|
|
||||||
## 0.24.19
|
## 0.24.19
|
||||||
|
|
||||||
@@ -521,7 +593,6 @@ Confession. I got the default values wrong. So scary and sorry.
|
|||||||
|
|
||||||
## 0.24.16
|
## 0.24.16
|
||||||
|
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
|
|
||||||
#### Peer-to-Peer
|
#### Peer-to-Peer
|
||||||
@@ -602,7 +673,6 @@ Sorry for the lack of replies. The ones that were not good are popping up, so I
|
|||||||
- Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic.
|
- Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic.
|
||||||
- Some file names have been changed to be more consistent.
|
- Some file names have been changed to be more consistent.
|
||||||
|
|
||||||
|
|
||||||
## 0.24.12
|
## 0.24.12
|
||||||
|
|
||||||
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
||||||
@@ -618,7 +688,6 @@ And, this is just a single web page, without any server-side code. It is a stati
|
|||||||
- And you can see the actual usage of this on [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) that a pseudo client for peer-to-peer synchronisation.
|
- And you can see the actual usage of this on [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) that a pseudo client for peer-to-peer synchronisation.
|
||||||
- Some UIs have been got isomorphic among Obsidian and web applications (for `webpeer`).
|
- Some UIs have been got isomorphic among Obsidian and web applications (for `webpeer`).
|
||||||
|
|
||||||
|
|
||||||
## 0.24.11
|
## 0.24.11
|
||||||
|
|
||||||
Peer-to-peer synchronisation has been implemented!
|
Peer-to-peer synchronisation has been implemented!
|
||||||
|
|||||||
Reference in New Issue
Block a user