mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-18 18:46:02 +00:00
Route application diagnostics through owned log paths
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## Status
|
||||
|
||||
Proposed — the implementation proof is complete, and `@vrtmrz/livesync-commonlib@0.1.0-rc.2` has been published and verified as the exact downstream dependency; the community-scanner preview remains pending.
|
||||
Proposed — the implementation proof is complete, `@vrtmrz/livesync-commonlib@0.1.0-rc.2` has been published and verified as the exact downstream dependency, and the Community directory scanner preview confirms the intended package boundary.
|
||||
|
||||
## Context
|
||||
|
||||
@@ -255,7 +255,11 @@ The Commonlib contract suite passes 21 tests covering Context results, both plat
|
||||
|
||||
The package-owned Trystero transport also completes the canonical Compose P2P synchronisation workflow with a local relay and two isolated CLI peers. In real Obsidian, the plug-in starts with one consistent `ObsidianServiceContext`, the representative server-selection and Setup URI Svelte dialogues mount and close through their normal controls, their mobile variants satisfy viewport, safe-area, and touch-target assertions, and the settings pane exposes only the effective deletion controls. These runtime checks complement the package tests without making Webapp maintenance the primary release gate.
|
||||
|
||||
The Community review lint completes with no errors. Advisory warnings remain in pre-existing UI sentence casing, deprecated Obsidian interfaces, unchecked dynamic setting data, and application-specific console output, especially in the Webapp. They are separate follow-up work and do not justify widening the package boundary or exposing additional compatibility paths.
|
||||
The Community directory scanner preview completes with no source-code errors. The former findings attributed to generated `_types`, raw `src/lib` source, Node built-ins, forbidden rule suppressions, unsupported Obsidian APIs, and undescribed directive comments are absent. This confirms that the registry dependency is recognised as a package boundary. The preview identified behaviour-neutral redundant CLI candidate types and Webapp File System Access API assertions; these are corrected in the host source rather than carried as known warnings.
|
||||
|
||||
The remaining findings belong to application code. Browser-app DOM creation and inline styling require a framework-neutral browser presentation boundary rather than Obsidian-only helpers. Direct diagnostic output was nevertheless resolved at its existing ownership boundaries: Webapp components use an injected log function backed by `BrowserAPIService`, WebPeer retains output in its Svelte log store, Obsidian modules use the established Logger path, and duplicate console emission was removed from `ModuleLog`. The later Webapp and WebPeer recomposition around maintained Context and serviceFeature APIs should preserve these explicit output paths. Declarative settings adoption and deprecated Obsidian interfaces still require separate, focused changes.
|
||||
|
||||
The dependency preview also reports `uuid`, but the installed and locked graph resolves PouchDB's UUID dependency to the patched `uuid@11.1.1` through the repository override, and `npm audit` does not report the UUID advisory. The scanner appears to infer the older declared PouchDB range rather than the resolved override, so this warning is treated as a scanner false positive unless a packed-artefact inspection shows otherwise. A separate dependency review remains necessary for the pre-existing CLI `werift`/`ip` advisory, for which npm currently offers no automatic fix, and the development-only Istanbul `js-yaml` advisory.
|
||||
|
||||
The local real-Obsidian suite verifies the actual loaded `ObsidianServiceContext`, all 18 services, Vault reflection, CouchDB and Object Storage transfer, remote-activity accounting, CLI-to-Obsidian encrypted synchronisation, startup scanning, two-Vault create, update, delete, ordinary rename, case-only rename, target mismatch, Hidden File Sync, Customisation Sync, and setting Markdown export. These checks establish observable results and host composition rather than relying on declaration compatibility alone.
|
||||
|
||||
@@ -263,7 +267,7 @@ The current browser Harness has a pre-existing settings-migration initialisation
|
||||
|
||||
## Scanner and Repository Consequences
|
||||
|
||||
Consuming the npm package is expected to remove the common-library source and generated `_types` from the plug-in repository's community scan, because package dependencies are treated as dependencies rather than maintained plug-in source. The proof removes the old integration, but this scanner expectation must still be verified before the decision is accepted and released.
|
||||
Consuming the npm package removes the common-library source and generated `_types` from the plug-in repository's Community directory scan, because package dependencies are treated as dependencies rather than maintained plug-in source. The branch preview confirms this expected boundary.
|
||||
|
||||
The change does not hide or resolve warnings in Self-hosted LiveSync-owned source. The scanner will continue to inspect the plug-in, CLI, Webapp, and WebPeer while those applications remain in the repository. Moving those applications to a LiveSync-family application repository may be considered after the package boundary is stable, but it is not part of this decision because the CLI and Webapp still import shared plug-in composition code.
|
||||
|
||||
@@ -303,14 +307,14 @@ The migration is complete only when:
|
||||
- Self-hosted LiveSync no longer has `src/lib`, `_types`, or `@lib/*` source aliases;
|
||||
- the CLI, Webapp, WebPeer, plug-in build, unit tests, integration tests, and focused real-Obsidian E2E pass against the reviewed package artefact;
|
||||
- common-library downstream CI records and tests the exact Self-hosted LiveSync ref;
|
||||
- a community scanner preview confirms the expected change in source findings; and
|
||||
- a Community directory scanner preview confirms the expected change in source findings; and
|
||||
- an external consumer can replace its submodule or custom loader with the documented package API.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The common library gains an independently consumable and testable artefact while retaining its domain ownership and history.
|
||||
- Self-hosted LiveSync release archives no longer need generated fallback declarations for an absent submodule.
|
||||
- Community scanning can distinguish plug-in source from a reviewed dependency, subject to preview verification.
|
||||
- Community directory scanning distinguishes plug-in source from the reviewed dependency.
|
||||
- Changes which span the package and plug-in require coordinated package and downstream validation rather than one atomic source commit.
|
||||
- Temporary compatibility exports increase the first package surface, but their pre-1.0 status and explicit classification prevent them from becoming silent permanent contracts.
|
||||
- Translation, Svelte, and platform dependencies become optional composition concerns rather than root-package side effects.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { MenuItem } from "@/apps/browser/BrowserMenu";
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||
|
||||
type Props = {
|
||||
item: MenuItem;
|
||||
@@ -13,7 +14,7 @@
|
||||
item.handler?.();
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
Logger(ex, LOG_LEVEL_VERBOSE, "browser-menu");
|
||||
}
|
||||
closeMenu();
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ type CLIP2PPeer = {
|
||||
};
|
||||
|
||||
type CandidateSummary = {
|
||||
id: string | "unknown";
|
||||
candidateType: string | "unknown";
|
||||
protocol: string | "unknown";
|
||||
relayProtocol: string | "unknown";
|
||||
id: string;
|
||||
candidateType: string;
|
||||
protocol: string;
|
||||
relayProtocol: string;
|
||||
};
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
@@ -98,7 +98,7 @@ function getReportValue<T extends string | number>(
|
||||
return typeof value === "string" || typeof value === "number" ? (value as T) : "unknown";
|
||||
}
|
||||
|
||||
function summariseCandidate(reports: unknown[], candidateId: string | "unknown"): CandidateSummary | undefined {
|
||||
function summariseCandidate(reports: unknown[], candidateId: string): CandidateSummary | undefined {
|
||||
if (candidateId === "unknown") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { LOG_LEVEL } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
/** Diagnostic output supplied by the Webapp host. */
|
||||
export type WebAppLog = (message: unknown, level: LOG_LEVEL, key?: string) => void;
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FilePath, UXStat } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { LOG_LEVEL_NOTICE, type FilePath, type UXStat } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { IFileSystemAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
|
||||
import { FSAPIPathAdapter } from "./FSAPIPathAdapter";
|
||||
import { FSAPITypeGuardAdapter } from "./FSAPITypeGuardAdapter";
|
||||
@@ -7,6 +7,7 @@ import { FileSystemAccessStorageAdapter } from "@vrtmrz/livesync-commonlib/brows
|
||||
import { FSAPIVaultAdapter } from "./FSAPIVaultAdapter";
|
||||
import type { FSAPIFile, FSAPIFolder, FSAPIStat } from "./FSAPITypes";
|
||||
import { shareRunningResult } from "octagonal-wheels/concurrency/lock_v2";
|
||||
import type { WebAppLog } from "../WebAppLog";
|
||||
|
||||
/**
|
||||
* Complete file system adapter implementation for FileSystem API
|
||||
@@ -21,7 +22,10 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
|
||||
private fileCache = new Map<string, FSAPIFile>();
|
||||
private handleCache = new Map<string, FileSystemFileHandle>();
|
||||
|
||||
constructor(private rootHandle: FileSystemDirectoryHandle) {
|
||||
constructor(
|
||||
private rootHandle: FileSystemDirectoryHandle,
|
||||
private readonly addLog: WebAppLog
|
||||
) {
|
||||
this.path = new FSAPIPathAdapter();
|
||||
this.typeGuard = new FSAPITypeGuardAdapter();
|
||||
this.conversion = new FSAPIConversionAdapter();
|
||||
@@ -30,14 +34,14 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
|
||||
}
|
||||
|
||||
private normalisePath(path: FilePath | string): string {
|
||||
return this.path.normalisePath(path as string);
|
||||
return this.path.normalisePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle for a given path
|
||||
*/
|
||||
private async getFileHandleByPath(p: FilePath | string): Promise<FileSystemFileHandle | null> {
|
||||
const pathStr = p as string;
|
||||
const pathStr = p;
|
||||
|
||||
// Check cache first
|
||||
const cached = this.handleCache.get(pathStr);
|
||||
@@ -213,7 +217,11 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error scanning directory ${relativePath}:`, error);
|
||||
this.addLog(
|
||||
`Error scanning directory '${relativePath || "."}': ${String(error)}`,
|
||||
LOG_LEVEL_NOTICE,
|
||||
"fsapi-scan"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { storageAdapterContractCases } from "@/apps/_test/storageAdapterContract";
|
||||
import { FSAPIFileSystemAdapter } from "./FSAPIFileSystemAdapter";
|
||||
import { FileSystemAccessStorageAdapter } from "@vrtmrz/livesync-commonlib/browser";
|
||||
import { FSAPIVaultAdapter } from "./FSAPIVaultAdapter";
|
||||
import { LOG_LEVEL_NOTICE } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
|
||||
class MemoryFileHandle {
|
||||
readonly kind = "file";
|
||||
@@ -146,11 +147,30 @@ describe("FSAPIFileSystemAdapter path case", () => {
|
||||
const root = memoryRoot as unknown as FileSystemDirectoryHandle;
|
||||
const vault = new FSAPIVaultAdapter(root);
|
||||
await vault.create("Calculus.md", "content");
|
||||
const adapter = new FSAPIFileSystemAdapter(root);
|
||||
const adapter = new FSAPIFileSystemAdapter(root, vi.fn());
|
||||
|
||||
await expect(adapter.getAbstractFileByPath("calculus.md")).resolves.toBeNull();
|
||||
await expect(adapter.getAbstractFileByPathInsensitive("calculus.md")).resolves.toEqual(
|
||||
expect.objectContaining({ path: "Calculus.md" })
|
||||
);
|
||||
});
|
||||
|
||||
it("reports scan failures through the injected Webapp log", async () => {
|
||||
const root = {
|
||||
name: "root",
|
||||
async *entries(): AsyncIterableIterator<[string, FileSystemHandle]> {
|
||||
throw new Error("scan failed");
|
||||
},
|
||||
} as unknown as FileSystemDirectoryHandle;
|
||||
const addLog = vi.fn();
|
||||
const adapter = new FSAPIFileSystemAdapter(root, addLog);
|
||||
|
||||
await adapter.scanDirectory();
|
||||
|
||||
expect(addLog).toHaveBeenCalledWith(
|
||||
"Error scanning directory '.': Error: scan failed",
|
||||
LOG_LEVEL_NOTICE,
|
||||
"fsapi-scan"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -101,7 +101,6 @@ async function startWithHistory(item: VaultHistoryItem): Promise<void> {
|
||||
const handle = await historyStore.activateHistoryItem(item);
|
||||
await startWithHandle(handle);
|
||||
} catch (error) {
|
||||
console.error("[Directory] Failed to open history vault:", error);
|
||||
setStatus("error", `Failed to open saved vault: ${String(error)}`);
|
||||
setBusyState(false);
|
||||
}
|
||||
@@ -113,7 +112,6 @@ async function startWithNewPicker(): Promise<void> {
|
||||
const handle = await historyStore.pickNewVault();
|
||||
await startWithHandle(handle);
|
||||
} catch (error) {
|
||||
console.error("[Directory] Failed to pick vault:", error);
|
||||
setStatus("warning", `Vault selection was cancelled or failed: ${String(error)}`);
|
||||
setBusyState(false);
|
||||
}
|
||||
@@ -132,7 +130,6 @@ async function initializeVaultSelector(): Promise<void> {
|
||||
|
||||
compatGlobal.addEventListener("load", () => {
|
||||
initializeVaultSelector().catch((error) => {
|
||||
console.error("Failed to initialize vault selector:", error);
|
||||
setStatus("error", `Initialization failed: ${String(error)}`);
|
||||
});
|
||||
});
|
||||
|
||||
+37
-34
@@ -7,7 +7,13 @@ import type { BrowserServiceHub } from "@vrtmrz/livesync-commonlib/compat/servic
|
||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
import { initialiseServiceModulesFSAPI, type FSAPIServiceModules } from "./serviceModules/FSAPIServiceModules";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
type LOG_LEVEL,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { BrowserAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/BrowserAPIService";
|
||||
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||
import { useOfflineScanner } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
|
||||
@@ -59,14 +65,16 @@ class LiveSyncWebApp {
|
||||
this.rootHandle = rootHandle;
|
||||
}
|
||||
|
||||
private addLog(message: unknown, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string): void {
|
||||
this.serviceHub?.API.addLog(message, level, key);
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
console.log("Self-hosted LiveSync WebApp");
|
||||
console.log("Initializing...");
|
||||
|
||||
console.log(`Vault directory: ${this.rootHandle.name}`);
|
||||
|
||||
// Create service context and hub
|
||||
this.serviceHub = createLiveSyncBrowserServiceHub<ServiceContext>();
|
||||
this.addLog("Self-hosted LiveSync WebApp", LOG_LEVEL_INFO, "initialise");
|
||||
this.addLog("Initialising...", LOG_LEVEL_VERBOSE, "initialise");
|
||||
this.addLog(`Vault directory: ${this.rootHandle.name}`, LOG_LEVEL_VERBOSE, "initialise");
|
||||
|
||||
// Setup API service
|
||||
(this.serviceHub.API as BrowserAPIService<ServiceContext>).getSystemVaultName.setHandler(
|
||||
@@ -79,9 +87,9 @@ class LiveSyncWebApp {
|
||||
settingService.saveData.setHandler(async (data: ObsidianLiveSyncSettings) => {
|
||||
try {
|
||||
await this.saveSettingsToFile(data);
|
||||
console.log("[Settings] Saved to .livesync/settings.json");
|
||||
this.addLog("Saved to .livesync/settings.json", LOG_LEVEL_VERBOSE, "settings");
|
||||
} catch (error) {
|
||||
console.error("[Settings] Failed to save:", error);
|
||||
this.addLog(`Failed to save settings: ${String(error)}`, LOG_LEVEL_NOTICE, "settings");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -89,11 +97,11 @@ class LiveSyncWebApp {
|
||||
try {
|
||||
const data = await this.loadSettingsFromFile();
|
||||
if (data) {
|
||||
console.log("[Settings] Loaded from .livesync/settings.json");
|
||||
this.addLog("Loaded from .livesync/settings.json", LOG_LEVEL_VERBOSE, "settings");
|
||||
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
|
||||
}
|
||||
} catch {
|
||||
console.log("[Settings] Failed to load, using defaults");
|
||||
this.addLog("Failed to load settings; using defaults", LOG_LEVEL_NOTICE, "settings");
|
||||
}
|
||||
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
|
||||
});
|
||||
@@ -101,7 +109,7 @@ class LiveSyncWebApp {
|
||||
// App lifecycle handlers
|
||||
this.serviceHub.appLifecycle.scheduleRestart.setHandler(() => {
|
||||
void (async () => {
|
||||
console.log("[AppLifecycle] Restart requested");
|
||||
this.addLog("Restart requested", LOG_LEVEL_INFO, "app-lifecycle");
|
||||
await this.shutdown();
|
||||
await this.initialize();
|
||||
compatGlobal.setTimeout(() => {
|
||||
@@ -152,19 +160,14 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
private async saveSettingsToFile(data: ObsidianLiveSyncSettings): Promise<void> {
|
||||
try {
|
||||
// Create .livesync directory if it doesn't exist
|
||||
const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true });
|
||||
// Create .livesync directory if it does not exist
|
||||
const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true });
|
||||
|
||||
// Create/overwrite settings.json
|
||||
const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE, { create: true });
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(JSON.stringify(data, null, 2));
|
||||
await writable.close();
|
||||
} catch (error) {
|
||||
console.error("[Settings] Error saving to file:", error);
|
||||
throw error;
|
||||
}
|
||||
// Create/overwrite settings.json
|
||||
const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE, { create: true });
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(JSON.stringify(data, null, 2));
|
||||
await writable.close();
|
||||
}
|
||||
|
||||
private async loadSettingsFromFile(): Promise<Partial<ObsidianLiveSyncSettings> | null> {
|
||||
@@ -186,47 +189,47 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("[Starting] Initializing LiveSync...");
|
||||
this.addLog("Initialising LiveSync...", LOG_LEVEL_INFO, "start");
|
||||
|
||||
const loadResult = await this.core.services.control.onLoad();
|
||||
if (!loadResult) {
|
||||
console.error("[Error] Failed to initialize LiveSync");
|
||||
this.addLog("Failed to initialise LiveSync", LOG_LEVEL_NOTICE, "start");
|
||||
this.showError("Failed to initialize LiveSync");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.core.services.control.onReady();
|
||||
|
||||
console.log("[Ready] LiveSync is running");
|
||||
this.addLog("LiveSync is running", LOG_LEVEL_INFO, "ready");
|
||||
|
||||
// Check if configured
|
||||
const settings = this.core.services.setting.currentSettings();
|
||||
if (!settings.isConfigured) {
|
||||
console.warn("[Warning] LiveSync is not configured yet");
|
||||
this.addLog("LiveSync is not configured yet", LOG_LEVEL_NOTICE, "configuration");
|
||||
this.showWarning("Please configure CouchDB connection in settings");
|
||||
} else {
|
||||
console.log("[Info] LiveSync is configured and ready");
|
||||
console.log(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`);
|
||||
this.addLog("LiveSync is configured and ready", LOG_LEVEL_INFO, "configuration");
|
||||
this.addLog(`Database: ${settings.couchDB_DBNAME}`, LOG_LEVEL_VERBOSE, "configuration");
|
||||
this.showSuccess("LiveSync is ready!");
|
||||
}
|
||||
|
||||
// Scan the directory to populate file cache
|
||||
const fileAccess = this.platformServiceModules?.vaultAccess;
|
||||
if (fileAccess) {
|
||||
console.log("[Scanning] Scanning vault directory...");
|
||||
this.addLog("Scanning vault directory...", LOG_LEVEL_VERBOSE, "scan");
|
||||
await fileAccess.fsapiAdapter.scanDirectory();
|
||||
const files = await fileAccess.fsapiAdapter.getFiles();
|
||||
console.log(`[Scanning] Found ${files.length} files`);
|
||||
this.addLog(`Found ${files.length} files`, LOG_LEVEL_VERBOSE, "scan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Error] Failed to start:", error);
|
||||
this.addLog(`Failed to start: ${String(error)}`, LOG_LEVEL_NOTICE, "start");
|
||||
this.showError(`Failed to start: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
if (this.core) {
|
||||
console.log("[Shutdown] Shutting down...");
|
||||
this.addLog("Shutting down...", LOG_LEVEL_INFO, "shutdown");
|
||||
|
||||
// Stop file watching
|
||||
const storageEventManager = this.platformServiceModules?.storageEventManager;
|
||||
@@ -236,7 +239,7 @@ class LiveSyncWebApp {
|
||||
|
||||
await this.core.services.control.onUnload();
|
||||
this.platformServiceModules = null;
|
||||
console.log("[Shutdown] Complete");
|
||||
this.addLog("Shutdown complete", LOG_LEVEL_INFO, "shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {
|
||||
import type { FileEventItemSentinel } from "@vrtmrz/livesync-commonlib/compat/managers/StorageEventManager";
|
||||
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { WebAppLog } from "../WebAppLog";
|
||||
|
||||
type FileSystemObserverRecord = {
|
||||
changedHandle?: FileSystemFileHandle | FileSystemDirectoryHandle;
|
||||
@@ -66,6 +68,8 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
|
||||
private storeName = "snapshots";
|
||||
private snapshotKey = "file-events";
|
||||
|
||||
constructor(private readonly addLog: WebAppLog) {}
|
||||
|
||||
private async openDB(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, 1);
|
||||
@@ -96,7 +100,7 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
|
||||
|
||||
db.close();
|
||||
} catch (error) {
|
||||
console.error("Failed to save snapshot:", error);
|
||||
this.addLog(`Failed to save snapshot: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-snapshot");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,12 +132,16 @@ class FSAPIStatusAdapter implements IStorageEventStatusAdapter {
|
||||
private lastUpdate = 0;
|
||||
private updateInterval = 5000; // Update every 5 seconds
|
||||
|
||||
constructor(private readonly addLog: WebAppLog) {}
|
||||
|
||||
updateStatus(status: { batched: number; processing: number; totalQueued: number }): void {
|
||||
const now = Date.now();
|
||||
if (now - this.lastUpdate > this.updateInterval) {
|
||||
if (status.totalQueued > 0 || status.processing > 0) {
|
||||
console.log(
|
||||
`[StorageEventManager] Batched: ${status.batched}, Processing: ${status.processing}, Total Queued: ${status.totalQueued}`
|
||||
this.addLog(
|
||||
`Batched: ${status.batched}, Processing: ${status.processing}, Total queued: ${status.totalQueued}`,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
"storage-events"
|
||||
);
|
||||
}
|
||||
this.lastUpdate = now;
|
||||
@@ -177,14 +185,17 @@ class FSAPIConverterAdapter implements IStorageEventConverterAdapter<FSAPIFile>
|
||||
class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
private observer: FileSystemObserverInstance | null = null;
|
||||
|
||||
constructor(private rootHandle: FileSystemDirectoryHandle) {}
|
||||
constructor(
|
||||
private rootHandle: FileSystemDirectoryHandle,
|
||||
private readonly addLog: WebAppLog
|
||||
) {}
|
||||
|
||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||
// Use FileSystemObserver if available (Chrome 124+)
|
||||
const FileSystemObserver = (compatGlobal as GlobalWithFileSystemObserver).FileSystemObserver;
|
||||
if (!FileSystemObserver) {
|
||||
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
|
||||
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
|
||||
this.addLog("FileSystemObserver is not available; file watching is disabled", LOG_LEVEL_INFO, "fsapi-watch");
|
||||
this.addLog("Chrome 124 or later supports real-time file watching", LOG_LEVEL_INFO, "fsapi-watch");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -203,7 +214,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[FileSystemObserver] ${type}: ${relativePath}`);
|
||||
this.addLog(`${type}: ${relativePath}`, LOG_LEVEL_VERBOSE, "filesystem-observer");
|
||||
|
||||
// Convert to our event handlers
|
||||
try {
|
||||
@@ -259,9 +270,10 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[FileSystemObserver] Error processing ${type} event for ${relativePath}:`,
|
||||
error
|
||||
this.addLog(
|
||||
`Error processing ${type} event for ${relativePath}: ${String(error)}`,
|
||||
LOG_LEVEL_NOTICE,
|
||||
"filesystem-observer"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -269,10 +281,10 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
|
||||
// Start observing
|
||||
await this.observer.observe(this.rootHandle, { recursive: true });
|
||||
console.log("[FSAPIWatchAdapter] FileSystemObserver started successfully");
|
||||
this.addLog("FileSystemObserver started successfully", LOG_LEVEL_INFO, "fsapi-watch");
|
||||
} catch (error) {
|
||||
console.error("[FSAPIWatchAdapter] Failed to start FileSystemObserver:", error);
|
||||
console.log("[FSAPIWatchAdapter] Falling back to manual sync mode");
|
||||
this.addLog(`Failed to start FileSystemObserver: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-watch");
|
||||
this.addLog("Falling back to manual sync mode", LOG_LEVEL_INFO, "fsapi-watch");
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
@@ -283,9 +295,9 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
try {
|
||||
this.observer.disconnect();
|
||||
this.observer = null;
|
||||
console.log("[FSAPIWatchAdapter] FileSystemObserver stopped");
|
||||
this.addLog("FileSystemObserver stopped", LOG_LEVEL_INFO, "fsapi-watch");
|
||||
} catch (error) {
|
||||
console.error("[FSAPIWatchAdapter] Error stopping observer:", error);
|
||||
this.addLog(`Error stopping observer: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-watch");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,11 +313,11 @@ export class FSAPIStorageEventManagerAdapter implements IStorageEventManagerAdap
|
||||
readonly status: FSAPIStatusAdapter;
|
||||
readonly converter: FSAPIConverterAdapter;
|
||||
|
||||
constructor(rootHandle: FileSystemDirectoryHandle) {
|
||||
constructor(rootHandle: FileSystemDirectoryHandle, addLog: WebAppLog) {
|
||||
this.typeGuard = new FSAPITypeGuardAdapter();
|
||||
this.persistence = new FSAPIPersistenceAdapter();
|
||||
this.watch = new FSAPIWatchAdapter(rootHandle);
|
||||
this.status = new FSAPIStatusAdapter();
|
||||
this.persistence = new FSAPIPersistenceAdapter(addLog);
|
||||
this.watch = new FSAPIWatchAdapter(rootHandle, addLog);
|
||||
this.status = new FSAPIStatusAdapter(addLog);
|
||||
this.converter = new FSAPIConverterAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ export class StorageEventManagerFSAPI extends StorageEventManagerBase<FSAPIStora
|
||||
core: LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands>,
|
||||
dependencies: StorageEventManagerBaseDependencies
|
||||
) {
|
||||
const adapter = new FSAPIStorageEventManagerAdapter(rootHandle);
|
||||
const adapter = new FSAPIStorageEventManagerAdapter(rootHandle, (message, level, key) =>
|
||||
dependencies.APIService.addLog(message, level, key)
|
||||
);
|
||||
super(adapter, dependencies);
|
||||
this.fsapiAdapter = adapter;
|
||||
this.core = core;
|
||||
|
||||
@@ -7,7 +7,9 @@ import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAd
|
||||
*/
|
||||
export class FileAccessFSAPI extends FileAccessBase<FSAPIFileSystemAdapter> {
|
||||
constructor(rootHandle: FileSystemDirectoryHandle, dependencies: FileAccessBaseDependencies) {
|
||||
const adapter = new FSAPIFileSystemAdapter(rootHandle);
|
||||
const adapter = new FSAPIFileSystemAdapter(rootHandle, (message, level, key) =>
|
||||
dependencies.APIService.addLog(message, level, key)
|
||||
);
|
||||
super(adapter, dependencies);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ let _logs = [] as string[];
|
||||
|
||||
const maxLines = 10000;
|
||||
setGlobalLogFunction((msg, level) => {
|
||||
console.log(msg);
|
||||
const msgstr = typeof msg === "string" ? msg : JSON.stringify(msg);
|
||||
const strLog = `${new Date().toISOString()}\u2001${msgstr}`;
|
||||
_logs.push(strLog);
|
||||
|
||||
@@ -23,16 +23,13 @@
|
||||
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => {}))
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
item.setTitle("🔧 Get Configuration").onClick(async () => {
|
||||
console.log("Get Configuration");
|
||||
});
|
||||
item.setTitle("🔧 Get Configuration").onClick(async () => {});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item) => {
|
||||
const mark = "checkmark";
|
||||
item.setTitle("Toggle Sync on connect")
|
||||
.onClick(async () => {
|
||||
console.log("Toggle Sync on connect");
|
||||
// await this.toggleProp(peer, "syncOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
@@ -41,7 +38,6 @@
|
||||
const mark = null;
|
||||
item.setTitle("Toggle Watch on connect")
|
||||
.onClick(async () => {
|
||||
console.log("Toggle Watch on connect");
|
||||
// await this.toggleProp(peer, "watchOnConnect");
|
||||
})
|
||||
.setIcon(mark);
|
||||
|
||||
@@ -91,7 +91,7 @@ export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueData
|
||||
// await closeDB();
|
||||
await deleteDB(dbKey, {
|
||||
blocked() {
|
||||
console.warn(`Database delete blocked for ${dbKey}`);
|
||||
Logger(`Database delete blocked for ${dbKey}`);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -74,12 +74,13 @@ export class ModuleDev extends AbstractObsidianModule {
|
||||
if (w) {
|
||||
const id = await this.services.path.path2id(filename as FilePathWithPrefix);
|
||||
const f = await this.core.localDatabase.getRaw(id);
|
||||
console.log(f);
|
||||
console.log(f._rev);
|
||||
this._log(f, LOG_LEVEL_VERBOSE);
|
||||
this._log(f._rev, LOG_LEVEL_VERBOSE);
|
||||
const revConflict = f._rev.split("-")[0] + "-" + (parseInt(f._rev.split("-")[1]) + 1).toString();
|
||||
console.log(await this.core.localDatabase.bulkDocsRaw([f], { new_edits: false }));
|
||||
console.log(
|
||||
await this.core.localDatabase.bulkDocsRaw([{ ...f, _rev: revConflict }], { new_edits: false })
|
||||
this._log(await this.core.localDatabase.bulkDocsRaw([f], { new_edits: false }), LOG_LEVEL_VERBOSE);
|
||||
this._log(
|
||||
await this.core.localDatabase.bulkDocsRaw([{ ...f, _rev: revConflict }], { new_edits: false }),
|
||||
LOG_LEVEL_VERBOSE
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -657,7 +657,6 @@ export class DocumentHistoryModal extends Modal {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
this.BlobURLs.forEach((value) => {
|
||||
console.log(value);
|
||||
if (value) URL.revokeObjectURL(value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -551,13 +551,6 @@ ${stringifyYaml(info)}
|
||||
return;
|
||||
}
|
||||
addDisplayLog(newMessage);
|
||||
if (message instanceof Error) {
|
||||
console.error(vaultName + ":" + newMessage);
|
||||
} else if (level >= LOG_LEVEL_INFO) {
|
||||
console.log(vaultName + ":" + newMessage);
|
||||
} else {
|
||||
console.debug(vaultName + ":" + newMessage);
|
||||
}
|
||||
if (!this.settings?.showOnlyIconsOnEditor) {
|
||||
this.statusLog.value = messageContent;
|
||||
}
|
||||
|
||||
@@ -121,8 +121,7 @@ export async function migrateDatabases(operationName: string, from: PouchDB.Data
|
||||
Logger(`Destroyed existing destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
|
||||
const dbTo2 = await openTo();
|
||||
const info2 = await dbTo2.info(); // ensure created
|
||||
console.log(info2);
|
||||
await dbTo2.info(); // ensure created
|
||||
Logger(`Re-created destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
|
||||
|
||||
const info = await from.info();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Decision from "@/modules/services/LiveSyncUI/components/Decision.svelte";
|
||||
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
|
||||
import { checkConfig, type ConfigCheckResult, type ResultError, type ResultErrorMessage } from "./utilCheckCouchDB";
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||
type Props = {
|
||||
trialRemoteSetting: ObsidianLiveSyncSettings;
|
||||
};
|
||||
@@ -15,10 +16,9 @@
|
||||
detectedIssues = [];
|
||||
try {
|
||||
const fixResults = await checkConfig(trialRemoteSetting);
|
||||
console.dir(fixResults);
|
||||
detectedIssues = fixResults;
|
||||
} catch (e) {
|
||||
console.error("Error during testAndFixSettings:", e);
|
||||
Logger(e, LOG_LEVEL_VERBOSE, "setup-couchdb-check");
|
||||
detectedIssues.push({ message: `Error during testAndFixSettings: ${e}`, result: "error", classes: [] });
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
processing = true;
|
||||
await issue.fix();
|
||||
} catch (e) {
|
||||
console.error("Error during fixIssue:", e);
|
||||
Logger(e, LOG_LEVEL_VERBOSE, "setup-couchdb-fix");
|
||||
}
|
||||
await testAndFixSettings();
|
||||
processing = false;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { SETTING_KEY_P2P_DEVICE_NAME } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import ExtraItems from "@/modules/services/LiveSyncUI/components/ExtraItems.svelte";
|
||||
import { TYPE_CANCELLED, type SetupRemoteP2PResultType } from "./setupDialogTypes";
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||
|
||||
const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS);
|
||||
let syncSetting = $state<P2PConnectionInfo>({ ...default_setting });
|
||||
@@ -137,7 +138,7 @@
|
||||
replicator.close();
|
||||
dummyPouch.destroy();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Logger(e, LOG_LEVEL_VERBOSE, "setup-p2p-cleanup");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -2,13 +2,14 @@ import { getLanguage, requireApiVersion } from "@/deps";
|
||||
import { createServiceFeature } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
|
||||
import { $msg, __onMissingTranslation, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
|
||||
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||
|
||||
function tryGetLanguage() {
|
||||
function tryGetLanguage(onError: (error: unknown) => void) {
|
||||
if (requireApiVersion("1.8.7")) {
|
||||
try {
|
||||
return getLanguage();
|
||||
} catch (e) {
|
||||
console.error("Failed to get Obsidian language, defaulting to 'en'", e);
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
return "en";
|
||||
@@ -20,7 +21,13 @@ export const enableI18nFeature = createServiceFeature(async ({ services: { setti
|
||||
let isChanged = false;
|
||||
const settings = setting.currentSettings();
|
||||
if (settings.displayLanguage == "") {
|
||||
const obsidianLanguage = tryGetLanguage();
|
||||
const obsidianLanguage = tryGetLanguage((error) => {
|
||||
API.addLog(
|
||||
`Failed to get Obsidian language; defaulting to 'en': ${String(error)}`,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
"i18n-language"
|
||||
);
|
||||
});
|
||||
if (
|
||||
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
||||
obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting
|
||||
|
||||
@@ -25,6 +25,7 @@ This will call for your help once again. I would be very grateful for your co-op
|
||||
|
||||
- Replaced the embedded Commonlib source and generated fallback declarations with a locked compiled package, reducing duplicated release and repository-scanner inputs without changing synchronisation behaviour.
|
||||
- Moved CLI standard input, prompting, and protocol output behind a host-injected Commonlib contract, and routed adapter diagnostics through the service logging API, without changing command output formats.
|
||||
- Routed Webapp, WebPeer, and plug-in diagnostics through their application-owned log paths instead of writing directly to the browser console.
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
Reference in New Issue
Block a user