mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-10 16:30:15 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 56a234e6d7 | |||
| 39014b2294 | |||
| 24e6c110a3 | |||
| f24d110552 | |||
| 7189c1c05a | |||
| 547afe9a86 | |||
| 7b5876037d | |||
| b714c00644 | |||
| e14e771bfb | |||
| 1130bbcee8 | |||
| 8841ef4619 | |||
| 45fe0b3682 | |||
| 8d3825abc9 |
@@ -31,3 +31,4 @@ coverage
|
|||||||
src/apps/cli/dist/*
|
src/apps/cli/dist/*
|
||||||
_testdata/**
|
_testdata/**
|
||||||
utils/bench/splitResults.csv
|
utils/bench/splitResults.csv
|
||||||
|
.eslintcache
|
||||||
@@ -17,7 +17,7 @@ The plugin uses a dynamic module system to reduce coupling and improve maintaina
|
|||||||
- `coreObsidian/` - Obsidian-specific core (e.g., `ModuleFileAccessObsidian`)
|
- `coreObsidian/` - Obsidian-specific core (e.g., `ModuleFileAccessObsidian`)
|
||||||
- `essential/` - Required modules (e.g., `ModuleMigration`, `ModuleKeyValueDB`)
|
- `essential/` - Required modules (e.g., `ModuleMigration`, `ModuleKeyValueDB`)
|
||||||
- `features/` - Optional features (e.g., `ModuleLog`, `ModuleObsidianSettings`)
|
- `features/` - Optional features (e.g., `ModuleLog`, `ModuleObsidianSettings`)
|
||||||
- `extras/` - Development/testing tools (e.g., `ModuleDev`, `ModuleIntegratedTest`)
|
- `extras/` - Development/testing tools (e.g., `ModuleDev`, ~~`ModuleIntegratedTest`~~)
|
||||||
- **Services**: Core services (e.g., `database`, `replicator`, `storageAccess`) are registered in `ServiceHub` and accessed by modules. They provide an extension point for add new behaviour without modifying existing code.
|
- **Services**: Core services (e.g., `database`, `replicator`, `storageAccess`) are registered in `ServiceHub` and accessed by modules. They provide an extension point for add new behaviour without modifying existing code.
|
||||||
- For example, checks before the replication can be added to the `replication.onBeforeReplicate` handler, and the handlers can be return `false` to prevent replication-starting. `vault.isTargetFile` also can be used to prevent processing specific files.
|
- For example, checks before the replication can be added to the `replication.onBeforeReplicate` handler, and the handlers can be return `false` to prevent replication-starting. `vault.isTargetFile` also can be used to prevent processing specific files.
|
||||||
- **ServiceModule**: A new type of module that directly depends on services.
|
- **ServiceModule**: A new type of module that directly depends on services.
|
||||||
|
|||||||
+52
-32
@@ -6,66 +6,86 @@ import * as sveltePlugin from "eslint-plugin-svelte";
|
|||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores([
|
globalIgnores([
|
||||||
"**/node_modules/*",
|
// Build outputs and legacy files
|
||||||
"**/jest.config.js",
|
"**/build",
|
||||||
|
"coverage",
|
||||||
|
"**/main.js",
|
||||||
|
"main_org.js",
|
||||||
|
"pouchdb-browser.js",
|
||||||
|
"version-bump.mjs",
|
||||||
|
"package.json",
|
||||||
|
"**/*.json",
|
||||||
|
"**/.eslintrc.js.bak",
|
||||||
|
// Files from linked dependencies (those files should not exist for most people).
|
||||||
|
"modules/octagonal-wheels/dist/**/*",
|
||||||
|
|
||||||
|
// Sub-projects (Exclude from root linting as they have different environments)
|
||||||
|
"src/apps/**/*",
|
||||||
|
"utils/**/*",
|
||||||
|
|
||||||
|
// Specific exclusions from common library (src/lib)
|
||||||
"src/lib/coverage",
|
"src/lib/coverage",
|
||||||
"src/lib/browsertest",
|
"src/lib/browsertest",
|
||||||
"**/test.ts",
|
|
||||||
"**/tests.ts",
|
|
||||||
"**/**test.ts",
|
|
||||||
"**/**.test.ts",
|
|
||||||
"**/*.unit.spec.ts",
|
|
||||||
"**/esbuild.*.mjs",
|
|
||||||
"**/terser.*.mjs",
|
|
||||||
"**/node_modules",
|
|
||||||
"**/build",
|
|
||||||
"**/.eslintrc.js.bak",
|
|
||||||
"src/lib/src/patches/pouchdb-utils",
|
|
||||||
"**/esbuild.config.mjs",
|
|
||||||
"**/rollup.config.js",
|
|
||||||
"modules/octagonal-wheels/rollup.config.js",
|
|
||||||
"modules/octagonal-wheels/dist/**/*",
|
|
||||||
"src/lib/test",
|
"src/lib/test",
|
||||||
"src/lib/_tools",
|
"src/lib/_tools",
|
||||||
|
"src/lib/src/patches/pouchdb-utils",
|
||||||
"src/lib/src/cli",
|
"src/lib/src/cli",
|
||||||
"**/main.js",
|
|
||||||
"src/apps/**/*",
|
|
||||||
".prettierrc.*.mjs",
|
|
||||||
".prettierrc.mjs",
|
|
||||||
"*.config.mjs",
|
|
||||||
"src/apps/**/*",
|
|
||||||
"src/lib/src/services/implements/browser/**",
|
"src/lib/src/services/implements/browser/**",
|
||||||
"src/lib/src/services/implements/headless/**",
|
"src/lib/src/services/implements/headless/**",
|
||||||
"src/lib/src/API",
|
"src/lib/src/API",
|
||||||
|
|
||||||
|
// Config files and build scripts
|
||||||
|
"**/jest.config.js",
|
||||||
|
"**/rollup.config.js",
|
||||||
|
"**/esbuild.config.mjs",
|
||||||
|
"**/terser.*.mjs",
|
||||||
|
".prettierrc.*.mjs",
|
||||||
|
".prettierrc.mjs",
|
||||||
|
"*.config.mjs",
|
||||||
|
"vite.*",
|
||||||
|
"vitest.*",
|
||||||
|
// Testing files (Simplified patterns)
|
||||||
|
"test/**",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.unit.spec.ts",
|
||||||
|
"**/test.ts",
|
||||||
|
"**/tests.ts",
|
||||||
]),
|
]),
|
||||||
...sveltePlugin.configs["flat/base"],
|
...sveltePlugin.configs["flat/base"],
|
||||||
...obsidianmd.configs.recommended,
|
...obsidianmd.configs.recommended,
|
||||||
{
|
{
|
||||||
files: ["**/*.ts"],
|
files: ["**/*.ts"],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: { ...globals.browser },
|
globals: { ...globals.browser, "PouchDB": "readonly" },
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: "./tsconfig.json",
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
// Base rules (turned off in favour of TS specific versions or explicitly disabled).
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
|
||||||
"no-unused-labels": "off",
|
"no-unused-labels": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
"no-prototype-builtins": "off",
|
"no-prototype-builtins": "off",
|
||||||
|
"require-await": "off",
|
||||||
|
// TypeScript specific rules
|
||||||
|
"@typescript-eslint/no-deprecated": "warn",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"require-await": "error",
|
|
||||||
"obsidianmd/rule-custom-message": "off", // Temporary
|
|
||||||
"obsidianmd/ui/sentence-case": "off", // Temporary
|
|
||||||
"@typescript-eslint/require-await": "warn",
|
"@typescript-eslint/require-await": "warn",
|
||||||
"@typescript-eslint/no-misused-promises": "warn",
|
"@typescript-eslint/no-misused-promises": "warn",
|
||||||
"@typescript-eslint/no-floating-promises": "warn",
|
"@typescript-eslint/no-floating-promises": "warn",
|
||||||
"no-async-promise-executor": "warn",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
|
||||||
|
// General rules
|
||||||
|
"no-async-promise-executor": "warn",
|
||||||
"no-constant-condition": ["error", { checkLoops: false }],
|
"no-constant-condition": ["error", { checkLoops: false }],
|
||||||
|
|
||||||
|
// Plugin specific overrides (Pending review)
|
||||||
|
"obsidianmd/rule-custom-message": "off",
|
||||||
|
"obsidianmd/ui/sentence-case": "off",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,7 +97,7 @@ export default defineConfig([
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
"no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
"no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
|
||||||
"obsidianmd/no-plugin-as-component": "off", // Temporary
|
"obsidianmd/no-plugin-as-component": "off",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||
Generated
+4
-4
@@ -25,7 +25,7 @@
|
|||||||
"micromatch": "^4.0.0",
|
"micromatch": "^4.0.0",
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^10.2.2",
|
||||||
"obsidian": "^1.12.3",
|
"obsidian": "^1.12.3",
|
||||||
"octagonal-wheels": "^0.1.45",
|
"octagonal-wheels": "^0.1.46",
|
||||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"werift": "^0.23.0",
|
"werift": "^0.23.0",
|
||||||
@@ -11491,9 +11491,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/octagonal-wheels": {
|
"node_modules/octagonal-wheels": {
|
||||||
"version": "0.1.45",
|
"version": "0.1.46",
|
||||||
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.45.tgz",
|
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.46.tgz",
|
||||||
"integrity": "sha512-gXoCrwoUIXhmu57YN4BxAtBe+JaYNJNaXaZuVjqjopwYKpH5p2mn1om6KjA22rgGPiIJFXkse2U28FFXoT3/0Q==",
|
"integrity": "sha512-19eB7b/WNNrZ4Xghu93f+NVJsbRiaZaIIzU1rn5shxb6SzwVBoOVkNPJdCAsONl6C1MwjaGDrPUS8CBXvPHjPg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"idb": "^8.0.3"
|
"idb": "^8.0.3"
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@
|
|||||||
"buildVite": "npx dotenv-cli -e .env -- vite build --mode production",
|
"buildVite": "npx dotenv-cli -e .env -- vite build --mode production",
|
||||||
"buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original",
|
"buildViteOriginal": "npx dotenv-cli -e .env -- vite build --mode original",
|
||||||
"buildDev": "node esbuild.config.mjs dev",
|
"buildDev": "node esbuild.config.mjs dev",
|
||||||
"lint": "eslint src",
|
"lint": "eslint --cache --concurrency auto src",
|
||||||
"svelte-check": "svelte-check --tsconfig ./tsconfig.json",
|
"svelte-check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"tsc-check": "tsc --noEmit",
|
"tsc-check": "tsc --noEmit",
|
||||||
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
"pretty": "npm run prettyNoWrite -- --write --log-level error",
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
"markdown-it": "^14.1.1",
|
"markdown-it": "^14.1.1",
|
||||||
"micromatch": "^4.0.0",
|
"micromatch": "^4.0.0",
|
||||||
"minimatch": "^10.2.2",
|
"minimatch": "^10.2.2",
|
||||||
"octagonal-wheels": "^0.1.45",
|
"octagonal-wheels": "^0.1.46",
|
||||||
"pouchdb-adapter-leveldb": "^9.0.0",
|
"pouchdb-adapter-leveldb": "^9.0.0",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"werift": "^0.23.0",
|
"werift": "^0.23.0",
|
||||||
|
|||||||
+2
-1
@@ -146,7 +146,7 @@ export const _requestToCouchDBFetch = async (
|
|||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
};
|
};
|
||||||
return await fetch(uri, requestParam);
|
return await _fetch(uri, requestParam);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _requestToCouchDB = async (
|
export const _requestToCouchDB = async (
|
||||||
@@ -214,6 +214,7 @@ import { BASE_IS_NEW, EVEN, TARGET_IS_NEW } from "@lib/common/models/shared.cons
|
|||||||
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
|
export { BASE_IS_NEW, EVEN, TARGET_IS_NEW };
|
||||||
// Why 2000? : ZIP FILE Does not have enough resolution.
|
// Why 2000? : ZIP FILE Does not have enough resolution.
|
||||||
import { compareMTime } from "@lib/common/utils.ts";
|
import { compareMTime } from "@lib/common/utils.ts";
|
||||||
|
import { _fetch } from "@/lib/src/common/coreEnvFunctions.ts";
|
||||||
export { compareMTime };
|
export { compareMTime };
|
||||||
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
||||||
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ import { ConflictResolveModal } from "../../modules/features/InteractiveConflict
|
|||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
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 "@/lib/src/common/i18n.ts";
|
||||||
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
@@ -564,7 +564,7 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
...data,
|
...data,
|
||||||
documentPath: this.getPath(wx),
|
documentPath: this.getPath(wx),
|
||||||
files: xFiles,
|
files: xFiles,
|
||||||
} as PluginDataExDisplay;
|
} satisfies PluginDataExDisplay;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
Submodule src/lib updated: 61741c1748...faf5c55fd7
@@ -12,8 +12,6 @@ import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidian
|
|||||||
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";
|
||||||
import { ModuleIntegratedTest } from "./modules/extras/ModuleIntegratedTest.ts";
|
|
||||||
import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
|
|
||||||
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||||
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts";
|
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts";
|
||||||
import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts";
|
import { ObsidianServiceHub } from "./modules/services/ObsidianServiceHub.ts";
|
||||||
@@ -156,8 +154,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin {
|
|||||||
new ModuleInteractiveConflictResolver(this, core),
|
new ModuleInteractiveConflictResolver(this, core),
|
||||||
new ModuleObsidianGlobalHistory(this, core),
|
new ModuleObsidianGlobalHistory(this, core),
|
||||||
new ModuleDev(this, core),
|
new ModuleDev(this, core),
|
||||||
new ModuleReplicateTest(this, core),
|
|
||||||
new ModuleIntegratedTest(this, core),
|
|
||||||
new SetupManager(core), // this should be moved to core?
|
new SetupManager(core), // this should be moved to core?
|
||||||
new ModuleMigration(core),
|
new ModuleMigration(core),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ButtonComponent } from "@/deps.ts";
|
import { ButtonComponent } from "@/deps.ts";
|
||||||
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts";
|
import { App, FuzzySuggestModal, MarkdownRenderer, Modal, Plugin, Setting } from "../../../deps.ts";
|
||||||
import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts";
|
import { EVENT_PLUGIN_UNLOADED, eventHub } from "../../../common/events.ts";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
class AutoClosableModal extends Modal {
|
class AutoClosableModal extends Modal {
|
||||||
_closeByUnload() {
|
_closeByUnload() {
|
||||||
@@ -121,7 +122,7 @@ export class PopoverSelectString extends FuzzySuggestModal<string> {
|
|||||||
this.callback = undefined;
|
this.callback = undefined;
|
||||||
}
|
}
|
||||||
override onClose(): void {
|
override onClose(): void {
|
||||||
setTimeout(() => {
|
compatGlobal.setTimeout(() => {
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
this.callback("");
|
this.callback("");
|
||||||
this.callback = undefined;
|
this.callback = undefined;
|
||||||
@@ -139,7 +140,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
isManuallyClosed = false;
|
isManuallyClosed = false;
|
||||||
defaultAction: string | undefined;
|
defaultAction: string | undefined;
|
||||||
timeout: number | undefined;
|
timeout: number | undefined;
|
||||||
timer: ReturnType<typeof setInterval> | undefined = undefined;
|
timer: ReturnType<typeof compatGlobal.setInterval> | undefined = undefined;
|
||||||
defaultButtonComponent: ButtonComponent | undefined;
|
defaultButtonComponent: ButtonComponent | undefined;
|
||||||
wideButton: boolean;
|
wideButton: boolean;
|
||||||
|
|
||||||
@@ -165,12 +166,12 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
this.wideButton = wideButton;
|
this.wideButton = wideButton;
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
this.timer = setInterval(() => {
|
this.timer = compatGlobal.setInterval(() => {
|
||||||
if (this.timeout === undefined) return;
|
if (this.timeout === undefined) return;
|
||||||
this.timeout--;
|
this.timeout--;
|
||||||
if (this.timeout < 0) {
|
if (this.timeout < 0) {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearInterval(this.timer);
|
compatGlobal.clearInterval(this.timer);
|
||||||
this.defaultButtonComponent?.setButtonText(`${defaultAction}`);
|
this.defaultButtonComponent?.setButtonText(`${defaultAction}`);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
}
|
}
|
||||||
@@ -213,7 +214,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
labelWrapper.empty();
|
labelWrapper.empty();
|
||||||
labelWrapper.style.display = "none";
|
labelWrapper.style.display = "none";
|
||||||
clearInterval(this.timer);
|
compatGlobal.clearInterval(this.timer);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
||||||
}
|
}
|
||||||
@@ -224,7 +225,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
this.isManuallyClosed = true;
|
this.isManuallyClosed = true;
|
||||||
this.result = button;
|
this.result = button;
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearInterval(this.timer);
|
compatGlobal.clearInterval(this.timer);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
}
|
}
|
||||||
this.close();
|
this.close();
|
||||||
@@ -247,7 +248,7 @@ export class MessageBox<T extends readonly string[]> extends AutoClosableModal {
|
|||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearInterval(this.timer);
|
compatGlobal.clearInterval(this.timer);
|
||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
}
|
}
|
||||||
if (this.isManuallyClosed) {
|
if (this.isManuallyClosed) {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
this.requestTimeoutInMs = options === undefined ? undefined : options.requestTimeout;
|
this.requestTimeoutInMs = options === undefined ? undefined : options.requestTimeout;
|
||||||
this.reverseProxyNoSignUrl = reverseProxyNoSignUrl;
|
this.reverseProxyNoSignUrl = reverseProxyNoSignUrl;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line require-await
|
|
||||||
override async handle(
|
override async handle(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
{ abortSignal }: HttpHandlerOptions = {}
|
{ abortSignal }: HttpHandlerOptions = {}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
hiddenFilesProcessingCount,
|
hiddenFilesProcessingCount,
|
||||||
} from "../../lib/src/mock_and_interop/stores.ts";
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
||||||
_everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
@@ -222,9 +223,9 @@ export class ModuleObsidianEvents extends AbstractObsidianModule {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.plugin.registerInterval(
|
this.plugin.registerInterval(
|
||||||
setInterval(() => {
|
compatGlobal.setInterval(() => {
|
||||||
__tick.value++;
|
__tick.value++;
|
||||||
}, 1000) as unknown as number
|
}, 1000)
|
||||||
);
|
);
|
||||||
|
|
||||||
let stableCheck = 3;
|
let stableCheck = 3;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export class ModuleDev extends AbstractObsidianModule {
|
|||||||
const filename = "test-create-conflict.md";
|
const filename = "test-create-conflict.md";
|
||||||
const content = `# Test create conflict\n\n`;
|
const content = `# Test create conflict\n\n`;
|
||||||
const w = await this.core.databaseFileAccess.store({
|
const w = await this.core.databaseFileAccess.store({
|
||||||
name: filename as FilePathWithPrefix,
|
name: filename,
|
||||||
path: filename as FilePathWithPrefix,
|
path: filename as FilePathWithPrefix,
|
||||||
body: new Blob([content], { type: "text/markdown" }),
|
body: new Blob([content], { type: "text/markdown" }),
|
||||||
stat: {
|
stat: {
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
|
||||||
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
|
|
||||||
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
|
||||||
import { AbstractObsidianModule } from "../AbstractObsidianModule";
|
|
||||||
|
|
||||||
export class ModuleIntegratedTest extends AbstractObsidianModule {
|
|
||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
|
||||||
await delay(100);
|
|
||||||
const start = Date.now();
|
|
||||||
while (!(await proc())) {
|
|
||||||
if (timeout > 0) {
|
|
||||||
if (Date.now() - start > timeout) {
|
|
||||||
this._log(`Timeout`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await delay(500);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
waitWithReplicating(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
|
||||||
return this.waitFor(async () => {
|
|
||||||
await this.tryReplicate();
|
|
||||||
return await proc();
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
async storageContentIsEqual(file: string, content: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const fileContent = await this.readStorageContent(file as FilePathWithPrefix);
|
|
||||||
if (fileContent === content) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// this._log(`Content is not same \n Expected:${content}\n Actual:${fileContent}`, LOG_LEVEL_VERBOSE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this._log(`Error: ${e}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async assert(proc: () => Promise<boolean>): Promise<boolean> {
|
|
||||||
if (!(await proc())) {
|
|
||||||
this._log(`Assertion failed`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
async __orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
|
||||||
if (!(await this._test(key, proc))) {
|
|
||||||
throw new Error(`${key}`);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
tryReplicate() {
|
|
||||||
if (!this.settings.liveSync) {
|
|
||||||
return shareRunningResult("replicate-test", async () => {
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async readStorageContent(file: FilePathWithPrefix): Promise<string | undefined> {
|
|
||||||
if (!(await this.core.storageAccess.isExistsIncludeHidden(file))) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return await this.core.storageAccess.readHiddenFileText(file);
|
|
||||||
}
|
|
||||||
async __proceed(no: number, title: string): Promise<boolean> {
|
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
|
||||||
const stepContent = `Step ${no}`;
|
|
||||||
await this.services.conflict.resolveByNewest(stepFile);
|
|
||||||
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
|
||||||
await this.__orDie(`Wait for acknowledge ${no}`, async () => {
|
|
||||||
if (
|
|
||||||
!(await this.waitWithReplicating(async () => {
|
|
||||||
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
|
||||||
}, 20000))
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
async __join(no: number, title: string): Promise<boolean> {
|
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
|
||||||
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
|
|
||||||
const stepContent = `Step ${no}`;
|
|
||||||
|
|
||||||
await this.__orDie(`Wait for step ${no} (${title})`, async () => {
|
|
||||||
if (
|
|
||||||
!(await this.waitWithReplicating(async () => {
|
|
||||||
return await this.storageContentIsEqual(stepFile, stepContent);
|
|
||||||
}, 20000))
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
await this.services.conflict.resolveByNewest(stepAckFile);
|
|
||||||
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
|
||||||
await this.tryReplicate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async performStep({
|
|
||||||
step,
|
|
||||||
title,
|
|
||||||
isGameChanger,
|
|
||||||
proc,
|
|
||||||
check,
|
|
||||||
}: {
|
|
||||||
step: number;
|
|
||||||
title: string;
|
|
||||||
isGameChanger: boolean;
|
|
||||||
proc: () => Promise<any>;
|
|
||||||
check: () => Promise<boolean>;
|
|
||||||
}): Promise<boolean> {
|
|
||||||
if (isGameChanger) {
|
|
||||||
await this.__proceed(step, title);
|
|
||||||
try {
|
|
||||||
await proc();
|
|
||||||
} catch (e) {
|
|
||||||
this._log(`Error: ${e}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return await this.__orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
|
||||||
} else {
|
|
||||||
return await this.__join(step, title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// // see scenario.md
|
|
||||||
// async testLeader(testMain: (testFileName: FilePathWithPrefix) => Promise<boolean>): Promise<boolean> {
|
|
||||||
|
|
||||||
// }
|
|
||||||
// async testReceiver(testMain: (testFileName: FilePathWithPrefix) => Promise<boolean>): Promise<boolean> {
|
|
||||||
|
|
||||||
// }
|
|
||||||
async nonLiveTestRunner(
|
|
||||||
isLeader: boolean,
|
|
||||||
testMain: (testFileName: FilePathWithPrefix, isLeader: boolean) => Promise<boolean>
|
|
||||||
): Promise<boolean> {
|
|
||||||
const storage = this.core.storageAccess;
|
|
||||||
// const database = this.core.databaseFileAccess;
|
|
||||||
// const _orDie = this._orDie.bind(this);
|
|
||||||
const testCommandFile = "IT.md" as FilePathWithPrefix;
|
|
||||||
const textCommandResponseFile = "ITx.md" as FilePathWithPrefix;
|
|
||||||
let testFileName: FilePathWithPrefix;
|
|
||||||
this.addTestResult(
|
|
||||||
"-------Starting ... ",
|
|
||||||
true,
|
|
||||||
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
|
||||||
);
|
|
||||||
if (isLeader) {
|
|
||||||
await this.__proceed(0, "start");
|
|
||||||
}
|
|
||||||
await this.tryReplicate();
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 0,
|
|
||||||
title: "Make sure that command File Not Exists",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => await storage.removeHidden(testCommandFile),
|
|
||||||
check: async () => !(await storage.isExistsIncludeHidden(testCommandFile)),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 1,
|
|
||||||
title: "Make sure that command File Not Exists On Receiver",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => await storage.removeHidden(textCommandResponseFile),
|
|
||||||
check: async () => !(await storage.isExistsIncludeHidden(textCommandResponseFile)),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 2,
|
|
||||||
title: "Decide the test file name",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {
|
|
||||||
testFileName = (Date.now() + "-" + Math.ceil(Math.random() * 1000) + ".md") as FilePathWithPrefix;
|
|
||||||
const testCommandFile = "IT.md" as FilePathWithPrefix;
|
|
||||||
await storage.writeFileAuto(testCommandFile, testFileName);
|
|
||||||
},
|
|
||||||
check: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 3,
|
|
||||||
title: "Wait for the command file to be arrived",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await storage.isExistsIncludeHidden(testCommandFile),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 4,
|
|
||||||
title: "Send the response file",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {
|
|
||||||
await storage.writeHiddenFileAuto(textCommandResponseFile, "!");
|
|
||||||
},
|
|
||||||
check: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 5,
|
|
||||||
title: "Wait for the response file to be arrived",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await storage.isExistsIncludeHidden(textCommandResponseFile),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 6,
|
|
||||||
title: "Proceed to begin the test",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 6,
|
|
||||||
title: "Begin the test",
|
|
||||||
isGameChanger: !false,
|
|
||||||
proc: async () => {},
|
|
||||||
check: () => {
|
|
||||||
return Promise.resolve(true);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// await this.step(0, isLeader, true);
|
|
||||||
try {
|
|
||||||
this.addTestResult("** Main------", true, ``);
|
|
||||||
if (isLeader) {
|
|
||||||
return await testMain(testFileName!, true);
|
|
||||||
} else {
|
|
||||||
const testFileName = await this.readStorageContent(testCommandFile);
|
|
||||||
this.addTestResult("testFileName", true, `Request client to use :${testFileName!}`);
|
|
||||||
return await testMain(testFileName! as FilePathWithPrefix, false);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.addTestResult("Teardown", true, `Deleting ${testFileName!}`);
|
|
||||||
await storage.removeHidden(testFileName!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
// Make sure the
|
|
||||||
}
|
|
||||||
|
|
||||||
async testBasic(filename: FilePathWithPrefix, isLeader: boolean): Promise<boolean> {
|
|
||||||
const storage = this.core.storageAccess;
|
|
||||||
const database = this.core.databaseFileAccess;
|
|
||||||
|
|
||||||
await this.addTestResult(
|
|
||||||
`---**Starting Basic Test**---`,
|
|
||||||
true,
|
|
||||||
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${filename}`
|
|
||||||
);
|
|
||||||
// if (isLeader) {
|
|
||||||
// await this._proceed(0);
|
|
||||||
// }
|
|
||||||
// await this.tryReplicate();
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 0,
|
|
||||||
title: "Make sure that file is not exist",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => !(await storage.isExists(filename)),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 1,
|
|
||||||
title: "Write a file",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World"),
|
|
||||||
check: async () => await storage.isExists(filename),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 2,
|
|
||||||
title: "Make sure the file is arrived",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await storage.isExists(filename),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 3,
|
|
||||||
title: "Update to Hello World 2",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World 2"),
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 4,
|
|
||||||
title: "Make sure the modified file is arrived",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 5,
|
|
||||||
title: "Update to Hello World 3",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World 3"),
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 6,
|
|
||||||
title: "Make sure the modified file is arrived",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const multiLineContent = `Line1:A
|
|
||||||
Line2:B
|
|
||||||
Line3:C
|
|
||||||
Line4:D`;
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 7,
|
|
||||||
title: "Update to Multiline",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => await storage.writeFileAuto(filename, multiLineContent),
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 8,
|
|
||||||
title: "Make sure the modified file is arrived",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
|
||||||
});
|
|
||||||
|
|
||||||
// While LiveSync, possibly cannot cause the conflict.
|
|
||||||
if (!this.settings.liveSync) {
|
|
||||||
// Step 9 Make Conflict But Resolvable
|
|
||||||
const multiLineContentL = `Line1:A
|
|
||||||
Line2:B
|
|
||||||
Line3:C!
|
|
||||||
Line4:D`;
|
|
||||||
const multiLineContentC = `Line1:A
|
|
||||||
Line2:bbbbb
|
|
||||||
Line3:C
|
|
||||||
Line4:D`;
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 9,
|
|
||||||
title: "Progress to be conflicted",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
await storage.writeFileAuto(filename, isLeader ? multiLineContentL : multiLineContentC);
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 10,
|
|
||||||
title: "Update As Conflicted",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: () => Promise.resolve(true),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 10,
|
|
||||||
title: "Make sure Automatically resolved",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 11,
|
|
||||||
title: "Make sure Automatically resolved",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sensiblyMergedContent = `Line1:A
|
|
||||||
Line2:bbbbb
|
|
||||||
Line3:C!
|
|
||||||
Line4:D`;
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 12,
|
|
||||||
title: "Make sure Sensibly Merged on Leader",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
|
||||||
});
|
|
||||||
await this.performStep({
|
|
||||||
step: 13,
|
|
||||||
title: "Make sure Sensibly Merged on Receiver",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.performStep({
|
|
||||||
step: 14,
|
|
||||||
title: "Delete File",
|
|
||||||
isGameChanger: isLeader,
|
|
||||||
proc: async () => {
|
|
||||||
await storage.removeHidden(filename);
|
|
||||||
},
|
|
||||||
check: async () => !(await storage.isExists(filename)),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.performStep({
|
|
||||||
step: 15,
|
|
||||||
title: "Make sure File is deleted",
|
|
||||||
isGameChanger: !isLeader,
|
|
||||||
proc: async () => {},
|
|
||||||
check: async () => !(await storage.isExists(filename)),
|
|
||||||
});
|
|
||||||
this._log(`The Basic Test has been completed`, LOG_LEVEL_NOTICE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async testBasicEvent(isLeader: boolean) {
|
|
||||||
this.settings.liveSync = false;
|
|
||||||
await this.saveSettings();
|
|
||||||
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
|
||||||
}
|
|
||||||
async testBasicLive(isLeader: boolean) {
|
|
||||||
this.settings.liveSync = true;
|
|
||||||
await this.saveSettings();
|
|
||||||
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async _everyModuleTestMultiDevice(): Promise<boolean> {
|
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
|
||||||
const isLeader = this.core.services.vault.vaultName().indexOf("recv") === -1;
|
|
||||||
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
|
|
||||||
try {
|
|
||||||
this._log(`Starting Test`);
|
|
||||||
await this.testBasicEvent(isLeader);
|
|
||||||
if (this.settings.remoteType == REMOTE_MINIO) await this.testBasicLive(isLeader);
|
|
||||||
} catch (e) {
|
|
||||||
this._log(e);
|
|
||||||
this._log(`Error: ${e}`);
|
|
||||||
return Promise.resolve(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
override onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
|
||||||
services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,590 +0,0 @@
|
|||||||
// I intend to discontinue maintenance of this class. It seems preferable to test it externally.
|
|
||||||
import { delay } from "octagonal-wheels/promises";
|
|
||||||
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
|
||||||
import { eventHub } from "../../common/events";
|
|
||||||
import { getWebCrypto } from "../../lib/src/mods.ts";
|
|
||||||
import { uint8ArrayToHexString } from "octagonal-wheels/binary/hex";
|
|
||||||
import { parseYaml, requestUrl, stringifyYaml } from "@/deps.ts";
|
|
||||||
import type { FilePath } from "../../lib/src/common/types.ts";
|
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
|
||||||
import { getFileRegExp } from "../../lib/src/common/utils.ts";
|
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface LSEvents {
|
|
||||||
"debug-sync-status": string[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModuleReplicateTest extends AbstractObsidianModule {
|
|
||||||
testRootPath = "_test/";
|
|
||||||
testInfoPath = "_testinfo/";
|
|
||||||
|
|
||||||
get isLeader() {
|
|
||||||
return (
|
|
||||||
this.services.vault.getVaultName().indexOf("dev") >= 0 &&
|
|
||||||
this.services.vault.vaultName().indexOf("recv") < 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get nameByKind() {
|
|
||||||
if (!this.isLeader) {
|
|
||||||
return "RECV";
|
|
||||||
} else if (this.isLeader) {
|
|
||||||
return "LEADER";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get pairName() {
|
|
||||||
if (this.isLeader) {
|
|
||||||
return "RECV";
|
|
||||||
} else if (!this.isLeader) {
|
|
||||||
return "LEADER";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watchIsSynchronised = false;
|
|
||||||
|
|
||||||
statusBarSyncStatus?: HTMLElement;
|
|
||||||
async readFileContent(file: string) {
|
|
||||||
try {
|
|
||||||
return await this.core.storageAccess.readHiddenFileText(file);
|
|
||||||
} catch {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async dumpList() {
|
|
||||||
if (this.settings.syncInternalFiles) {
|
|
||||||
this._log("Write file list (Include Hidden)");
|
|
||||||
await this.__dumpFileListIncludeHidden("files.md");
|
|
||||||
} else {
|
|
||||||
this._log("Write file list");
|
|
||||||
await this.__dumpFileList("files.md");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
|
||||||
await this.dumpList();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
|
||||||
this.addCommand({
|
|
||||||
id: "dump-file-structure-normal",
|
|
||||||
name: `Dump Structure (Normal)`,
|
|
||||||
callback: () => {
|
|
||||||
void this.__dumpFileList("files.md").finally(() => {
|
|
||||||
void this.refreshSyncStatus();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.addCommand({
|
|
||||||
id: "dump-file-structure-ih",
|
|
||||||
name: "Dump Structure (Include Hidden)",
|
|
||||||
callback: () => {
|
|
||||||
const d = "files.md";
|
|
||||||
void this.__dumpFileListIncludeHidden(d);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.addCommand({
|
|
||||||
id: "dump-file-structure-auto",
|
|
||||||
name: "Dump Structure",
|
|
||||||
callback: () => {
|
|
||||||
void this.dumpList();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.addCommand({
|
|
||||||
id: "dump-file-test",
|
|
||||||
name: `Perform Test (Dev) ${this.isLeader ? "(Leader)" : "(Recv)"}`,
|
|
||||||
callback: () => {
|
|
||||||
void this.performTestManually();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.addCommand({
|
|
||||||
id: "watch-sync-result",
|
|
||||||
name: `Watch sync result is matched between devices`,
|
|
||||||
callback: () => {
|
|
||||||
this.watchIsSynchronised = !this.watchIsSynchronised;
|
|
||||||
void this.refreshSyncStatus();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.app.vault.on("modify", async (file) => {
|
|
||||||
if (file.path.startsWith(this.testInfoPath)) {
|
|
||||||
await this.refreshSyncStatus();
|
|
||||||
} else {
|
|
||||||
scheduleTask("dumpStatus", 125, async () => {
|
|
||||||
await this.dumpList();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.statusBarSyncStatus = this.plugin.addStatusBarItem();
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
async getSyncStatusAsText() {
|
|
||||||
const fileMine = this.testInfoPath + this.nameByKind + "/" + "files.md";
|
|
||||||
const filePair = this.testInfoPath + this.pairName + "/" + "files.md";
|
|
||||||
const mine = parseYaml(await this.readFileContent(fileMine));
|
|
||||||
const pair = parseYaml(await this.readFileContent(filePair));
|
|
||||||
const result = [] as string[];
|
|
||||||
if (mine.length != pair.length) {
|
|
||||||
result.push(`File count is different: ${mine.length} vs ${pair.length}`);
|
|
||||||
}
|
|
||||||
const filesAll = new Set([...mine.map((e: any) => e.path), ...pair.map((e: any) => e.path)]);
|
|
||||||
for (const file of filesAll) {
|
|
||||||
const mineFile = mine.find((e: any) => e.path == file);
|
|
||||||
const pairFile = pair.find((e: any) => e.path == file);
|
|
||||||
if (!mineFile || !pairFile) {
|
|
||||||
result.push(`File not found: ${file}`);
|
|
||||||
} else {
|
|
||||||
if (mineFile.size != pairFile.size) {
|
|
||||||
result.push(`Size is different: ${file} ${mineFile.size} vs ${pairFile.size}`);
|
|
||||||
}
|
|
||||||
if (mineFile.hash != pairFile.hash) {
|
|
||||||
result.push(`Hash is different: ${file} ${mineFile.hash} vs ${pairFile.hash}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventHub.emitEvent("debug-sync-status", result);
|
|
||||||
return result.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshSyncStatus() {
|
|
||||||
if (this.watchIsSynchronised) {
|
|
||||||
// Normal Files
|
|
||||||
const syncStatus = await this.getSyncStatusAsText();
|
|
||||||
if (syncStatus) {
|
|
||||||
this.statusBarSyncStatus!.setText(`Sync Status: Having Error`);
|
|
||||||
this._log(`Sync Status: Having Error\n${syncStatus}`, LOG_LEVEL_INFO);
|
|
||||||
} else {
|
|
||||||
this.statusBarSyncStatus!.setText(`Sync Status: Synchronised`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.statusBarSyncStatus!.setText("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async __dumpFileList(outFile?: string) {
|
|
||||||
if (!this.core || !this.core.storageAccess) {
|
|
||||||
this._log("No storage access", LOG_LEVEL_INFO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const files = await this.core.storageAccess.getFiles();
|
|
||||||
const out = [] as any[];
|
|
||||||
const webcrypto = await getWebCrypto();
|
|
||||||
for (const file of files) {
|
|
||||||
if (!(await this.services.vault.isTargetFile(file.path))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (file.path.startsWith(this.testInfoPath)) continue;
|
|
||||||
const stat = await this.core.storageAccess.stat(file.path);
|
|
||||||
if (stat) {
|
|
||||||
const hashSrc = await this.core.storageAccess.readHiddenFileBinary(file.path);
|
|
||||||
const hash = await webcrypto.subtle.digest("SHA-1", hashSrc);
|
|
||||||
const hashStr = uint8ArrayToHexString(new Uint8Array(hash));
|
|
||||||
const item = {
|
|
||||||
path: file.path,
|
|
||||||
name: file.name,
|
|
||||||
size: stat.size,
|
|
||||||
mtime: stat.mtime,
|
|
||||||
hash: hashStr,
|
|
||||||
};
|
|
||||||
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
|
||||||
out.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
||||||
if (outFile) {
|
|
||||||
outFile = this.testInfoPath + this.nameByKind + "/" + outFile;
|
|
||||||
await this.core.storageAccess.ensureDir(outFile);
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(outFile, stringifyYaml(out));
|
|
||||||
} else {
|
|
||||||
// console.dir(out);
|
|
||||||
}
|
|
||||||
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
|
|
||||||
}
|
|
||||||
|
|
||||||
async __dumpFileListIncludeHidden(outFile?: string) {
|
|
||||||
const ignorePatterns = getFileRegExp(this.core.settings, "syncInternalFilesIgnorePatterns");
|
|
||||||
const targetPatterns = getFileRegExp(this.core.settings, "syncInternalFilesTargetPatterns");
|
|
||||||
const out = [] as any[];
|
|
||||||
const files = await this.core.storageAccess.getFilesIncludeHidden("", targetPatterns, ignorePatterns);
|
|
||||||
// console.dir(files);
|
|
||||||
const webcrypto = await getWebCrypto();
|
|
||||||
for (const file of files) {
|
|
||||||
// if (!await this.core.$$isTargetFile(file)) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
if (file.startsWith(this.testInfoPath)) continue;
|
|
||||||
const stat = await this.core.storageAccess.statHidden(file);
|
|
||||||
if (stat) {
|
|
||||||
const hashSrc = await this.core.storageAccess.readHiddenFileBinary(file);
|
|
||||||
const hash = await webcrypto.subtle.digest("SHA-1", hashSrc);
|
|
||||||
const hashStr = uint8ArrayToHexString(new Uint8Array(hash));
|
|
||||||
const item = {
|
|
||||||
path: file,
|
|
||||||
name: file.split("/").pop(),
|
|
||||||
size: stat.size,
|
|
||||||
mtime: stat.mtime,
|
|
||||||
hash: hashStr,
|
|
||||||
};
|
|
||||||
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
|
||||||
out.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.sort((a, b) => a.path.localeCompare(b.path));
|
|
||||||
if (outFile) {
|
|
||||||
outFile = this.testInfoPath + this.nameByKind + "/" + outFile;
|
|
||||||
await this.core.storageAccess.ensureDir(outFile);
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(outFile, stringifyYaml(out));
|
|
||||||
} else {
|
|
||||||
// console.dir(out);
|
|
||||||
}
|
|
||||||
this._log(`Dumped ${out.length} files`, LOG_LEVEL_NOTICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async collectTestFiles() {
|
|
||||||
const remoteTopDir = "https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/refs/heads/main/";
|
|
||||||
const files = [
|
|
||||||
"README.md",
|
|
||||||
"docs/adding_translations.md",
|
|
||||||
"docs/design_docs_of_journalsync.md",
|
|
||||||
"docs/design_docs_of_keep_newborn_chunks.md",
|
|
||||||
"docs/design_docs_of_prefixed_hidden_file_sync.md",
|
|
||||||
"docs/design_docs_of_sharing_tweak_value.md",
|
|
||||||
"docs/quick_setup_cn.md",
|
|
||||||
"docs/quick_setup_ja.md",
|
|
||||||
"docs/quick_setup.md",
|
|
||||||
"docs/settings_ja.md",
|
|
||||||
"docs/settings.md",
|
|
||||||
"docs/setup_cloudant_ja.md",
|
|
||||||
"docs/setup_cloudant.md",
|
|
||||||
"docs/setup_flyio.md",
|
|
||||||
"docs/setup_own_server_cn.md",
|
|
||||||
"docs/setup_own_server_ja.md",
|
|
||||||
"docs/setup_own_server.md",
|
|
||||||
"docs/tech_info_ja.md",
|
|
||||||
"docs/tech_info.md",
|
|
||||||
"docs/terms.md",
|
|
||||||
"docs/troubleshooting.md",
|
|
||||||
"images/1.png",
|
|
||||||
"images/2.png",
|
|
||||||
"images/corrupted_data.png",
|
|
||||||
"images/hatch.png",
|
|
||||||
"images/lock_pattern1.png",
|
|
||||||
"images/lock_pattern2.png",
|
|
||||||
"images/quick_setup_1.png",
|
|
||||||
"images/quick_setup_2.png",
|
|
||||||
"images/quick_setup_3.png",
|
|
||||||
"images/quick_setup_3b.png",
|
|
||||||
"images/quick_setup_4.png",
|
|
||||||
"images/quick_setup_5.png",
|
|
||||||
"images/quick_setup_6.png",
|
|
||||||
"images/quick_setup_7.png",
|
|
||||||
"images/quick_setup_8.png",
|
|
||||||
"images/quick_setup_9_1.png",
|
|
||||||
"images/quick_setup_9_2.png",
|
|
||||||
"images/quick_setup_10.png",
|
|
||||||
"images/remote_db_setting.png",
|
|
||||||
"images/write_logs_into_the_file.png",
|
|
||||||
];
|
|
||||||
for (const file of files) {
|
|
||||||
const remote = remoteTopDir + file;
|
|
||||||
const local = this.testRootPath + file;
|
|
||||||
try {
|
|
||||||
const f = (await requestUrl(remote)).arrayBuffer;
|
|
||||||
await this.core.storageAccess.ensureDir(local);
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(local, f);
|
|
||||||
} catch (ex) {
|
|
||||||
this._log(`Could not fetch ${remote}`, LOG_LEVEL_VERBOSE);
|
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.dumpList();
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
|
||||||
await delay(100);
|
|
||||||
const start = Date.now();
|
|
||||||
while (!(await proc())) {
|
|
||||||
if (timeout > 0) {
|
|
||||||
if (Date.now() - start > timeout) {
|
|
||||||
this._log(`Timeout`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await delay(500);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async testConflictedManually1() {
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
|
|
||||||
const commonFile = `Resolve!
|
|
||||||
*****, the amazing chocolatier!!`;
|
|
||||||
|
|
||||||
if (this.isLeader) {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
|
||||||
timeout: 30,
|
|
||||||
defaultOption: "Yes",
|
|
||||||
})) == "no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileA = `Resolve to KEEP THIS
|
|
||||||
Willy Wonka, Willy Wonka, the amazing chocolatier!!`;
|
|
||||||
|
|
||||||
const fileB = `Resolve to DISCARD THIS
|
|
||||||
Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
|
||||||
|
|
||||||
if (this.isLeader) {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileA);
|
|
||||||
} else {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileB);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to check the result of Manually 1?", {
|
|
||||||
timeout: 30,
|
|
||||||
defaultOption: "Yes",
|
|
||||||
})) == "no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(await this.waitFor(async () => {
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
return (
|
|
||||||
(await this.__assertStorageContent(
|
|
||||||
(this.testRootPath + "wonka.md") as FilePath,
|
|
||||||
fileA,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)) == true
|
|
||||||
);
|
|
||||||
}, 30000))
|
|
||||||
) {
|
|
||||||
return await this.__assertStorageContent((this.testRootPath + "wonka.md") as FilePath, fileA, false, true);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
// We have to check the result
|
|
||||||
}
|
|
||||||
|
|
||||||
async testConflictedManually2() {
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
|
|
||||||
const commonFile = `Resolve To concatenate
|
|
||||||
ABCDEFG`;
|
|
||||||
|
|
||||||
if (this.isLeader) {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
|
||||||
timeout: 30,
|
|
||||||
defaultOption: "Yes",
|
|
||||||
})) == "no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileA = `Resolve to Concatenate
|
|
||||||
ABCDEFGHIJKLMNOPQRSTYZ`;
|
|
||||||
|
|
||||||
const fileB = `Resolve to Concatenate
|
|
||||||
AJKLMNOPQRSTUVWXYZ`;
|
|
||||||
|
|
||||||
const concatenated = `Resolve to Concatenate
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|
||||||
if (this.isLeader) {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileA);
|
|
||||||
} else {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileB);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to test conflict Manually 2?", {
|
|
||||||
timeout: 30,
|
|
||||||
defaultOption: "Yes",
|
|
||||||
})) == "no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(await this.waitFor(async () => {
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
return (
|
|
||||||
(await this.__assertStorageContent(
|
|
||||||
(this.testRootPath + "concat.md") as FilePath,
|
|
||||||
concatenated,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
)) == true
|
|
||||||
);
|
|
||||||
}, 30000))
|
|
||||||
) {
|
|
||||||
return await this.__assertStorageContent(
|
|
||||||
(this.testRootPath + "concat.md") as FilePath,
|
|
||||||
concatenated,
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async testConflictAutomatic() {
|
|
||||||
if (this.isLeader) {
|
|
||||||
const baseDoc = `Tasks!
|
|
||||||
- [ ] Task 1
|
|
||||||
- [ ] Task 2
|
|
||||||
- [ ] Task 3
|
|
||||||
- [ ] Task 4
|
|
||||||
`;
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
|
||||||
}
|
|
||||||
await delay(100);
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
|
||||||
timeout: 30,
|
|
||||||
defaultOption: "Yes",
|
|
||||||
})) == "no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const mod1Doc = `Tasks!
|
|
||||||
- [ ] Task 1
|
|
||||||
- [v] Task 2
|
|
||||||
- [ ] Task 3
|
|
||||||
- [ ] Task 4
|
|
||||||
`;
|
|
||||||
|
|
||||||
const mod2Doc = `Tasks!
|
|
||||||
- [ ] Task 1
|
|
||||||
- [ ] Task 2
|
|
||||||
- [v] Task 3
|
|
||||||
- [ ] Task 4
|
|
||||||
`;
|
|
||||||
if (this.isLeader) {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod1Doc);
|
|
||||||
} else {
|
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await delay(1000);
|
|
||||||
if (
|
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
|
||||||
"no"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
const mergedDoc = `Tasks!
|
|
||||||
- [ ] Task 1
|
|
||||||
- [v] Task 2
|
|
||||||
- [v] Task 3
|
|
||||||
- [ ] Task 4
|
|
||||||
`;
|
|
||||||
return this.__assertStorageContent((this.testRootPath + "task.md") as FilePath, mergedDoc, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No longer tested
|
|
||||||
async checkConflictResolution() {
|
|
||||||
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
|
|
||||||
await this.services.conflict.resolveAllConflictedFilesByNewerOnes();
|
|
||||||
await this.services.conflict.resolveAllConflictedFilesByNewerOnes();
|
|
||||||
await this.services.replication.replicate();
|
|
||||||
await delay(1000);
|
|
||||||
if (!(await this.testConflictAutomatic())) {
|
|
||||||
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(await this.testConflictedManually1())) {
|
|
||||||
this._log("Conflict resolution (Manual1) failed", LOG_LEVEL_NOTICE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!(await this.testConflictedManually2())) {
|
|
||||||
this._log("Conflict resolution (Manual2) failed", LOG_LEVEL_NOTICE);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async __assertStorageContent(
|
|
||||||
fileName: FilePath,
|
|
||||||
content: string,
|
|
||||||
inverted = false,
|
|
||||||
showResult = false
|
|
||||||
): Promise<boolean | string> {
|
|
||||||
try {
|
|
||||||
const fileContent = await this.core.storageAccess.readHiddenFileText(fileName);
|
|
||||||
let result = fileContent === content;
|
|
||||||
if (inverted) {
|
|
||||||
result = !result;
|
|
||||||
}
|
|
||||||
if (result) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if (showResult) {
|
|
||||||
this._log(`Content is not same \n Expected:${content}\n Actual:${fileContent}`, LOG_LEVEL_VERBOSE);
|
|
||||||
}
|
|
||||||
return `Content is not same \n Expected:${content}\n Actual:${fileContent}`;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this._log(`Cannot assert storage content: ${e}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async performTestManually() {
|
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
|
||||||
await this.checkConflictResolution();
|
|
||||||
// await this.collectTestFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// testResults = writable<[boolean, string, string][]>([]);
|
|
||||||
// testResults: string[] = [];
|
|
||||||
|
|
||||||
// $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
|
||||||
// const logLine = `${name}: ${key} ${summary ?? ""}`;
|
|
||||||
// this.testResults.update((results) => {
|
|
||||||
// results.push([result, logLine, message ?? ""]);
|
|
||||||
// return results;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
private async _everyModuleTestMultiDevice(): Promise<boolean> {
|
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
|
||||||
// this.core.$$addTestResult("DevModule", "Test", true);
|
|
||||||
// return Promise.resolve(true);
|
|
||||||
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
|
||||||
return this.testDone();
|
|
||||||
}
|
|
||||||
override onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
|
||||||
services.appLifecycle.onSettingLoaded.addHandler(this._everyOnloadAfterLoadSettings.bind(this));
|
|
||||||
services.replication.onBeforeReplicate.addHandler(this._everyBeforeReplicate.bind(this));
|
|
||||||
services.test.testMultiDevice.addHandler(this._everyModuleTestMultiDevice.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { perf_trench } from "./tests.ts";
|
import { perf_trench } from "./tests.ts";
|
||||||
import { MarkdownRenderer, Notice } from "../../../deps.ts";
|
import { MarkdownRenderer, Notice } from "../../../deps.ts";
|
||||||
import type { ModuleDev } from "../ModuleDev.ts";
|
import type { ModuleDev } from "../ModuleDev.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events.ts";
|
import { EVENT_LAYOUT_READY, eventHub } from "../../../common/events.ts";
|
||||||
import { writable } from "svelte/store";
|
|
||||||
export let plugin: ObsidianLiveSyncPlugin;
|
export let plugin: ObsidianLiveSyncPlugin;
|
||||||
export let moduleDev: ModuleDev;
|
export let moduleDev: ModuleDev;
|
||||||
$: core = plugin.core;
|
$: core = plugin.core;
|
||||||
let performanceTestResult = "";
|
let performanceTestResult = "";
|
||||||
let functionCheckResult = "";
|
|
||||||
let testRunning = false;
|
let testRunning = false;
|
||||||
let prefTestResultEl: HTMLDivElement;
|
let prefTestResultEl: HTMLDivElement;
|
||||||
let isReady = false;
|
let isReady = false;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { fireAndForget, getDocData, readContent } from "../../../lib/src/common/
|
|||||||
import { isPlainText, stripPrefix } from "../../../lib/src/string_and_binary/path.ts";
|
import { isPlainText, stripPrefix } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import { scheduleOnceIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
import { scheduleOnceIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
import type { LiveSyncBaseCore } from "@/LiveSyncBaseCore.ts";
|
||||||
|
import { compatGlobal } from "@lib/common/coreEnvFunctions.ts";
|
||||||
|
|
||||||
function isImage(path: string) {
|
function isImage(path: string) {
|
||||||
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
||||||
@@ -501,9 +502,9 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
searchInput.addClass("history-search-input");
|
searchInput.addClass("history-search-input");
|
||||||
searchInput.addEventListener("input", () => {
|
searchInput.addEventListener("input", () => {
|
||||||
if (this.searchTimeout) {
|
if (this.searchTimeout) {
|
||||||
clearTimeout(this.searchTimeout);
|
compatGlobal.clearTimeout(this.searchTimeout);
|
||||||
}
|
}
|
||||||
this.searchTimeout = window.setTimeout(() => {
|
this.searchTimeout = compatGlobal.setTimeout(() => {
|
||||||
void this.performSearch(searchInput.value);
|
void this.performSearch(searchInput.value);
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { WorkspaceLeaf } from "@/deps.ts";
|
|||||||
import LogPaneComponent from "./LogPane.svelte";
|
import LogPaneComponent from "./LogPane.svelte";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { SvelteItemView } from "../../../common/SvelteItemView.ts";
|
import { SvelteItemView } from "../../../common/SvelteItemView.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "@lib/common/i18n.ts";
|
||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
export const VIEW_TYPE_LOG = "log-log";
|
export const VIEW_TYPE_LOG = "log-log";
|
||||||
//Log view
|
//Log view
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { addIcon, debounce, normalizePath, Notice, stringifyYaml, type Workspace
|
|||||||
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
||||||
import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
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 "@lib/common/i18n.ts";
|
||||||
import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector.ts";
|
import { P2PLogCollector } from "@/lib/src/replication/trystero/P2PLogCollector.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
import { LiveSyncError } from "@lib/common/LSError.ts";
|
import { LiveSyncError } from "@lib/common/LSError.ts";
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import {
|
|||||||
type ValueComponent,
|
type ValueComponent,
|
||||||
} from "@/deps.ts";
|
} from "@/deps.ts";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { unique } from "octagonal-wheels/collection";
|
||||||
import {
|
import { LEVEL_ADVANCED, LEVEL_POWER_USER, statusDisplay, type ConfigurationItem } from "@lib/common/types.ts";
|
||||||
LEVEL_ADVANCED,
|
|
||||||
LEVEL_POWER_USER,
|
|
||||||
statusDisplay,
|
|
||||||
type ConfigurationItem,
|
|
||||||
} from "../../../lib/src/common/types.ts";
|
|
||||||
import { createStub, type ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import { createStub, type ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
import {
|
import {
|
||||||
type AllSettingItemKey,
|
type AllSettingItemKey,
|
||||||
@@ -23,7 +18,7 @@ import {
|
|||||||
type AllNumericItemKey,
|
type AllNumericItemKey,
|
||||||
type AllBooleanItemKey,
|
type AllBooleanItemKey,
|
||||||
} from "./settingConstants.ts";
|
} from "./settingConstants.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "@lib/common/i18n.ts";
|
||||||
import { findAttrFromParent, wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts";
|
import { findAttrFromParent, wrapMemo, type AutoWireOption, type OnUpdateResult } from "./SettingPane.ts";
|
||||||
|
|
||||||
export class LiveSyncSetting extends Setting {
|
export class LiveSyncSetting extends Setting {
|
||||||
|
|||||||
@@ -1,29 +1,16 @@
|
|||||||
import { stringifyYaml } from "../../../deps.ts";
|
|
||||||
import {
|
import {
|
||||||
type ObsidianLiveSyncSettings,
|
|
||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type DocumentID,
|
type DocumentID,
|
||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
LOG_LEVEL_VERBOSE,
|
LOG_LEVEL_VERBOSE,
|
||||||
type LoadedEntry,
|
type LoadedEntry,
|
||||||
REMOTE_COUCHDB,
|
|
||||||
REMOTE_MINIO,
|
|
||||||
type MetaEntry,
|
type MetaEntry,
|
||||||
type FilePath,
|
type FilePath,
|
||||||
DEFAULT_SETTINGS,
|
} from "@lib/common/types.ts";
|
||||||
} from "../../../lib/src/common/types.ts";
|
import { createBlob, getFileRegExp, isDocContentSame, readAsBlob } from "@lib/common/utils.ts";
|
||||||
import {
|
import { Logger } from "@lib/common/logger.ts";
|
||||||
createBlob,
|
import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "@lib/string_and_binary/path.ts";
|
||||||
getFileRegExp,
|
import { $msg } from "@lib/common/i18n.ts";
|
||||||
isDocContentSame,
|
|
||||||
parseHeaderValues,
|
|
||||||
readAsBlob,
|
|
||||||
} from "../../../lib/src/common/utils.ts";
|
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
|
||||||
import { isCloudantURI } from "../../../lib/src/pouchdb/utils_couchdb.ts";
|
|
||||||
import { requestToCouchDBWithCredentials } from "../../../common/utils.ts";
|
|
||||||
import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "../../../lib/src/string_and_binary/path.ts";
|
|
||||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||||
import {
|
import {
|
||||||
@@ -32,14 +19,13 @@ import {
|
|||||||
EVENT_REQUEST_RUN_DOCTOR,
|
EVENT_REQUEST_RUN_DOCTOR,
|
||||||
EVENT_REQUEST_RUN_FIX_INCOMPLETE,
|
EVENT_REQUEST_RUN_FIX_INCOMPLETE,
|
||||||
eventHub,
|
eventHub,
|
||||||
} from "../../../common/events.ts";
|
} from "@/common/events.ts";
|
||||||
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
|
import { ICHeader, ICXHeader, PSCHeader } from "@/common/types.ts";
|
||||||
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
|
import { HiddenFileSync } from "@/features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
|
import { EVENT_REQUEST_SHOW_HISTORY } from "@/common/obsidianEvents.ts";
|
||||||
import { generateCredentialObject } from "../../../lib/src/replication/httplib.ts";
|
|
||||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
import type { PageFunctions } from "./SettingPane.ts";
|
import type { PageFunctions } from "./SettingPane.ts";
|
||||||
import { generateReport } from "@/common/reportTool.ts";
|
import { isNotFoundError } from "@lib/common/utils.doc.ts";
|
||||||
export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
|
export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement, { addPanel }: PageFunctions): void {
|
||||||
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
|
||||||
// hatchWarn.addClass("op-warn-info");
|
// hatchWarn.addClass("op-warn-info");
|
||||||
@@ -160,14 +146,14 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
}
|
}
|
||||||
if (!(await addOn.storeInternalFileToDatabase(file, true))) {
|
if (!(await addOn.storeInternalFileToDatabase(file, true))) {
|
||||||
Logger(
|
Logger(
|
||||||
`Failed to store the file to the database (Hidden file): ${file}`,
|
`Failed to store the file to the database (Hidden file): ${file.path}`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!(await this.core.fileHandler.storeFileToDB(file as FilePath, true))) {
|
if (!(await this.core.fileHandler.storeFileToDB(file, true))) {
|
||||||
Logger(
|
Logger(
|
||||||
`Failed to store the file to the database: ${file}`,
|
`Failed to store the file to the database: ${file}`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
@@ -406,8 +392,8 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ret, LOG_LEVEL_VERBOSE);
|
Logger(ret, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
} catch (ex: any) {
|
} catch (ex: unknown) {
|
||||||
if (ex?.status == 404) {
|
if (isNotFoundError(ex)) {
|
||||||
// We can perform this safely
|
// We can perform this safely
|
||||||
if ((await this.core.localDatabase.putRaw(newDoc)).ok) {
|
if ((await this.core.localDatabase.putRaw(newDoc)).ok) {
|
||||||
Logger(`${docName} has been converted`, LOG_LEVEL_NOTICE);
|
Logger(`${docName} has been converted`, LOG_LEVEL_NOTICE);
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
|||||||
xxhash64: "xxhash64 (Fastest)",
|
xxhash64: "xxhash64 (Fastest)",
|
||||||
"mixed-purejs": "PureJS fallback (Fast, W/O WebAssembly)",
|
"mixed-purejs": "PureJS fallback (Fast, W/O WebAssembly)",
|
||||||
sha1: "Older fallback (Slow, W/O WebAssembly)",
|
sha1: "Older fallback (Slow, W/O WebAssembly)",
|
||||||
} as Record<HashAlgorithm, string>,
|
} satisfies Record<HashAlgorithm, string>,
|
||||||
});
|
});
|
||||||
this.addOnSaved("hashAlg", async () => {
|
this.addOnSaved("hashAlg", async () => {
|
||||||
await this.core.localDatabase._prepareHashFunctions();
|
await this.core.localDatabase._prepareHashFunctions();
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||||
import InfoNote from "@/lib/src/UI/components/InfoNote.svelte";
|
|
||||||
import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte";
|
|
||||||
import Check from "@/lib/src/UI/components/Check.svelte";
|
|
||||||
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
||||||
const TYPE_SCAN_QR_CODE = "scan-qr-code";
|
const TYPE_SCAN_QR_CODE = "scan-qr-code";
|
||||||
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
import Options from "@/lib/src/UI/components/Options.svelte";
|
import Options from "@/lib/src/UI/components/Options.svelte";
|
||||||
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
import Instruction from "@/lib/src/UI/components/Instruction.svelte";
|
||||||
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
import UserDecisions from "@/lib/src/UI/components/UserDecisions.svelte";
|
||||||
import InfoNote from "@/lib/src/UI/components/InfoNote.svelte";
|
|
||||||
import ExtraItems from "@/lib/src/UI/components/ExtraItems.svelte";
|
|
||||||
import Check from "@/lib/src/UI/components/Check.svelte";
|
|
||||||
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
const TYPE_USE_SETUP_URI = "use-setup-uri";
|
||||||
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
const TYPE_CONFIGURE_MANUALLY = "configure-manually";
|
||||||
const TYPE_CANCELLED = "cancelled";
|
const TYPE_CANCELLED = "cancelled";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts";
|
import type { InjectableServiceHub } from "@lib/services/implements/injectable/InjectableServiceHub.ts";
|
||||||
import type { LiveSyncCore } from "../../main.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts";
|
import { initialiseWorkerModule } from "@lib/worker/bgWorker.ts";
|
||||||
|
import { manifestVersion, packageVersion } from "@lib/common/coreEnvVars.ts";
|
||||||
|
|
||||||
export class ModuleLiveSyncMain extends AbstractModule {
|
export class ModuleLiveSyncMain extends AbstractModule {
|
||||||
async _onLiveSyncReady() {
|
async _onLiveSyncReady() {
|
||||||
@@ -89,11 +90,6 @@ export class ModuleLiveSyncMain extends AbstractModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// this.addUIs();
|
// this.addUIs();
|
||||||
//@ts-ignore
|
|
||||||
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
|
|
||||||
//@ts-ignore
|
|
||||||
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
|
||||||
|
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
||||||
await this.services.setting.loadSettings();
|
await this.services.setting.loadSettings();
|
||||||
if (!(await this.services.appLifecycle.onSettingLoaded())) {
|
if (!(await this.services.appLifecycle.onSettingLoaded())) {
|
||||||
|
|||||||
@@ -5,9 +5,17 @@ import { ObsHttpHandler } from "../essentialObsidian/APILib/ObsHttpHandler";
|
|||||||
import { ObsidianConfirm } from "./ObsidianConfirm";
|
import { ObsidianConfirm } from "./ObsidianConfirm";
|
||||||
import type { Confirm } from "@lib/interfaces/Confirm";
|
import type { Confirm } from "@lib/interfaces/Confirm";
|
||||||
import { requestUrl, type RequestUrlParam } from "@/deps";
|
import { requestUrl, type RequestUrlParam } from "@/deps";
|
||||||
|
import { compatGlobal } from "@/lib/src/common/coreEnvFunctions";
|
||||||
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
||||||
// This is a migration step.
|
// This is a migration step.
|
||||||
|
|
||||||
|
declare module "obsidian" {
|
||||||
|
interface App {
|
||||||
|
appId?: string;
|
||||||
|
isMobile?: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceContext> {
|
export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceContext> {
|
||||||
_customHandler: ObsHttpHandler | undefined;
|
_customHandler: ObsHttpHandler | undefined;
|
||||||
_confirmInstance: Confirm;
|
_confirmInstance: Confirm;
|
||||||
@@ -83,8 +91,7 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
override isMobile(): boolean {
|
override isMobile(): boolean {
|
||||||
//@ts-ignore : internal API
|
return "isMobile" in this.app ? !!this.app.isMobile : false;
|
||||||
return this.app.isMobile;
|
|
||||||
}
|
}
|
||||||
override getAppID(): string {
|
override getAppID(): string {
|
||||||
return `${"appId" in this.app ? this.app.appId : ""}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
@@ -95,7 +102,7 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
override getAppVersion(): string {
|
override getAppVersion(): string {
|
||||||
const navigatorString = globalThis.navigator?.userAgent ?? "";
|
const navigatorString = compatGlobal.navigator?.userAgent ?? "";
|
||||||
const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/);
|
const match = navigatorString.match(/obsidian\/([0-9]+\.[0-9]+\.[0-9]+)/);
|
||||||
if (match && match.length >= 2) {
|
if (match && match.length >= 2) {
|
||||||
return match[1];
|
return match[1];
|
||||||
@@ -196,7 +203,7 @@ export class ObsidianAPIService extends InjectableAPIService<ObsidianServiceCont
|
|||||||
}
|
}
|
||||||
|
|
||||||
override setInterval(handler: () => void, timeout: number): number {
|
override setInterval(handler: () => void, timeout: number): number {
|
||||||
const timerId = globalThis.setInterval(handler, timeout) as unknown as number;
|
const timerId = compatGlobal.setInterval(handler, timeout);
|
||||||
this.context.plugin.registerInterval(timerId);
|
this.context.plugin.registerInterval(timerId);
|
||||||
return timerId;
|
return timerId;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -171,7 +171,7 @@ body {
|
|||||||
|
|
||||||
.sls-setting-menu-buttons {
|
.sls-setting-menu-buttons {
|
||||||
border: 1px solid var(--sls-col-warn);
|
border: 1px solid var(--sls-col-warn);
|
||||||
padding: 2px;
|
/* padding: 2px; */
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-image: linear-gradient(-45deg,
|
background-image: linear-gradient(-45deg,
|
||||||
@@ -180,7 +180,7 @@ body {
|
|||||||
background-size: 30px 30px;
|
background-size: 30px 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
/* justify-content: flex-end; */
|
||||||
padding: 0.5em 0.25em;
|
padding: 0.5em 0.25em;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -315,9 +315,9 @@ body {
|
|||||||
|
|
||||||
|
|
||||||
.sls-setting-obsolete {
|
.sls-setting-obsolete {
|
||||||
background-image: linear-gradient(-45deg,
|
/* background-image: linear-gradient(-45deg,
|
||||||
var(--sls-col-warn-stripe1) 25%, var(--sls-col-warn-stripe2) 25%, var(--sls-col-warn-stripe2) 50%,
|
var(--sls-col-warn-stripe1) 25%, var(--sls-col-warn-stripe2) 25%, var(--sls-col-warn-stripe2) 50%,
|
||||||
var(--sls-col-warn-stripe1) 50%, var(--sls-col-warn-stripe1) 75%, var(--sls-col-warn-stripe2) 75%, var(--sls-col-warn-stripe2));
|
var(--sls-col-warn-stripe1) 50%, var(--sls-col-warn-stripe1) 75%, var(--sls-col-warn-stripe2) 75%, var(--sls-col-warn-stripe2)); */
|
||||||
background-image: linear-gradient(-45deg,
|
background-image: linear-gradient(-45deg,
|
||||||
transparent 25%, rgba(var(--background-secondary), 0.1) 25%, rgba(var(--background-secondary), 0.1) 50%, transparent 50%, transparent 75%, rgba(var(--background-secondary), 0.1) 75%, rgba(var(--background-secondary), 0.1));
|
transparent 25%, rgba(var(--background-secondary), 0.1) 25%, rgba(var(--background-secondary), 0.1) 50%, transparent 50%, transparent 75%, rgba(var(--background-secondary), 0.1) 75%, rgba(var(--background-secondary), 0.1));
|
||||||
background-size: 60px 60px;
|
background-size: 60px 60px;
|
||||||
|
|||||||
+2
-3
@@ -2,7 +2,6 @@
|
|||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES2018",
|
"target": "ES2018",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
@@ -20,8 +19,8 @@
|
|||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@lib/*": ["src/lib/src/*"]
|
"@lib/*": ["./src/lib/src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts"],
|
"include": ["**/*.ts", "test/**/*.test.ts", "**/*.unit.spec.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user