mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-12 17:25:56 +00:00
chore(format): no intentional behaviour change - runs pretty
This commit is contained in:
@@ -17,13 +17,10 @@ export const EVENT_REQUEST_OPEN_SETTING_WIZARD = "request-open-setting-wizard";
|
|||||||
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
export const EVENT_REQUEST_OPEN_SETUP_URI = "request-open-setup-uri";
|
||||||
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
|
export const EVENT_REQUEST_COPY_SETUP_URI = "request-copy-setup-uri";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab";
|
export const EVENT_REQUEST_RELOAD_SETTING_TAB = "reload-setting-tab";
|
||||||
|
|
||||||
export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-dialog";
|
export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-dialog";
|
||||||
|
|
||||||
|
|
||||||
// export const EVENT_FILE_CHANGED = "file-changed";
|
// export const EVENT_FILE_CHANGED = "file-changed";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -37,17 +34,16 @@ declare global {
|
|||||||
[EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings;
|
[EVENT_SETTING_SAVED]: ObsidianLiveSyncSettings;
|
||||||
[EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin;
|
[EVENT_PLUGIN_LOADED]: ObsidianLiveSyncPlugin;
|
||||||
[EVENT_LAYOUT_READY]: undefined;
|
[EVENT_LAYOUT_READY]: undefined;
|
||||||
"event-file-changed": { file: FilePathWithPrefix, automated: boolean };
|
"event-file-changed": { file: FilePathWithPrefix; automated: boolean };
|
||||||
"document-stub-created":
|
"document-stub-created": {
|
||||||
{
|
toc: Set<string>;
|
||||||
toc: Set<string>, stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } }
|
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } };
|
||||||
},
|
};
|
||||||
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
|
[EVENT_REQUEST_OPEN_SETTINGS]: undefined;
|
||||||
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
|
[EVENT_REQUEST_OPEN_SETTING_WIZARD]: undefined;
|
||||||
[EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix, old: FilePathWithPrefix };
|
[EVENT_FILE_RENAMED]: { newPath: FilePathWithPrefix; old: FilePathWithPrefix };
|
||||||
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
|
[EVENT_LEAF_ACTIVE_CHANGED]: undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { eventHub };
|
export { eventHub };
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export const EVENT_REQUEST_SHOW_HISTORY = "show-history";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
[EVENT_REQUEST_SHOW_HISTORY]: { file: TFile, fileOnDB: LoadedEntry } | { file: FilePathWithPrefix, fileOnDB: LoadedEntry };
|
[EVENT_REQUEST_SHOW_HISTORY]:
|
||||||
|
| { file: TFile; fileOnDB: LoadedEntry }
|
||||||
|
| { file: FilePathWithPrefix; fileOnDB: LoadedEntry };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { type PluginManifest, TFile } from "../deps.ts";
|
import { type PluginManifest, TFile } from "../deps.ts";
|
||||||
import { type DatabaseEntry, type EntryBody, type FilePath, type UXFileInfoStub, type UXInternalFileInfoStub } from "../lib/src/common/types.ts";
|
import {
|
||||||
|
type DatabaseEntry,
|
||||||
|
type EntryBody,
|
||||||
|
type FilePath,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
type UXInternalFileInfoStub,
|
||||||
|
} from "../lib/src/common/types.ts";
|
||||||
|
|
||||||
export interface PluginDataEntry extends DatabaseEntry {
|
export interface PluginDataEntry extends DatabaseEntry {
|
||||||
deviceVaultName: string;
|
deviceVaultName: string;
|
||||||
@@ -55,15 +61,15 @@ export type FileEventArgs = {
|
|||||||
cache?: CacheData;
|
cache?: CacheData;
|
||||||
oldPath?: string;
|
oldPath?: string;
|
||||||
ctx?: any;
|
ctx?: any;
|
||||||
}
|
};
|
||||||
export type FileEventItem = {
|
export type FileEventItem = {
|
||||||
type: FileEventType,
|
type: FileEventType;
|
||||||
args: FileEventArgs,
|
args: FileEventArgs;
|
||||||
key: string,
|
key: string;
|
||||||
skipBatchWait?: boolean,
|
skipBatchWait?: boolean;
|
||||||
cancelled?: boolean,
|
cancelled?: boolean;
|
||||||
batched?: boolean
|
batched?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Hidden items (Now means `chunk`)
|
// Hidden items (Now means `chunk`)
|
||||||
export const CHeader = "h:";
|
export const CHeader = "h:";
|
||||||
@@ -82,4 +88,3 @@ export const ICXHeader = "ix:";
|
|||||||
|
|
||||||
export const FileWatchEventQueueMax = 10;
|
export const FileWatchEventQueueMax = 10;
|
||||||
export const configURIBase = "obsidian://setuplivesync?settings=";
|
export const configURIBase = "obsidian://setuplivesync?settings=";
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,48 @@
|
|||||||
import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "../deps.ts";
|
import { normalizePath, Platform, TAbstractFile, type RequestUrlParam, requestUrl } from "../deps.ts";
|
||||||
import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "../lib/src/string_and_binary/path.ts";
|
import {
|
||||||
|
path2id_base,
|
||||||
|
id2path_base,
|
||||||
|
isValidFilenameInLinux,
|
||||||
|
isValidFilenameInDarwin,
|
||||||
|
isValidFilenameInWidows,
|
||||||
|
isValidFilenameInAndroid,
|
||||||
|
stripAllPrefixes,
|
||||||
|
} from "../lib/src/string_and_binary/path.ts";
|
||||||
|
|
||||||
import { Logger } from "../lib/src/common/logger.ts";
|
import { Logger } from "../lib/src/common/logger.ts";
|
||||||
import { LOG_LEVEL_VERBOSE, type AnyEntry, type DocumentID, type EntryHasPath, type FilePath, type FilePathWithPrefix, type UXFileInfo, type UXFileInfoStub } from "../lib/src/common/types.ts";
|
import {
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type AnyEntry,
|
||||||
|
type DocumentID,
|
||||||
|
type EntryHasPath,
|
||||||
|
type FilePath,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type UXFileInfo,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
} from "../lib/src/common/types.ts";
|
||||||
import { CHeader, ICHeader, ICHeaderLength, ICXHeader, PSCHeader } from "./types.ts";
|
import { CHeader, ICHeader, ICHeaderLength, ICXHeader, PSCHeader } from "./types.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||||
import { writeString } from "../lib/src/string_and_binary/convert.ts";
|
import { writeString } from "../lib/src/string_and_binary/convert.ts";
|
||||||
import { fireAndForget } from "../lib/src/common/utils.ts";
|
import { fireAndForget } from "../lib/src/common/utils.ts";
|
||||||
import { sameChangePairs } from "./stores.ts";
|
import { sameChangePairs } from "./stores.ts";
|
||||||
|
|
||||||
export { scheduleTask, setPeriodicTask, cancelTask, cancelAllTasks, cancelPeriodicTask, cancelAllPeriodicTask, } from "../lib/src/concurrency/task.ts";
|
export {
|
||||||
|
scheduleTask,
|
||||||
|
setPeriodicTask,
|
||||||
|
cancelTask,
|
||||||
|
cancelAllTasks,
|
||||||
|
cancelPeriodicTask,
|
||||||
|
cancelAllPeriodicTask,
|
||||||
|
} from "../lib/src/concurrency/task.ts";
|
||||||
|
|
||||||
// For backward compatibility, using the path for determining id.
|
// For backward compatibility, using the path for determining id.
|
||||||
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
||||||
// The first slash will be deleted when the path is normalized.
|
// The first slash will be deleted when the path is normalized.
|
||||||
export async function path2id(filename: FilePathWithPrefix | FilePath, obfuscatePassphrase: string | false, caseInsensitive: boolean): Promise<DocumentID> {
|
export async function path2id(
|
||||||
|
filename: FilePathWithPrefix | FilePath,
|
||||||
|
obfuscatePassphrase: string | false,
|
||||||
|
caseInsensitive: boolean
|
||||||
|
): Promise<DocumentID> {
|
||||||
const temp = filename.split(":");
|
const temp = filename.split(":");
|
||||||
const path = temp.pop();
|
const path = temp.pop();
|
||||||
const normalizedPath = normalizePath(path as FilePath);
|
const normalizedPath = normalizePath(path as FilePath);
|
||||||
@@ -35,7 +63,6 @@ export function id2path(id: DocumentID, entry?: EntryHasPath): FilePathWithPrefi
|
|||||||
}
|
}
|
||||||
export function getPath(entry: AnyEntry) {
|
export function getPath(entry: AnyEntry) {
|
||||||
return id2path(entry._id, entry);
|
return id2path(entry._id, entry);
|
||||||
|
|
||||||
}
|
}
|
||||||
export function getPathWithoutPrefix(entry: AnyEntry) {
|
export function getPathWithoutPrefix(entry: AnyEntry) {
|
||||||
const f = getPath(entry);
|
const f = getPath(entry);
|
||||||
@@ -50,7 +77,6 @@ export function getPathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWi
|
|||||||
return file.path;
|
return file.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const memos: { [key: string]: any } = {};
|
const memos: { [key: string]: any } = {};
|
||||||
export function memoObject<T>(key: string, obj: T): T {
|
export function memoObject<T>(key: string, obj: T): T {
|
||||||
memos[key] = obj;
|
memos[key] = obj;
|
||||||
@@ -59,7 +85,7 @@ export function memoObject<T>(key: string, obj: T): T {
|
|||||||
export async function memoIfNotExist<T>(key: string, func: () => T | Promise<T>): Promise<T> {
|
export async function memoIfNotExist<T>(key: string, func: () => T | Promise<T>): Promise<T> {
|
||||||
if (!(key in memos)) {
|
if (!(key in memos)) {
|
||||||
const w = func();
|
const w = func();
|
||||||
const v = w instanceof Promise ? (await w) : w;
|
const v = w instanceof Promise ? await w : w;
|
||||||
memos[key] = v;
|
memos[key] = v;
|
||||||
}
|
}
|
||||||
return memos[key] as T;
|
return memos[key] as T;
|
||||||
@@ -75,7 +101,6 @@ export function disposeMemoObject(key: string) {
|
|||||||
delete memos[key];
|
delete memos[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function isValidPath(filename: string) {
|
export function isValidPath(filename: string) {
|
||||||
if (Platform.isDesktop) {
|
if (Platform.isDesktop) {
|
||||||
// if(Platform.isMacOS) return isValidFilenameInDarwin(filename);
|
// if(Platform.isMacOS) return isValidFilenameInDarwin(filename);
|
||||||
@@ -94,11 +119,10 @@ export function trimPrefix(target: string, prefix: string) {
|
|||||||
return target.startsWith(prefix) ? target.substring(prefix.length) : target;
|
return target.startsWith(prefix) ? target.substring(prefix.length) : target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns is internal chunk of file
|
* returns is internal chunk of file
|
||||||
* @param id ID
|
* @param id ID
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function isInternalMetadata(id: FilePath | FilePathWithPrefix | DocumentID): boolean {
|
export function isInternalMetadata(id: FilePath | FilePathWithPrefix | DocumentID): boolean {
|
||||||
return id.startsWith(ICHeader);
|
return id.startsWith(ICHeader);
|
||||||
@@ -107,7 +131,7 @@ export function stripInternalMetadataPrefix<T extends FilePath | FilePathWithPre
|
|||||||
return id.substring(ICHeaderLength) as T;
|
return id.substring(ICHeaderLength) as T;
|
||||||
}
|
}
|
||||||
export function id2InternalMetadataId(id: DocumentID): DocumentID {
|
export function id2InternalMetadataId(id: DocumentID): DocumentID {
|
||||||
return ICHeader + id as DocumentID;
|
return (ICHeader + id) as DocumentID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const CHeaderLength = CHeader.length;
|
// const CHeaderLength = CHeader.length;
|
||||||
@@ -122,7 +146,6 @@ export function isCustomisationSyncMetadata(str: string): boolean {
|
|||||||
return str.startsWith(ICXHeader);
|
return str.startsWith(ICXHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class PeriodicProcessor {
|
export class PeriodicProcessor {
|
||||||
_process: () => Promise<any>;
|
_process: () => Promise<any>;
|
||||||
_timer?: number;
|
_timer?: number;
|
||||||
@@ -141,12 +164,16 @@ export class PeriodicProcessor {
|
|||||||
enable(interval: number) {
|
enable(interval: number) {
|
||||||
this.disable();
|
this.disable();
|
||||||
if (interval == 0) return;
|
if (interval == 0) return;
|
||||||
this._timer = window.setInterval(() => fireAndForget(async () => {
|
this._timer = window.setInterval(
|
||||||
await this.process();
|
() =>
|
||||||
if (this._plugin.$$isUnloaded()) {
|
fireAndForget(async () => {
|
||||||
this.disable();
|
await this.process();
|
||||||
}
|
if (this._plugin.$$isUnloaded()) {
|
||||||
}), interval);
|
this.disable();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
interval
|
||||||
|
);
|
||||||
this._plugin.registerInterval(this._timer);
|
this._plugin.registerInterval(this._timer);
|
||||||
}
|
}
|
||||||
disable() {
|
disable() {
|
||||||
@@ -157,11 +184,21 @@ export class PeriodicProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const _requestToCouchDBFetch = async (baseUri: string, username: string, password: string, path?: string, body?: string | any, method?: string) => {
|
export const _requestToCouchDBFetch = async (
|
||||||
|
baseUri: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
path?: string,
|
||||||
|
body?: string | any,
|
||||||
|
method?: string
|
||||||
|
) => {
|
||||||
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
||||||
const encoded = window.btoa(utf8str);
|
const encoded = window.btoa(utf8str);
|
||||||
const authHeader = "Basic " + encoded;
|
const authHeader = "Basic " + encoded;
|
||||||
const transformedHeaders: Record<string, string> = { authorization: authHeader, "content-type": "application/json" };
|
const transformedHeaders: Record<string, string> = {
|
||||||
|
authorization: authHeader,
|
||||||
|
"content-type": "application/json",
|
||||||
|
};
|
||||||
const uri = `${baseUri}/${path}`;
|
const uri = `${baseUri}/${path}`;
|
||||||
const requestParam = {
|
const requestParam = {
|
||||||
url: uri,
|
url: uri,
|
||||||
@@ -171,9 +208,17 @@ export const _requestToCouchDBFetch = async (baseUri: string, username: string,
|
|||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
};
|
};
|
||||||
return await fetch(uri, requestParam);
|
return await fetch(uri, requestParam);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const _requestToCouchDB = async (baseUri: string, username: string, password: string, origin: string, path?: string, body?: any, method?: string) => {
|
export const _requestToCouchDB = async (
|
||||||
|
baseUri: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
origin: string,
|
||||||
|
path?: string,
|
||||||
|
body?: any,
|
||||||
|
method?: string
|
||||||
|
) => {
|
||||||
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
const utf8str = String.fromCharCode.apply(null, [...writeString(`${username}:${password}`)]);
|
||||||
const encoded = window.btoa(utf8str);
|
const encoded = window.btoa(utf8str);
|
||||||
const authHeader = "Basic " + encoded;
|
const authHeader = "Basic " + encoded;
|
||||||
@@ -187,23 +232,32 @@ export const _requestToCouchDB = async (baseUri: string, username: string, passw
|
|||||||
body: body ? JSON.stringify(body) : undefined,
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
};
|
};
|
||||||
return await requestUrl(requestParam);
|
return await requestUrl(requestParam);
|
||||||
}
|
};
|
||||||
export const requestToCouchDB = async (baseUri: string, username: string, password: string, origin: string = "", key?: string, body?: string, method?: string) => {
|
export const requestToCouchDB = async (
|
||||||
|
baseUri: string,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
origin: string = "",
|
||||||
|
key?: string,
|
||||||
|
body?: string,
|
||||||
|
method?: string
|
||||||
|
) => {
|
||||||
const uri = `_node/_local/_config${key ? "/" + key : ""}`;
|
const uri = `_node/_local/_config${key ? "/" + key : ""}`;
|
||||||
return await _requestToCouchDB(baseUri, username, password, origin, uri, body, method);
|
return await _requestToCouchDB(baseUri, username, password, origin, uri, body, method);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const BASE_IS_NEW = Symbol("base");
|
export const BASE_IS_NEW = Symbol("base");
|
||||||
export const TARGET_IS_NEW = Symbol("target");
|
export const TARGET_IS_NEW = Symbol("target");
|
||||||
export const EVEN = Symbol("even");
|
export const EVEN = Symbol("even");
|
||||||
|
|
||||||
|
|
||||||
// Why 2000? : ZIP FILE Does not have enough resolution.
|
// Why 2000? : ZIP FILE Does not have enough resolution.
|
||||||
const resolution = 2000;
|
const resolution = 2000;
|
||||||
export function compareMTime(baseMTime: number, targetMTime: number): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN {
|
export function compareMTime(
|
||||||
const truncatedBaseMTime = (~~(baseMTime / resolution)) * resolution;
|
baseMTime: number,
|
||||||
const truncatedTargetMTime = (~~(targetMTime / resolution)) * resolution;
|
targetMTime: number
|
||||||
|
): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN {
|
||||||
|
const truncatedBaseMTime = ~~(baseMTime / resolution) * resolution;
|
||||||
|
const truncatedTargetMTime = ~~(targetMTime / resolution) * resolution;
|
||||||
// Logger(`Resolution MTime ${truncatedBaseMTime} and ${truncatedTargetMTime} `, LOG_LEVEL_VERBOSE);
|
// Logger(`Resolution MTime ${truncatedBaseMTime} and ${truncatedTargetMTime} `, LOG_LEVEL_VERBOSE);
|
||||||
if (truncatedBaseMTime == truncatedTargetMTime) return EVEN;
|
if (truncatedBaseMTime == truncatedTargetMTime) return EVEN;
|
||||||
if (truncatedBaseMTime > truncatedTargetMTime) return BASE_IS_NEW;
|
if (truncatedBaseMTime > truncatedTargetMTime) return BASE_IS_NEW;
|
||||||
@@ -215,7 +269,7 @@ export function markChangesAreSame(file: AnyEntry | string | UXFileInfoStub, mti
|
|||||||
if (mtime1 === mtime2) return true;
|
if (mtime1 === mtime2) return true;
|
||||||
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
||||||
const pairs = sameChangePairs.get(key, []) || [];
|
const pairs = sameChangePairs.get(key, []) || [];
|
||||||
if (pairs.some(e => e == mtime1 || e == mtime2)) {
|
if (pairs.some((e) => e == mtime1 || e == mtime2)) {
|
||||||
sameChangePairs.set(key, [...new Set([...pairs, mtime1, mtime2])]);
|
sameChangePairs.set(key, [...new Set([...pairs, mtime1, mtime2])]);
|
||||||
} else {
|
} else {
|
||||||
sameChangePairs.set(key, [mtime1, mtime2]);
|
sameChangePairs.set(key, [mtime1, mtime2]);
|
||||||
@@ -224,17 +278,20 @@ export function markChangesAreSame(file: AnyEntry | string | UXFileInfoStub, mti
|
|||||||
export function isMarkedAsSameChanges(file: UXFileInfoStub | AnyEntry | string, mtimes: number[]) {
|
export function isMarkedAsSameChanges(file: UXFileInfoStub | AnyEntry | string, mtimes: number[]) {
|
||||||
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
||||||
const pairs = sameChangePairs.get(key, []) || [];
|
const pairs = sameChangePairs.get(key, []) || [];
|
||||||
if (mtimes.every(e => pairs.indexOf(e) !== -1)) {
|
if (mtimes.every((e) => pairs.indexOf(e) !== -1)) {
|
||||||
return EVEN;
|
return EVEN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function compareFileFreshness(baseFile: UXFileInfoStub | AnyEntry | undefined, checkTarget: UXFileInfo | AnyEntry | undefined): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN {
|
export function compareFileFreshness(
|
||||||
|
baseFile: UXFileInfoStub | AnyEntry | undefined,
|
||||||
|
checkTarget: UXFileInfo | AnyEntry | undefined
|
||||||
|
): typeof BASE_IS_NEW | typeof TARGET_IS_NEW | typeof EVEN {
|
||||||
if (baseFile === undefined && checkTarget == undefined) return EVEN;
|
if (baseFile === undefined && checkTarget == undefined) return EVEN;
|
||||||
if (baseFile == undefined) return TARGET_IS_NEW;
|
if (baseFile == undefined) return TARGET_IS_NEW;
|
||||||
if (checkTarget == undefined) return BASE_IS_NEW;
|
if (checkTarget == undefined) return BASE_IS_NEW;
|
||||||
|
|
||||||
const modifiedBase = "stat" in baseFile ? baseFile?.stat?.mtime ?? 0 : baseFile?.mtime ?? 0;
|
const modifiedBase = "stat" in baseFile ? (baseFile?.stat?.mtime ?? 0) : (baseFile?.mtime ?? 0);
|
||||||
const modifiedTarget = "stat" in checkTarget ? checkTarget?.stat?.mtime ?? 0 : checkTarget?.mtime ?? 0;
|
const modifiedTarget = "stat" in checkTarget ? (checkTarget?.stat?.mtime ?? 0) : (checkTarget?.mtime ?? 0);
|
||||||
|
|
||||||
if (modifiedBase && modifiedTarget && isMarkedAsSameChanges(baseFile, [modifiedBase, modifiedTarget])) {
|
if (modifiedBase && modifiedTarget && isMarkedAsSameChanges(baseFile, [modifiedBase, modifiedTarget])) {
|
||||||
return EVEN;
|
return EVEN;
|
||||||
@@ -242,21 +299,27 @@ export function compareFileFreshness(baseFile: UXFileInfoStub | AnyEntry | undef
|
|||||||
return compareMTime(modifiedBase, modifiedTarget);
|
return compareMTime(modifiedBase, modifiedTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
const _cached = new Map<string, {
|
const _cached = new Map<
|
||||||
value: any;
|
string,
|
||||||
context: Map<string, any>;
|
{
|
||||||
}>();
|
value: any;
|
||||||
|
context: Map<string, any>;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
export type MemoOption = {
|
export type MemoOption = {
|
||||||
key: string;
|
key: string;
|
||||||
forceUpdate?: boolean;
|
forceUpdate?: boolean;
|
||||||
validator?: (context: Map<string, any>) => boolean;
|
validator?: (context: Map<string, any>) => boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function useMemo<T>({ key, forceUpdate, validator }: MemoOption, updateFunc: (context: Map<string, any>, prev: T) => T): T {
|
export function useMemo<T>(
|
||||||
|
{ key, forceUpdate, validator }: MemoOption,
|
||||||
|
updateFunc: (context: Map<string, any>, prev: T) => T
|
||||||
|
): T {
|
||||||
const cached = _cached.get(key);
|
const cached = _cached.get(key);
|
||||||
const context = cached?.context || new Map<string, any>();
|
const context = cached?.context || new Map<string, any>();
|
||||||
if (cached && !forceUpdate && (!validator || validator && !validator(context))) {
|
if (cached && !forceUpdate && (!validator || (validator && !validator(context)))) {
|
||||||
return cached.value;
|
return cached.value;
|
||||||
}
|
}
|
||||||
const value = updateFunc(context, cached?.value);
|
const value = updateFunc(context, cached?.value);
|
||||||
@@ -267,11 +330,14 @@ export function useMemo<T>({ key, forceUpdate, validator }: MemoOption, updateFu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// const _static = new Map<string, any>();
|
// const _static = new Map<string, any>();
|
||||||
const _staticObj = new Map<string, {
|
const _staticObj = new Map<
|
||||||
value: any
|
string,
|
||||||
}>();
|
{
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
export function useStatic<T>(key: string): { value: (T | undefined) };
|
export function useStatic<T>(key: string): { value: T | undefined };
|
||||||
export function useStatic<T>(key: string, initial: T): { value: T };
|
export function useStatic<T>(key: string, initial: T): { value: T };
|
||||||
export function useStatic<T>(key: string, initial?: T) {
|
export function useStatic<T>(key: string, initial?: T) {
|
||||||
// if (!_static.has(key) && initial) {
|
// if (!_static.has(key) && initial) {
|
||||||
@@ -288,9 +354,9 @@ export function useStatic<T>(key: string, initial?: T) {
|
|||||||
return this._buf as T;
|
return this._buf as T;
|
||||||
},
|
},
|
||||||
set value(value: T) {
|
set value(value: T) {
|
||||||
this._buf = value
|
this._buf = value;
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
_staticObj.set(key, obj);
|
_staticObj.set(key, obj);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -310,4 +376,4 @@ export function displayRev(rev: string) {
|
|||||||
|
|
||||||
// export function getPathFromUXFileInfo(file: UXFileInfoStub | UXFileInfo | string) {
|
// export function getPathFromUXFileInfo(file: UXFileInfoStub | UXFileInfo | string) {
|
||||||
// return (typeof file == "string" ? file : file.path) as FilePathWithPrefix;
|
// return (typeof file == "string" ? file : file.path) as FilePathWithPrefix;
|
||||||
// }
|
// }
|
||||||
|
|||||||
40
src/deps.ts
40
src/deps.ts
@@ -1,13 +1,39 @@
|
|||||||
import { type FilePath } from "./lib/src/common/types.ts";
|
import { type FilePath } from "./lib/src/common/types.ts";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
addIcon, App, debounce, Editor, FuzzySuggestModal, MarkdownRenderer, MarkdownView, Modal, Notice, Platform, Plugin, PluginSettingTab, requestUrl, sanitizeHTMLToDom, Setting, stringifyYaml, TAbstractFile, TextAreaComponent, TFile, TFolder,
|
addIcon,
|
||||||
parseYaml, ItemView, WorkspaceLeaf
|
App,
|
||||||
|
debounce,
|
||||||
|
Editor,
|
||||||
|
FuzzySuggestModal,
|
||||||
|
MarkdownRenderer,
|
||||||
|
MarkdownView,
|
||||||
|
Modal,
|
||||||
|
Notice,
|
||||||
|
Platform,
|
||||||
|
Plugin,
|
||||||
|
PluginSettingTab,
|
||||||
|
requestUrl,
|
||||||
|
sanitizeHTMLToDom,
|
||||||
|
Setting,
|
||||||
|
stringifyYaml,
|
||||||
|
TAbstractFile,
|
||||||
|
TextAreaComponent,
|
||||||
|
TFile,
|
||||||
|
TFolder,
|
||||||
|
parseYaml,
|
||||||
|
ItemView,
|
||||||
|
WorkspaceLeaf,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
export type { DataWriteOptions, PluginManifest, RequestUrlParam, RequestUrlResponse, MarkdownFileInfo, ListedFiles } from "obsidian";
|
export type {
|
||||||
import {
|
DataWriteOptions,
|
||||||
normalizePath as normalizePath_
|
PluginManifest,
|
||||||
|
RequestUrlParam,
|
||||||
|
RequestUrlResponse,
|
||||||
|
MarkdownFileInfo,
|
||||||
|
ListedFiles,
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
|
import { normalizePath as normalizePath_ } from "obsidian";
|
||||||
const normalizePath = normalizePath_ as <T extends string | FilePath>(from: T) => T;
|
const normalizePath = normalizePath_ as <T extends string | FilePath>(from: T) => T;
|
||||||
export { normalizePath }
|
export { normalizePath };
|
||||||
export { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
|
export { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,11 @@ export class PluginDialogModal extends Modal {
|
|||||||
this.contentEl.style.overflow = "auto";
|
this.contentEl.style.overflow = "auto";
|
||||||
this.contentEl.style.display = "flex";
|
this.contentEl.style.display = "flex";
|
||||||
this.contentEl.style.flexDirection = "column";
|
this.contentEl.style.flexDirection = "column";
|
||||||
this.titleEl.setText("Customization Sync (Beta3)")
|
this.titleEl.setText("Customization Sync (Beta3)");
|
||||||
if (!this.component) {
|
if (!this.component) {
|
||||||
this.component = new PluginPane({
|
this.component = new PluginPane({
|
||||||
target: contentEl, props: { plugin: this.plugin },
|
target: contentEl,
|
||||||
|
props: { plugin: this.plugin },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,4 +33,4 @@ export class PluginDialogModal extends Modal {
|
|||||||
this.component = undefined;
|
this.component = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,18 @@ export class JsonResolveModal extends Modal {
|
|||||||
hideLocal: boolean;
|
hideLocal: boolean;
|
||||||
title: string = "Conflicted Setting";
|
title: string = "Conflicted Setting";
|
||||||
|
|
||||||
constructor(app: App, filename: FilePath,
|
constructor(
|
||||||
docs: LoadedEntry[], callback: (keepRev?: string, mergedStr?: string) => Promise<void>,
|
app: App,
|
||||||
nameA?: string, nameB?: string, defaultSelect?: string,
|
filename: FilePath,
|
||||||
keepOrder?: boolean, hideLocal?: boolean, title: string = "Conflicted Setting") {
|
docs: LoadedEntry[],
|
||||||
|
callback: (keepRev?: string, mergedStr?: string) => Promise<void>,
|
||||||
|
nameA?: string,
|
||||||
|
nameB?: string,
|
||||||
|
defaultSelect?: string,
|
||||||
|
keepOrder?: boolean,
|
||||||
|
hideLocal?: boolean,
|
||||||
|
title: string = "Conflicted Setting"
|
||||||
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
@@ -57,14 +65,14 @@ export class JsonResolveModal extends Modal {
|
|||||||
defaultSelect: this.defaultSelect,
|
defaultSelect: this.defaultSelect,
|
||||||
keepOrder: this.keepOrder,
|
keepOrder: this.keepOrder,
|
||||||
hideLocal: this.hideLocal,
|
hideLocal: this.hideLocal,
|
||||||
callback: (keepRev: string | undefined, mergedStr: string | undefined) => this.UICallback(keepRev, mergedStr),
|
callback: (keepRev: string | undefined, mergedStr: string | undefined) =>
|
||||||
|
this.UICallback(keepRev, mergedStr),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,17 @@
|
|||||||
import { Logger } from "octagonal-wheels/common/logger";
|
import { Logger } from "octagonal-wheels/common/logger";
|
||||||
import { getPath } from "../common/utils.ts";
|
import { getPath } from "../common/utils.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, type AnyEntry, type DocumentID, type EntryHasPath, type FilePath, type FilePathWithPrefix, type LOG_LEVEL } from "../lib/src/common/types.ts";
|
import {
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
type AnyEntry,
|
||||||
|
type DocumentID,
|
||||||
|
type EntryHasPath,
|
||||||
|
type FilePath,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type LOG_LEVEL,
|
||||||
|
} from "../lib/src/common/types.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||||
|
|
||||||
|
|
||||||
export abstract class LiveSyncCommands {
|
export abstract class LiveSyncCommands {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
get app() {
|
get app() {
|
||||||
@@ -49,5 +57,4 @@ export abstract class LiveSyncCommands {
|
|||||||
// console.log(msg);
|
// console.log(msg);
|
||||||
Logger(msg, level, key);
|
Logger(msg, level, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
529
src/main.ts
529
src/main.ts
@@ -1,9 +1,31 @@
|
|||||||
import { Plugin } from "./deps";
|
import { Plugin } from "./deps";
|
||||||
import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type LOG_LEVEL, type diff_result, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, type HasSettings, type MetaEntry, type UXFileInfoStub, type MISSING_OR_ERROR, type AUTO_MERGED, type RemoteDBSettings, type TweakValues, } from "./lib/src/common/types.ts";
|
import {
|
||||||
|
type EntryDoc,
|
||||||
|
type LoadedEntry,
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
type LOG_LEVEL,
|
||||||
|
type diff_result,
|
||||||
|
type DatabaseConnectingStatus,
|
||||||
|
type EntryHasPath,
|
||||||
|
type DocumentID,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type FilePath,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
type HasSettings,
|
||||||
|
type MetaEntry,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
type MISSING_OR_ERROR,
|
||||||
|
type AUTO_MERGED,
|
||||||
|
type RemoteDBSettings,
|
||||||
|
type TweakValues,
|
||||||
|
} from "./lib/src/common/types.ts";
|
||||||
import { type FileEventItem } from "./common/types.ts";
|
import { type FileEventItem } from "./common/types.ts";
|
||||||
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
||||||
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv } from "./lib/src/replication/LiveSyncAbstractReplicator.js";
|
import {
|
||||||
|
LiveSyncAbstractReplicator,
|
||||||
|
type LiveSyncReplicatorEnv,
|
||||||
|
} from "./lib/src/replication/LiveSyncAbstractReplicator.js";
|
||||||
import { type KeyValueDatabase } from "./common/KeyValueDB.ts";
|
import { type KeyValueDatabase } from "./common/KeyValueDB.ts";
|
||||||
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
||||||
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
@@ -60,7 +82,6 @@ import { ModuleReplicateTest } from "./modules/extras/ModuleReplicateTest.ts";
|
|||||||
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
||||||
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
||||||
|
|
||||||
|
|
||||||
function throwShouldBeOverridden(): never {
|
function throwShouldBeOverridden(): never {
|
||||||
throw new Error("This function should be overridden by the module.");
|
throw new Error("This function should be overridden by the module.");
|
||||||
}
|
}
|
||||||
@@ -78,11 +99,15 @@ const InterceptiveAny = Promise.resolve(undefined);
|
|||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLocalDBEnv, LiveSyncReplicatorEnv, LiveSyncJournalReplicatorEnv, LiveSyncCouchDBReplicatorEnv, HasSettings<ObsidianLiveSyncSettings> {
|
export default class ObsidianLiveSyncPlugin
|
||||||
|
extends Plugin
|
||||||
|
implements
|
||||||
|
LiveSyncLocalDBEnv,
|
||||||
|
LiveSyncReplicatorEnv,
|
||||||
|
LiveSyncJournalReplicatorEnv,
|
||||||
|
LiveSyncCouchDBReplicatorEnv,
|
||||||
|
HasSettings<ObsidianLiveSyncSettings>
|
||||||
|
{
|
||||||
// --> Module System
|
// --> Module System
|
||||||
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
||||||
for (const addon of this.addOns) {
|
for (const addon of this.addOns) {
|
||||||
@@ -134,7 +159,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
// Common modules
|
// Common modules
|
||||||
// Note: Platform-dependent functions are not entirely dependent on the core only, as they are from platform-dependent modules. Stubbing is sometimes required.
|
// Note: Platform-dependent functions are not entirely dependent on the core only, as they are from platform-dependent modules. Stubbing is sometimes required.
|
||||||
new ModuleCheckRemoteSize(this),
|
new ModuleCheckRemoteSize(this),
|
||||||
// Test and Dev Modules
|
// Test and Dev Modules
|
||||||
new ModuleDev(this, this),
|
new ModuleDev(this, this),
|
||||||
new ModuleReplicateTest(this, this),
|
new ModuleReplicateTest(this, this),
|
||||||
new ModuleIntegratedTest(this, this),
|
new ModuleIntegratedTest(this, this),
|
||||||
@@ -142,25 +167,43 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
||||||
// <-- Module System
|
// <-- Module System
|
||||||
|
|
||||||
$$isSuspended(): boolean { throwShouldBeOverridden(); }
|
$$isSuspended(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$setSuspended(value: boolean): void { throwShouldBeOverridden(); }
|
$$setSuspended(value: boolean): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$isDatabaseReady(): boolean { throwShouldBeOverridden(); }
|
$$isDatabaseReady(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$getDeviceAndVaultName(): string { throwShouldBeOverridden(); }
|
$$getDeviceAndVaultName(): string {
|
||||||
$$setDeviceAndVaultName(name: string): void { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$setDeviceAndVaultName(name: string): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void { throwShouldBeOverridden() }
|
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
||||||
$$isReady(): boolean { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
$$markIsReady(): void { throwShouldBeOverridden(); }
|
}
|
||||||
$$resetIsReady(): void { throwShouldBeOverridden(); }
|
$$isReady(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$markIsReady(): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$resetIsReady(): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// Following are plugged by the modules.
|
// Following are plugged by the modules.
|
||||||
|
|
||||||
settings!: ObsidianLiveSyncSettings;
|
settings!: ObsidianLiveSyncSettings;
|
||||||
localDatabase!: LiveSyncLocalDB;
|
localDatabase!: LiveSyncLocalDB;
|
||||||
simpleStore!: SimpleStore<CheckPointInfo>
|
simpleStore!: SimpleStore<CheckPointInfo>;
|
||||||
replicator!: LiveSyncAbstractReplicator;
|
replicator!: LiveSyncAbstractReplicator;
|
||||||
confirm!: Confirm;
|
confirm!: Confirm;
|
||||||
storageAccess!: StorageAccess;
|
storageAccess!: StorageAccess;
|
||||||
@@ -169,24 +212,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
rebuilder!: Rebuilder;
|
rebuilder!: Rebuilder;
|
||||||
|
|
||||||
kvDB!: KeyValueDatabase;
|
kvDB!: KeyValueDatabase;
|
||||||
getDatabase(): PouchDB.Database<EntryDoc> { return this.localDatabase.localDatabase; }
|
getDatabase(): PouchDB.Database<EntryDoc> {
|
||||||
getSettings(): ObsidianLiveSyncSettings { return this.settings; }
|
return this.localDatabase.localDatabase;
|
||||||
|
}
|
||||||
|
getSettings(): ObsidianLiveSyncSettings {
|
||||||
|
return this.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
$$markFileListPossiblyChanged(): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$customFetchHandler(): ObsHttpHandler {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void { throwShouldBeOverridden(); }
|
$$getLastPostFailedBySize(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$customFetchHandler(): ObsHttpHandler { throwShouldBeOverridden(); }
|
$$isStorageInsensitive(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$getLastPostFailedBySize(): boolean { throwShouldBeOverridden(); }
|
$$shouldCheckCaseInsensitive(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$isUnloaded(): boolean {
|
||||||
|
throwShouldBeOverridden();
|
||||||
$$isStorageInsensitive(): boolean { throwShouldBeOverridden() }
|
}
|
||||||
|
|
||||||
$$shouldCheckCaseInsensitive(): boolean { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$isUnloaded(): boolean { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
requestCount = reactiveSource(0);
|
requestCount = reactiveSource(0);
|
||||||
responseCount = reactiveSource(0);
|
responseCount = reactiveSource(0);
|
||||||
@@ -202,7 +257,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
|
|
||||||
_totalProcessingCount?: ReactiveValue<number>;
|
_totalProcessingCount?: ReactiveValue<number>;
|
||||||
|
|
||||||
|
|
||||||
replicationStat = reactiveSource({
|
replicationStat = reactiveSource({
|
||||||
sent: 0,
|
sent: 0,
|
||||||
arrived: 0,
|
arrived: 0,
|
||||||
@@ -210,61 +264,102 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
maxPushSeq: 0,
|
maxPushSeq: 0,
|
||||||
lastSyncPullSeq: 0,
|
lastSyncPullSeq: 0,
|
||||||
lastSyncPushSeq: 0,
|
lastSyncPushSeq: 0,
|
||||||
syncStatus: "CLOSED" as DatabaseConnectingStatus
|
syncStatus: "CLOSED" as DatabaseConnectingStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
$$isReloadingScheduled(): boolean { throwShouldBeOverridden(); }
|
$$isReloadingScheduled(): boolean {
|
||||||
$$getReplicator(): LiveSyncAbstractReplicator { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
$$connectRemoteCouchDB(uri: string, auth: {
|
$$getReplicator(): LiveSyncAbstractReplicator {
|
||||||
username: string;
|
|
||||||
password: string
|
|
||||||
}, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean, performSetup: boolean, skipInfo: boolean, compression: boolean): Promise<string | {
|
|
||||||
db: PouchDB.Database<EntryDoc>;
|
|
||||||
info: PouchDB.Core.DatabaseInfo
|
|
||||||
}> {
|
|
||||||
throwShouldBeOverridden();
|
throwShouldBeOverridden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$$connectRemoteCouchDB(
|
||||||
|
uri: string,
|
||||||
|
auth: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
},
|
||||||
|
disableRequestURI: boolean,
|
||||||
|
passphrase: string | false,
|
||||||
|
useDynamicIterationCount: boolean,
|
||||||
|
performSetup: boolean,
|
||||||
|
skipInfo: boolean,
|
||||||
|
compression: boolean
|
||||||
|
): Promise<
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
db: PouchDB.Database<EntryDoc>;
|
||||||
|
info: PouchDB.Core.DatabaseInfo;
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$isMobile(): boolean { throwShouldBeOverridden(); }
|
$$isMobile(): boolean {
|
||||||
$$vaultName(): string { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$vaultName(): string {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// --> Path
|
// --> Path
|
||||||
|
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined { throwShouldBeOverridden(); }
|
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// <-- Path
|
// <-- Path
|
||||||
|
|
||||||
// --> Path conversion
|
// --> Path conversion
|
||||||
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { throwShouldBeOverridden(); }
|
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> { throwShouldBeOverridden(); }
|
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// <!-- Path conversion
|
// <!-- Path conversion
|
||||||
|
|
||||||
// --> Database
|
// --> Database
|
||||||
$$createPouchDBInstance<T extends object>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> { throwShouldBeOverridden(); }
|
$$createPouchDBInstance<T extends object>(
|
||||||
|
name?: string,
|
||||||
|
options?: PouchDB.Configuration.DatabaseConfiguration
|
||||||
|
): PouchDB.Database<T> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$allOnDBUnload(db: LiveSyncLocalDB): void { return; }
|
$allOnDBUnload(db: LiveSyncLocalDB): void {
|
||||||
$allOnDBClose(db: LiveSyncLocalDB): void { return; }
|
return;
|
||||||
|
}
|
||||||
|
$allOnDBClose(db: LiveSyncLocalDB): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// <!-- Database
|
// <!-- Database
|
||||||
|
|
||||||
$anyNewReplicator(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<LiveSyncAbstractReplicator> { throwShouldBeOverridden(); }
|
$anyNewReplicator(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> { return InterceptiveEvery; }
|
}
|
||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> { return InterceptiveEvery; }
|
|
||||||
|
|
||||||
|
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
|
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
// end interfaces
|
// end interfaces
|
||||||
|
|
||||||
$$getVaultName(): string { throwShouldBeOverridden(); }
|
$$getVaultName(): string {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$getSimpleStore<T>(kind: string): SimpleStore<T> {
|
||||||
$$getSimpleStore<T>(kind: string): SimpleStore<T> { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
// trench!: Trench;
|
// trench!: Trench;
|
||||||
|
|
||||||
// --> Events
|
// --> Events
|
||||||
@@ -306,64 +401,114 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$everyOnLayoutReady(): Promise<boolean> {
|
||||||
$everyOnLayoutReady(): Promise<boolean> { return InterceptiveEvery }
|
return InterceptiveEvery;
|
||||||
$everyOnFirstInitialize(): Promise<boolean> { return InterceptiveEvery }
|
}
|
||||||
|
$everyOnFirstInitialize(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
// Some Module should call this function to start the plugin.
|
// Some Module should call this function to start the plugin.
|
||||||
$$onLiveSyncReady(): Promise<false | undefined> { throwShouldBeOverridden(); }
|
$$onLiveSyncReady(): Promise<false | undefined> {
|
||||||
$$wireUpEvents(): void { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
$$onLiveSyncLoad(): Promise<void> { throwShouldBeOverridden(); }
|
}
|
||||||
|
$$wireUpEvents(): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$onLiveSyncLoad(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$onLiveSyncUnload(): Promise<void> { throwShouldBeOverridden(); }
|
$$onLiveSyncUnload(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$allScanStat(): Promise<boolean> {
|
$allScanStat(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
return InterceptiveAll;
|
||||||
}
|
}
|
||||||
$everyOnloadStart(): Promise<boolean> { return InterceptiveEvery; }
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> { return InterceptiveEvery; }
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> { return InterceptiveEvery; }
|
$everyOnload(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
$anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> { return InterceptiveAny; }
|
$anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allStartOnUnload(): Promise<boolean> {
|
||||||
|
return InterceptiveAll;
|
||||||
|
}
|
||||||
|
$allOnUnload(): Promise<boolean> {
|
||||||
|
return InterceptiveAll;
|
||||||
|
}
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> { return InterceptiveAll }
|
$$openDatabase(): Promise<boolean> {
|
||||||
$allOnUnload(): Promise<boolean> { return InterceptiveAll; }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$realizeSettingSyncMode(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$performRestart() {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$openDatabase(): Promise<boolean> { throwShouldBeOverridden() }
|
$$clearUsedPassphrase(): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$loadSettings(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$realizeSettingSyncMode(): Promise<void> { throwShouldBeOverridden(); }
|
$$saveDeviceAndVaultName(): void {
|
||||||
$$performRestart() { throwShouldBeOverridden(); }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$clearUsedPassphrase(): void { throwShouldBeOverridden() }
|
$$saveSettingData(): Promise<void> {
|
||||||
$$loadSettings(): Promise<void> { throwShouldBeOverridden() }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$saveDeviceAndVaultName(): void { throwShouldBeOverridden(); }
|
$anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
$$saveSettingData(): Promise<void> { throwShouldBeOverridden() }
|
$everyCommitPendingFileEvent(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
|
||||||
$anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> { return InterceptiveAny; }
|
// ->
|
||||||
|
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
$everyCommitPendingFileEvent(): Promise<boolean> { return InterceptiveEvery }
|
$$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// ->
|
$$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> { return InterceptiveAny; }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> { throwShouldBeOverridden() }
|
$$waitForAllConflictProcessed(): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
$$queueConflictCheck(file: FilePathWithPrefix): Promise<void> { throwShouldBeOverridden() }
|
}
|
||||||
|
|
||||||
$$waitForAllConflictProcessed(): Promise<boolean> { throwShouldBeOverridden() }
|
|
||||||
|
|
||||||
//<-- Conflict Check
|
//<-- Conflict Check
|
||||||
|
|
||||||
$anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> { return InterceptiveAny; }
|
$anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
$anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> { return InterceptiveAny; }
|
}
|
||||||
|
|
||||||
|
$anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
//---> Sync
|
//---> Sync
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
@@ -393,93 +538,169 @@ export default class ObsidianLiveSyncPlugin extends Plugin implements LiveSyncLo
|
|||||||
return InterceptiveEvery;
|
return InterceptiveEvery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$checkAndAskUseRemoteConfiguration(
|
||||||
|
settings: RemoteDBSettings
|
||||||
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> { throwShouldBeOverridden(); }
|
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$checkAndAskUseRemoteConfiguration(settings: RemoteDBSettings): Promise<{ result: false | TweakValues, requireFetch: boolean }> { throwShouldBeOverridden(); }
|
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> { return InterceptiveEvery; }
|
$$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> {
|
||||||
$$replicate(showMessage: boolean = false): Promise<boolean | void> { throwShouldBeOverridden() }
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> { throwShouldBeOverridden() }
|
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
$$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> { throwShouldBeOverridden() }
|
$$replicateAllToServer(
|
||||||
|
showingNotice: boolean = false,
|
||||||
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> { return InterceptiveAny; }
|
sendChunksInBulkDisabled: boolean = false
|
||||||
|
): Promise<boolean> {
|
||||||
$$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
throwShouldBeOverridden();
|
||||||
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> { throwShouldBeOverridden() }
|
}
|
||||||
|
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// Remote Governing
|
// Remote Governing
|
||||||
$$markRemoteLocked(lockByClean: boolean = false): Promise<void> { throwShouldBeOverridden() }
|
$$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$markRemoteUnlocked(): Promise<void> { throwShouldBeOverridden() }
|
$$markRemoteUnlocked(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
$$markRemoteResolved(): Promise<void> { throwShouldBeOverridden() }
|
$$markRemoteResolved(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// <-- Remote Governing
|
// <-- Remote Governing
|
||||||
|
|
||||||
|
$$isFileSizeExceeded(size: number): boolean {
|
||||||
$$isFileSizeExceeded(size: number): boolean { throwShouldBeOverridden() }
|
|
||||||
|
|
||||||
$$performFullScan(showingNotice?: boolean): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean | undefined> { return InterceptiveAny; }
|
|
||||||
$$resolveConflictByDeletingRev(path: FilePathWithPrefix, deleteRevision: string, subTitle = ""): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> { throwShouldBeOverridden(); }
|
|
||||||
$$resolveConflict(filename: FilePathWithPrefix): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
$anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$waitForReplicationOnce(): Promise<boolean | void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$resetLocalDatabase(): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$tryResetRemoteDatabase(): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$tryCreateRemoteDatabase(): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$$askReload(message?: string) { throwShouldBeOverridden(); }
|
|
||||||
$$scheduleAppReload() { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
//--- Setup
|
|
||||||
$allSuspendAllSync(): Promise<boolean> { return InterceptiveAll; }
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> { return InterceptiveAll; }
|
|
||||||
|
|
||||||
$allAskUsingOptionalSyncFeature(opt: {
|
|
||||||
enableFetch?: boolean,
|
|
||||||
enableOverwrite?: boolean
|
|
||||||
}): Promise<boolean> {
|
|
||||||
throwShouldBeOverridden();
|
throwShouldBeOverridden();
|
||||||
}
|
}
|
||||||
$anyConfigureOptionalSyncFeature(mode: string): Promise<void> { throwShouldBeOverridden(); }
|
|
||||||
|
|
||||||
$$showView(viewType: string): Promise<void> { throwShouldBeOverridden(); }
|
$$performFullScan(showingNotice?: boolean): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$anyResolveConflictByUI(
|
||||||
|
filename: FilePathWithPrefix,
|
||||||
|
conflictCheckResult: diff_result
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
$$resolveConflictByDeletingRev(
|
||||||
|
path: FilePathWithPrefix,
|
||||||
|
deleteRevision: string,
|
||||||
|
subTitle = ""
|
||||||
|
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$waitForReplicationOnce(): Promise<boolean | void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$resetLocalDatabase(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$tryResetRemoteDatabase(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$tryCreateRemoteDatabase(): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$askReload(message?: string) {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$$scheduleAppReload() {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- Setup
|
||||||
|
$allSuspendAllSync(): Promise<boolean> {
|
||||||
|
return InterceptiveAll;
|
||||||
|
}
|
||||||
|
$allSuspendExtraSync(): Promise<boolean> {
|
||||||
|
return InterceptiveAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
$anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
|
$$showView(viewType: string): Promise<void> {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
||||||
$everyModuleTest(): Promise<boolean> { return InterceptiveEvery; }
|
$everyModuleTest(): Promise<boolean> {
|
||||||
$everyModuleTestMultiDevice(): Promise<boolean> { return InterceptiveEvery; }
|
return InterceptiveEvery;
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void { throwShouldBeOverridden(); }
|
}
|
||||||
|
$everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
|
return InterceptiveEvery;
|
||||||
|
}
|
||||||
|
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
||||||
|
throwShouldBeOverridden();
|
||||||
|
}
|
||||||
|
|
||||||
_isThisModuleEnabled(): boolean { return true; }
|
_isThisModuleEnabled(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
$anyGetAppId(): Promise<string | undefined> { return InterceptiveAny; }
|
|
||||||
|
|
||||||
|
$anyGetAppId(): Promise<string | undefined> {
|
||||||
|
return InterceptiveAny;
|
||||||
|
}
|
||||||
|
|
||||||
// Plug-in's overrideable functions
|
// Plug-in's overrideable functions
|
||||||
onload() { void this.$$onLiveSyncLoad(); }
|
onload() {
|
||||||
async saveSettings() { await this.$$saveSettingData(); }
|
void this.$$onLiveSyncLoad();
|
||||||
onunload() { return void this.$$onLiveSyncUnload(); }
|
}
|
||||||
|
async saveSettings() {
|
||||||
|
await this.$$saveSettingData();
|
||||||
|
}
|
||||||
|
onunload() {
|
||||||
|
return void this.$$onLiveSyncUnload();
|
||||||
|
}
|
||||||
// <-- Plug-in's overrideable functions
|
// <-- Plug-in's overrideable functions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// For now,
|
// For now,
|
||||||
export type LiveSyncCore = ObsidianLiveSyncPlugin;
|
export type LiveSyncCore = ObsidianLiveSyncPlugin;
|
||||||
|
|||||||
@@ -3,8 +3,14 @@ import type { LOG_LEVEL } from "../lib/src/common/types";
|
|||||||
import type { LiveSyncCore } from "../main";
|
import type { LiveSyncCore } from "../main";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { unique } from "octagonal-wheels/collection";
|
||||||
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
||||||
import type { ICoreModuleBase, AllInjectableProps, AllExecuteProps, EveryExecuteProps, AnyExecuteProps, ICoreModule } from "./ModuleTypes";
|
import type {
|
||||||
|
ICoreModuleBase,
|
||||||
|
AllInjectableProps,
|
||||||
|
AllExecuteProps,
|
||||||
|
EveryExecuteProps,
|
||||||
|
AnyExecuteProps,
|
||||||
|
ICoreModule,
|
||||||
|
} from "./ModuleTypes";
|
||||||
|
|
||||||
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
||||||
return key.startsWith("$");
|
return key.startsWith("$");
|
||||||
@@ -14,7 +20,6 @@ function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
|||||||
return key.startsWith("$$");
|
return key.startsWith("$$");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
||||||
return key.startsWith("$all");
|
return key.startsWith("$all");
|
||||||
}
|
}
|
||||||
@@ -35,15 +40,17 @@ function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
|||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
*/
|
*/
|
||||||
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
||||||
const allKeys = unique([...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
const allKeys = unique([
|
||||||
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target)))]).filter(e => e.startsWith("$")) as (keyof ICoreModule)[];
|
...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
||||||
|
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
||||||
|
]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
||||||
const moduleMap = new Map<string, IObsidianModule[]>();
|
const moduleMap = new Map<string, IObsidianModule[]>();
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
for (const key of allKeys) {
|
for (const key of allKeys) {
|
||||||
if (isOverridableKey(key)) {
|
if (isOverridableKey(key)) {
|
||||||
if (key in module) {
|
if (key in module) {
|
||||||
const list = moduleMap.get(key) || [];
|
const list = moduleMap.get(key) || [];
|
||||||
if (typeof module[key] === 'function') {
|
if (typeof module[key] === "function") {
|
||||||
module[key] = module[key].bind(module) as any;
|
module[key] = module[key].bind(module) as any;
|
||||||
}
|
}
|
||||||
list.push(module);
|
list.push(module);
|
||||||
@@ -74,7 +81,7 @@ export function injectModules<T extends ICoreModule>(target: T, modules: ICoreMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
@@ -94,7 +101,7 @@ export function injectModules<T extends ICoreModule>(target: T, modules: ICoreMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
@@ -115,7 +122,7 @@ export function injectModules<T extends ICoreModule>(target: T, modules: ICoreMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
for (const module of modules) {
|
for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
@@ -127,7 +134,6 @@ export function injectModules<T extends ICoreModule>(target: T, modules: ICoreMo
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export abstract class AbstractModule {
|
export abstract class AbstractModule {
|
||||||
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) {
|
if (typeof msg === "string" && level !== LOG_LEVEL_NOTICE) {
|
||||||
@@ -173,11 +179,10 @@ export abstract class AbstractModule {
|
|||||||
return this.testFail(`${key} failed: ${ret}`);
|
return this.testFail(`${key} failed: ${ret}`);
|
||||||
}
|
}
|
||||||
this.addTestResult(key, true, "");
|
this.addTestResult(key, true, "");
|
||||||
}
|
} catch (ex: any) {
|
||||||
catch (ex: any) {
|
|
||||||
this.addTestResult(key, false, "Failed by Exception", ex.toString());
|
this.addTestResult(key, false, "Failed by Exception", ex.toString());
|
||||||
return this.testFail(`${key} failed: ${ex}`);
|
return this.testFail(`${key} failed: ${ex}`);
|
||||||
}
|
}
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,12 @@ import type ObsidianLiveSyncPlugin from "../main";
|
|||||||
import { AbstractModule } from "./AbstractModule.ts";
|
import { AbstractModule } from "./AbstractModule.ts";
|
||||||
import type { ChainableExecuteFunction, OverridableFunctionsKeys } from "./ModuleTypes";
|
import type { ChainableExecuteFunction, OverridableFunctionsKeys } from "./ModuleTypes";
|
||||||
|
|
||||||
|
|
||||||
export type IObsidianModuleBase = OverridableFunctionsKeys<ObsidianLiveSyncPlugin>;
|
export type IObsidianModuleBase = OverridableFunctionsKeys<ObsidianLiveSyncPlugin>;
|
||||||
export type IObsidianModule = Prettify<Partial<IObsidianModuleBase>>
|
export type IObsidianModule = Prettify<Partial<IObsidianModuleBase>>;
|
||||||
export type ModuleKeys = keyof IObsidianModule;
|
export type ModuleKeys = keyof IObsidianModule;
|
||||||
export type ChainableModuleProps = ChainableExecuteFunction<ObsidianLiveSyncPlugin>;
|
export type ChainableModuleProps = ChainableExecuteFunction<ObsidianLiveSyncPlugin>;
|
||||||
|
|
||||||
|
|
||||||
export abstract class AbstractObsidianModule extends AbstractModule {
|
export abstract class AbstractObsidianModule extends AbstractModule {
|
||||||
|
|
||||||
addCommand = this.plugin.addCommand.bind(this.plugin);
|
addCommand = this.plugin.addCommand.bind(this.plugin);
|
||||||
registerView = this.plugin.registerView.bind(this.plugin);
|
registerView = this.plugin.registerView.bind(this.plugin);
|
||||||
addRibbonIcon = this.plugin.addRibbonIcon.bind(this.plugin);
|
addRibbonIcon = this.plugin.addRibbonIcon.bind(this.plugin);
|
||||||
@@ -31,13 +28,15 @@ export abstract class AbstractObsidianModule extends AbstractModule {
|
|||||||
return this.plugin.app;
|
return this.plugin.app;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(public plugin: ObsidianLiveSyncPlugin, public core: LiveSyncCore) {
|
constructor(
|
||||||
|
public plugin: ObsidianLiveSyncPlugin,
|
||||||
|
public core: LiveSyncCore
|
||||||
|
) {
|
||||||
super(core);
|
super(core);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
||||||
|
|
||||||
|
|
||||||
_isMainReady() {
|
_isMainReady() {
|
||||||
return this.core.$$isReady();
|
return this.core.$$isReady();
|
||||||
}
|
}
|
||||||
@@ -50,6 +49,6 @@ export abstract class AbstractObsidianModule extends AbstractModule {
|
|||||||
|
|
||||||
//should be overridden
|
//should be overridden
|
||||||
_isThisModuleEnabled() {
|
_isThisModuleEnabled() {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,53 @@
|
|||||||
import type { Prettify } from "../lib/src/common/types";
|
import type { Prettify } from "../lib/src/common/types";
|
||||||
import type { LiveSyncCore } from "../main";
|
import type { LiveSyncCore } from "../main";
|
||||||
|
|
||||||
|
|
||||||
export type OverridableFunctionsKeys<T> = {
|
export type OverridableFunctionsKeys<T> = {
|
||||||
[K in keyof T as K extends `$${string}` ? K : never]: T[K];
|
[K in keyof T as K extends `$${string}` ? K : never]: T[K];
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ChainableExecuteFunction<T> = {
|
export type ChainableExecuteFunction<T> = {
|
||||||
[K in keyof T as K extends `$${string}` ? (T[K] extends (...args: any) => ChainableFunctionResult ? K : never) : never]: T[K];
|
[K in keyof T as K extends `$${string}`
|
||||||
}
|
? T[K] extends (...args: any) => ChainableFunctionResult
|
||||||
|
? K
|
||||||
|
: never
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
export type ICoreModuleBase = OverridableFunctionsKeys<LiveSyncCore>;
|
export type ICoreModuleBase = OverridableFunctionsKeys<LiveSyncCore>;
|
||||||
export type ICoreModule = Prettify<Partial<ICoreModuleBase>>
|
export type ICoreModule = Prettify<Partial<ICoreModuleBase>>;
|
||||||
export type CoreModuleKeys = keyof ICoreModule;
|
export type CoreModuleKeys = keyof ICoreModule;
|
||||||
|
|
||||||
export type ChainableFunctionResult =
|
export type ChainableFunctionResult =
|
||||||
Promise<boolean | undefined | string>
|
| Promise<boolean | undefined | string>
|
||||||
| Promise<boolean | undefined>
|
| Promise<boolean | undefined>
|
||||||
| Promise<boolean>
|
| Promise<boolean>
|
||||||
| Promise<void>;
|
| Promise<void>;
|
||||||
export type ChainableFunctionResultOrAll = Promise<boolean | undefined | string | void>;
|
export type ChainableFunctionResultOrAll = Promise<boolean | undefined | string | void>;
|
||||||
|
|
||||||
type AllExecuteFunction<T> = {
|
type AllExecuteFunction<T> = {
|
||||||
[K in keyof T as K extends `$all${string}` ? (T[K] extends (...args: any[]) => ChainableFunctionResultOrAll ? K : never) : never]: T[K];
|
[K in keyof T as K extends `$all${string}`
|
||||||
}
|
? T[K] extends (...args: any[]) => ChainableFunctionResultOrAll
|
||||||
|
? K
|
||||||
|
: never
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
type EveryExecuteFunction<T> = {
|
type EveryExecuteFunction<T> = {
|
||||||
[K in keyof T as K extends `$every${string}` ? (T[K] extends (...args: any[]) => ChainableFunctionResult ? K : never) : never]: T[K];
|
[K in keyof T as K extends `$every${string}`
|
||||||
}
|
? T[K] extends (...args: any[]) => ChainableFunctionResult
|
||||||
|
? K
|
||||||
|
: never
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
type AnyExecuteFunction<T> = {
|
type AnyExecuteFunction<T> = {
|
||||||
[K in keyof T as K extends `$any${string}` ? (T[K] extends (...args: any[]) => ChainableFunctionResult ? K : never) : never]: T[K];
|
[K in keyof T as K extends `$any${string}`
|
||||||
}
|
? T[K] extends (...args: any[]) => ChainableFunctionResult
|
||||||
|
? K
|
||||||
|
: never
|
||||||
|
: never]: T[K];
|
||||||
|
};
|
||||||
type InjectableFunction<T> = {
|
type InjectableFunction<T> = {
|
||||||
[K in keyof T as K extends `$$${string}` ? (T[K] extends (...args: any[]) => any ? K : never) : never]: T[K];
|
[K in keyof T as K extends `$$${string}` ? (T[K] extends (...args: any[]) => any ? K : never) : never]: T[K];
|
||||||
}
|
};
|
||||||
export type AllExecuteProps = AllExecuteFunction<LiveSyncCore>;
|
export type AllExecuteProps = AllExecuteFunction<LiveSyncCore>;
|
||||||
export type EveryExecuteProps = EveryExecuteFunction<LiveSyncCore>;
|
export type EveryExecuteProps = EveryExecuteFunction<LiveSyncCore>;
|
||||||
export type AnyExecuteProps = AnyExecuteFunction<LiveSyncCore>;
|
export type AnyExecuteProps = AnyExecuteFunction<LiveSyncCore>;
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
|
import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
|
||||||
import { getPathFromUXFileInfo, isInternalMetadata, markChangesAreSame } from "../../common/utils";
|
import { getPathFromUXFileInfo, isInternalMetadata, markChangesAreSame } from "../../common/utils";
|
||||||
import type { UXFileInfoStub, FilePathWithPrefix, UXFileInfo, MetaEntry, LoadedEntry, FilePath, SavingEntry } from "../../lib/src/common/types";
|
import type {
|
||||||
|
UXFileInfoStub,
|
||||||
|
FilePathWithPrefix,
|
||||||
|
UXFileInfo,
|
||||||
|
MetaEntry,
|
||||||
|
LoadedEntry,
|
||||||
|
FilePath,
|
||||||
|
SavingEntry,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
||||||
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { isPlainText, shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
import { isPlainText, shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
||||||
import { createBlob, createTextBlob, delay, determineTypeFromBlob, isDocContentSame, readContent } from "../../lib/src/common/utils";
|
import {
|
||||||
|
createBlob,
|
||||||
|
createTextBlob,
|
||||||
|
delay,
|
||||||
|
determineTypeFromBlob,
|
||||||
|
isDocContentSame,
|
||||||
|
readContent,
|
||||||
|
} from "../../lib/src/common/utils";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
|
|
||||||
|
|
||||||
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
|
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.core.databaseFileAccess = this;
|
this.core.databaseFileAccess = this;
|
||||||
@@ -18,7 +32,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
|
|
||||||
async $everyModuleTest(): Promise<boolean> {
|
async $everyModuleTest(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc"
|
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
|
||||||
// Before test, we need to delete completely.
|
// Before test, we need to delete completely.
|
||||||
const conflicts = await this.getConflictedRevs("autoTest.md" as FilePathWithPrefix);
|
const conflicts = await this.getConflictedRevs("autoTest.md" as FilePathWithPrefix);
|
||||||
for (const rev of conflicts) {
|
for (const rev of conflicts) {
|
||||||
@@ -27,16 +41,21 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
await this.delete("autoTest.md" as FilePathWithPrefix);
|
await this.delete("autoTest.md" as FilePathWithPrefix);
|
||||||
// OK, begin!
|
// OK, begin!
|
||||||
|
|
||||||
await this._test("storeContent", async () => (await this.storeContent("autoTest.md" as FilePathWithPrefix, testString)));
|
await this._test(
|
||||||
|
"storeContent",
|
||||||
|
async () => await this.storeContent("autoTest.md" as FilePathWithPrefix, testString)
|
||||||
|
);
|
||||||
// For test, we need to clear the caches.
|
// For test, we need to clear the caches.
|
||||||
await this.localDatabase.hashCaches.clear();
|
await this.localDatabase.hashCaches.clear();
|
||||||
await this._test("readContent", async () => {
|
await this._test("readContent", async () => {
|
||||||
const content = await this.fetch("autoTest.md" as FilePathWithPrefix);
|
const content = await this.fetch("autoTest.md" as FilePathWithPrefix);
|
||||||
if (!content) return "File not found";
|
if (!content) return "File not found";
|
||||||
if (content.deleted) return "File is deleted";
|
if (content.deleted) return "File is deleted";
|
||||||
return (await content.body.text() == testString) ? true : `Content is not same ${await content.body.text()}`;
|
return (await content.body.text()) == testString
|
||||||
|
? true
|
||||||
|
: `Content is not same ${await content.body.text()}`;
|
||||||
});
|
});
|
||||||
await this._test("delete", async () => (await this.delete("autoTest.md" as FilePathWithPrefix)));
|
await this._test("delete", async () => await this.delete("autoTest.md" as FilePathWithPrefix));
|
||||||
await this._test("read deleted content", async () => {
|
await this._test("read deleted content", async () => {
|
||||||
const content = await this.fetch("autoTest.md" as FilePathWithPrefix);
|
const content = await this.fetch("autoTest.md" as FilePathWithPrefix);
|
||||||
if (!content) return true;
|
if (!content) return true;
|
||||||
@@ -49,7 +68,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
|
|
||||||
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
||||||
const path = getPathFromUXFileInfo(file);
|
const path = getPathFromUXFileInfo(file);
|
||||||
if (!await this.core.$$isTargetFile(path)) {
|
if (!(await this.core.$$isTargetFile(path))) {
|
||||||
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -61,7 +80,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: UXFileInfoStub | FilePathWithPrefix, rev?: string): Promise<boolean> {
|
async delete(file: UXFileInfoStub | FilePathWithPrefix, rev?: string): Promise<boolean> {
|
||||||
if (!await this.checkIsTargetFile(file)) {
|
if (!(await this.checkIsTargetFile(file))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const fullPath = getPathFromUXFileInfo(file);
|
const fullPath = getPathFromUXFileInfo(file);
|
||||||
@@ -95,13 +114,18 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
type: "file",
|
type: "file",
|
||||||
},
|
},
|
||||||
body: blob,
|
body: blob,
|
||||||
}
|
};
|
||||||
return await this._store(dummyUXFileInfo, true, false, false);
|
return await this._store(dummyUXFileInfo, true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean, onlyChunks?: boolean): Promise<boolean> {
|
async _store(
|
||||||
|
file: UXFileInfo,
|
||||||
|
force: boolean = false,
|
||||||
|
skipCheck?: boolean,
|
||||||
|
onlyChunks?: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
if (!skipCheck) {
|
if (!skipCheck) {
|
||||||
if (!await this.checkIsTargetFile(file)) {
|
if (!(await this.checkIsTargetFile(file))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,17 +171,33 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
const oldData = { data: old.data, deleted: old._deleted || old.deleted };
|
const oldData = { data: old.data, deleted: old._deleted || old.deleted };
|
||||||
const newData = { data: d.data, deleted: d._deleted || d.deleted };
|
const newData = { data: d.data, deleted: d._deleted || d.deleted };
|
||||||
if (oldData.deleted != newData.deleted) return false;
|
if (oldData.deleted != newData.deleted) return false;
|
||||||
if (!await isDocContentSame(old.data, newData.data)) return false;
|
if (!(await isDocContentSame(old.data, newData.data))) return false;
|
||||||
this._log(msg + "Skipped (not changed) " + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
msg + "Skipped (not changed) " + fullPath + (d._deleted || d.deleted ? " (deleted)" : ""),
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
markChangesAreSame(old, d.mtime, old.mtime);
|
markChangesAreSame(old, d.mtime, old.mtime);
|
||||||
return true;
|
return true;
|
||||||
// d._rev = old._rev;
|
// d._rev = old._rev;
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (force) {
|
if (force) {
|
||||||
this._log(msg + "Error, Could not check the diff for the old one." + (force ? "force writing." : "") + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
msg +
|
||||||
|
"Error, Could not check the diff for the old one." +
|
||||||
|
(force ? "force writing." : "") +
|
||||||
|
fullPath +
|
||||||
|
(d._deleted || d.deleted ? " (deleted)" : ""),
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this._log(msg + "Error, Could not check the diff for the old one." + fullPath + ((d._deleted || d.deleted) ? " (deleted)" : ""), LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
msg +
|
||||||
|
"Error, Could not check the diff for the old one." +
|
||||||
|
fullPath +
|
||||||
|
(d._deleted || d.deleted ? " (deleted)" : ""),
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return !force;
|
return !force;
|
||||||
@@ -177,7 +217,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getConflictedRevs(file: UXFileInfoStub | FilePathWithPrefix): Promise<string[]> {
|
async getConflictedRevs(file: UXFileInfoStub | FilePathWithPrefix): Promise<string[]> {
|
||||||
if (!await this.checkIsTargetFile(file)) {
|
if (!(await this.checkIsTargetFile(file))) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const filename = getPathFromUXFileInfo(file);
|
const filename = getPathFromUXFileInfo(file);
|
||||||
@@ -188,9 +228,13 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
return doc._conflicts || [];
|
return doc._conflicts || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(file: UXFileInfoStub | FilePathWithPrefix,
|
async fetch(
|
||||||
rev?: string, waitForReady?: boolean, skipCheck = false): Promise<UXFileInfo | false> {
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
if (skipCheck && !await this.checkIsTargetFile(file)) {
|
rev?: string,
|
||||||
|
waitForReady?: boolean,
|
||||||
|
skipCheck = false
|
||||||
|
): Promise<UXFileInfo | false> {
|
||||||
|
if (skipCheck && !(await this.checkIsTargetFile(file))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,39 +254,55 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
},
|
},
|
||||||
body: data,
|
body: data,
|
||||||
deleted: entry.deleted || entry._deleted,
|
deleted: entry.deleted || entry._deleted,
|
||||||
}
|
};
|
||||||
if (isInternalMetadata(entry.path)) {
|
if (isInternalMetadata(entry.path)) {
|
||||||
fileInfo.isInternal = true;
|
fileInfo.isInternal = true;
|
||||||
}
|
}
|
||||||
return fileInfo;
|
return fileInfo;
|
||||||
}
|
}
|
||||||
async fetchEntryMeta(file: UXFileInfoStub | FilePathWithPrefix,
|
async fetchEntryMeta(
|
||||||
rev?: string, skipCheck = false): Promise<MetaEntry | false> {
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
|
rev?: string,
|
||||||
|
skipCheck = false
|
||||||
|
): Promise<MetaEntry | false> {
|
||||||
const filename = getPathFromUXFileInfo(file);
|
const filename = getPathFromUXFileInfo(file);
|
||||||
if (skipCheck && !await this.checkIsTargetFile(file)) {
|
if (skipCheck && !(await this.checkIsTargetFile(file))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = await this.localDatabase.getDBEntryMeta(
|
const doc = await this.localDatabase.getDBEntryMeta(filename, rev ? { rev: rev } : undefined, true);
|
||||||
filename, rev ? { rev: rev } : undefined, true);
|
|
||||||
if (doc === false) {
|
if (doc === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return doc as MetaEntry;
|
return doc as MetaEntry;
|
||||||
}
|
}
|
||||||
async fetchEntryFromMeta(meta: MetaEntry, waitForReady: boolean = true, skipCheck = false): Promise<LoadedEntry | false> {
|
async fetchEntryFromMeta(
|
||||||
if (skipCheck && !await this.checkIsTargetFile(meta.path)) {
|
meta: MetaEntry,
|
||||||
|
waitForReady: boolean = true,
|
||||||
|
skipCheck = false
|
||||||
|
): Promise<LoadedEntry | false> {
|
||||||
|
if (skipCheck && !(await this.checkIsTargetFile(meta.path))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const doc = await this.localDatabase.getDBEntryFromMeta(meta as LoadedEntry, undefined, false, waitForReady, true);
|
const doc = await this.localDatabase.getDBEntryFromMeta(
|
||||||
|
meta as LoadedEntry,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
waitForReady,
|
||||||
|
true
|
||||||
|
);
|
||||||
if (doc === false) {
|
if (doc === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
async fetchEntry(file: UXFileInfoStub | FilePathWithPrefix,
|
async fetchEntry(
|
||||||
rev?: string, waitForReady: boolean = true, skipCheck = false): Promise<LoadedEntry | false> {
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
if (skipCheck && !await this.checkIsTargetFile(file)) {
|
rev?: string,
|
||||||
|
waitForReady: boolean = true,
|
||||||
|
skipCheck = false
|
||||||
|
): Promise<LoadedEntry | false> {
|
||||||
|
if (skipCheck && !(await this.checkIsTargetFile(file))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const entry = await this.fetchEntryMeta(file, rev, true);
|
const entry = await this.fetchEntryMeta(file, rev, true);
|
||||||
@@ -253,7 +313,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
async deleteFromDBbyPath(fullPath: FilePath | FilePathWithPrefix, rev?: string): Promise<boolean> {
|
async deleteFromDBbyPath(fullPath: FilePath | FilePathWithPrefix, rev?: string): Promise<boolean> {
|
||||||
if (!await this.checkIsTargetFile(fullPath)) {
|
if (!(await this.checkIsTargetFile(fullPath))) {
|
||||||
this._log(`storeFromStorage: File is not target: ${fullPath}`);
|
this._log(`storeFromStorage: File is not target: ${fullPath}`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -262,5 +322,4 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
eventHub.emitEvent(EVENT_FILE_SAVED);
|
eventHub.emitEvent(EVENT_FILE_SAVED);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import type { FileEventItem } from "../../common/types";
|
import type { FileEventItem } from "../../common/types";
|
||||||
import type { FilePath, FilePathWithPrefix, MetaEntry, UXFileInfo, UXFileInfoStub, UXInternalFileInfoStub } from "../../lib/src/common/types";
|
import type {
|
||||||
|
FilePath,
|
||||||
|
FilePathWithPrefix,
|
||||||
|
MetaEntry,
|
||||||
|
UXFileInfo,
|
||||||
|
UXFileInfoStub,
|
||||||
|
UXInternalFileInfoStub,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { compareFileFreshness, EVEN, getPath, getPathFromUXFileInfo, getPathWithoutPrefix, markChangesAreSame } from "../../common/utils";
|
import {
|
||||||
|
compareFileFreshness,
|
||||||
|
EVEN,
|
||||||
|
getPath,
|
||||||
|
getPathFromUXFileInfo,
|
||||||
|
getPathWithoutPrefix,
|
||||||
|
markChangesAreSame,
|
||||||
|
} from "../../common/utils";
|
||||||
import { getDocDataAsArray, isDocContentSame, readContent } from "../../lib/src/common/utils";
|
import { getDocDataAsArray, isDocContentSame, readContent } from "../../lib/src/common/utils";
|
||||||
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
import type { ICoreModule } from "../ModuleTypes";
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
|
|
||||||
export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
get db() {
|
get db() {
|
||||||
return this.core.databaseFileAccess;
|
return this.core.databaseFileAccess;
|
||||||
}
|
}
|
||||||
@@ -31,10 +44,14 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
if (!readFile) {
|
if (!readFile) {
|
||||||
throw new Error(`File ${file.path} is not exist on the storage`);
|
throw new Error(`File ${file.path} is not exist on the storage`);
|
||||||
}
|
}
|
||||||
return readFile
|
return readFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force: boolean = false, onlyChunks: boolean = false): Promise<boolean | undefined> {
|
async storeFileToDB(
|
||||||
|
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
|
||||||
|
force: boolean = false,
|
||||||
|
onlyChunks: boolean = false
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -42,10 +59,13 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
// const file = item.args.file;
|
// const file = item.args.file;
|
||||||
if (file.isInternal) {
|
if (file.isInternal) {
|
||||||
this._log(`Internal file ${file.path} is not allowed to be processed on processFileEvent`, LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
`Internal file ${file.path} is not allowed to be processed on processFileEvent`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// First, check the file on the database
|
// First, check the file on the database
|
||||||
const entry = await this.db.fetchEntry(file, undefined, true, true);
|
const entry = await this.db.fetchEntry(file, undefined, true, true);
|
||||||
|
|
||||||
if (!entry || entry.deleted || entry._deleted) {
|
if (!entry || entry.deleted || entry._deleted) {
|
||||||
@@ -114,10 +134,13 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
// const file = item.args.file;
|
// const file = item.args.file;
|
||||||
if (file.isInternal) {
|
if (file.isInternal) {
|
||||||
this._log(`Internal file ${file.path} is not allowed to be processed on processFileEvent`, LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
`Internal file ${file.path} is not allowed to be processed on processFileEvent`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// First, check the file on the database
|
// First, check the file on the database
|
||||||
const entry = await this.db.fetchEntry(file, undefined, true, true);
|
const entry = await this.db.fetchEntry(file, undefined, true, true);
|
||||||
if (!entry || entry.deleted || entry._deleted) {
|
if (!entry || entry.deleted || entry._deleted) {
|
||||||
this._log(`File ${file.path} is not exist or already deleted on the database`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${file.path} is not exist or already deleted on the database`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -135,24 +158,39 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return await this.db.delete(file);
|
return await this.db.delete(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteRevisionFromDB(info: UXFileInfoStub | FilePath | FilePathWithPrefix, rev: string): Promise<boolean | undefined> {
|
async deleteRevisionFromDB(
|
||||||
|
info: UXFileInfoStub | FilePath | FilePathWithPrefix,
|
||||||
|
rev: string
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
//TODO: Possibly check the conflicting.
|
//TODO: Possibly check the conflicting.
|
||||||
return await this.db.delete(info, rev);
|
return await this.db.delete(info, rev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async resolveConflictedByDeletingRevision(
|
||||||
async resolveConflictedByDeletingRevision(info: UXFileInfoStub | FilePath, rev: string): Promise<boolean | undefined> {
|
info: UXFileInfoStub | FilePath,
|
||||||
if (!await this.deleteRevisionFromDB(info, rev)) {
|
rev: string
|
||||||
this._log(`Failed to delete the conflicted revision ${rev} of ${getPathFromUXFileInfo(info)}`, LOG_LEVEL_VERBOSE);
|
): Promise<boolean | undefined> {
|
||||||
|
if (!(await this.deleteRevisionFromDB(info, rev))) {
|
||||||
|
this._log(
|
||||||
|
`Failed to delete the conflicted revision ${rev} of ${getPathFromUXFileInfo(info)}`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!await this.dbToStorageWithSpecificRev(info, rev, true)) {
|
if (!(await this.dbToStorageWithSpecificRev(info, rev, true))) {
|
||||||
this._log(`Failed to apply the resolved revision ${rev} of ${getPathFromUXFileInfo(info)} to the storage`, LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
`Failed to apply the resolved revision ${rev} of ${getPathFromUXFileInfo(info)} to the storage`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async dbToStorageWithSpecificRev(info: UXFileInfoStub | UXFileInfo | FilePath | null, rev: string, force?: boolean): Promise<boolean> {
|
async dbToStorageWithSpecificRev(
|
||||||
|
info: UXFileInfoStub | UXFileInfo | FilePath | null,
|
||||||
|
rev: string,
|
||||||
|
force?: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -166,12 +204,18 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return await this.dbToStorage(docEntry, file, force);
|
return await this.dbToStorage(docEntry, file, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dbToStorage(entryInfo: MetaEntry | FilePathWithPrefix, info: UXFileInfoStub | UXFileInfo | FilePath | null, force?: boolean): Promise<boolean> {
|
async dbToStorage(
|
||||||
|
entryInfo: MetaEntry | FilePathWithPrefix,
|
||||||
|
info: UXFileInfoStub | UXFileInfo | FilePath | null,
|
||||||
|
force?: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
const mode = file == null ? "create" : "modify";
|
const mode = file == null ? "create" : "modify";
|
||||||
|
|
||||||
const docEntry = typeof entryInfo === "string" ?
|
const docEntry =
|
||||||
await this.db.fetchEntryMeta(entryInfo, undefined, true) : await this.db.fetchEntryMeta(entryInfo.path, undefined, true);
|
typeof entryInfo === "string"
|
||||||
|
? await this.db.fetchEntryMeta(entryInfo, undefined, true)
|
||||||
|
: await this.db.fetchEntryMeta(entryInfo.path, undefined, true);
|
||||||
if (!docEntry) {
|
if (!docEntry) {
|
||||||
this._log(`File ${entryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${entryInfo} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
@@ -255,7 +299,10 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
// Let's apply the changes.
|
// Let's apply the changes.
|
||||||
} else {
|
} else {
|
||||||
this._log(`File ${docRead.path} ${existOnStorage ? "(new) " : ""} ${force ? " (forced)" : ""}`, LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
`File ${docRead.path} ${existOnStorage ? "(new) " : ""} ${force ? " (forced)" : ""}`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await this.storage.ensureDir(path);
|
await this.storage.ensureDir(path);
|
||||||
const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });
|
const ret = await this.storage.writeFileAuto(path, docData, { ctime: docRead.ctime, mtime: docRead.mtime });
|
||||||
@@ -268,7 +315,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
const eventItem = item.args;
|
const eventItem = item.args;
|
||||||
const type = item.type;
|
const type = item.type;
|
||||||
const path = eventItem.file.path;
|
const path = eventItem.file.path;
|
||||||
if (!await this.core.$$isTargetFile(path)) {
|
if (!(await this.core.$$isTargetFile(path))) {
|
||||||
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -296,7 +343,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
async $anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean | undefined> {
|
async $anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean | undefined> {
|
||||||
return await serialized(entry.path, async () => {
|
return await serialized(entry.path, async () => {
|
||||||
if (!await this.core.$$isTargetFile(entry.path)) {
|
if (!(await this.core.$$isTargetFile(entry.path))) {
|
||||||
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -312,13 +359,15 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
// Nothing to do and other modules should also nothing to do.
|
// Nothing to do and other modules should also nothing to do.
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this._log(`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`, LOG_LEVEL_VERBOSE);
|
this._log(
|
||||||
|
`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
const ret = await this.dbToStorage(entry, targetFile);
|
const ret = await this.dbToStorage(entry, targetFile);
|
||||||
this._log(`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Done`);
|
this._log(`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Done`);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createAllChunks(showingNotice?: boolean): Promise<void> {
|
async createAllChunks(showingNotice?: boolean): Promise<void> {
|
||||||
@@ -329,11 +378,16 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
const filesStorageSrc = this.storage.getFiles();
|
const filesStorageSrc = this.storage.getFiles();
|
||||||
const incProcessed = () => {
|
const incProcessed = () => {
|
||||||
processed++;
|
processed++;
|
||||||
if (processed % 25 == 0) this._log(`Creating missing chunks: ${processed} of ${total} files`, showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "chunkCreation");
|
if (processed % 25 == 0)
|
||||||
}
|
this._log(
|
||||||
|
`Creating missing chunks: ${processed} of ${total} files`,
|
||||||
|
showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
|
||||||
|
"chunkCreation"
|
||||||
|
);
|
||||||
|
};
|
||||||
const total = filesStorageSrc.length;
|
const total = filesStorageSrc.length;
|
||||||
const procAllChunks = filesStorageSrc.map(async (file) => {
|
const procAllChunks = filesStorageSrc.map(async (file) => {
|
||||||
if (!await this.core.$$isTargetFile(file)) {
|
if (!(await this.core.$$isTargetFile(file))) {
|
||||||
incProcessed();
|
incProcessed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -352,6 +406,10 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
await Promise.all(procAllChunks);
|
await Promise.all(procAllChunks);
|
||||||
this._log(`Creating chunks Done: ${processed} of ${total} files`, showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "chunkCreation");
|
this._log(
|
||||||
|
`Creating chunks Done: ${processed} of ${total} files`,
|
||||||
|
showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
|
||||||
|
"chunkCreation"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICoreModule {
|
export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -23,5 +22,4 @@ export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICore
|
|||||||
$$isDatabaseReady(): boolean {
|
$$isDatabaseReady(): boolean {
|
||||||
return this.localDatabase != null && this.localDatabase.isReady;
|
return this.localDatabase != null && this.localDatabase.isReady;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { AbstractModule } from "../AbstractModule";
|
|||||||
import type { ICoreModule } from "../ModuleTypes";
|
import type { ICoreModule } from "../ModuleTypes";
|
||||||
|
|
||||||
export class ModulePeriodicProcess extends AbstractModule implements ICoreModule {
|
export class ModulePeriodicProcess extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.core.$$replicate());
|
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.core.$$replicate());
|
||||||
|
|
||||||
_disablePeriodic() {
|
_disablePeriodic() {
|
||||||
@@ -11,20 +10,19 @@ export class ModulePeriodicProcess extends AbstractModule implements ICoreModule
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
_resumePeriodic() {
|
_resumePeriodic() {
|
||||||
this.periodicSyncProcessor.enable(this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0);
|
this.periodicSyncProcessor.enable(
|
||||||
|
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
|
||||||
|
);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnUnload() {
|
$allOnUnload() {
|
||||||
return this._disablePeriodic();
|
return this._disablePeriodic();
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyBeforeRealizeSetting(): Promise<boolean> {
|
$everyBeforeRealizeSetting(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this._disablePeriodic();
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
$everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this._disablePeriodic();
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
$everyAfterResumeProcess(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this._resumePeriodic();
|
||||||
@@ -32,4 +30,4 @@ export class ModulePeriodicProcess extends AbstractModule implements ICoreModule
|
|||||||
$everyAfterRealizeSetting(): Promise<boolean> {
|
$everyAfterRealizeSetting(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this._resumePeriodic();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import type { ICoreModule } from "../ModuleTypes";
|
|||||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
|
|
||||||
export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
||||||
$$createPouchDBInstance<T extends object>(name?: string, options?: PouchDB.Configuration.DatabaseConfiguration): PouchDB.Database<T> {
|
$$createPouchDBInstance<T extends object>(
|
||||||
|
name?: string,
|
||||||
|
options?: PouchDB.Configuration.DatabaseConfiguration
|
||||||
|
): PouchDB.Database<T> {
|
||||||
const optionPass = options ?? {};
|
const optionPass = options ?? {};
|
||||||
if (this.settings.useIndexedDBAdapter) {
|
if (this.settings.useIndexedDBAdapter) {
|
||||||
optionPass.adapter = "indexeddb";
|
optionPass.adapter = "indexeddb";
|
||||||
@@ -13,4 +16,4 @@ export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return new PouchDB(name, optionPass);
|
return new PouchDB(name, optionPass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
import { delay } from "octagonal-wheels/promises";
|
||||||
import { FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3_HR, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, REMOTE_COUCHDB, REMOTE_MINIO } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
FLAGMD_REDFLAG2_HR,
|
||||||
|
FLAGMD_REDFLAG3_HR,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
REMOTE_COUCHDB,
|
||||||
|
REMOTE_MINIO,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
@@ -7,12 +14,13 @@ import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchd
|
|||||||
import { fetchAllUsedChunks } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
import { fetchAllUsedChunks } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
|
|
||||||
export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebuilder {
|
export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebuilder {
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.core.rebuilder = this;
|
this.core.rebuilder = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async $performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void> {
|
async $performRebuildDB(
|
||||||
|
method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"
|
||||||
|
): Promise<void> {
|
||||||
if (method == "localOnly") {
|
if (method == "localOnly") {
|
||||||
await this.$fetchLocal();
|
await this.$fetchLocal();
|
||||||
}
|
}
|
||||||
@@ -27,11 +35,13 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async askUsingOptionalFeature(opt: {
|
async askUsingOptionalFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
||||||
enableFetch?: boolean;
|
if (
|
||||||
enableOverwrite?: boolean;
|
(await this.core.confirm.askYesNoDialog(
|
||||||
}) {
|
"Do you want to enable extra features? If you are new to Self-hosted LiveSync, try the core feature first!",
|
||||||
if (await this.core.confirm.askYesNoDialog("Do you want to enable extra features? If you are new to Self-hosted LiveSync, try the core feature first!", { title: "Enable extra features", defaultOption: "No", timeout: 15 }) == "yes") {
|
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
|
||||||
|
)) == "yes"
|
||||||
|
) {
|
||||||
await this.core.$allAskUsingOptionalSyncFeature(opt);
|
await this.core.$allAskUsingOptionalSyncFeature(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +65,6 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
return this.rebuildRemote();
|
return this.rebuildRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async rebuildEverything() {
|
async rebuildEverything() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.core.$allSuspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
@@ -73,7 +82,6 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.core.$$replicateAllToServer(true);
|
await this.core.$$replicateAllToServer(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true, true);
|
await this.core.$$replicateAllToServer(true, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$rebuildEverything(): Promise<void> {
|
$rebuildEverything(): Promise<void> {
|
||||||
@@ -116,7 +124,6 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.localDatabase.resetDatabase();
|
await this.localDatabase.resetDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async suspendAllSync() {
|
async suspendAllSync() {
|
||||||
this.core.settings.liveSync = false;
|
this.core.settings.liveSync = false;
|
||||||
this.core.settings.periodicReplication = false;
|
this.core.settings.periodicReplication = false;
|
||||||
@@ -130,7 +137,10 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
async suspendReflectingDatabase() {
|
async suspendReflectingDatabase() {
|
||||||
if (this.core.settings.doNotSuspendOnFetching) return;
|
if (this.core.settings.doNotSuspendOnFetching) return;
|
||||||
if (this.core.settings.remoteType == REMOTE_MINIO) return;
|
if (this.core.settings.remoteType == REMOTE_MINIO) return;
|
||||||
this._log(`Suspending reflection: Database and storage changes will not be reflected in each other until completely finished the fetching.`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`Suspending reflection: Database and storage changes will not be reflected in each other until completely finished the fetching.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
this.core.settings.suspendParseReplicationResult = true;
|
this.core.settings.suspendParseReplicationResult = true;
|
||||||
this.core.settings.suspendFileWatching = true;
|
this.core.settings.suspendFileWatching = true;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
@@ -144,7 +154,6 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.core.$$performFullScan(true);
|
await this.core.$$performFullScan(true);
|
||||||
await this.core.$everyBeforeReplicate(false); //TODO: Check actual need of this.
|
await this.core.$everyBeforeReplicate(false); //TODO: Check actual need of this.
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
async askUseNewAdapter() {
|
async askUseNewAdapter() {
|
||||||
if (!this.core.settings.useIndexedDBAdapter) {
|
if (!this.core.settings.useIndexedDBAdapter) {
|
||||||
@@ -153,7 +162,13 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
const CHOICE_NO = "No, keep compatibility";
|
const CHOICE_NO = "No, keep compatibility";
|
||||||
const choices = [CHOICE_YES, CHOICE_NO];
|
const choices = [CHOICE_YES, CHOICE_NO];
|
||||||
|
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Database adapter", message, choices, CHOICE_YES, 10);
|
const ret = await this.core.confirm.confirmWithMessage(
|
||||||
|
"Database adapter",
|
||||||
|
message,
|
||||||
|
choices,
|
||||||
|
CHOICE_YES,
|
||||||
|
10
|
||||||
|
);
|
||||||
if (ret == CHOICE_YES) {
|
if (ret == CHOICE_YES) {
|
||||||
this.core.settings.useIndexedDBAdapter = true;
|
this.core.settings.useIndexedDBAdapter = true;
|
||||||
}
|
}
|
||||||
@@ -200,10 +215,18 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
await this.core.$$resetLocalDatabase();
|
await this.core.$$resetLocalDatabase();
|
||||||
}
|
}
|
||||||
async fetchRemoteChunks() {
|
async fetchRemoteChunks() {
|
||||||
if (!this.core.settings.doNotSuspendOnFetching && this.core.settings.readChunksOnline && this.core.settings.remoteType == REMOTE_COUCHDB) {
|
if (
|
||||||
|
!this.core.settings.doNotSuspendOnFetching &&
|
||||||
|
this.core.settings.readChunksOnline &&
|
||||||
|
this.core.settings.remoteType == REMOTE_COUCHDB
|
||||||
|
) {
|
||||||
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
||||||
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
|
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.$$isMobile(), true);
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
|
this.settings,
|
||||||
|
this.core.$$isMobile(),
|
||||||
|
true
|
||||||
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
this._log(remoteDB, LOG_LEVEL_NOTICE);
|
this._log(remoteDB, LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
@@ -218,9 +241,14 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (i++ % 10) this._log(`Check and Processing ${i} / ${files.length}`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
if (i++ % 10)
|
||||||
|
this._log(
|
||||||
|
`Check and Processing ${i} / ${files.length}`,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
"resolveAllConflictedFilesByNewerOnes"
|
||||||
|
);
|
||||||
await this.core.$anyResolveConflictByNewest(file);
|
await this.core.$anyResolveConflictByNewest(file);
|
||||||
}
|
}
|
||||||
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ import { purgeUnreferencedChunks, balanceChunkPurgedDBs } from "../../lib/src/po
|
|||||||
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
||||||
import { throttle } from "octagonal-wheels/function";
|
import { throttle } from "octagonal-wheels/function";
|
||||||
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||||
import { SYNCINFO_ID, VER, type EntryBody, type EntryDoc, type LoadedEntry, type MetaEntry } from "../../lib/src/common/types";
|
import {
|
||||||
|
SYNCINFO_ID,
|
||||||
|
VER,
|
||||||
|
type EntryBody,
|
||||||
|
type EntryDoc,
|
||||||
|
type LoadedEntry,
|
||||||
|
type MetaEntry,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils";
|
import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils";
|
||||||
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
||||||
@@ -17,13 +24,12 @@ import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
|
|||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
|
|
||||||
export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
eventHub.onEvent(EVENT_FILE_SAVED, () => {
|
||||||
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
|
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
|
||||||
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce());
|
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce());
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +37,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
const replicator = await this.core.$anyNewReplicator();
|
const replicator = await this.core.$anyNewReplicator();
|
||||||
if (!replicator) {
|
if (!replicator) {
|
||||||
this._log("No replicator is available, this is the fatal error.", LOG_LEVEL_NOTICE);
|
this._log("No replicator is available, this is the fatal error.", LOG_LEVEL_NOTICE);
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
this.core.replicator = replicator;
|
this.core.replicator = replicator;
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
@@ -48,7 +54,6 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.setReplicator();
|
return this.setReplicator();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
@@ -56,7 +61,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
//--?
|
//--?
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.core.$$isReady()) return;
|
||||||
if (isLockAcquired("cleanup")) {
|
if (isLockAcquired("cleanup")) {
|
||||||
Logger("Database cleaning up is in process. replication has been cancelled", LOG_LEVEL_NOTICE);
|
Logger("Database cleaning up is in process. replication has been cancelled", LOG_LEVEL_NOTICE);
|
||||||
@@ -66,11 +71,11 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
Logger("Open settings and check message, please. replication has been cancelled.", LOG_LEVEL_NOTICE);
|
Logger("Open settings and check message, please. replication has been cancelled.", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!await this.core.$everyCommitPendingFileEvent()) {
|
if (!(await this.core.$everyCommitPendingFileEvent())) {
|
||||||
Logger("Some file events are pending. Replication has been cancelled.", LOG_LEVEL_NOTICE);
|
Logger("Some file events are pending. Replication has been cancelled.", LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!await this.core.$everyBeforeReplicate(showMessage)) {
|
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
|
||||||
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -80,32 +85,41 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
if (!ret) {
|
if (!ret) {
|
||||||
if (this.core.replicator.tweakSettingsMismatched) {
|
if (this.core.replicator.tweakSettingsMismatched) {
|
||||||
await this.core.$$askResolvingMismatchedTweaks();
|
await this.core.$$askResolvingMismatchedTweaks();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
||||||
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
||||||
Logger(`The remote database has been cleaned.`, showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
Logger(
|
||||||
|
`The remote database has been cleaned.`,
|
||||||
|
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
await skipIfDuplicated("cleanup", async () => {
|
await skipIfDuplicated("cleanup", async () => {
|
||||||
const count = await purgeUnreferencedChunks(this.localDatabase.localDatabase, true);
|
const count = await purgeUnreferencedChunks(this.localDatabase.localDatabase, true);
|
||||||
const message = `The remote database has been cleaned up.
|
const message = `The remote database has been cleaned up.
|
||||||
To synchronize, this device must be also cleaned up. ${count} chunk(s) will be erased from this device.
|
To synchronize, this device must be also cleaned up. ${count} chunk(s) will be erased from this device.
|
||||||
However, If there are many chunks to be deleted, maybe fetching again is faster.
|
However, If there are many chunks to be deleted, maybe fetching again is faster.
|
||||||
We will lose the history of this device if we fetch the remote database again.
|
We will lose the history of this device if we fetch the remote database again.
|
||||||
Even if you choose to clean up, you will see this option again if you exit Obsidian and then synchronise again.`
|
Even if you choose to clean up, you will see this option again if you exit Obsidian and then synchronise again.`;
|
||||||
const CHOICE_FETCH = "Fetch again";
|
const CHOICE_FETCH = "Fetch again";
|
||||||
const CHOICE_CLEAN = "Cleanup";
|
const CHOICE_CLEAN = "Cleanup";
|
||||||
const CHOICE_DISMISS = "Dismiss";
|
const CHOICE_DISMISS = "Dismiss";
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Cleaned", message, [
|
const ret = await this.core.confirm.confirmWithMessage(
|
||||||
CHOICE_FETCH,
|
"Cleaned",
|
||||||
CHOICE_CLEAN,
|
message,
|
||||||
CHOICE_DISMISS], CHOICE_DISMISS, 30);
|
[CHOICE_FETCH, CHOICE_CLEAN, CHOICE_DISMISS],
|
||||||
|
CHOICE_DISMISS,
|
||||||
|
30
|
||||||
|
);
|
||||||
if (ret == CHOICE_FETCH) {
|
if (ret == CHOICE_FETCH) {
|
||||||
await this.core.rebuilder.$performRebuildDB("localOnly");
|
await this.core.rebuilder.$performRebuildDB("localOnly");
|
||||||
}
|
}
|
||||||
if (ret == CHOICE_CLEAN) {
|
if (ret == CHOICE_CLEAN) {
|
||||||
const replicator = this.core.$$getReplicator();
|
const replicator = this.core.$$getReplicator();
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(this.settings, this.core.$$isMobile(), true);
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
|
this.settings,
|
||||||
|
this.core.$$isMobile(),
|
||||||
|
true
|
||||||
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
Logger(remoteDB, LOG_LEVEL_NOTICE);
|
Logger(remoteDB, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -114,16 +128,23 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||||
this.localDatabase.hashCaches.clear();
|
this.localDatabase.hashCaches.clear();
|
||||||
// Perform the synchronisation once.
|
// Perform the synchronisation once.
|
||||||
if (await this.core.replicator.openReplication(this.settings, false, showMessage, true)) {
|
if (
|
||||||
|
await this.core.replicator.openReplication(this.settings, false, showMessage, true)
|
||||||
|
) {
|
||||||
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
||||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||||
this.localDatabase.hashCaches.clear();
|
this.localDatabase.hashCaches.clear();
|
||||||
await this.core.$$getReplicator().markRemoteResolved(this.settings);
|
await this.core.$$getReplicator().markRemoteResolved(this.settings);
|
||||||
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO)
|
Logger(
|
||||||
|
"The local database has been cleaned up.",
|
||||||
|
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Logger("Replication has been cancelled. Please try it again.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO)
|
Logger(
|
||||||
|
"Replication has been cancelled. Please try it again.",
|
||||||
|
showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -131,23 +152,30 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
The remote database has been rebuilt.
|
The remote database has been rebuilt.
|
||||||
To synchronize, this device must fetch everything again once.
|
To synchronize, this device must fetch everything again once.
|
||||||
Or if you are sure know what had been happened, we can unlock the database from the setting dialog.
|
Or if you are sure know what had been happened, we can unlock the database from the setting dialog.
|
||||||
`
|
`;
|
||||||
const CHOICE_FETCH = "Fetch again";
|
const CHOICE_FETCH = "Fetch again";
|
||||||
const CHOICE_DISMISS = "Dismiss";
|
const CHOICE_DISMISS = "Dismiss";
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Locked", message, [
|
const ret = await this.core.confirm.confirmWithMessage(
|
||||||
CHOICE_FETCH,
|
"Locked",
|
||||||
CHOICE_DISMISS], CHOICE_DISMISS, 10);
|
message,
|
||||||
|
[CHOICE_FETCH, CHOICE_DISMISS],
|
||||||
|
CHOICE_DISMISS,
|
||||||
|
10
|
||||||
|
);
|
||||||
if (ret == CHOICE_FETCH) {
|
if (ret == CHOICE_FETCH) {
|
||||||
const CHOICE_RESTART = "Restart";
|
const CHOICE_RESTART = "Restart";
|
||||||
const CHOICE_WITHOUT_RESTART = "Without restart";
|
const CHOICE_WITHOUT_RESTART = "Without restart";
|
||||||
if (await this.core.confirm.askSelectStringDialogue(
|
if (
|
||||||
"Self-hosted LiveSync restarts Obsidian to fetch everything safely. However, you can do it without restarting. Please choose one.",
|
(await this.core.confirm.askSelectStringDialogue(
|
||||||
[CHOICE_RESTART, CHOICE_WITHOUT_RESTART], {
|
"Self-hosted LiveSync restarts Obsidian to fetch everything safely. However, you can do it without restarting. Please choose one.",
|
||||||
title: "Fetch again",
|
[CHOICE_RESTART, CHOICE_WITHOUT_RESTART],
|
||||||
defaultAction: CHOICE_RESTART,
|
{
|
||||||
timeout: 30,
|
title: "Fetch again",
|
||||||
}
|
defaultAction: CHOICE_RESTART,
|
||||||
) == CHOICE_RESTART) {
|
timeout: 30,
|
||||||
|
}
|
||||||
|
)) == CHOICE_RESTART
|
||||||
|
) {
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
// await this.core.$$scheduleAppReload();
|
// await this.core.$$scheduleAppReload();
|
||||||
return;
|
return;
|
||||||
@@ -165,16 +193,18 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
|
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
||||||
this.replicationResultProcessor.suspend()
|
this.replicationResultProcessor.suspend();
|
||||||
}
|
}
|
||||||
this.replicationResultProcessor.enqueueAll(docs);
|
this.replicationResultProcessor.enqueueAll(docs);
|
||||||
if (!this.settings.suspendParseReplicationResult && this.replicationResultProcessor.isSuspended) {
|
if (!this.settings.suspendParseReplicationResult && this.replicationResultProcessor.isSuspended) {
|
||||||
this.replicationResultProcessor.resume()
|
this.replicationResultProcessor.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_saveQueuedFiles = throttle(() => {
|
_saveQueuedFiles = throttle(() => {
|
||||||
const saveData = this.replicationResultProcessor._queue.filter(e => e !== undefined && e !== null).map((e) => e?._id ?? "" as string) as string[];
|
const saveData = this.replicationResultProcessor._queue
|
||||||
const kvDBKey = "queued-files"
|
.filter((e) => e !== undefined && e !== null)
|
||||||
|
.map((e) => e?._id ?? ("" as string)) as string[];
|
||||||
|
const kvDBKey = "queued-files";
|
||||||
// localStorage.setItem(lsKey, saveData);
|
// localStorage.setItem(lsKey, saveData);
|
||||||
fireAndForget(() => this.core.kvDB.set(kvDBKey, saveData));
|
fireAndForget(() => this.core.kvDB.set(kvDBKey, saveData));
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -184,19 +214,19 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
async loadQueuedFiles() {
|
async loadQueuedFiles() {
|
||||||
if (this.settings.suspendParseReplicationResult) return;
|
if (this.settings.suspendParseReplicationResult) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
const kvDBKey = "queued-files"
|
const kvDBKey = "queued-files";
|
||||||
// const ids = [...new Set(JSON.parse(localStorage.getItem(lsKey) || "[]"))] as string[];
|
// const ids = [...new Set(JSON.parse(localStorage.getItem(lsKey) || "[]"))] as string[];
|
||||||
const ids = [...new Set(await this.core.kvDB.get<string[]>(kvDBKey) ?? [])];
|
const ids = [...new Set((await this.core.kvDB.get<string[]>(kvDBKey)) ?? [])];
|
||||||
const batchSize = 100;
|
const batchSize = 100;
|
||||||
const chunkedIds = arrayToChunkedArray(ids, batchSize);
|
const chunkedIds = arrayToChunkedArray(ids, batchSize);
|
||||||
for await (const idsBatch of chunkedIds) {
|
for await (const idsBatch of chunkedIds) {
|
||||||
const ret = await this.localDatabase.allDocsRaw<EntryDoc>({
|
const ret = await this.localDatabase.allDocsRaw<EntryDoc>({
|
||||||
keys: idsBatch,
|
keys: idsBatch,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
limit: 100
|
limit: 100,
|
||||||
});
|
});
|
||||||
const docs = ret.rows.filter(e => e.doc).map(e => e.doc) as PouchDB.Core.ExistingDocument<EntryDoc>[];
|
const docs = ret.rows.filter((e) => e.doc).map((e) => e.doc) as PouchDB.Core.ExistingDocument<EntryDoc>[];
|
||||||
const errors = ret.rows.filter(e => !e.doc && !e.value.deleted);
|
const errors = ret.rows.filter((e) => !e.doc && !e.value.deleted);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
Logger("Some queued processes were not resurrected");
|
Logger("Some queued processes were not resurrected");
|
||||||
Logger(JSON.stringify(errors), LOG_LEVEL_VERBOSE);
|
Logger(JSON.stringify(errors), LOG_LEVEL_VERBOSE);
|
||||||
@@ -204,131 +234,163 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
this.replicationResultProcessor.enqueueAll(docs);
|
this.replicationResultProcessor.enqueueAll(docs);
|
||||||
await this.replicationResultProcessor.waitForAllProcessed();
|
await this.replicationResultProcessor.waitForAllProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
replicationResultProcessor = new QueueProcessor(async (docs: PouchDB.Core.ExistingDocument<EntryDoc>[]) => {
|
replicationResultProcessor = new QueueProcessor(
|
||||||
if (this.settings.suspendParseReplicationResult) return;
|
async (docs: PouchDB.Core.ExistingDocument<EntryDoc>[]) => {
|
||||||
const change = docs[0];
|
if (this.settings.suspendParseReplicationResult) return;
|
||||||
if (!change) return;
|
const change = docs[0];
|
||||||
if (isChunk(change._id)) {
|
if (!change) return;
|
||||||
// SendSignal?
|
if (isChunk(change._id)) {
|
||||||
// this.parseIncomingChunk(change);
|
// SendSignal?
|
||||||
sendValue(`leaf-${change._id}`, change);
|
// this.parseIncomingChunk(change);
|
||||||
return;
|
sendValue(`leaf-${change._id}`, change);
|
||||||
}
|
|
||||||
if (await this.core.$anyModuleParsedReplicationResultItem(change)) return;
|
|
||||||
// any addon needs this item?
|
|
||||||
// for (const proc of this.core.addOns) {
|
|
||||||
// if (await proc.parseReplicationResultItem(change)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (change.type == "versioninfo") {
|
|
||||||
if (change.version > VER) {
|
|
||||||
this.core.replicator.closeReplication();
|
|
||||||
Logger(`Remote database updated to incompatible version. update your Self-hosted LiveSync plugin.`, LOG_LEVEL_NOTICE);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (change._id == SYNCINFO_ID || // Synchronisation information data
|
|
||||||
change._id.startsWith("_design") //design document
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isAnyNote(change)) {
|
|
||||||
const docPath = getPath(change);
|
|
||||||
if (!await this.core.$$isTargetFile(docPath)) {
|
|
||||||
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.databaseQueuedProcessor._isSuspended) {
|
if (await this.core.$anyModuleParsedReplicationResultItem(change)) return;
|
||||||
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
|
// any addon needs this item?
|
||||||
}
|
// for (const proc of this.core.addOns) {
|
||||||
const size = change.size;
|
// if (await proc.parseReplicationResultItem(change)) {
|
||||||
if (this.core.$$isFileSizeExceeded(size)) {
|
// return;
|
||||||
Logger(`Processing ${docPath} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
// }
|
||||||
|
// }
|
||||||
|
if (change.type == "versioninfo") {
|
||||||
|
if (change.version > VER) {
|
||||||
|
this.core.replicator.closeReplication();
|
||||||
|
Logger(
|
||||||
|
`Remote database updated to incompatible version. update your Self-hosted LiveSync plugin.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.databaseQueuedProcessor.enqueue(change);
|
if (
|
||||||
}
|
change._id == SYNCINFO_ID || // Synchronisation information data
|
||||||
return;
|
change._id.startsWith("_design") //design document
|
||||||
}, {
|
) {
|
||||||
batchSize: 1,
|
return;
|
||||||
suspended: true,
|
}
|
||||||
concurrentLimit: 100,
|
if (isAnyNote(change)) {
|
||||||
delay: 0,
|
const docPath = getPath(change);
|
||||||
totalRemainingReactiveSource: this.core.replicationResultCount
|
if (!(await this.core.$$isTargetFile(docPath))) {
|
||||||
}).replaceEnqueueProcessor((queue, newItem) => {
|
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
||||||
const q = queue.filter(e => e._id != newItem._id);
|
return;
|
||||||
return [...q, newItem];
|
}
|
||||||
}).startPipeline().onUpdateProgress(() => {
|
if (this.databaseQueuedProcessor._isSuspended) {
|
||||||
this.saveQueuedFiles();
|
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
|
||||||
});
|
}
|
||||||
|
const size = change.size;
|
||||||
databaseQueuedProcessor = new QueueProcessor(async (docs: EntryBody[]) => {
|
if (this.core.$$isFileSizeExceeded(size)) {
|
||||||
const dbDoc = docs[0] as LoadedEntry; // It has no `data`
|
Logger(
|
||||||
const path = getPath(dbDoc);
|
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
);
|
||||||
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
return;
|
||||||
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, {}, false, true, true);
|
}
|
||||||
if (!doc) {
|
this.databaseQueuedProcessor.enqueue(change);
|
||||||
Logger(`Something went wrong while gathering content of ${path} (${dbDoc._id.substring(0, 8)}, ${dbDoc._rev?.substring(0, 10)}) `, LOG_LEVEL_NOTICE)
|
}
|
||||||
return;
|
return;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
batchSize: 1,
|
||||||
|
suspended: true,
|
||||||
|
concurrentLimit: 100,
|
||||||
|
delay: 0,
|
||||||
|
totalRemainingReactiveSource: this.core.replicationResultCount,
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.replaceEnqueueProcessor((queue, newItem) => {
|
||||||
|
const q = queue.filter((e) => e._id != newItem._id);
|
||||||
|
return [...q, newItem];
|
||||||
|
})
|
||||||
|
.startPipeline()
|
||||||
|
.onUpdateProgress(() => {
|
||||||
|
this.saveQueuedFiles();
|
||||||
|
});
|
||||||
|
|
||||||
if (await this.core.$anyProcessOptionalSyncFiles(dbDoc)) {
|
databaseQueuedProcessor = new QueueProcessor(
|
||||||
// Already processed
|
async (docs: EntryBody[]) => {
|
||||||
} else if (isValidPath(getPath(doc))) {
|
const dbDoc = docs[0] as LoadedEntry; // It has no `data`
|
||||||
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
const path = getPath(dbDoc);
|
||||||
} else {
|
|
||||||
Logger(`Skipped: ${doc._id.substring(0, 8)}`, LOG_LEVEL_VERBOSE);
|
// If `Read chunks online` is disabled, chunks should be transferred before here.
|
||||||
|
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
|
||||||
|
const doc = await this.localDatabase.getDBEntryFromMeta({ ...dbDoc }, {}, false, true, true);
|
||||||
|
if (!doc) {
|
||||||
|
Logger(
|
||||||
|
`Something went wrong while gathering content of ${path} (${dbDoc._id.substring(0, 8)}, ${dbDoc._rev?.substring(0, 10)}) `,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.core.$anyProcessOptionalSyncFiles(dbDoc)) {
|
||||||
|
// Already processed
|
||||||
|
} else if (isValidPath(getPath(doc))) {
|
||||||
|
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
||||||
|
} else {
|
||||||
|
Logger(`Skipped: ${doc._id.substring(0, 8)}`, LOG_LEVEL_VERBOSE);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
suspended: true,
|
||||||
|
batchSize: 1,
|
||||||
|
concurrentLimit: 10,
|
||||||
|
yieldThreshold: 1,
|
||||||
|
delay: 0,
|
||||||
|
totalRemainingReactiveSource: this.core.databaseQueueCount,
|
||||||
}
|
}
|
||||||
return;
|
)
|
||||||
}, {
|
.replaceEnqueueProcessor((queue, newItem) => {
|
||||||
suspended: true,
|
const q = queue.filter((e) => e._id != newItem._id);
|
||||||
batchSize: 1,
|
return [...q, newItem];
|
||||||
concurrentLimit: 10,
|
})
|
||||||
yieldThreshold: 1,
|
.startPipeline();
|
||||||
delay: 0,
|
|
||||||
totalRemainingReactiveSource: this.core.databaseQueueCount
|
|
||||||
}).replaceEnqueueProcessor((queue, newItem) => {
|
|
||||||
const q = queue.filter(e => e._id != newItem._id);
|
|
||||||
return [...q, newItem];
|
|
||||||
}).startPipeline();
|
|
||||||
|
|
||||||
|
storageApplyingProcessor = new QueueProcessor(
|
||||||
storageApplyingProcessor = new QueueProcessor(async (docs: MetaEntry[]) => {
|
async (docs: MetaEntry[]) => {
|
||||||
const entry = docs[0];
|
const entry = docs[0];
|
||||||
await this.core.$anyProcessReplicatedDoc(entry);
|
await this.core.$anyProcessReplicatedDoc(entry);
|
||||||
return;
|
return;
|
||||||
}, {
|
},
|
||||||
suspended: true,
|
{
|
||||||
batchSize: 1,
|
suspended: true,
|
||||||
concurrentLimit: 6,
|
batchSize: 1,
|
||||||
yieldThreshold: 1,
|
concurrentLimit: 6,
|
||||||
delay: 0,
|
yieldThreshold: 1,
|
||||||
totalRemainingReactiveSource: this.core.storageApplyingCount
|
delay: 0,
|
||||||
}).replaceEnqueueProcessor((queue, newItem) => {
|
totalRemainingReactiveSource: this.core.storageApplyingCount,
|
||||||
const q = queue.filter(e => e._id != newItem._id);
|
}
|
||||||
return [...q, newItem];
|
)
|
||||||
}).startPipeline()
|
.replaceEnqueueProcessor((queue, newItem) => {
|
||||||
|
const q = queue.filter((e) => e._id != newItem._id);
|
||||||
|
return [...q, newItem];
|
||||||
|
})
|
||||||
|
.startPipeline();
|
||||||
|
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
$everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
this.core.replicator.closeReplication();
|
this.core.replicator.closeReplication();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateAllToServer(showingNotice: boolean = false, sendChunksInBulkDisabled: boolean = false): Promise<boolean> {
|
async $$replicateAllToServer(
|
||||||
|
showingNotice: boolean = false,
|
||||||
|
sendChunksInBulkDisabled: boolean = false
|
||||||
|
): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return false;
|
if (!this.core.$$isReady()) return false;
|
||||||
if (!await this.core.$everyBeforeReplicate(showingNotice)) {
|
if (!(await this.core.$everyBeforeReplicate(showingNotice))) {
|
||||||
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
Logger(`Replication has been cancelled by some module failure`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!sendChunksInBulkDisabled) {
|
if (!sendChunksInBulkDisabled) {
|
||||||
if (this.core.replicator instanceof LiveSyncCouchDBReplicator) {
|
if (this.core.replicator instanceof LiveSyncCouchDBReplicator) {
|
||||||
if (await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", { defaultOption: "No", timeout: 20 }) == "yes") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Do you want to send all chunks before replication?", {
|
||||||
|
defaultOption: "No",
|
||||||
|
timeout: 20,
|
||||||
|
})) == "yes"
|
||||||
|
) {
|
||||||
await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0);
|
await this.core.replicator.sendChunks(this.core.settings, undefined, true, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +413,4 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
async $$waitForReplicationOnce(): Promise<boolean | void> {
|
async $$waitForReplicationOnce(): Promise<boolean | void> {
|
||||||
return await shareRunningResult(`replication`, () => this.core.$$replicate());
|
return await shareRunningResult(`replication`, () => this.core.$$replicate());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
|
|||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
||||||
if (settings.remoteType == REMOTE_MINIO) {
|
if (settings.remoteType == REMOTE_MINIO) {
|
||||||
return undefined!
|
return undefined!;
|
||||||
}
|
}
|
||||||
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
$everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (this.settings.remoteType != REMOTE_MINIO) {
|
if (this.settings.remoteType != REMOTE_MINIO) {
|
||||||
@@ -30,4 +29,4 @@ export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModu
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,4 @@ export class ModuleReplicatorMinIO extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
return undefined!;
|
return undefined!;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import { LRUCache } from "octagonal-wheels/memory/LRUCache";
|
import { LRUCache } from "octagonal-wheels/memory/LRUCache";
|
||||||
import { getPathFromUXFileInfo, id2path, isInternalMetadata, path2id, stripInternalMetadataPrefix, useMemo } from "../../common/utils";
|
import {
|
||||||
import { LOG_LEVEL_VERBOSE, type DocumentID, type EntryHasPath, type FilePath, type FilePathWithPrefix, type ObsidianLiveSyncSettings, type UXFileInfoStub } from "../../lib/src/common/types";
|
getPathFromUXFileInfo,
|
||||||
|
id2path,
|
||||||
|
isInternalMetadata,
|
||||||
|
path2id,
|
||||||
|
stripInternalMetadataPrefix,
|
||||||
|
useMemo,
|
||||||
|
} from "../../common/utils";
|
||||||
|
import {
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type DocumentID,
|
||||||
|
type EntryHasPath,
|
||||||
|
type FilePath,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { addPrefix, isAcceptedAll, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
import { addPrefix, isAcceptedAll, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
import type { ICoreModule } from "../ModuleTypes";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
import { isDirty } from "../../lib/src/common/utils";
|
import { isDirty } from "../../lib/src/common/utils";
|
||||||
export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
reloadIgnoreFiles() {
|
reloadIgnoreFiles() {
|
||||||
this.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
||||||
@@ -32,10 +45,13 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
async $$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
async $$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
const destPath = addPrefix(filename, prefix ?? "");
|
const destPath = addPrefix(filename, prefix ?? "");
|
||||||
return await path2id(destPath, this.settings.usePathObfuscation ? this.settings.passphrase : "", !this.settings.handleFilenameCaseSensitive);
|
return await path2id(
|
||||||
|
destPath,
|
||||||
|
this.settings.usePathObfuscation ? this.settings.passphrase : "",
|
||||||
|
!this.settings.handleFilenameCaseSensitive
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$$isFileSizeExceeded(size: number) {
|
$$isFileSizeExceeded(size: number) {
|
||||||
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
||||||
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
||||||
@@ -45,7 +61,6 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void {
|
$$markFileListPossiblyChanged(): void {
|
||||||
this.totalFileEventCount++;
|
this.totalFileEventCount++;
|
||||||
}
|
}
|
||||||
@@ -58,38 +73,39 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
async $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
||||||
|
const fileCount = useMemo<Record<string, number>>(
|
||||||
const fileCount = useMemo<Record<string, number>>({
|
{
|
||||||
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
||||||
}, (ctx, prev) => {
|
},
|
||||||
if (keepFileCheckList && prev) return prev;
|
(ctx, prev) => {
|
||||||
if (!keepFileCheckList && prev && !this.fileListPossiblyChanged) {
|
if (keepFileCheckList && prev) return prev;
|
||||||
return prev;
|
if (!keepFileCheckList && prev && !this.fileListPossiblyChanged) {
|
||||||
}
|
|
||||||
const fileList = (ctx.get("fileList") ?? []) as FilePathWithPrefix[];
|
|
||||||
// const fileNameList = (ctx.get("fileNameList") ?? []) as FilePath[];
|
|
||||||
// const fileNames =
|
|
||||||
const vaultFiles = this.core.storageAccess.getFileNames().sort();
|
|
||||||
if (prev && vaultFiles.length == fileList.length) {
|
|
||||||
const fl3 = new Set([...fileList, ...vaultFiles]);
|
|
||||||
if (fileList.length == fl3.size && vaultFiles.length == fl3.size) {
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
}
|
const fileList = (ctx.get("fileList") ?? []) as FilePathWithPrefix[];
|
||||||
ctx.set("fileList", vaultFiles);
|
// const fileNameList = (ctx.get("fileNameList") ?? []) as FilePath[];
|
||||||
|
// const fileNames =
|
||||||
|
const vaultFiles = this.core.storageAccess.getFileNames().sort();
|
||||||
const fileCount: Record<string, number> = {};
|
if (prev && vaultFiles.length == fileList.length) {
|
||||||
for (const file of vaultFiles) {
|
const fl3 = new Set([...fileList, ...vaultFiles]);
|
||||||
const lc = file.toLowerCase();
|
if (fileList.length == fl3.size && vaultFiles.length == fl3.size) {
|
||||||
if (!fileCount[lc]) {
|
return prev;
|
||||||
fileCount[lc] = 1;
|
}
|
||||||
} else {
|
|
||||||
fileCount[lc]++;
|
|
||||||
}
|
}
|
||||||
|
ctx.set("fileList", vaultFiles);
|
||||||
|
|
||||||
|
const fileCount: Record<string, number> = {};
|
||||||
|
for (const file of vaultFiles) {
|
||||||
|
const lc = file.toLowerCase();
|
||||||
|
if (!fileCount[lc]) {
|
||||||
|
fileCount[lc] = 1;
|
||||||
|
} else {
|
||||||
|
fileCount[lc]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileCount;
|
||||||
}
|
}
|
||||||
return fileCount;
|
);
|
||||||
})
|
|
||||||
|
|
||||||
const filepath = getPathFromUXFileInfo(file);
|
const filepath = getPathFromUXFileInfo(file);
|
||||||
const lc = filepath.toLowerCase();
|
const lc = filepath.toLowerCase();
|
||||||
@@ -100,7 +116,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
const fileNameLC = getPathFromUXFileInfo(file).split("/").pop()?.toLowerCase();
|
const fileNameLC = getPathFromUXFileInfo(file).split("/").pop()?.toLowerCase();
|
||||||
if (this.settings.useIgnoreFiles) {
|
if (this.settings.useIgnoreFiles) {
|
||||||
if (this.ignoreFiles.some(e => e.toLowerCase() == fileNameLC)) {
|
if (this.ignoreFiles.some((e) => e.toLowerCase() == fileNameLC)) {
|
||||||
// We must reload ignore files due to the its change.
|
// We must reload ignore files due to the its change.
|
||||||
await this.readIgnoreFile(filepath);
|
await this.readIgnoreFile(filepath);
|
||||||
}
|
}
|
||||||
@@ -109,11 +125,11 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.localDatabase?.isTargetFile(filepath)) return false;
|
if (!this.localDatabase?.isTargetFile(filepath)) return false;
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreFileCache = new LRUCache<string, string[] | false>(300, 250000, true);
|
ignoreFileCache = new LRUCache<string, string[] | false>(300, 250000, true);
|
||||||
ignoreFiles = [] as string[]
|
ignoreFiles = [] as string[];
|
||||||
async readIgnoreFile(path: string) {
|
async readIgnoreFile(path: string) {
|
||||||
try {
|
try {
|
||||||
const file = await this.core.storageAccess.readFileText(path);
|
const file = await this.core.storageAccess.readFileText(path);
|
||||||
@@ -138,14 +154,18 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
if (!this.settings.useIgnoreFiles) {
|
if (!this.settings.useIgnoreFiles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const filepath = getPathFromUXFileInfo(file)
|
const filepath = getPathFromUXFileInfo(file);
|
||||||
if (this.ignoreFileCache.has(filepath)) {
|
if (this.ignoreFileCache.has(filepath)) {
|
||||||
// Renew
|
// Renew
|
||||||
await this.readIgnoreFile(filepath);
|
await this.readIgnoreFile(filepath);
|
||||||
}
|
}
|
||||||
if (!await isAcceptedAll(stripAllPrefixes(filepath), this.ignoreFiles, (filename) => this.getIgnoreFile(filename))) {
|
if (
|
||||||
|
!(await isAcceptedAll(stripAllPrefixes(filepath), this.ignoreFiles, (filename) =>
|
||||||
|
this.getIgnoreFile(filename)
|
||||||
|
))
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,21 @@ Do you want to enable this?
|
|||||||
> - 2000: Warn if the remote storage size exceeds 2GB.
|
> - 2000: Warn if the remote storage size exceeds 2GB.
|
||||||
|
|
||||||
If we have reached the limit, we will be asked to enlarge the limit step by step.
|
If we have reached the limit, we will be asked to enlarge the limit step by step.
|
||||||
`
|
`;
|
||||||
const ANSWER_0 = "No, never warn please";
|
const ANSWER_0 = "No, never warn please";
|
||||||
const ANSWER_800 = "800MB (Cloudant, fly.io)";
|
const ANSWER_800 = "800MB (Cloudant, fly.io)";
|
||||||
const ANSWER_2000 = "2GB (Standard)";
|
const ANSWER_2000 = "2GB (Standard)";
|
||||||
const ASK_ME_NEXT_TIME = "Ask me later";
|
const ASK_ME_NEXT_TIME = "Ask me later";
|
||||||
|
|
||||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [ANSWER_0, ANSWER_800, ANSWER_2000, ASK_ME_NEXT_TIME], {
|
const ret = await this.core.confirm.askSelectStringDialogue(
|
||||||
defaultAction: ASK_ME_NEXT_TIME,
|
message,
|
||||||
title: "Setting up database size notification", timeout: 40
|
[ANSWER_0, ANSWER_800, ANSWER_2000, ASK_ME_NEXT_TIME],
|
||||||
});
|
{
|
||||||
|
defaultAction: ASK_ME_NEXT_TIME,
|
||||||
|
title: "Setting up database size notification",
|
||||||
|
timeout: 40,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (ret == ANSWER_0) {
|
if (ret == ANSWER_0) {
|
||||||
this.settings.notifyThresholdOfRemoteStorageSize = 0;
|
this.settings.notifyThresholdOfRemoteStorageSize = 0;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
@@ -68,13 +73,20 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
|||||||
const ANSWER_ENLARGE_LIMIT = `increase to ${newMax}MB`;
|
const ANSWER_ENLARGE_LIMIT = `increase to ${newMax}MB`;
|
||||||
const ANSWER_REBUILD = "Rebuild Everything Now";
|
const ANSWER_REBUILD = "Rebuild Everything Now";
|
||||||
const ANSWER_IGNORE = "Dismiss";
|
const ANSWER_IGNORE = "Dismiss";
|
||||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [ANSWER_ENLARGE_LIMIT, ANSWER_REBUILD, ANSWER_IGNORE,], {
|
const ret = await this.core.confirm.askSelectStringDialogue(
|
||||||
defaultAction: ANSWER_IGNORE,
|
message,
|
||||||
title: "Remote storage size exceeded the limit", timeout: 60
|
[ANSWER_ENLARGE_LIMIT, ANSWER_REBUILD, ANSWER_IGNORE],
|
||||||
|
{
|
||||||
});
|
defaultAction: ANSWER_IGNORE,
|
||||||
|
title: "Remote storage size exceeded the limit",
|
||||||
|
timeout: 60,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (ret == ANSWER_REBUILD) {
|
if (ret == ANSWER_REBUILD) {
|
||||||
const ret = await this.core.confirm.askYesNoDialog("This may take a bit of a long time. Do you really want to rebuild everything now?", { defaultOption: "No" });
|
const ret = await this.core.confirm.askYesNoDialog(
|
||||||
|
"This may take a bit of a long time. Do you really want to rebuild everything now?",
|
||||||
|
{ defaultOption: "No" }
|
||||||
|
);
|
||||||
if (ret == "yes") {
|
if (ret == "yes") {
|
||||||
this.core.settings.notifyThresholdOfRemoteStorageSize = -1;
|
this.core.settings.notifyThresholdOfRemoteStorageSize = -1;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
@@ -82,13 +94,19 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
|||||||
}
|
}
|
||||||
} else if (ret == ANSWER_ENLARGE_LIMIT) {
|
} else if (ret == ANSWER_ENLARGE_LIMIT) {
|
||||||
this.settings.notifyThresholdOfRemoteStorageSize = ~~(estimatedSize / 1024 / 1024) + 100;
|
this.settings.notifyThresholdOfRemoteStorageSize = ~~(estimatedSize / 1024 / 1024) + 100;
|
||||||
this._log(`Threshold has been enlarged to ${this.settings.notifyThresholdOfRemoteStorageSize}MB`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`Threshold has been enlarged to ${this.settings.notifyThresholdOfRemoteStorageSize}MB`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
} else {
|
} else {
|
||||||
// Dismiss or Close the dialog
|
// Dismiss or Close the dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
this._log(`Remote storage size: ${sizeToHumanReadable(estimatedSize)} exceeded ${sizeToHumanReadable(this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024)} `, LOG_LEVEL_INFO);
|
this._log(
|
||||||
|
`Remote storage size: ${sizeToHumanReadable(estimatedSize)} exceeded ${sizeToHumanReadable(this.settings.notifyThresholdOfRemoteStorageSize * 1024 * 1024)} `,
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this._log(`Remote storage size: ${sizeToHumanReadable(estimatedSize)}`, LOG_LEVEL_INFO);
|
this._log(`Remote storage size: ${sizeToHumanReadable(estimatedSize)}`, LOG_LEVEL_INFO);
|
||||||
}
|
}
|
||||||
@@ -97,5 +115,4 @@ If we have reached the limit, we will be asked to enlarge the limit step by step
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
|||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleConflictChecker extends AbstractModule implements ICoreModule {
|
export class ModuleConflictChecker extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
async $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
const path = file;
|
const path = file;
|
||||||
if (this.settings.checkConflictOnlyOnOpen) {
|
if (this.settings.checkConflictOnlyOnOpen) {
|
||||||
@@ -36,40 +35,44 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO-> Move to ModuleConflictResolver?
|
// TODO-> Move to ModuleConflictResolver?
|
||||||
conflictResolveQueue = new QueueProcessor(async (filenames: FilePathWithPrefix[]) => {
|
conflictResolveQueue = new QueueProcessor(
|
||||||
await this.core.$$resolveConflict(filenames[0]);
|
async (filenames: FilePathWithPrefix[]) => {
|
||||||
}, {
|
await this.core.$$resolveConflict(filenames[0]);
|
||||||
suspended: false,
|
},
|
||||||
batchSize: 1,
|
{
|
||||||
concurrentLimit: 1,
|
suspended: false,
|
||||||
delay: 10,
|
batchSize: 1,
|
||||||
keepResultUntilDownstreamConnected: false
|
concurrentLimit: 1,
|
||||||
}).replaceEnqueueProcessor((queue, newEntity) => {
|
delay: 10,
|
||||||
|
keepResultUntilDownstreamConnected: false,
|
||||||
|
}
|
||||||
|
).replaceEnqueueProcessor((queue, newEntity) => {
|
||||||
const filename = newEntity;
|
const filename = newEntity;
|
||||||
sendValue("cancel-resolve-conflict:" + filename, true);
|
sendValue("cancel-resolve-conflict:" + filename, true);
|
||||||
const newQueue = [...queue].filter(e => e != newEntity);
|
const newQueue = [...queue].filter((e) => e != newEntity);
|
||||||
return [...newQueue, newEntity];
|
return [...newQueue, newEntity];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
conflictCheckQueue = // First process - Check is the file actually need resolve -
|
conflictCheckQueue = // First process - Check is the file actually need resolve -
|
||||||
new QueueProcessor((files: FilePathWithPrefix[]) => {
|
new QueueProcessor(
|
||||||
const filename = files[0];
|
(files: FilePathWithPrefix[]) => {
|
||||||
// const file = await this.core.storageAccess.isExists(filename);
|
const filename = files[0];
|
||||||
// if (!file) return [];
|
// const file = await this.core.storageAccess.isExists(filename);
|
||||||
// if (!(file instanceof TFile)) return;
|
// if (!file) return [];
|
||||||
// if ((file instanceof TFolder)) return [];
|
// if (!(file instanceof TFile)) return;
|
||||||
// Check again?
|
// if ((file instanceof TFolder)) return [];
|
||||||
return Promise.resolve([filename]);
|
// Check again?
|
||||||
// this.conflictResolveQueue.enqueueWithKey(filename, { filename, file });
|
return Promise.resolve([filename]);
|
||||||
}, {
|
// this.conflictResolveQueue.enqueueWithKey(filename, { filename, file });
|
||||||
suspended: false,
|
},
|
||||||
batchSize: 1,
|
{
|
||||||
concurrentLimit: 5,
|
suspended: false,
|
||||||
delay: 10,
|
batchSize: 1,
|
||||||
keepResultUntilDownstreamConnected: true,
|
concurrentLimit: 5,
|
||||||
pipeTo: this.conflictResolveQueue,
|
delay: 10,
|
||||||
totalRemainingReactiveSource: this.core.conflictProcessQueueCount
|
keepResultUntilDownstreamConnected: true,
|
||||||
});
|
pipeTo: this.conflictResolveQueue,
|
||||||
|
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { AUTO_MERGED, CANCELLED, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, MISSING_OR_ERROR, NOT_CONFLICTED, type diff_check_result, type FilePathWithPrefix } from "../../lib/src/common/types";
|
import {
|
||||||
|
AUTO_MERGED,
|
||||||
|
CANCELLED,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
MISSING_OR_ERROR,
|
||||||
|
NOT_CONFLICTED,
|
||||||
|
type diff_check_result,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { compareMTime, displayRev, TARGET_IS_NEW } from "../../common/utils";
|
import { compareMTime, displayRev, TARGET_IS_NEW } from "../../common/utils";
|
||||||
import diff_match_patch from "diff-match-patch";
|
import diff_match_patch from "diff-match-patch";
|
||||||
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleConflictResolver extends AbstractModule implements ICoreModule {
|
export class ModuleConflictResolver extends AbstractModule implements ICoreModule {
|
||||||
|
async $$resolveConflictByDeletingRev(
|
||||||
async $$resolveConflictByDeletingRev(path: FilePathWithPrefix, deleteRevision: string, subTitle = ""): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
path: FilePathWithPrefix,
|
||||||
|
deleteRevision: string,
|
||||||
|
subTitle = ""
|
||||||
|
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
||||||
const title = `Resolving ${subTitle ? `[${subTitle}]` : ""}:`;
|
const title = `Resolving ${subTitle ? `[${subTitle}]` : ""}:`;
|
||||||
if (!await this.core.fileHandler.deleteRevisionFromDB(path, deleteRevision)) {
|
if (!(await this.core.fileHandler.deleteRevisionFromDB(path, deleteRevision))) {
|
||||||
this._log(`${title} Could not delete conflicted revision ${displayRev(deleteRevision)} of ${path}`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`${title} Could not delete conflicted revision ${displayRev(deleteRevision)} of ${path}`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
this._log(`${title} Conflicted revision deleted ${displayRev(deleteRevision)} ${path}`, LOG_LEVEL_INFO);
|
this._log(`${title} Conflicted revision deleted ${displayRev(deleteRevision)} ${path}`, LOG_LEVEL_INFO);
|
||||||
@@ -20,7 +35,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
return AUTO_MERGED;
|
return AUTO_MERGED;
|
||||||
}
|
}
|
||||||
// If no conflicts were found, write the resolved content to the storage.
|
// If no conflicts were found, write the resolved content to the storage.
|
||||||
if (!await this.core.fileHandler.dbToStorage(path, stripAllPrefixes(path), true)) {
|
if (!(await this.core.fileHandler.dbToStorage(path, stripAllPrefixes(path), true))) {
|
||||||
this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE);
|
this._log(`Could not write the resolved content to the storage: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
@@ -28,7 +43,6 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
return AUTO_MERGED;
|
return AUTO_MERGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async checkConflictAndPerformAutoMerge(path: FilePathWithPrefix): Promise<diff_check_result> {
|
async checkConflictAndPerformAutoMerge(path: FilePathWithPrefix): Promise<diff_check_result> {
|
||||||
//
|
//
|
||||||
const ret = await this.localDatabase.tryAutoMerge(path, !this.settings.disableMarkdownAutoMerge);
|
const ret = await this.localDatabase.tryAutoMerge(path, !this.settings.disableMarkdownAutoMerge);
|
||||||
@@ -40,7 +54,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
const p = ret.result;
|
const p = ret.result;
|
||||||
// Merged content is coming.
|
// Merged content is coming.
|
||||||
// 1. Store the merged content to the storage
|
// 1. Store the merged content to the storage
|
||||||
if (!await this.core.databaseFileAccess.storeContent(path, p)) {
|
if (!(await this.core.databaseFileAccess.storeContent(path, p))) {
|
||||||
this._log(`Merged content cannot be stored:${path}`, LOG_LEVEL_NOTICE);
|
this._log(`Merged content cannot be stored:${path}`, LOG_LEVEL_NOTICE);
|
||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
@@ -65,13 +79,17 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
const isBinary = !isPlainText(path);
|
const isBinary = !isPlainText(path);
|
||||||
const alwaysNewer = this.settings.resolveConflictsByNewerFile;
|
const alwaysNewer = this.settings.resolveConflictsByNewerFile;
|
||||||
if (isSame || isBinary || alwaysNewer) {
|
if (isSame || isBinary || alwaysNewer) {
|
||||||
const result = compareMTime(leftLeaf.mtime, rightLeaf.mtime)
|
const result = compareMTime(leftLeaf.mtime, rightLeaf.mtime);
|
||||||
let loser = leftLeaf;
|
let loser = leftLeaf;
|
||||||
// if (lMtime > rMtime) {
|
// if (lMtime > rMtime) {
|
||||||
if (result != TARGET_IS_NEW) {
|
if (result != TARGET_IS_NEW) {
|
||||||
loser = rightLeaf;
|
loser = rightLeaf;
|
||||||
}
|
}
|
||||||
const subTitle = [`${isSame ? "same" : ""}`, `${isBinary ? "binary" : ""}`, `${alwaysNewer ? "alwaysNewer" : ""}`].join(",");
|
const subTitle = [
|
||||||
|
`${isSame ? "same" : ""}`,
|
||||||
|
`${isBinary ? "binary" : ""}`,
|
||||||
|
`${alwaysNewer ? "alwaysNewer" : ""}`,
|
||||||
|
].join(",");
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
||||||
}
|
}
|
||||||
// make diff.
|
// make diff.
|
||||||
@@ -86,13 +104,15 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
async $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
// const filename = filenames[0];
|
// const filename = filenames[0];
|
||||||
return await serialized(`conflict-resolve:${filename}`, async () => {
|
return await serialized(`conflict-resolve:${filename}`, async () => {
|
||||||
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
||||||
if (conflictCheckResult === MISSING_OR_ERROR || conflictCheckResult === NOT_CONFLICTED || conflictCheckResult === CANCELLED) {
|
if (
|
||||||
|
conflictCheckResult === MISSING_OR_ERROR ||
|
||||||
|
conflictCheckResult === NOT_CONFLICTED ||
|
||||||
|
conflictCheckResult === CANCELLED
|
||||||
|
) {
|
||||||
// nothing to do.
|
// nothing to do.
|
||||||
this._log(`conflict:Nothing to do:${filename}`);
|
this._log(`conflict:Nothing to do:${filename}`);
|
||||||
return;
|
return;
|
||||||
@@ -110,7 +130,10 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
if (this.settings.showMergeDialogOnlyOnActive) {
|
if (this.settings.showMergeDialogOnlyOnActive) {
|
||||||
const af = this.core.$$getActiveFilePath();
|
const af = this.core.$$getActiveFilePath();
|
||||||
if (af && af != filename) {
|
if (af && af != filename) {
|
||||||
this._log(`${filename} is conflicted. Merging process has been postponed to the file have got opened.`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,18 +147,26 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
if (revs.length == 0) {
|
if (revs.length == 0) {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
const mTimeAndRev = (await Promise.all(revs.map(async (rev) => {
|
const mTimeAndRev = (
|
||||||
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
|
await Promise.all(
|
||||||
if (leaf == false) {
|
revs.map(async (rev) => {
|
||||||
return [0, rev] as [number, string];
|
const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev);
|
||||||
}
|
if (leaf == false) {
|
||||||
return [leaf.mtime, rev] as [number, string];
|
return [0, rev] as [number, string];
|
||||||
}))).sort((a, b) => b[0] - a[0]);
|
}
|
||||||
this._log(`Resolving conflict by newest: ${filename} (Newest: ${new Date(mTimeAndRev[0][0]).toLocaleString()}) (${mTimeAndRev.length} revisions exists)`);
|
return [leaf.mtime, rev] as [number, string];
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).sort((a, b) => b[0] - a[0]);
|
||||||
|
this._log(
|
||||||
|
`Resolving conflict by newest: ${filename} (Newest: ${new Date(mTimeAndRev[0][0]).toLocaleString()}) (${mTimeAndRev.length} revisions exists)`
|
||||||
|
);
|
||||||
for (let i = 1; i < mTimeAndRev.length; i++) {
|
for (let i = 1; i < mTimeAndRev.length; i++) {
|
||||||
this._log(`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`);
|
this._log(
|
||||||
|
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
|
||||||
|
);
|
||||||
await this.core.$$resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST");
|
await this.core.$$resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { normalizePath } from "../../deps.ts";
|
import { normalizePath } from "../../deps.ts";
|
||||||
import { FLAGMD_REDFLAG, FLAGMD_REDFLAG2, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3, FLAGMD_REDFLAG3_HR } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
FLAGMD_REDFLAG,
|
||||||
|
FLAGMD_REDFLAG2,
|
||||||
|
FLAGMD_REDFLAG2_HR,
|
||||||
|
FLAGMD_REDFLAG3,
|
||||||
|
FLAGMD_REDFLAG3_HR,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async isFlagFileExist(path: string) {
|
async isFlagFileExist(path: string) {
|
||||||
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
||||||
if (redflag) {
|
if (redflag) {
|
||||||
@@ -26,9 +31,11 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRedFlagRaised = async () => await this.isFlagFileExist(FLAGMD_REDFLAG)
|
isRedFlagRaised = async () => await this.isFlagFileExist(FLAGMD_REDFLAG);
|
||||||
isRedFlag2Raised = async () => await this.isFlagFileExist(FLAGMD_REDFLAG2) || await this.isFlagFileExist(FLAGMD_REDFLAG2_HR)
|
isRedFlag2Raised = async () =>
|
||||||
isRedFlag3Raised = async () => await this.isFlagFileExist(FLAGMD_REDFLAG3) || await this.isFlagFileExist(FLAGMD_REDFLAG3_HR)
|
(await this.isFlagFileExist(FLAGMD_REDFLAG2)) || (await this.isFlagFileExist(FLAGMD_REDFLAG2_HR));
|
||||||
|
isRedFlag3Raised = async () =>
|
||||||
|
(await this.isFlagFileExist(FLAGMD_REDFLAG3)) || (await this.isFlagFileExist(FLAGMD_REDFLAG3_HR));
|
||||||
|
|
||||||
async deleteRedFlag2() {
|
async deleteRedFlag2() {
|
||||||
await this.deleteFlagFile(FLAGMD_REDFLAG2);
|
await this.deleteFlagFile(FLAGMD_REDFLAG2);
|
||||||
@@ -47,14 +54,24 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
if (isRedFlagRaised || isRedFlag2Raised || isRedFlag3Raised) {
|
if (isRedFlagRaised || isRedFlag2Raised || isRedFlag3Raised) {
|
||||||
if (isRedFlag2Raised) {
|
if (isRedFlag2Raised) {
|
||||||
if (await this.core.confirm.askYesNoDialog("Rebuild everything has been scheduled! Are you sure to rebuild everything?", { defaultOption: "Yes", timeout: 0 }) !== "yes") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog(
|
||||||
|
"Rebuild everything has been scheduled! Are you sure to rebuild everything?",
|
||||||
|
{ defaultOption: "Yes", timeout: 0 }
|
||||||
|
)) !== "yes"
|
||||||
|
) {
|
||||||
await this.deleteRedFlag2();
|
await this.deleteRedFlag2();
|
||||||
await this.core.$$performRestart();
|
await this.core.$$performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isRedFlag3Raised) {
|
if (isRedFlag3Raised) {
|
||||||
if (await this.core.confirm.askYesNoDialog("Fetch again has been scheduled! Are you sure?", { defaultOption: "Yes", timeout: 0 }) !== "yes") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Fetch again has been scheduled! Are you sure?", {
|
||||||
|
defaultOption: "Yes",
|
||||||
|
timeout: 0,
|
||||||
|
})) !== "yes"
|
||||||
|
) {
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
await this.core.$$performRestart();
|
await this.core.$$performRestart();
|
||||||
return false;
|
return false;
|
||||||
@@ -66,39 +83,63 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
this.settings.suspendFileWatching = true;
|
this.settings.suspendFileWatching = true;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
if (isRedFlag2Raised) {
|
if (isRedFlag2Raised) {
|
||||||
this._log(`${FLAGMD_REDFLAG2} or ${FLAGMD_REDFLAG2_HR} has been detected! Self-hosted LiveSync suspends all sync and rebuild everything.`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`${FLAGMD_REDFLAG2} or ${FLAGMD_REDFLAG2_HR} has been detected! Self-hosted LiveSync suspends all sync and rebuild everything.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
await this.core.rebuilder.$rebuildEverything();
|
await this.core.rebuilder.$rebuildEverything();
|
||||||
await this.deleteRedFlag2();
|
await this.deleteRedFlag2();
|
||||||
if (await this.core.confirm.askYesNoDialog("Do you want to resume file and database processing, and restart obsidian now?", { defaultOption: "Yes", timeout: 15 }) == "yes") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog(
|
||||||
|
"Do you want to resume file and database processing, and restart obsidian now?",
|
||||||
|
{ defaultOption: "Yes", timeout: 15 }
|
||||||
|
)) == "yes"
|
||||||
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.core.$$performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (isRedFlag3Raised) {
|
} else if (isRedFlag3Raised) {
|
||||||
this._log(`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
const makeLocalChunkBeforeSync = ((await this.core.confirm.askYesNoDialog(`Do you want to create local chunks before fetching?
|
`${FLAGMD_REDFLAG3} or ${FLAGMD_REDFLAG3_HR} has been detected! Self-hosted LiveSync will discard the local database and fetch everything from the remote once again.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
const makeLocalChunkBeforeSync =
|
||||||
|
(await this.core.confirm.askYesNoDialog(
|
||||||
|
`Do you want to create local chunks before fetching?
|
||||||
> [!MORE]-
|
> [!MORE]-
|
||||||
> If creating local chunks before fetching, only the difference between the local and remote will be fetched.
|
> If creating local chunks before fetching, only the difference between the local and remote will be fetched.
|
||||||
|
|
||||||
`, { defaultOption: "Yes", title: "Trick to transfer efficiently" })) == "yes");
|
`,
|
||||||
|
{ defaultOption: "Yes", title: "Trick to transfer efficiently" }
|
||||||
|
)) == "yes";
|
||||||
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync);
|
await this.core.rebuilder.$fetchLocal(makeLocalChunkBeforeSync);
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
if (this.settings.suspendFileWatching) {
|
if (this.settings.suspendFileWatching) {
|
||||||
if (await this.core.confirm.askYesNoDialog("Do you want to resume file and database processing, and restart obsidian now?", { defaultOption: "Yes", timeout: 15 }) == "yes") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog(
|
||||||
|
"Do you want to resume file and database processing, and restart obsidian now?",
|
||||||
|
{ defaultOption: "Yes", timeout: 15 }
|
||||||
|
)) == "yes"
|
||||||
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.core.$$performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._log("Your content of files will be synchronised gradually. Please wait for the completion.", LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"Your content of files will be synchronised gradually. Please wait for the completion.",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Case of FLAGMD_REDFLAG.
|
// Case of FLAGMD_REDFLAG.
|
||||||
this.settings.writeLogToTheFile = true;
|
this.settings.writeLogToTheFile = true;
|
||||||
// await this.plugin.openDatabase();
|
// await this.plugin.openDatabase();
|
||||||
const warningMessage = "The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
|
const warningMessage =
|
||||||
|
"The red flag is raised! The whole initialize steps are skipped, and any file changes are not captured.";
|
||||||
this._log(warningMessage, LOG_LEVEL_NOTICE);
|
this._log(warningMessage, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,5 +149,4 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ export class ModuleRemoteGovernor extends AbstractModule implements ICoreModule
|
|||||||
async $$markRemoteResolved(): Promise<void> {
|
async $$markRemoteResolved(): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteResolved(this.settings);
|
return await this.core.replicator.markRemoteResolved(this.settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
import { Logger, LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
||||||
import { extractObject } from "octagonal-wheels/object";
|
import { extractObject } from "octagonal-wheels/object";
|
||||||
import { TweakValuesShouldMatchedTemplate, CompatibilityBreakingTweakValues, confName, type TweakValues, type RemoteDBSettings } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
TweakValuesShouldMatchedTemplate,
|
||||||
|
CompatibilityBreakingTweakValues,
|
||||||
|
confName,
|
||||||
|
type TweakValues,
|
||||||
|
type RemoteDBSettings,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
@@ -38,11 +44,13 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`;
|
table += `| ${confName(key)} | ${valueMine} | ${valuePreferred} | \n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalMessage = rebuildRequired ? `
|
const additionalMessage = rebuildRequired
|
||||||
|
? `
|
||||||
|
|
||||||
**Note**: We have detected that some of the values are different to make incompatible the local database with the remote database.
|
**Note**: We have detected that some of the values are different to make incompatible the local database with the remote database.
|
||||||
If you choose to use the configured values, the local database will be rebuilt, and if you choose to use the values of this device, the remote database will be rebuilt.
|
If you choose to use the configured values, the local database will be rebuilt, and if you choose to use the values of this device, the remote database will be rebuilt.
|
||||||
Both of them takes a few minutes. Please choose after considering the situation.` : "";
|
Both of them takes a few minutes. Please choose after considering the situation.`
|
||||||
|
: "";
|
||||||
|
|
||||||
const message = `
|
const message = `
|
||||||
Your configuration has not been matched with the one on the remote server.
|
Your configuration has not been matched with the one on the remote server.
|
||||||
@@ -67,9 +75,16 @@ Please select which one you want to use.
|
|||||||
const CHOICE_AND_VALUES = [
|
const CHOICE_AND_VALUES = [
|
||||||
[CHOICE_USE_REMOTE, preferred],
|
[CHOICE_USE_REMOTE, preferred],
|
||||||
[CHOICE_USR_MINE, true],
|
[CHOICE_USR_MINE, true],
|
||||||
[CHOICE_DISMISS, false]]
|
[CHOICE_DISMISS, false],
|
||||||
|
];
|
||||||
const CHOICES = Object.fromEntries(CHOICE_AND_VALUES) as Record<string, TweakValues | boolean>;
|
const CHOICES = Object.fromEntries(CHOICE_AND_VALUES) as Record<string, TweakValues | boolean>;
|
||||||
const retKey = await this.core.confirm.confirmWithMessage("Tweaks Mismatched or Changed", message, Object.keys(CHOICES), CHOICE_DISMISS, 60);
|
const retKey = await this.core.confirm.confirmWithMessage(
|
||||||
|
"Tweaks Mismatched or Changed",
|
||||||
|
message,
|
||||||
|
Object.keys(CHOICES),
|
||||||
|
CHOICE_DISMISS,
|
||||||
|
60
|
||||||
|
);
|
||||||
if (!retKey) return "IGNORE";
|
if (!retKey) return "IGNORE";
|
||||||
const conf = CHOICES[retKey];
|
const conf = CHOICES[retKey];
|
||||||
|
|
||||||
@@ -78,7 +93,10 @@ Please select which one you want to use.
|
|||||||
if (rebuildRequired) {
|
if (rebuildRequired) {
|
||||||
await this.core.rebuilder.$rebuildRemote();
|
await this.core.rebuilder.$rebuildRemote();
|
||||||
}
|
}
|
||||||
Logger(`Tweak values on the remote server have been updated. Your other device will see this message.`, LOG_LEVEL_NOTICE);
|
Logger(
|
||||||
|
`Tweak values on the remote server have been updated. Your other device will see this message.`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return "CHECKAGAIN";
|
return "CHECKAGAIN";
|
||||||
}
|
}
|
||||||
if (conf) {
|
if (conf) {
|
||||||
@@ -92,10 +110,11 @@ Please select which one you want to use.
|
|||||||
return "CHECKAGAIN";
|
return "CHECKAGAIN";
|
||||||
}
|
}
|
||||||
return "IGNORE";
|
return "IGNORE";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$checkAndAskUseRemoteConfiguration(trialSetting: RemoteDBSettings): Promise<{ result: false | TweakValues, requireFetch: boolean }> {
|
async $$checkAndAskUseRemoteConfiguration(
|
||||||
|
trialSetting: RemoteDBSettings
|
||||||
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
const replicator = await this.core.$anyNewReplicator(trialSetting);
|
const replicator = await this.core.$anyNewReplicator(trialSetting);
|
||||||
if (await replicator.tryConnectRemote(trialSetting)) {
|
if (await replicator.tryConnectRemote(trialSetting)) {
|
||||||
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
||||||
@@ -122,14 +141,20 @@ Please select which one you want to use.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (differenceCount === 0) {
|
if (differenceCount === 0) {
|
||||||
this._log("The settings in the remote database are the same as the local database.", LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"The settings in the remote database are the same as the local database.",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
const additionalMessage = (rebuildRequired && this.core.settings.isConfigured) ? `
|
const additionalMessage =
|
||||||
|
rebuildRequired && this.core.settings.isConfigured
|
||||||
|
? `
|
||||||
|
|
||||||
>[!WARNING]
|
>[!WARNING]
|
||||||
> Some remote configurations are not compatible with the local database of this device. Rebuilding the local database will be required.
|
> Some remote configurations are not compatible with the local database of this device. Rebuilding the local database will be required.
|
||||||
***Please ensure that you have time and are connected to a stable network to apply!***` : "";
|
***Please ensure that you have time and are connected to a stable network to apply!***`
|
||||||
|
: "";
|
||||||
|
|
||||||
const message = `
|
const message = `
|
||||||
The settings in the remote database are as follows.
|
The settings in the remote database are as follows.
|
||||||
@@ -152,7 +177,7 @@ ${additionalMessage}`;
|
|||||||
const retKey = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
|
const retKey = await this.core.confirm.askSelectStringDialogue(message, CHOICES, {
|
||||||
title: "Use Remote Configuration",
|
title: "Use Remote Configuration",
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
defaultAction: CHOICE_DISMISS
|
defaultAction: CHOICE_DISMISS,
|
||||||
});
|
});
|
||||||
if (!retKey) return { result: false, requireFetch: false };
|
if (!retKey) return { result: false, requireFetch: false };
|
||||||
if (retKey === CHOICE_DISMISS) return { result: false, requireFetch: false };
|
if (retKey === CHOICE_DISMISS) return { result: false, requireFetch: false };
|
||||||
@@ -168,4 +193,4 @@ ${additionalMessage}`;
|
|||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,20 @@ import { normalizePath, TFile, TFolder, type ListedFiles } from "obsidian";
|
|||||||
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import type { FilePath, FilePathWithPrefix, UXDataWriteOptions, UXFileInfo, UXFileInfoStub, UXFolderInfo, UXStat } from "../../lib/src/common/types";
|
import type {
|
||||||
|
FilePath,
|
||||||
|
FilePathWithPrefix,
|
||||||
|
UXDataWriteOptions,
|
||||||
|
UXFileInfo,
|
||||||
|
UXFileInfoStub,
|
||||||
|
UXFolderInfo,
|
||||||
|
UXStat,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/utilObsidian.ts";
|
import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/utilObsidian.ts";
|
||||||
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
||||||
import type { StorageAccess } from "../interfaces/StorageAccess";
|
import type { StorageAccess } from "../interfaces/StorageAccess";
|
||||||
import { createBlob } from "../../lib/src/common/utils";
|
import { createBlob } from "../../lib/src/common/utils";
|
||||||
|
|
||||||
|
|
||||||
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
||||||
vaultAccess!: SerializedFileAccess;
|
vaultAccess!: SerializedFileAccess;
|
||||||
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core);
|
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core);
|
||||||
@@ -53,7 +60,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
return this.vaultAccess.vaultModify(file, data, opt);
|
return this.vaultAccess.vaultModify(file, data, opt);
|
||||||
} else if (file === null) {
|
} else if (file === null) {
|
||||||
return await this.vaultAccess.vaultCreate(path, data, opt) instanceof TFile;
|
return (await this.vaultAccess.vaultCreate(path, data, opt)) instanceof TFile;
|
||||||
} else {
|
} else {
|
||||||
this._log(`Could not write file (Possibly already exists as a folder): ${path}`, LOG_LEVEL_VERBOSE);
|
this._log(`Could not write file (Possibly already exists as a folder): ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
@@ -90,7 +97,6 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
}
|
}
|
||||||
async appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise<boolean> {
|
async appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await this.vaultAccess.adapterAppend(path, data, opt);
|
await this.vaultAccess.adapterAppend(path, data, opt);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -107,12 +113,11 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
ctime: file.stat.ctime,
|
ctime: file.stat.ctime,
|
||||||
mtime: file.stat.mtime,
|
mtime: file.stat.mtime,
|
||||||
size: file.stat.size,
|
size: file.stat.size,
|
||||||
type: "file"
|
type: "file",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Could not stat file (Possibly does not exist): ${path}`);
|
throw new Error(`Could not stat file (Possibly does not exist): ${path}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
statHidden(path: string): Promise<UXStat | null> {
|
statHidden(path: string): Promise<UXStat | null> {
|
||||||
return this.vaultAccess.adapterStat(path);
|
return this.vaultAccess.adapterStat(path);
|
||||||
@@ -140,7 +145,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
return await this.vaultAccess.adapterReadBinary(path);
|
return await this.vaultAccess.adapterReadBinary(path);
|
||||||
}
|
}
|
||||||
async isExistsIncludeHidden(path: string): Promise<boolean> {
|
async isExistsIncludeHidden(path: string): Promise<boolean> {
|
||||||
return await this.vaultAccess.adapterStat(path) !== null;
|
return (await this.vaultAccess.adapterStat(path)) !== null;
|
||||||
}
|
}
|
||||||
async ensureDir(path: string): Promise<boolean> {
|
async ensureDir(path: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
@@ -180,8 +185,8 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
const data = await this.vaultAccess.vaultReadAuto(file);
|
const data = await this.vaultAccess.vaultReadAuto(file);
|
||||||
return {
|
return {
|
||||||
...stub,
|
...stub,
|
||||||
body: createBlob(data)
|
body: createBlob(data),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
getStub(path: string): UXFileInfoStub | UXFolderInfo | null {
|
getStub(path: string): UXFileInfoStub | UXFolderInfo | null {
|
||||||
const file = this.vaultAccess.getAbstractFileByPath(path);
|
const file = this.vaultAccess.getAbstractFileByPath(path);
|
||||||
@@ -193,10 +198,10 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
getFiles(): UXFileInfoStub[] {
|
getFiles(): UXFileInfoStub[] {
|
||||||
return this.vaultAccess.getFiles().map(f => TFileToUXFileInfoStub(f));
|
return this.vaultAccess.getFiles().map((f) => TFileToUXFileInfoStub(f));
|
||||||
}
|
}
|
||||||
getFileNames(): FilePath[] {
|
getFileNames(): FilePath[] {
|
||||||
return this.vaultAccess.getFiles().map(f => f.path as FilePath);
|
return this.vaultAccess.getFiles().map((f) => f.path as FilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFilesIncludeHidden(
|
async getFilesIncludeHidden(
|
||||||
@@ -213,20 +218,20 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
skipFolder = skipFolder.map(e => e.toLowerCase());
|
skipFolder = skipFolder.map((e) => e.toLowerCase());
|
||||||
|
|
||||||
let files = [] as string[];
|
let files = [] as string[];
|
||||||
for (const file of w.files) {
|
for (const file of w.files) {
|
||||||
if (excludeFilter && excludeFilter.some(ee => file.match(ee))) {
|
if (excludeFilter && excludeFilter.some((ee) => file.match(ee))) {
|
||||||
// If excludeFilter and includeFilter are both set, the file will be included in the list.
|
// If excludeFilter and includeFilter are both set, the file will be included in the list.
|
||||||
if (includeFilter) {
|
if (includeFilter) {
|
||||||
if (!includeFilter.some(e => file.match(e))) continue;
|
if (!includeFilter.some((e) => file.match(e))) continue;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (includeFilter) {
|
if (includeFilter) {
|
||||||
if (!includeFilter.some(e => file.match(e))) continue;
|
if (!includeFilter.some((e) => file.match(e))) continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
||||||
files.push(file);
|
files.push(file);
|
||||||
@@ -234,19 +239,19 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
|
|
||||||
for (const v of w.folders) {
|
for (const v of w.folders) {
|
||||||
const folderName = (v.split("/").pop() ?? "").toLowerCase();
|
const folderName = (v.split("/").pop() ?? "").toLowerCase();
|
||||||
if (skipFolder.some(e => folderName === e)) {
|
if (skipFolder.some((e) => folderName === e)) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
if (excludeFilter && excludeFilter.some(e => v.match(e))) {
|
if (excludeFilter && excludeFilter.some((e) => v.match(e))) {
|
||||||
if (includeFilter) {
|
if (includeFilter) {
|
||||||
if (!includeFilter.some(e => v.match(e))) {
|
if (!includeFilter.some((e) => v.match(e))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeFilter) {
|
if (includeFilter) {
|
||||||
if (!includeFilter.some(e => v.match(e))) continue;
|
if (!includeFilter.some((e) => v.match(e))) continue;
|
||||||
}
|
}
|
||||||
// OK, deep dive!
|
// OK, deep dive!
|
||||||
files = files.concat(await this.getFilesIncludeHidden(v, includeFilter, excludeFilter, skipFolder));
|
files = files.concat(await this.getFilesIncludeHidden(v, includeFilter, excludeFilter, skipFolder));
|
||||||
@@ -258,7 +263,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
this.vaultAccess.touch(path as FilePath);
|
this.vaultAccess.touch(path as FilePath);
|
||||||
}
|
}
|
||||||
recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean {
|
recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean {
|
||||||
const xFile = typeof file === "string" ? this.vaultAccess.getAbstractFileByPath(file) as TFile : file;
|
const xFile = typeof file === "string" ? (this.vaultAccess.getAbstractFileByPath(file) as TFile) : file;
|
||||||
if (xFile === null) return false;
|
if (xFile === null) return false;
|
||||||
if (xFile instanceof TFolder) return false;
|
if (xFile instanceof TFolder) return false;
|
||||||
return this.vaultAccess.recentlyTouched(xFile);
|
return this.vaultAccess.recentlyTouched(xFile);
|
||||||
@@ -303,7 +308,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
|
|
||||||
async _deleteVaultItem(file: TFile | TFolder) {
|
async _deleteVaultItem(file: TFile | TFolder) {
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
if (!await this.core.$$isTargetFile(file.path)) return;
|
if (!(await this.core.$$isTargetFile(file.path))) return;
|
||||||
}
|
}
|
||||||
const dir = file.parent;
|
const dir = file.parent;
|
||||||
if (this.settings.trashInsteadDelete) {
|
if (this.settings.trashInsteadDelete) {
|
||||||
@@ -316,7 +321,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
this._log(`files: ${dir.children.length}`);
|
this._log(`files: ${dir.children.length}`);
|
||||||
if (dir.children.length == 0) {
|
if (dir.children.length == 0) {
|
||||||
if (!this.settings.doNotDeleteFolder) {
|
if (!this.settings.doNotDeleteFolder) {
|
||||||
this._log(`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`);
|
this._log(
|
||||||
|
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
|
||||||
|
);
|
||||||
await this._deleteVaultItem(dir);
|
await this._deleteVaultItem(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,4 +338,4 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
return await this._deleteVaultItem(file);
|
return await this._deleteVaultItem(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from '../AbstractObsidianModule.ts';
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { scheduleTask } from 'octagonal-wheels/concurrency/task';
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from '../../common/utils.ts';
|
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
||||||
import { askSelectString, askString, askYesNo, confirmWithMessage, confirmWithMessageWithWideButton } from './UILib/dialogs.ts';
|
import {
|
||||||
import { Notice } from '../../deps.ts';
|
askSelectString,
|
||||||
import type { Confirm } from '../interfaces/Confirm.ts';
|
askString,
|
||||||
|
askYesNo,
|
||||||
|
confirmWithMessage,
|
||||||
|
confirmWithMessageWithWideButton,
|
||||||
|
} from "./UILib/dialogs.ts";
|
||||||
|
import { Notice } from "../../deps.ts";
|
||||||
|
import type { Confirm } from "../interfaces/Confirm.ts";
|
||||||
|
|
||||||
// This module cannot be a common module because it depends on Obsidian's API.
|
// This module cannot be a common module because it depends on Obsidian's API.
|
||||||
// However, we have to make compatible one for other platform.
|
// However, we have to make compatible one for other platform.
|
||||||
|
|
||||||
export class ModuleInputUIObsidian extends AbstractObsidianModule implements IObsidianModule, Confirm {
|
export class ModuleInputUIObsidian extends AbstractObsidianModule implements IObsidianModule, Confirm {
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.core.confirm = this;
|
this.core.confirm = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -22,8 +27,18 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
return askString(this.app, title, key, placeholder, isPassword);
|
return askString(this.app, title, key, placeholder, isPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
async askYesNoDialog(message: string, opt: { title?: string, defaultOption?: "Yes" | "No", timeout?: number } = { title: "Confirmation" }): Promise<"yes" | "no"> {
|
async askYesNoDialog(
|
||||||
const ret = await confirmWithMessageWithWideButton(this.plugin, opt.title || "Confirmation", message, ["Yes", "No"], opt.defaultOption ?? "No", opt.timeout);
|
message: string,
|
||||||
|
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number } = { title: "Confirmation" }
|
||||||
|
): Promise<"yes" | "no"> {
|
||||||
|
const ret = await confirmWithMessageWithWideButton(
|
||||||
|
this.plugin,
|
||||||
|
opt.title || "Confirmation",
|
||||||
|
message,
|
||||||
|
["Yes", "No"],
|
||||||
|
opt.defaultOption ?? "No",
|
||||||
|
opt.timeout
|
||||||
|
);
|
||||||
return ret == "Yes" ? "yes" : "no";
|
return ret == "Yes" ? "yes" : "no";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +46,19 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
return askSelectString(this.app, message, items);
|
return askSelectString(this.app, message, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
askSelectStringDialogue(message: string, buttons: string[], opt: { title?: string, defaultAction: (typeof buttons)[number], timeout?: number }): Promise<(typeof buttons)[number] | false> {
|
askSelectStringDialogue(
|
||||||
return confirmWithMessageWithWideButton(this.plugin, opt.title || "Select", message, buttons, opt.defaultAction, opt.timeout);
|
message: string,
|
||||||
|
buttons: string[],
|
||||||
|
opt: { title?: string; defaultAction: (typeof buttons)[number]; timeout?: number }
|
||||||
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
|
return confirmWithMessageWithWideButton(
|
||||||
|
this.plugin,
|
||||||
|
opt.title || "Select",
|
||||||
|
message,
|
||||||
|
buttons,
|
||||||
|
opt.defaultAction,
|
||||||
|
opt.timeout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
|
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void) {
|
||||||
@@ -40,9 +66,11 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
|
const [beforeText, afterText] = dialogText.split("{HERE}", 2);
|
||||||
doc.createEl("span", undefined, (a) => {
|
doc.createEl("span", undefined, (a) => {
|
||||||
a.appendText(beforeText);
|
a.appendText(beforeText);
|
||||||
a.appendChild(a.createEl("a", undefined, (anchor) => {
|
a.appendChild(
|
||||||
anchorCallback(anchor);
|
a.createEl("a", undefined, (anchor) => {
|
||||||
}));
|
anchorCallback(anchor);
|
||||||
|
})
|
||||||
|
);
|
||||||
a.appendText(afterText);
|
a.appendText(afterText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -55,8 +83,7 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
}
|
}
|
||||||
scheduleTask(popupKey + "-close", 20000, () => {
|
scheduleTask(popupKey + "-close", 20000, () => {
|
||||||
const popup = retrieveMemoObject<Notice>(popupKey);
|
const popup = retrieveMemoObject<Notice>(popupKey);
|
||||||
if (!popup)
|
if (!popup) return;
|
||||||
return;
|
|
||||||
if (popup?.noticeEl?.isShown()) {
|
if (popup?.noticeEl?.isShown()) {
|
||||||
popup.hide();
|
popup.hide();
|
||||||
}
|
}
|
||||||
@@ -64,8 +91,13 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
confirmWithMessage(title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false> {
|
confirmWithMessage(
|
||||||
|
title: string,
|
||||||
|
contentMd: string,
|
||||||
|
buttons: string[],
|
||||||
|
defaultAction: (typeof buttons)[number],
|
||||||
|
timeout?: number
|
||||||
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class AutoClosableModal extends Modal {
|
|||||||
onClose() {
|
onClose() {
|
||||||
if (this.removeEvent) {
|
if (this.removeEvent) {
|
||||||
this.removeEvent();
|
this.removeEvent();
|
||||||
this.removeEvent = undefined
|
this.removeEvent = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,14 @@ export class InputStringDialog extends AutoClosableModal {
|
|||||||
isManuallyClosed = false;
|
isManuallyClosed = false;
|
||||||
isPassword = false;
|
isPassword = false;
|
||||||
|
|
||||||
constructor(app: App, title: string, key: string, placeholder: string, isPassword: boolean, onSubmit: (result: string | false) => void) {
|
constructor(
|
||||||
|
app: App,
|
||||||
|
title: string,
|
||||||
|
key: string,
|
||||||
|
placeholder: string,
|
||||||
|
isPassword: boolean,
|
||||||
|
onSubmit: (result: string | false) => void
|
||||||
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.onSubmit = onSubmit;
|
this.onSubmit = onSubmit;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -45,27 +52,32 @@ export class InputStringDialog extends AutoClosableModal {
|
|||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
this.titleEl.setText(this.title);
|
this.titleEl.setText(this.title);
|
||||||
const formEl = contentEl.createDiv();
|
const formEl = contentEl.createDiv();
|
||||||
new Setting(formEl).setName(this.key).setClass(this.isPassword ? "password-input" : "normal-input").addText((text) =>
|
new Setting(formEl)
|
||||||
text.onChange((value) => {
|
.setName(this.key)
|
||||||
this.result = value;
|
.setClass(this.isPassword ? "password-input" : "normal-input")
|
||||||
})
|
.addText((text) =>
|
||||||
);
|
text.onChange((value) => {
|
||||||
new Setting(formEl).addButton((btn) =>
|
this.result = value;
|
||||||
btn
|
|
||||||
.setButtonText("Ok")
|
|
||||||
.setCta()
|
|
||||||
.onClick(() => {
|
|
||||||
this.isManuallyClosed = true;
|
|
||||||
this.close();
|
|
||||||
})
|
})
|
||||||
).addButton((btn) =>
|
);
|
||||||
btn
|
new Setting(formEl)
|
||||||
.setButtonText("Cancel")
|
.addButton((btn) =>
|
||||||
.setCta()
|
btn
|
||||||
.onClick(() => {
|
.setButtonText("Ok")
|
||||||
this.close();
|
.setCta()
|
||||||
})
|
.onClick(() => {
|
||||||
);
|
this.isManuallyClosed = true;
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((btn) =>
|
||||||
|
btn
|
||||||
|
.setButtonText("Cancel")
|
||||||
|
.setCta()
|
||||||
|
.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
@@ -81,13 +93,18 @@ export class InputStringDialog extends AutoClosableModal {
|
|||||||
}
|
}
|
||||||
export class PopoverSelectString extends FuzzySuggestModal<string> {
|
export class PopoverSelectString extends FuzzySuggestModal<string> {
|
||||||
app: App;
|
app: App;
|
||||||
callback: ((e: string) => void) | undefined = () => { };
|
callback: ((e: string) => void) | undefined = () => {};
|
||||||
getItemsFun: () => string[] = () => {
|
getItemsFun: () => string[] = () => {
|
||||||
return ["yes", "no"];
|
return ["yes", "no"];
|
||||||
|
};
|
||||||
|
|
||||||
}
|
constructor(
|
||||||
|
app: App,
|
||||||
constructor(app: App, note: string, placeholder: string | undefined, getItemsFun: (() => string[]) | undefined, callback: (e: string) => void) {
|
note: string,
|
||||||
|
placeholder: string | undefined,
|
||||||
|
getItemsFun: (() => string[]) | undefined,
|
||||||
|
callback: (e: string) => void
|
||||||
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.setPlaceholder((placeholder ?? "y/n) ") + note);
|
this.setPlaceholder((placeholder ?? "y/n) ") + note);
|
||||||
@@ -119,7 +136,6 @@ export class PopoverSelectString extends FuzzySuggestModal<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class MessageBox extends AutoClosableModal {
|
export class MessageBox extends AutoClosableModal {
|
||||||
|
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
title: string;
|
title: string;
|
||||||
contentMd: string;
|
contentMd: string;
|
||||||
@@ -134,7 +150,16 @@ export class MessageBox extends AutoClosableModal {
|
|||||||
|
|
||||||
onSubmit: (result: string | false) => void;
|
onSubmit: (result: string | false) => void;
|
||||||
|
|
||||||
constructor(plugin: Plugin, title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout: number | undefined, wideButton: boolean, onSubmit: (result: (typeof buttons)[number] | false) => void) {
|
constructor(
|
||||||
|
plugin: Plugin,
|
||||||
|
title: string,
|
||||||
|
contentMd: string,
|
||||||
|
buttons: string[],
|
||||||
|
defaultAction: (typeof buttons)[number],
|
||||||
|
timeout: number | undefined,
|
||||||
|
wideButton: boolean,
|
||||||
|
onSubmit: (result: (typeof buttons)[number] | false) => void
|
||||||
|
) {
|
||||||
super(plugin.app);
|
super(plugin.app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -196,20 +221,18 @@ export class MessageBox extends AutoClosableModal {
|
|||||||
this.timer = undefined;
|
this.timer = undefined;
|
||||||
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
this.defaultButtonComponent?.setButtonText(`${this.defaultAction}`);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
for (const button of this.buttons) {
|
for (const button of this.buttons) {
|
||||||
buttonSetting.addButton((btn) => {
|
buttonSetting.addButton((btn) => {
|
||||||
btn
|
btn.setButtonText(button).onClick(() => {
|
||||||
.setButtonText(button)
|
this.isManuallyClosed = true;
|
||||||
.onClick(() => {
|
this.result = button;
|
||||||
this.isManuallyClosed = true;
|
if (this.timer) {
|
||||||
this.result = button;
|
clearInterval(this.timer);
|
||||||
if (this.timer) {
|
this.timer = undefined;
|
||||||
clearInterval(this.timer);
|
}
|
||||||
this.timer = undefined;
|
this.close();
|
||||||
}
|
});
|
||||||
this.close();
|
|
||||||
})
|
|
||||||
if (button == this.defaultAction) {
|
if (button == this.defaultAction) {
|
||||||
this.defaultButtonComponent = btn;
|
this.defaultButtonComponent = btn;
|
||||||
btn.setCta();
|
btn.setCta();
|
||||||
@@ -219,8 +242,7 @@ export class MessageBox extends AutoClosableModal {
|
|||||||
btn.buttonEl.style.width = "100%";
|
btn.buttonEl.style.width = "100%";
|
||||||
}
|
}
|
||||||
return btn;
|
return btn;
|
||||||
}
|
});
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,25 +262,42 @@ export class MessageBox extends AutoClosableModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function confirmWithMessage(
|
||||||
|
plugin: Plugin,
|
||||||
|
title: string,
|
||||||
export function confirmWithMessage(plugin: Plugin, title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false> {
|
contentMd: string,
|
||||||
|
buttons: string[],
|
||||||
|
defaultAction: (typeof buttons)[number],
|
||||||
|
timeout?: number
|
||||||
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const dialog = new MessageBox(plugin, title, contentMd, buttons, defaultAction, timeout, false, (result) => res(result));
|
const dialog = new MessageBox(plugin, title, contentMd, buttons, defaultAction, timeout, false, (result) =>
|
||||||
|
res(result)
|
||||||
|
);
|
||||||
dialog.open();
|
dialog.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
export function confirmWithMessageWithWideButton(plugin: Plugin, title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false> {
|
export function confirmWithMessageWithWideButton(
|
||||||
|
plugin: Plugin,
|
||||||
|
title: string,
|
||||||
|
contentMd: string,
|
||||||
|
buttons: string[],
|
||||||
|
defaultAction: (typeof buttons)[number],
|
||||||
|
timeout?: number
|
||||||
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const dialog = new MessageBox(plugin, title, contentMd, buttons, defaultAction, timeout, true, (result) => res(result));
|
const dialog = new MessageBox(plugin, title, contentMd, buttons, defaultAction, timeout, true, (result) =>
|
||||||
|
res(result)
|
||||||
|
);
|
||||||
dialog.open();
|
dialog.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
|
export const askYesNo = (app: App, message: string): Promise<"yes" | "no"> => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const popover = new PopoverSelectString(app, message, undefined, undefined, (result) => res(result as "yes" | "no"));
|
const popover = new PopoverSelectString(app, message, undefined, undefined, (result) =>
|
||||||
|
res(result as "yes" | "no")
|
||||||
|
);
|
||||||
popover.open();
|
popover.open();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -271,11 +310,15 @@ export const askSelectString = (app: App, message: string, items: string[]): Pro
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const askString = (
|
||||||
export const askString = (app: App, title: string, key: string, placeholder: string, isPassword: boolean = false): Promise<string | false> => {
|
app: App,
|
||||||
|
title: string,
|
||||||
|
key: string,
|
||||||
|
placeholder: string,
|
||||||
|
isPassword: boolean = false
|
||||||
|
): Promise<string | false> => {
|
||||||
return new Promise((res) => {
|
return new Promise((res) => {
|
||||||
const dialog = new InputStringDialog(app, title, key, placeholder, isPassword, (result) => res(result));
|
const dialog = new InputStringDialog(app, title, key, placeholder, isPassword, (result) => res(result));
|
||||||
dialog.open();
|
dialog.open();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { markChangesAreSame } from "../../../common/utils.ts";
|
|||||||
import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
||||||
|
|
||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
||||||
return `fl:${typeof (file) == "string" ? file : file.path}`;
|
return `fl:${typeof file == "string" ? file : file.path}`;
|
||||||
}
|
}
|
||||||
function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike {
|
function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike {
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
@@ -35,9 +35,9 @@ async function processWriteFile<T>(file: TFile | TFolder | string | UXFileInfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SerializedFileAccess {
|
export class SerializedFileAccess {
|
||||||
app: App
|
app: App;
|
||||||
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>
|
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
||||||
constructor(app: App, plugin: typeof this["plugin"]) {
|
constructor(app: App, plugin: (typeof this)["plugin"]) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
@@ -72,10 +72,12 @@ export class SerializedFileAccess {
|
|||||||
|
|
||||||
async adapterWrite(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
async adapterWrite(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (typeof (data) === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options));
|
return await processWriteFile(file, () =>
|
||||||
|
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,19 +99,17 @@ export class SerializedFileAccess {
|
|||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
||||||
if (typeof (data) === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, async () => {
|
return await processWriteFile(file, async () => {
|
||||||
const oldData = await this.app.vault.read(file);
|
const oldData = await this.app.vault.read(file);
|
||||||
if (data === oldData) {
|
if (data === oldData) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await this.app.vault.modify(file, data, options)
|
await this.app.vault.modify(file, data, options);
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, async () => {
|
return await processWriteFile(file, async () => {
|
||||||
const oldData = await this.app.vault.readBinary(file);
|
const oldData = await this.app.vault.readBinary(file);
|
||||||
@@ -117,13 +117,17 @@ export class SerializedFileAccess {
|
|||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
await this.app.vault.modifyBinary(file, toArrayBuffer(data), options)
|
await this.app.vault.modifyBinary(file, toArrayBuffer(data), options);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async vaultCreate(path: string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions): Promise<TFile> {
|
async vaultCreate(
|
||||||
if (typeof (data) === "string") {
|
path: string,
|
||||||
|
data: string | ArrayBuffer | Uint8Array,
|
||||||
|
options?: DataWriteOptions
|
||||||
|
): Promise<TFile> {
|
||||||
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(path, () => this.app.vault.create(path, data, options));
|
return await processWriteFile(path, () => this.app.vault.create(path, data, options));
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(path, () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
|
return await processWriteFile(path, () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
|
||||||
@@ -135,7 +139,7 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) {
|
async adapterAppend(normalizedPath: string, data: string, options?: DataWriteOptions) {
|
||||||
return await this.app.vault.adapter.append(normalizedPath, data, options)
|
return await this.app.vault.adapter.append(normalizedPath, data, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: TFile | TFolder, force = false) {
|
async delete(file: TFile | TFolder, force = false) {
|
||||||
@@ -145,8 +149,6 @@ export class SerializedFileAccess {
|
|||||||
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
isStorageInsensitive(): boolean {
|
isStorageInsensitive(): boolean {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return this.app.vault.adapter.insensitive ?? true;
|
return this.app.vault.adapter.insensitive ?? true;
|
||||||
@@ -188,22 +190,23 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
touchedFiles: string[] = [];
|
touchedFiles: string[] = [];
|
||||||
|
|
||||||
|
|
||||||
touch(file: TFile | FilePath) {
|
touch(file: TFile | FilePath) {
|
||||||
const f = file instanceof TFile ? file : this.getAbstractFileByPath(file) as TFile;
|
const f = file instanceof TFile ? file : (this.getAbstractFileByPath(file) as TFile);
|
||||||
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
|
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
|
||||||
this.touchedFiles.unshift(key);
|
this.touchedFiles.unshift(key);
|
||||||
this.touchedFiles = this.touchedFiles.slice(0, 100);
|
this.touchedFiles = this.touchedFiles.slice(0, 100);
|
||||||
}
|
}
|
||||||
recentlyTouched(file: TFile | InternalFileInfo | UXFileInfoStub) {
|
recentlyTouched(file: TFile | InternalFileInfo | UXFileInfoStub) {
|
||||||
const key = "stat" in file ? `${file.path}-${file.stat.mtime}-${file.stat.size}` : `${file.path}-${file.mtime}-${file.size}`;
|
const key =
|
||||||
|
"stat" in file
|
||||||
|
? `${file.path}-${file.stat.mtime}-${file.stat.size}`
|
||||||
|
: `${file.path}-${file.mtime}-${file.size}`;
|
||||||
if (this.touchedFiles.indexOf(key) == -1) return false;
|
if (this.touchedFiles.indexOf(key) == -1) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
clearTouched() {
|
clearTouched() {
|
||||||
this.touchedFiles = [];
|
this.touchedFiles = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
import { TAbstractFile, TFile, TFolder } from "../../../deps.ts";
|
import { TAbstractFile, TFile, TFolder } from "../../../deps.ts";
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts";
|
import { shouldBeIgnored } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import { DEFAULT_SETTINGS, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, type FilePath, type FilePathWithPrefix, type UXFileInfoStub, type UXInternalFileInfoStub } from "../../../lib/src/common/types.ts";
|
import {
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
LOG_LEVEL_DEBUG,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type FilePath,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
type UXInternalFileInfoStub,
|
||||||
|
} from "../../../lib/src/common/types.ts";
|
||||||
import { delay, fireAndForget } from "../../../lib/src/common/utils.ts";
|
import { delay, fireAndForget } from "../../../lib/src/common/utils.ts";
|
||||||
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
|
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
|
||||||
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
|
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
|
||||||
import { finishAllWaitingForTimeout, finishWaitingForTimeout, isWaitingForTimeout, waitForTimeout } from "../../../lib/src/concurrency/task.ts";
|
import {
|
||||||
|
finishAllWaitingForTimeout,
|
||||||
|
finishWaitingForTimeout,
|
||||||
|
isWaitingForTimeout,
|
||||||
|
waitForTimeout,
|
||||||
|
} from "../../../lib/src/concurrency/task.ts";
|
||||||
import { Semaphore } from "../../../lib/src/concurrency/semaphore.ts";
|
import { Semaphore } from "../../../lib/src/concurrency/semaphore.ts";
|
||||||
import type { LiveSyncCore } from "../../../main.ts";
|
import type { LiveSyncCore } from "../../../main.ts";
|
||||||
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
||||||
|
|
||||||
|
|
||||||
export type FileEvent = {
|
export type FileEvent = {
|
||||||
type: FileEventType;
|
type: FileEventType;
|
||||||
file: UXFileInfoStub | UXInternalFileInfoStub;
|
file: UXFileInfoStub | UXInternalFileInfoStub;
|
||||||
@@ -21,19 +35,15 @@ export type FileEvent = {
|
|||||||
skipBatchWait?: boolean;
|
skipBatchWait?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export abstract class StorageEventManager {
|
export abstract class StorageEventManager {
|
||||||
abstract beginWatch(): void;
|
abstract beginWatch(): void;
|
||||||
abstract flushQueue(): void;
|
abstract flushQueue(): void;
|
||||||
abstract appendQueue(items: FileEvent[], ctx?: any): Promise<void>;
|
abstract appendQueue(items: FileEvent[], ctx?: any): Promise<void>;
|
||||||
abstract cancelQueue(key: string): void;
|
abstract cancelQueue(key: string): void;
|
||||||
abstract isWaiting(filename: FilePath): boolean;
|
abstract isWaiting(filename: FilePath): boolean;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class StorageEventManagerObsidian extends StorageEventManager {
|
export class StorageEventManagerObsidian extends StorageEventManager {
|
||||||
|
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
core: LiveSyncCore;
|
core: LiveSyncCore;
|
||||||
|
|
||||||
@@ -41,10 +51,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
||||||
}
|
}
|
||||||
get batchSaveMinimumDelay(): number {
|
get batchSaveMinimumDelay(): number {
|
||||||
return this.core.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay
|
return this.core.settings?.batchSaveMinimumDelay ?? DEFAULT_SETTINGS.batchSaveMinimumDelay;
|
||||||
}
|
}
|
||||||
get batchSaveMaximumDelay(): number {
|
get batchSaveMaximumDelay(): number {
|
||||||
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay
|
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
||||||
}
|
}
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
||||||
super();
|
super();
|
||||||
@@ -83,10 +93,11 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
const data = info?.data as string;
|
const data = info?.data as string;
|
||||||
const fi: FileEvent = {
|
const fi: FileEvent = {
|
||||||
type: "CHANGED", file: TFileToUXFileInfoStub(file), cachedData: data,
|
type: "CHANGED",
|
||||||
}
|
file: TFileToUXFileInfoStub(file),
|
||||||
void this.appendQueue([
|
cachedData: data,
|
||||||
fi])
|
};
|
||||||
|
void this.appendQueue([fi]);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
||||||
@@ -109,17 +120,27 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([
|
void this.appendQueue(
|
||||||
{
|
[
|
||||||
type: "DELETE", file: {
|
{
|
||||||
path: oldFile as FilePath, name: file.name, stat: {
|
type: "DELETE",
|
||||||
mtime: file.stat.mtime,
|
file: {
|
||||||
ctime: file.stat.ctime,
|
path: oldFile as FilePath,
|
||||||
size: file.stat.size,
|
name: file.name,
|
||||||
type: "file"
|
stat: {
|
||||||
}, deleted: true
|
mtime: file.stat.mtime,
|
||||||
}, skipBatchWait: true
|
ctime: file.stat.ctime,
|
||||||
}, { type: "CREATE", file: fileInfo, skipBatchWait: true },], ctx);
|
size: file.stat.size,
|
||||||
|
type: "file",
|
||||||
|
},
|
||||||
|
deleted: true,
|
||||||
|
},
|
||||||
|
skipBatchWait: true,
|
||||||
|
},
|
||||||
|
{ type: "CREATE", file: fileInfo, skipBatchWait: true },
|
||||||
|
],
|
||||||
|
ctx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Watch raw events (Internal API)
|
// Watch raw events (Internal API)
|
||||||
@@ -142,16 +163,23 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
if (!path.startsWith(this.plugin.app.vault.configDir)) return;
|
if (!path.startsWith(this.plugin.app.vault.configDir)) return;
|
||||||
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns
|
const ignorePatterns = this.plugin.settings.syncInternalFilesIgnorePatterns
|
||||||
.replace(/\n| /g, "")
|
.replace(/\n| /g, "")
|
||||||
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
.split(",")
|
||||||
if (ignorePatterns.some(e => path.match(e))) return;
|
.filter((e) => e)
|
||||||
|
.map((e) => new RegExp(e, "i"));
|
||||||
|
if (ignorePatterns.some((e) => path.match(e))) return;
|
||||||
if (path.endsWith("/")) {
|
if (path.endsWith("/")) {
|
||||||
// Folder
|
// Folder
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void this.appendQueue([
|
void this.appendQueue(
|
||||||
{
|
[
|
||||||
type: "INTERNAL", file: InternalFileToUXFileInfoStub(path),
|
{
|
||||||
}], null);
|
type: "INTERNAL",
|
||||||
|
file: InternalFileToUXFileInfoStub(path),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Cache file and waiting to can be proceed.
|
// Cache file and waiting to can be proceed.
|
||||||
async appendQueue(params: FileEvent[], ctx?: any) {
|
async appendQueue(params: FileEvent[], ctx?: any) {
|
||||||
@@ -164,25 +192,22 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
if (shouldBeIgnored(param.file.path)) {
|
if (shouldBeIgnored(param.file.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const atomicKey = [
|
const atomicKey = [0, 0, 0, 0, 0, 0].map((e) => `${Math.floor(Math.random() * 100000)}`).join("-");
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0].map(e => `${Math.floor(Math.random() * 100000)}`).join("-");
|
|
||||||
const type = param.type;
|
const type = param.type;
|
||||||
const file = param.file;
|
const file = param.file;
|
||||||
const oldPath = param.oldPath;
|
const oldPath = param.oldPath;
|
||||||
if (type !== "INTERNAL") {
|
if (type !== "INTERNAL") {
|
||||||
const size = (file as UXFileInfoStub).stat.size;
|
const size = (file as UXFileInfoStub).stat.size;
|
||||||
if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
|
if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
|
||||||
Logger(`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`, LOG_LEVEL_NOTICE);
|
Logger(
|
||||||
|
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (file instanceof TFolder) continue;
|
if (file instanceof TFolder) continue;
|
||||||
if (!await this.core.$$isTargetFile(file.path)) continue;
|
if (!(await this.core.$$isTargetFile(file.path))) continue;
|
||||||
|
|
||||||
// Stop cache using to prevent the corruption;
|
// Stop cache using to prevent the corruption;
|
||||||
// let cache: null | string | ArrayBuffer;
|
// let cache: null | string | ArrayBuffer;
|
||||||
@@ -197,13 +222,19 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
|
|
||||||
let cache: string | undefined = undefined;
|
let cache: string | undefined = undefined;
|
||||||
if (param.cachedData) {
|
if (param.cachedData) {
|
||||||
cache = param.cachedData
|
cache = param.cachedData;
|
||||||
}
|
}
|
||||||
this.enqueue({
|
this.enqueue({
|
||||||
type, args: {
|
type,
|
||||||
file: file, oldPath, cache, ctx,
|
args: {
|
||||||
}, skipBatchWait: param.skipBatchWait, key: atomicKey
|
file: file,
|
||||||
})
|
oldPath,
|
||||||
|
cache,
|
||||||
|
ctx,
|
||||||
|
},
|
||||||
|
skipBatchWait: param.skipBatchWait,
|
||||||
|
key: atomicKey,
|
||||||
|
});
|
||||||
processFiles.add(file.path as FilePath);
|
processFiles.add(file.path as FilePath);
|
||||||
if (oldPath) {
|
if (oldPath) {
|
||||||
processFiles.add(oldPath as FilePath);
|
processFiles.add(oldPath as FilePath);
|
||||||
@@ -238,7 +269,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
Logger(`Processing ${filename}: Started`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Started`, LOG_LEVEL_DEBUG);
|
||||||
let noMoreFiles = false;
|
let noMoreFiles = false;
|
||||||
do {
|
do {
|
||||||
const target = this.bufferedQueuedItems.find(e => e.args.file.path == filename);
|
const target = this.bufferedQueuedItems.find((e) => e.args.file.path == filename);
|
||||||
if (target === undefined) {
|
if (target === undefined) {
|
||||||
noMoreFiles = true;
|
noMoreFiles = true;
|
||||||
break;
|
break;
|
||||||
@@ -251,7 +282,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
// }
|
// }
|
||||||
const type = target.type;
|
const type = target.type;
|
||||||
if (target.cancelled) {
|
if (target.cancelled) {
|
||||||
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG)
|
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
|
||||||
this.cancelStandingBy(target);
|
this.cancelStandingBy(target);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -261,19 +292,31 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
let canWait = true;
|
let canWait = true;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (waitedSince !== undefined) {
|
if (waitedSince !== undefined) {
|
||||||
if (waitedSince + (this.batchSaveMaximumDelay * 1000) < now) {
|
if (waitedSince + this.batchSaveMaximumDelay * 1000 < now) {
|
||||||
Logger(`Processing ${filename}: Could not wait no more: ${operationType}`, LOG_LEVEL_INFO)
|
Logger(
|
||||||
|
`Processing ${filename}: Could not wait no more: ${operationType}`,
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
canWait = false;
|
canWait = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canWait) {
|
if (canWait) {
|
||||||
if (waitedSince === undefined) this.waitedSince.set(filename, now)
|
if (waitedSince === undefined) this.waitedSince.set(filename, now);
|
||||||
target.batched = true
|
target.batched = true;
|
||||||
Logger(`Processing ${filename}: Waiting for batch save delay: ${operationType}`, LOG_LEVEL_DEBUG)
|
Logger(
|
||||||
|
`Processing ${filename}: Waiting for batch save delay: ${operationType}`,
|
||||||
|
LOG_LEVEL_DEBUG
|
||||||
|
);
|
||||||
this.updateStatus();
|
this.updateStatus();
|
||||||
const result = await waitForTimeout(`storage-event-manager-batchsave-${filename}`, this.batchSaveMinimumDelay * 1000);
|
const result = await waitForTimeout(
|
||||||
|
`storage-event-manager-batchsave-${filename}`,
|
||||||
|
this.batchSaveMinimumDelay * 1000
|
||||||
|
);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
Logger(`Processing ${filename}: Cancelled by new queue: ${operationType}`, LOG_LEVEL_DEBUG)
|
Logger(
|
||||||
|
`Processing ${filename}: Cancelled by new queue: ${operationType}`,
|
||||||
|
LOG_LEVEL_DEBUG
|
||||||
|
);
|
||||||
// If could not wait for the timeout, possibly we got a new queue. therefore, currently processing one should be cancelled
|
// If could not wait for the timeout, possibly we got a new queue. therefore, currently processing one should be cancelled
|
||||||
this.cancelStandingBy(target);
|
this.cancelStandingBy(target);
|
||||||
continue;
|
continue;
|
||||||
@@ -281,16 +324,19 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger(`Processing ${filename}:Requested to perform immediately ${filename}: ${operationType}`, LOG_LEVEL_DEBUG)
|
Logger(
|
||||||
|
`Processing ${filename}:Requested to perform immediately ${filename}: ${operationType}`,
|
||||||
|
LOG_LEVEL_DEBUG
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Logger(`Processing ${filename}: Request main to process: ${operationType}`, LOG_LEVEL_DEBUG)
|
Logger(`Processing ${filename}: Request main to process: ${operationType}`, LOG_LEVEL_DEBUG);
|
||||||
await this.requestProcessQueue(target);
|
await this.requestProcessQueue(target);
|
||||||
} while (!noMoreFiles)
|
} while (!noMoreFiles);
|
||||||
} finally {
|
} finally {
|
||||||
release()
|
release();
|
||||||
}
|
}
|
||||||
Logger(`Processing ${filename}: Finished`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Finished`, LOG_LEVEL_DEBUG);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelStandingBy(fei: FileEventItem) {
|
cancelStandingBy(fei: FileEventItem) {
|
||||||
@@ -302,30 +348,30 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
try {
|
try {
|
||||||
this.processingCount++;
|
this.processingCount++;
|
||||||
this.bufferedQueuedItems.remove(fei);
|
this.bufferedQueuedItems.remove(fei);
|
||||||
this.updateStatus()
|
this.updateStatus();
|
||||||
this.waitedSince.delete(fei.args.file.path);
|
this.waitedSince.delete(fei.args.file.path);
|
||||||
await this.handleFileEvent(fei);
|
await this.handleFileEvent(fei);
|
||||||
} finally {
|
} finally {
|
||||||
this.processingCount--;
|
this.processingCount--;
|
||||||
this.updateStatus()
|
this.updateStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isWaiting(filename: FilePath) {
|
isWaiting(filename: FilePath) {
|
||||||
return isWaitingForTimeout(`storage-event-manager-batchsave-${filename}`);
|
return isWaitingForTimeout(`storage-event-manager-batchsave-${filename}`);
|
||||||
}
|
}
|
||||||
flushQueue() {
|
flushQueue() {
|
||||||
this.bufferedQueuedItems.forEach(e => e.skipBatchWait = true)
|
this.bufferedQueuedItems.forEach((e) => (e.skipBatchWait = true));
|
||||||
finishAllWaitingForTimeout("storage-event-manager-batchsave-", true);
|
finishAllWaitingForTimeout("storage-event-manager-batchsave-", true);
|
||||||
}
|
}
|
||||||
cancelQueue(key: string) {
|
cancelQueue(key: string) {
|
||||||
this.bufferedQueuedItems.forEach(e => {
|
this.bufferedQueuedItems.forEach((e) => {
|
||||||
if (e.key === key) e.skipBatchWait = true
|
if (e.key === key) e.skipBatchWait = true;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
updateStatus() {
|
updateStatus() {
|
||||||
const allItems = this.bufferedQueuedItems.filter(e => !e.cancelled)
|
const allItems = this.bufferedQueuedItems.filter((e) => !e.cancelled);
|
||||||
const batchedCount = allItems.filter(e => e.batched && !e.skipBatchWait).length;
|
const batchedCount = allItems.filter((e) => e.batched && !e.skipBatchWait).length;
|
||||||
this.core.batched.value = batchedCount
|
this.core.batched.value = batchedCount;
|
||||||
this.core.processing.value = this.processingCount;
|
this.core.processing.value = this.processingCount;
|
||||||
this.core.totalQueued.value = allItems.length - batchedCount;
|
this.core.totalQueued.value = allItems.length - batchedCount;
|
||||||
}
|
}
|
||||||
@@ -336,7 +382,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
return await serialized(lockKey, async () => {
|
return await serialized(lockKey, async () => {
|
||||||
// TODO CHECK
|
// TODO CHECK
|
||||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||||
const last = Number(await this.core.kvDB.get(key) || 0);
|
const last = Number((await this.core.kvDB.get(key)) || 0);
|
||||||
if (queue.type == "INTERNAL" || file.isInternal) {
|
if (queue.type == "INTERNAL" || file.isInternal) {
|
||||||
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
|
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
|
||||||
} else {
|
} else {
|
||||||
@@ -346,13 +392,16 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
} else {
|
} else {
|
||||||
if (file.stat.mtime == last) {
|
if (file.stat.mtime == last) {
|
||||||
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
// Should Cancel the relative operations? (e.g. rename)
|
// Should Cancel the relative operations? (e.g. rename)
|
||||||
// this.cancelRelativeEvent(queue);
|
// this.cancelRelativeEvent(queue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!await this.core.$anyHandlerProcessesFileEvent(queue)) {
|
if (!(await this.core.$anyHandlerProcessesFileEvent(queue))) {
|
||||||
Logger(`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`, LOG_LEVEL_INFO);
|
Logger(
|
||||||
// cancel running queues and remove one of atomic operation (e.g. rename)
|
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
|
// cancel running queues and remove one of atomic operation (e.g. rename)
|
||||||
this.cancelRelativeEvent(queue);
|
this.cancelRelativeEvent(queue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -364,4 +413,4 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
cancelRelativeEvent(item: FileEventItem): void {
|
cancelRelativeEvent(item: FileEventItem): void {
|
||||||
this.cancelQueue(item.key);
|
this.cancelQueue(item.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,22 @@ import type { SerializedFileAccess } from "./SerializedFileAccess.ts";
|
|||||||
import { addPrefix, isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
import { addPrefix, isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||||
import { createBlob } from "../../../lib/src/common/utils.ts";
|
import { createBlob } from "../../../lib/src/common/utils.ts";
|
||||||
import type { FilePath, FilePathWithPrefix, UXFileInfo, UXFileInfoStub, UXFolderInfo, UXInternalFileInfoStub } from "../../../lib/src/common/types.ts";
|
import type {
|
||||||
|
FilePath,
|
||||||
|
FilePathWithPrefix,
|
||||||
|
UXFileInfo,
|
||||||
|
UXFileInfoStub,
|
||||||
|
UXFolderInfo,
|
||||||
|
UXInternalFileInfoStub,
|
||||||
|
} from "../../../lib/src/common/types.ts";
|
||||||
import type { LiveSyncCore } from "../../../main.ts";
|
import type { LiveSyncCore } from "../../../main.ts";
|
||||||
|
|
||||||
export async function TFileToUXFileInfo(core: LiveSyncCore, file: TFile, prefix?: string, deleted?: boolean): Promise<UXFileInfo> {
|
export async function TFileToUXFileInfo(
|
||||||
|
core: LiveSyncCore,
|
||||||
|
file: TFile,
|
||||||
|
prefix?: string,
|
||||||
|
deleted?: boolean
|
||||||
|
): Promise<UXFileInfo> {
|
||||||
const isPlain = isPlainText(file.name);
|
const isPlain = isPlainText(file.name);
|
||||||
const possiblyLarge = !isPlain;
|
const possiblyLarge = !isPlain;
|
||||||
let content: Blob;
|
let content: Blob;
|
||||||
@@ -34,11 +46,14 @@ export async function TFileToUXFileInfo(core: LiveSyncCore, file: TFile, prefix?
|
|||||||
type: "file",
|
type: "file",
|
||||||
},
|
},
|
||||||
body: content,
|
body: content,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function InternalFileToUXFileInfo(fullPath: string, vaultAccess: SerializedFileAccess, prefix: string = ICHeader): Promise<UXFileInfo> {
|
export async function InternalFileToUXFileInfo(
|
||||||
|
fullPath: string,
|
||||||
|
vaultAccess: SerializedFileAccess,
|
||||||
|
prefix: string = ICHeader
|
||||||
|
): Promise<UXFileInfo> {
|
||||||
const name = fullPath.split("/").pop() as string;
|
const name = fullPath.split("/").pop() as string;
|
||||||
const stat = await vaultAccess.adapterStat(fullPath);
|
const stat = await vaultAccess.adapterStat(fullPath);
|
||||||
if (stat == null) throw new Error(`File not found: ${fullPath}`);
|
if (stat == null) throw new Error(`File not found: ${fullPath}`);
|
||||||
@@ -64,7 +79,7 @@ export async function InternalFileToUXFileInfo(fullPath: string, vaultAccess: Se
|
|||||||
type: "file",
|
type: "file",
|
||||||
},
|
},
|
||||||
body: content,
|
body: content,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TFileToUXFileInfoStub(file: TFile | TAbstractFile, deleted?: boolean): UXFileInfoStub {
|
export function TFileToUXFileInfoStub(file: TFile | TAbstractFile, deleted?: boolean): UXFileInfoStub {
|
||||||
@@ -81,8 +96,8 @@ export function TFileToUXFileInfoStub(file: TFile | TAbstractFile, deleted?: boo
|
|||||||
ctime: file.stat.ctime,
|
ctime: file.stat.ctime,
|
||||||
type: "file",
|
type: "file",
|
||||||
},
|
},
|
||||||
deleted: deleted
|
deleted: deleted,
|
||||||
}
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
export function InternalFileToUXFileInfoStub(filename: FilePathWithPrefix, deleted?: boolean): UXInternalFileInfoStub {
|
export function InternalFileToUXFileInfoStub(filename: FilePathWithPrefix, deleted?: boolean): UXInternalFileInfoStub {
|
||||||
@@ -93,8 +108,8 @@ export function InternalFileToUXFileInfoStub(filename: FilePathWithPrefix, delet
|
|||||||
isFolder: false,
|
isFolder: false,
|
||||||
stat: undefined,
|
stat: undefined,
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
deleted
|
deleted,
|
||||||
}
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
export function TFolderToUXFileInfoStub(file: TFolder): UXFolderInfo {
|
export function TFolderToUXFileInfoStub(file: TFolder): UXFolderInfo {
|
||||||
@@ -103,7 +118,7 @@ export function TFolderToUXFileInfoStub(file: TFolder): UXFolderInfo {
|
|||||||
path: file.path as FilePathWithPrefix,
|
path: file.path as FilePathWithPrefix,
|
||||||
parent: file.parent?.path as FilePath | undefined,
|
parent: file.parent?.path as FilePath | undefined,
|
||||||
isFolder: true,
|
isFolder: true,
|
||||||
children: file.children.map(e => TFileToUXFileInfoStub(e)),
|
children: file.children.map((e) => TFileToUXFileInfoStub(e)),
|
||||||
}
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,35 @@ import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
|||||||
import { throttle } from "octagonal-wheels/function";
|
import { throttle } from "octagonal-wheels/function";
|
||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
import { BASE_IS_NEW, compareFileFreshness, EVEN, getPath, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts";
|
import { BASE_IS_NEW, compareFileFreshness, EVEN, getPath, isValidPath, TARGET_IS_NEW } from "../../common/utils.ts";
|
||||||
import { type FilePathWithPrefixLC, type FilePathWithPrefix, type MetaEntry, isMetaEntry, type EntryDoc, LOG_LEVEL_VERBOSE, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, type UXFileInfoStub } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
type FilePathWithPrefixLC,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type MetaEntry,
|
||||||
|
isMetaEntry,
|
||||||
|
type EntryDoc,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_DEBUG,
|
||||||
|
type UXFileInfoStub,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
||||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleInitializerFile extends AbstractModule implements ICoreModule {
|
export class ModuleInitializerFile extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async $$performFullScan(showingNotice?: boolean): Promise<void> {
|
async $$performFullScan(showingNotice?: boolean): Promise<void> {
|
||||||
|
|
||||||
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
||||||
const isInitialized = await (this.core.kvDB.get<boolean>("initialized")) || false;
|
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
|
||||||
// synchronize all files between database and storage.
|
// synchronize all files between database and storage.
|
||||||
if (!this.settings.isConfigured) {
|
if (!this.settings.isConfigured) {
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
this._log("LiveSync is not configured yet. Synchronising between the storage and the local database is now prevented.", LOG_LEVEL_NOTICE, "syncAll");
|
this._log(
|
||||||
|
"LiveSync is not configured yet. Synchronising between the storage and the local database is now prevented.",
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
"syncAll"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,22 +60,25 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
return path as FilePathWithPrefixLC;
|
return path as FilePathWithPrefixLC;
|
||||||
}
|
}
|
||||||
return (path as string).toLowerCase() as FilePathWithPrefixLC;
|
return (path as string).toLowerCase() as FilePathWithPrefixLC;
|
||||||
}
|
};
|
||||||
|
|
||||||
// If handleFilenameCaseSensitive is enabled, `FilePathWithPrefixLC` is the same as `FilePathWithPrefix`.
|
// If handleFilenameCaseSensitive is enabled, `FilePathWithPrefixLC` is the same as `FilePathWithPrefix`.
|
||||||
|
|
||||||
const storageFileNameMap = Object.fromEntries(_filesStorage.map((e) => [
|
const storageFileNameMap = Object.fromEntries(
|
||||||
e.path, e] as [FilePathWithPrefix, UXFileInfoStub]));
|
_filesStorage.map((e) => [e.path, e] as [FilePathWithPrefix, UXFileInfoStub])
|
||||||
|
);
|
||||||
|
|
||||||
const storageFileNames = Object.keys(storageFileNameMap) as FilePathWithPrefix[];
|
const storageFileNames = Object.keys(storageFileNameMap) as FilePathWithPrefix[];
|
||||||
|
|
||||||
const storageFileNameCapsPair = storageFileNames.map((e) => [
|
const storageFileNameCapsPair = storageFileNames.map(
|
||||||
e, convertCase(e)] as [FilePathWithPrefix, FilePathWithPrefixLC]);
|
(e) => [e, convertCase(e)] as [FilePathWithPrefix, FilePathWithPrefixLC]
|
||||||
|
);
|
||||||
|
|
||||||
// const storageFileNameCS2CI = Object.fromEntries(storageFileNameCapsPair) as Record<FilePathWithPrefix, FilePathWithPrefixLC>;
|
// const storageFileNameCS2CI = Object.fromEntries(storageFileNameCapsPair) as Record<FilePathWithPrefix, FilePathWithPrefixLC>;
|
||||||
const storageFileNameCI2CS = Object.fromEntries(storageFileNameCapsPair.map(e => [
|
const storageFileNameCI2CS = Object.fromEntries(storageFileNameCapsPair.map((e) => [e[1], e[0]])) as Record<
|
||||||
e[1], e[0]])) as Record<FilePathWithPrefixLC, FilePathWithPrefix>;
|
FilePathWithPrefixLC,
|
||||||
|
FilePathWithPrefix
|
||||||
|
>;
|
||||||
|
|
||||||
this._log("Collecting local files on the DB", LOG_LEVEL_VERBOSE);
|
this._log("Collecting local files on the DB", LOG_LEVEL_VERBOSE);
|
||||||
const _DBEntries = [] as MetaEntry[];
|
const _DBEntries = [] as MetaEntry[];
|
||||||
@@ -70,10 +86,15 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
let count = 0;
|
let count = 0;
|
||||||
for await (const doc of this.localDatabase.findAllNormalDocs()) {
|
for await (const doc of this.localDatabase.findAllNormalDocs()) {
|
||||||
count++;
|
count++;
|
||||||
if (count % 25 == 0) this._log(`Collecting local files on the DB: ${count}`, showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO, "syncAll");
|
if (count % 25 == 0)
|
||||||
|
this._log(
|
||||||
|
`Collecting local files on the DB: ${count}`,
|
||||||
|
showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO,
|
||||||
|
"syncAll"
|
||||||
|
);
|
||||||
const path = getPath(doc);
|
const path = getPath(doc);
|
||||||
|
|
||||||
if (isValidPath(path) && await this.core.$$isTargetFile(path, true)) {
|
if (isValidPath(path) && (await this.core.$$isTargetFile(path, true))) {
|
||||||
if (!isMetaEntry(doc)) {
|
if (!isMetaEntry(doc)) {
|
||||||
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
||||||
continue;
|
continue;
|
||||||
@@ -82,24 +103,28 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const databaseFileNameMap = Object.fromEntries(_DBEntries.map((e) => [
|
const databaseFileNameMap = Object.fromEntries(
|
||||||
getPath(e), e] as [FilePathWithPrefix, MetaEntry]));
|
_DBEntries.map((e) => [getPath(e), e] as [FilePathWithPrefix, MetaEntry])
|
||||||
|
);
|
||||||
const databaseFileNames = Object.keys(databaseFileNameMap) as FilePathWithPrefix[];
|
const databaseFileNames = Object.keys(databaseFileNameMap) as FilePathWithPrefix[];
|
||||||
const databaseFileNameCapsPair = databaseFileNames.map((e) => [
|
const databaseFileNameCapsPair = databaseFileNames.map(
|
||||||
e, convertCase(e)] as [FilePathWithPrefix, FilePathWithPrefixLC]);
|
(e) => [e, convertCase(e)] as [FilePathWithPrefix, FilePathWithPrefixLC]
|
||||||
|
);
|
||||||
// const databaseFileNameCS2CI = Object.fromEntries(databaseFileNameCapsPair) as Record<FilePathWithPrefix, FilePathWithPrefixLC>;
|
// const databaseFileNameCS2CI = Object.fromEntries(databaseFileNameCapsPair) as Record<FilePathWithPrefix, FilePathWithPrefixLC>;
|
||||||
const databaseFileNameCI2CS = Object.fromEntries(databaseFileNameCapsPair.map(e => [
|
const databaseFileNameCI2CS = Object.fromEntries(databaseFileNameCapsPair.map((e) => [e[1], e[0]])) as Record<
|
||||||
e[1], e[0]])) as Record<FilePathWithPrefix, FilePathWithPrefixLC>;
|
FilePathWithPrefix,
|
||||||
|
FilePathWithPrefixLC
|
||||||
|
>;
|
||||||
|
|
||||||
const allFiles = unique([
|
const allFiles = unique([
|
||||||
...Object.keys(databaseFileNameCI2CS),
|
...Object.keys(databaseFileNameCI2CS),
|
||||||
...Object.keys(storageFileNameCI2CS)]) as FilePathWithPrefixLC[];
|
...Object.keys(storageFileNameCI2CS),
|
||||||
|
]) as FilePathWithPrefixLC[];
|
||||||
|
|
||||||
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
|
|
||||||
|
|
||||||
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
||||||
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
||||||
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
||||||
@@ -128,92 +153,110 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
let success = 0;
|
let success = 0;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
const step = 10;
|
const step = 10;
|
||||||
const processor = new QueueProcessor(async (e) => {
|
const processor = new QueueProcessor(
|
||||||
try {
|
async (e) => {
|
||||||
await callback(e[0]);
|
try {
|
||||||
success++;
|
await callback(e[0]);
|
||||||
// return
|
success++;
|
||||||
} catch (ex) {
|
// return
|
||||||
this._log(`Error while ${procedureName}`, LOG_LEVEL_NOTICE);
|
} catch (ex) {
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(`Error while ${procedureName}`, LOG_LEVEL_NOTICE);
|
||||||
failed++;
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
failed++;
|
||||||
if ((success + failed) % step == 0) {
|
}
|
||||||
const msg = `${procedureName}: DONE:${success}, FAILED:${failed}, LAST:${processor._queue.length}`;
|
if ((success + failed) % step == 0) {
|
||||||
updateLog(procedureName, msg);
|
const msg = `${procedureName}: DONE:${success}, FAILED:${failed}, LAST:${processor._queue.length}`;
|
||||||
}
|
updateLog(procedureName, msg);
|
||||||
return;
|
}
|
||||||
}, {
|
return;
|
||||||
batchSize: 1,
|
},
|
||||||
concurrentLimit: 10,
|
{
|
||||||
delay: 0,
|
batchSize: 1,
|
||||||
suspended: true,
|
concurrentLimit: 10,
|
||||||
maintainDelay: false,
|
delay: 0,
|
||||||
interval: 0
|
suspended: true,
|
||||||
}, objects)
|
maintainDelay: false,
|
||||||
|
interval: 0,
|
||||||
|
},
|
||||||
|
objects
|
||||||
|
);
|
||||||
await processor.waitForAllDoneAndTerminate();
|
await processor.waitForAllDoneAndTerminate();
|
||||||
const msg = `${procedureName} All done: DONE:${success}, FAILED:${failed}`;
|
const msg = `${procedureName} All done: DONE:${success}, FAILED:${failed}`;
|
||||||
updateLog(procedureName, msg)
|
updateLog(procedureName, msg);
|
||||||
}
|
};
|
||||||
initProcess.push(runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
initProcess.push(
|
||||||
// console.warn("UPDATE DATABASE", e);
|
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
||||||
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
// console.warn("UPDATE DATABASE", e);
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
||||||
const path = file.path;
|
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
const path = file.path;
|
||||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
eventHub.emitEvent("event-file-changed", { file: path, automated: true });
|
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
||||||
} else {
|
eventHub.emitEvent("event-file-changed", { file: path, automated: true });
|
||||||
this._log(`UPDATE DATABASE: ${e} has been skipped due to file size exceeding the limit`, logLevel);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
initProcess.push(runAll("UPDATE STORAGE", filesExistOnlyInDatabase, async (e) => {
|
|
||||||
const w = databaseFileNameMap[databaseFileNameCI2CS[e]];
|
|
||||||
const path = getPath(w) ?? e;
|
|
||||||
if (w && !(w.deleted || w._deleted)) {
|
|
||||||
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
|
||||||
// await this.pullFile(path, undefined, false, undefined, false);
|
|
||||||
// Memo: No need to force
|
|
||||||
await this.core.fileHandler.dbToStorage(path, null, true);
|
|
||||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(e, true));
|
|
||||||
eventHub.emitEvent("event-file-changed", {
|
|
||||||
file: e, automated: true
|
|
||||||
});
|
|
||||||
this._log(`Check or pull from db:${path} OK`);
|
|
||||||
} else {
|
} else {
|
||||||
this._log(`UPDATE STORAGE: ${path} has been skipped due to file size exceeding the limit`, logLevel);
|
this._log(`UPDATE DATABASE: ${e} has been skipped due to file size exceeding the limit`, logLevel);
|
||||||
}
|
}
|
||||||
} else if (w) {
|
})
|
||||||
this._log(`Deletion history skipped: ${path}`, LOG_LEVEL_VERBOSE);
|
);
|
||||||
} else {
|
initProcess.push(
|
||||||
this._log(`entry not found: ${path}`);
|
runAll("UPDATE STORAGE", filesExistOnlyInDatabase, async (e) => {
|
||||||
}
|
const w = databaseFileNameMap[databaseFileNameCI2CS[e]];
|
||||||
}));
|
const path = getPath(w) ?? e;
|
||||||
|
if (w && !(w.deleted || w._deleted)) {
|
||||||
|
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
||||||
|
// await this.pullFile(path, undefined, false, undefined, false);
|
||||||
|
// Memo: No need to force
|
||||||
|
await this.core.fileHandler.dbToStorage(path, null, true);
|
||||||
|
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(e, true));
|
||||||
|
eventHub.emitEvent("event-file-changed", {
|
||||||
|
file: e,
|
||||||
|
automated: true,
|
||||||
|
});
|
||||||
|
this._log(`Check or pull from db:${path} OK`);
|
||||||
|
} else {
|
||||||
|
this._log(
|
||||||
|
`UPDATE STORAGE: ${path} has been skipped due to file size exceeding the limit`,
|
||||||
|
logLevel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (w) {
|
||||||
|
this._log(`Deletion history skipped: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
} else {
|
||||||
|
this._log(`entry not found: ${path}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const fileMap = filesExistBoth.map(path => {
|
const fileMap = filesExistBoth.map((path) => {
|
||||||
const file = storageFileNameMap[storageFileNameCI2CS[path]];
|
const file = storageFileNameMap[storageFileNameCI2CS[path]];
|
||||||
const doc = databaseFileNameMap[databaseFileNameCI2CS[path]];
|
const doc = databaseFileNameMap[databaseFileNameCI2CS[path]];
|
||||||
return { file, doc }
|
return { file, doc };
|
||||||
})
|
});
|
||||||
initProcess.push(runAll("SYNC DATABASE AND STORAGE", fileMap, async (e) => {
|
initProcess.push(
|
||||||
const { file, doc } = e;
|
runAll("SYNC DATABASE AND STORAGE", fileMap, async (e) => {
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
const { file, doc } = e;
|
||||||
await this.syncFileBetweenDBandStorage(file, doc);
|
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
||||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(getPath(doc), true));
|
await this.syncFileBetweenDBandStorage(file, doc);
|
||||||
eventHub.emitEvent("event-file-changed", {
|
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(getPath(doc), true));
|
||||||
file: getPath(doc), automated: true
|
eventHub.emitEvent("event-file-changed", {
|
||||||
});
|
file: getPath(doc),
|
||||||
} else {
|
automated: true,
|
||||||
this._log(`SYNC DATABASE AND STORAGE: ${getPath(doc)} has been skipped due to file size exceeding the limit`, logLevel);
|
});
|
||||||
}
|
} else {
|
||||||
}))
|
this._log(
|
||||||
|
`SYNC DATABASE AND STORAGE: ${getPath(doc)} has been skipped due to file size exceeding the limit`,
|
||||||
|
logLevel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(initProcess);
|
await Promise.all(initProcess);
|
||||||
|
|
||||||
// this.setStatusBarText(`NOW TRACKING!`);
|
// this.setStatusBarText(`NOW TRACKING!`);
|
||||||
this._log("Initialized, NOW TRACKING!");
|
this._log("Initialized, NOW TRACKING!");
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
await (this.core.kvDB.set("initialized", true))
|
await this.core.kvDB.set("initialized", true);
|
||||||
}
|
}
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
||||||
@@ -222,14 +265,14 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
|
|
||||||
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
throw new Error(`Missing doc:${(file as any).path}`)
|
throw new Error(`Missing doc:${(file as any).path}`);
|
||||||
}
|
}
|
||||||
if ("path" in file) {
|
if ("path" in file) {
|
||||||
const w = this.core.storageAccess.getFileStub((file as any).path);
|
const w = this.core.storageAccess.getFileStub((file as any).path);
|
||||||
if (w) {
|
if (w) {
|
||||||
file = w;
|
file = w;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Missing file:${(file as any).path}`)
|
throw new Error(`Missing file:${(file as any).path}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,21 +283,28 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log("STORAGE -> DB :" + file.path);
|
this._log("STORAGE -> DB :" + file.path);
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
eventHub.emitEvent("event-file-changed", {
|
eventHub.emitEvent("event-file-changed", {
|
||||||
file: file.path, automated: true
|
file: file.path,
|
||||||
|
automated: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this._log(`STORAGE -> DB : ${file.path} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`STORAGE -> DB : ${file.path} has been skipped due to file size exceeding the limit`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TARGET_IS_NEW:
|
case TARGET_IS_NEW:
|
||||||
if (!this.core.$$isFileSizeExceeded(doc.size)) {
|
if (!this.core.$$isFileSizeExceeded(doc.size)) {
|
||||||
this._log("STORAGE <- DB :" + file.path);
|
this._log("STORAGE <- DB :" + file.path);
|
||||||
if (!await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
if (!(await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true))) {
|
||||||
this._log(`STORAGE <- DB : Cloud not read ${file.path}, possibly deleted`, LOG_LEVEL_NOTICE);
|
this._log(`STORAGE <- DB : Cloud not read ${file.path}, possibly deleted`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
return caches;
|
return caches;
|
||||||
} else {
|
} else {
|
||||||
this._log(`STORAGE <- DB : ${file.path} has been skipped due to file size exceeding the limit`, LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
`STORAGE <- DB : ${file.path} has been skipped due to file size exceeding the limit`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EVEN:
|
case EVEN:
|
||||||
@@ -263,31 +313,29 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
default:
|
default:
|
||||||
this._log("STORAGE ?? DB :" + file.path + " Something got weird");
|
this._log("STORAGE ?? DB :" + file.path + " Something got weird");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// This method uses an old version of database accessor, which is not recommended.
|
// This method uses an old version of database accessor, which is not recommended.
|
||||||
// TODO: Fix
|
// TODO: Fix
|
||||||
async collectDeletedFiles() {
|
async collectDeletedFiles() {
|
||||||
const limitDays = this.settings.automaticallyDeleteMetadataOfDeletedFiles;
|
const limitDays = this.settings.automaticallyDeleteMetadataOfDeletedFiles;
|
||||||
if (limitDays <= 0) return;
|
if (limitDays <= 0) return;
|
||||||
this._log(`Checking expired file history`);
|
this._log(`Checking expired file history`);
|
||||||
const limit = Date.now() - (86400 * 1000 * limitDays);
|
const limit = Date.now() - 86400 * 1000 * limitDays;
|
||||||
const notes: {
|
const notes: {
|
||||||
path: string,
|
path: string;
|
||||||
mtime: number,
|
mtime: number;
|
||||||
ttl: number,
|
ttl: number;
|
||||||
doc: PouchDB.Core.ExistingDocument<EntryDoc & PouchDB.Core.AllDocsMeta>
|
doc: PouchDB.Core.ExistingDocument<EntryDoc & PouchDB.Core.AllDocsMeta>;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
if (isAnyNote(doc)) {
|
if (isAnyNote(doc)) {
|
||||||
if (doc.deleted && (doc.mtime - limit) < 0) {
|
if (doc.deleted && doc.mtime - limit < 0) {
|
||||||
notes.push({
|
notes.push({
|
||||||
path: getPath(doc),
|
path: getPath(doc),
|
||||||
mtime: doc.mtime,
|
mtime: doc.mtime,
|
||||||
ttl: (doc.mtime - limit) / 1000 / 86400,
|
ttl: (doc.mtime - limit) / 1000 / 86400,
|
||||||
doc: doc
|
doc: doc,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,11 +356,11 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
|
|
||||||
async $$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> {
|
async $$initializeDatabase(showingNotice: boolean = false, reopenDatabase = true): Promise<boolean> {
|
||||||
this.core.$$resetIsReady();
|
this.core.$$resetIsReady();
|
||||||
if ((!reopenDatabase) || await this.core.$$openDatabase()) {
|
if (!reopenDatabase || (await this.core.$$openDatabase())) {
|
||||||
if (this.localDatabase.isReady) {
|
if (this.localDatabase.isReady) {
|
||||||
await this.core.$$performFullScan(showingNotice);
|
await this.core.$$performFullScan(showingNotice);
|
||||||
}
|
}
|
||||||
if (!await this.core.$everyOnDatabaseInitialized(showingNotice)) {
|
if (!(await this.core.$everyOnDatabaseInitialized(showingNotice))) {
|
||||||
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
|
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -325,4 +373,4 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
tryCloseKvDB() {
|
tryCloseKvDB() {
|
||||||
try {
|
try {
|
||||||
this.core.kvDB?.close();
|
this.core.kvDB?.close();
|
||||||
@@ -42,7 +41,7 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
async $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!await this.openKeyValueDB()) {
|
if (!(await this.openKeyValueDB())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.core.simpleStore = this.core.$$getSimpleStore<any>("os");
|
this.core.simpleStore = this.core.$$getSimpleStore<any>("os");
|
||||||
@@ -60,11 +59,21 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
delete: async (key: string): Promise<void> => {
|
delete: async (key: string): Promise<void> => {
|
||||||
await this.core.kvDB.del(`${prefix}${key}`);
|
await this.core.kvDB.del(`${prefix}${key}`);
|
||||||
},
|
},
|
||||||
keys: async (from: string | undefined, to: string | undefined, count?: number | undefined): Promise<string[]> => {
|
keys: async (
|
||||||
const ret = this.core.kvDB.keys(IDBKeyRange.bound(`${prefix}${from || ""}`, `${prefix}${to || ""}`), count);
|
from: string | undefined,
|
||||||
return (await ret).map(e => e.toString()).filter(e => e.startsWith(prefix)).map(e => e.substring(prefix.length));
|
to: string | undefined,
|
||||||
}
|
count?: number | undefined
|
||||||
}
|
): Promise<string[]> => {
|
||||||
|
const ret = this.core.kvDB.keys(
|
||||||
|
IDBKeyRange.bound(`${prefix}${from || ""}`, `${prefix}${to || ""}`),
|
||||||
|
count
|
||||||
|
);
|
||||||
|
return (await ret)
|
||||||
|
.map((e) => e.toString())
|
||||||
|
.filter((e) => e.startsWith(prefix))
|
||||||
|
.map((e) => e.substring(prefix.length));
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.openKeyValueDB();
|
return this.openKeyValueDB();
|
||||||
@@ -72,7 +81,7 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
async $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
async $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const kvDBKey = "queued-files"
|
const kvDBKey = "queued-files";
|
||||||
await this.core.kvDB.del(kvDBKey);
|
await this.core.kvDB.del(kvDBKey);
|
||||||
// localStorage.removeItem(lsKey);
|
// localStorage.removeItem(lsKey);
|
||||||
await this.core.kvDB.destroy();
|
await this.core.kvDB.destroy();
|
||||||
@@ -87,4 +96,4 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from 'octagonal-wheels/common/logger.js';
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger.js";
|
||||||
import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from '../../lib/src/common/types.js';
|
import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from "../../lib/src/common/types.js";
|
||||||
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, EVENT_REQUEST_OPEN_SETUP_URI, eventHub } from '../../common/events.ts';
|
import {
|
||||||
|
EVENT_REQUEST_OPEN_SETTING_WIZARD,
|
||||||
|
EVENT_REQUEST_OPEN_SETTINGS,
|
||||||
|
EVENT_REQUEST_OPEN_SETUP_URI,
|
||||||
|
eventHub,
|
||||||
|
} from "../../common/events.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
const URI_DOC = "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use";
|
const URI_DOC = "https://github.com/vrtmrz/obsidian-livesync/blob/main/README.md#how-to-use";
|
||||||
|
|
||||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async migrateDisableBulkSend() {
|
async migrateDisableBulkSend() {
|
||||||
if (this.settings.sendChunksBulk) {
|
if (this.settings.sendChunksBulk) {
|
||||||
this._log("Send chunks in bulk has been enabled, however, this feature had been corrupted. Sorry for your inconvenience. Automatically disabled.", LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"Send chunks in bulk has been enabled, however, this feature had been corrupted. Sorry for your inconvenience. Automatically disabled.",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
this.settings.sendChunksBulk = false;
|
this.settings.sendChunksBulk = false;
|
||||||
this.settings.sendChunksBulkMaxSize = 1;
|
this.settings.sendChunksBulkMaxSize = 1;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
@@ -20,20 +27,27 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
const old = this.settings.settingVersion;
|
const old = this.settings.settingVersion;
|
||||||
const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
||||||
// Check each migrations(old -> current)
|
// Check each migrations(old -> current)
|
||||||
if (!await this.migrateToCaseInsensitive(old, current)) {
|
if (!(await this.migrateToCaseInsensitive(old, current))) {
|
||||||
this._log(`Migration failed or cancelled from ${old} to ${current}`, LOG_LEVEL_NOTICE);
|
this._log(`Migration failed or cancelled from ${old} to ${current}`, LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async migrateToCaseInsensitive(old: number, current: number) {
|
async migrateToCaseInsensitive(old: number, current: number) {
|
||||||
if (this.settings.handleFilenameCaseSensitive !== undefined && this.settings.doNotUseFixedRevisionForChunks !== undefined) {
|
if (
|
||||||
|
this.settings.handleFilenameCaseSensitive !== undefined &&
|
||||||
|
this.settings.doNotUseFixedRevisionForChunks !== undefined
|
||||||
|
) {
|
||||||
if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) {
|
if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) {
|
||||||
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE && this.settings.handleFilenameCaseSensitive !== undefined && this.settings.doNotUseFixedRevisionForChunks !== undefined) {
|
if (
|
||||||
|
old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE &&
|
||||||
|
this.settings.handleFilenameCaseSensitive !== undefined &&
|
||||||
|
this.settings.doNotUseFixedRevisionForChunks !== undefined
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,9 +57,14 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
try {
|
try {
|
||||||
const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings);
|
const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings);
|
||||||
if (remoteInfo) {
|
if (remoteInfo) {
|
||||||
remoteHandleFilenameCaseSensitive = "handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false;
|
remoteHandleFilenameCaseSensitive =
|
||||||
remoteDoNotUseFixedRevisionForChunks = "doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false;
|
"handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false;
|
||||||
if (remoteHandleFilenameCaseSensitive !== undefined || remoteDoNotUseFixedRevisionForChunks !== undefined) {
|
remoteDoNotUseFixedRevisionForChunks =
|
||||||
|
"doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false;
|
||||||
|
if (
|
||||||
|
remoteHandleFilenameCaseSensitive !== undefined ||
|
||||||
|
remoteDoNotUseFixedRevisionForChunks !== undefined
|
||||||
|
) {
|
||||||
remoteChecked = true;
|
remoteChecked = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -79,7 +98,13 @@ ___Note2: The chunks are completely immutable, we can fetch only the metadata an
|
|||||||
const OPTION_FETCH = "Yes, fetch again";
|
const OPTION_FETCH = "Yes, fetch again";
|
||||||
const DISMISS = "No, please ask again";
|
const DISMISS = "No, please ask again";
|
||||||
const options = [OPTION_FETCH, DISMISS];
|
const options = [OPTION_FETCH, DISMISS];
|
||||||
const ret = await this.core.confirm.confirmWithMessage("Case Sensitivity", message, options, "No, please ask again", 40);
|
const ret = await this.core.confirm.confirmWithMessage(
|
||||||
|
"Case Sensitivity",
|
||||||
|
message,
|
||||||
|
options,
|
||||||
|
"No, please ask again",
|
||||||
|
40
|
||||||
|
);
|
||||||
if (ret == OPTION_FETCH) {
|
if (ret == OPTION_FETCH) {
|
||||||
this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false;
|
this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false;
|
||||||
this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false;
|
this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false;
|
||||||
@@ -96,7 +121,6 @@ ___Note2: The chunks are completely immutable, we can fetch only the metadata an
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENABLE_BOTH = "Enable both";
|
const ENABLE_BOTH = "Enable both";
|
||||||
@@ -120,10 +144,7 @@ ___However, to enable either of these changes, both remote and local databases n
|
|||||||
- If you do not have enough time, please choose \`${DISMISS}\`. You will be prompted again later.
|
- If you do not have enough time, please choose \`${DISMISS}\`. You will be prompted again later.
|
||||||
- If you have rebuilt the database on another device, please select \`${DISMISS}\` and try synchronizing again. Since a difference has been detected, you will be prompted again.
|
- If you have rebuilt the database on another device, please select \`${DISMISS}\` and try synchronizing again. Since a difference has been detected, you will be prompted again.
|
||||||
`;
|
`;
|
||||||
const options = [
|
const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS];
|
||||||
ENABLE_BOTH,
|
|
||||||
ENABLE_FILENAME_CASE_INSENSITIVE,
|
|
||||||
ENABLE_FIXED_REVISION_FOR_CHUNKS];
|
|
||||||
if (remoteChecked) {
|
if (remoteChecked) {
|
||||||
options.push(ADJUST_TO_REMOTE);
|
options.push(ADJUST_TO_REMOTE);
|
||||||
}
|
}
|
||||||
@@ -152,13 +173,11 @@ ___However, to enable either of these changes, both remote and local databases n
|
|||||||
case DISMISS:
|
case DISMISS:
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
await this.core.$$performRestart();
|
await this.core.$$performRestart();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialMessage() {
|
async initialMessage() {
|
||||||
@@ -174,16 +193,14 @@ Note: If you do not know what it is, please refer to the [documentation](${URI_D
|
|||||||
const USE_SETUP = "Yes, I have";
|
const USE_SETUP = "Yes, I have";
|
||||||
const NEXT = "No, I do not have";
|
const NEXT = "No, I do not have";
|
||||||
|
|
||||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_SETUP, NEXT], {
|
||||||
USE_SETUP, NEXT], {
|
|
||||||
title: "Welcome to Self-hosted LiveSync",
|
title: "Welcome to Self-hosted LiveSync",
|
||||||
defaultAction: USE_SETUP
|
defaultAction: USE_SETUP,
|
||||||
});
|
});
|
||||||
if (ret === USE_SETUP) {
|
if (ret === USE_SETUP) {
|
||||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETUP_URI);
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (ret == NEXT) {
|
||||||
else if (ret == NEXT) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -199,10 +216,9 @@ How do you want to set it up manually?`;
|
|||||||
const USE_SETUP = "Set it up all manually";
|
const USE_SETUP = "Set it up all manually";
|
||||||
const NEXT = "Remind me at the next launch";
|
const NEXT = "Remind me at the next launch";
|
||||||
|
|
||||||
const ret = await this.core.confirm.askSelectStringDialogue(message, [
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [USE_MINIMAL, USE_SETUP, NEXT], {
|
||||||
USE_MINIMAL, USE_SETUP, NEXT], {
|
|
||||||
title: "Recommendation to use Setup URI",
|
title: "Recommendation to use Setup URI",
|
||||||
defaultAction: USE_MINIMAL
|
defaultAction: USE_MINIMAL,
|
||||||
});
|
});
|
||||||
if (ret === USE_MINIMAL) {
|
if (ret === USE_MINIMAL) {
|
||||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTING_WIZARD);
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTING_WIZARD);
|
||||||
@@ -211,8 +227,7 @@ How do you want to set it up manually?`;
|
|||||||
if (ret === USE_SETUP) {
|
if (ret === USE_SETUP) {
|
||||||
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTINGS);
|
eventHub.emitEvent(EVENT_REQUEST_OPEN_SETTINGS);
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (ret == NEXT) {
|
||||||
else if (ret == NEXT) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -229,12 +244,14 @@ How do you want to set it up manually?`;
|
|||||||
}
|
}
|
||||||
if (!this.settings.isConfigured) {
|
if (!this.settings.isConfigured) {
|
||||||
// Case sensitivity
|
// Case sensitivity
|
||||||
if (!await this.initialMessage() || !await this.askAgainForSetupURI()) {
|
if (!(await this.initialMessage()) || !(await this.askAgainForSetupURI())) {
|
||||||
this._log("The setup has been cancelled, Self-hosted LiveSync waiting for your setup!", LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"The setup has been cancelled, Self-hosted LiveSync waiting for your setup!",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// This file is based on a file that was published by the @remotely-save, under the Apache 2 License.
|
// This file is based on a file that was published by the @remotely-save, under the Apache 2 License.
|
||||||
// I would love to express my deepest gratitude to the original authors for their hard work and dedication. Without their contributions, this project would not have been possible.
|
// I would love to express my deepest gratitude to the original authors for their hard work and dedication. Without their contributions, this project would not have been possible.
|
||||||
//
|
//
|
||||||
// Original Implementation is here: https://github.com/remotely-save/remotely-save/blob/28b99557a864ef59c19d2ad96101196e401718f0/src/remoteForS3.ts
|
// Original Implementation is here: https://github.com/remotely-save/remotely-save/blob/28b99557a864ef59c19d2ad96101196e401718f0/src/remoteForS3.ts
|
||||||
|
|
||||||
import {
|
import { FetchHttpHandler, type FetchHttpHandlerOptions } from "@smithy/fetch-http-handler";
|
||||||
FetchHttpHandler,
|
|
||||||
type FetchHttpHandlerOptions,
|
|
||||||
} from "@smithy/fetch-http-handler";
|
|
||||||
import { HttpRequest, HttpResponse, type HttpHandlerOptions } from "@smithy/protocol-http";
|
import { HttpRequest, HttpResponse, type HttpHandlerOptions } from "@smithy/protocol-http";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { requestTimeout } from "@smithy/fetch-http-handler/dist-es/request-timeout";
|
import { requestTimeout } from "@smithy/fetch-http-handler/dist-es/request-timeout";
|
||||||
@@ -25,20 +22,13 @@ import { requestUrl, type RequestUrlParam } from "../../../deps.ts";
|
|||||||
export class ObsHttpHandler extends FetchHttpHandler {
|
export class ObsHttpHandler extends FetchHttpHandler {
|
||||||
requestTimeoutInMs: number | undefined;
|
requestTimeoutInMs: number | undefined;
|
||||||
reverseProxyNoSignUrl: string | undefined;
|
reverseProxyNoSignUrl: string | undefined;
|
||||||
constructor(
|
constructor(options?: FetchHttpHandlerOptions, reverseProxyNoSignUrl?: string) {
|
||||||
options?: FetchHttpHandlerOptions,
|
|
||||||
reverseProxyNoSignUrl?: string
|
|
||||||
) {
|
|
||||||
super(options);
|
super(options);
|
||||||
this.requestTimeoutInMs =
|
this.requestTimeoutInMs = options === undefined ? undefined : options.requestTimeout;
|
||||||
options === undefined ? undefined : options.requestTimeout;
|
|
||||||
this.reverseProxyNoSignUrl = reverseProxyNoSignUrl;
|
this.reverseProxyNoSignUrl = reverseProxyNoSignUrl;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line require-await
|
// eslint-disable-next-line require-await
|
||||||
async handle(
|
async handle(request: HttpRequest, { abortSignal }: HttpHandlerOptions = {}): Promise<{ response: HttpResponse }> {
|
||||||
request: HttpRequest,
|
|
||||||
{ abortSignal }: HttpHandlerOptions = {}
|
|
||||||
): Promise<{ response: HttpResponse }> {
|
|
||||||
if (abortSignal?.aborted) {
|
if (abortSignal?.aborted) {
|
||||||
const abortError = new Error("Request aborted");
|
const abortError = new Error("Request aborted");
|
||||||
abortError.name = "AbortError";
|
abortError.name = "AbortError";
|
||||||
@@ -54,18 +44,13 @@ export class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { port, method } = request;
|
const { port, method } = request;
|
||||||
let url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ""
|
let url = `${request.protocol}//${request.hostname}${port ? `:${port}` : ""}${path}`;
|
||||||
}${path}`;
|
if (this.reverseProxyNoSignUrl !== undefined && this.reverseProxyNoSignUrl !== "") {
|
||||||
if (
|
|
||||||
this.reverseProxyNoSignUrl !== undefined &&
|
|
||||||
this.reverseProxyNoSignUrl !== ""
|
|
||||||
) {
|
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
urlObj.host = this.reverseProxyNoSignUrl;
|
urlObj.host = this.reverseProxyNoSignUrl;
|
||||||
url = urlObj.href;
|
url = urlObj.href;
|
||||||
}
|
}
|
||||||
const body =
|
const body = method === "GET" || method === "HEAD" ? undefined : request.body;
|
||||||
method === "GET" || method === "HEAD" ? undefined : request.body;
|
|
||||||
|
|
||||||
const transformedHeaders: Record<string, string> = {};
|
const transformedHeaders: Record<string, string> = {};
|
||||||
for (const key of Object.keys(request.headers)) {
|
for (const key of Object.keys(request.headers)) {
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from '../AbstractObsidianModule.ts';
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE } from 'octagonal-wheels/common/logger';
|
import { LOG_LEVEL_DEBUG, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from '../../deps.ts';
|
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
|
||||||
import { type EntryDoc, type FilePathWithPrefix } from '../../lib/src/common/types.ts';
|
import { type EntryDoc, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
import { getPathFromTFile } from '../../common/utils.ts';
|
import { getPathFromTFile } from "../../common/utils.ts";
|
||||||
import { disableEncryption, enableEncryption, isCloudantURI, isValidRemoteCouchDBURI, replicationFilter } from '../../lib/src/pouchdb/utils_couchdb.ts';
|
import {
|
||||||
import { setNoticeClass } from '../../lib/src/mock_and_interop/wrapper.ts';
|
disableEncryption,
|
||||||
import { ObsHttpHandler } from './APILib/ObsHttpHandler.ts';
|
enableEncryption,
|
||||||
import { PouchDB } from '../../lib/src/pouchdb/pouchdb-browser.ts';
|
isCloudantURI,
|
||||||
import { reactive, reactiveSource } from 'octagonal-wheels/dataobject/reactive';
|
isValidRemoteCouchDBURI,
|
||||||
|
replicationFilter,
|
||||||
|
} from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
|
import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
|
||||||
|
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
|
||||||
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
|
||||||
|
import { reactive, reactiveSource } from "octagonal-wheels/dataobject/reactive";
|
||||||
|
|
||||||
setNoticeClass(Notice);
|
setNoticeClass(Notice);
|
||||||
|
|
||||||
|
|
||||||
async function fetchByAPI(request: RequestUrlParam): Promise<RequestUrlResponse> {
|
async function fetchByAPI(request: RequestUrlParam): Promise<RequestUrlResponse> {
|
||||||
const ret = await requestUrl(request);
|
const ret = await requestUrl(request);
|
||||||
if (ret.status - (ret.status % 100) !== 200) {
|
if (ret.status - (ret.status % 100) !== 200) {
|
||||||
@@ -27,11 +32,11 @@ async function fetchByAPI(request: RequestUrlParam): Promise<RequestUrlResponse>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
_customHandler!: ObsHttpHandler;
|
_customHandler!: ObsHttpHandler;
|
||||||
authHeaderSource = reactiveSource<string>("");
|
authHeaderSource = reactiveSource<string>("");
|
||||||
authHeader = reactive(() =>
|
authHeader = reactive(() =>
|
||||||
this.authHeaderSource.value == "" ? "" : "Basic " + window.btoa(this.authHeaderSource.value));
|
this.authHeaderSource.value == "" ? "" : "Basic " + window.btoa(this.authHeaderSource.value)
|
||||||
|
);
|
||||||
|
|
||||||
last_successful_post = false;
|
last_successful_post = false;
|
||||||
$$customFetchHandler(): ObsHttpHandler {
|
$$customFetchHandler(): ObsHttpHandler {
|
||||||
@@ -42,11 +47,20 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
return !this.last_successful_post;
|
return !this.last_successful_post;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean, performSetup: boolean, skipInfo: boolean, compression: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
|
async $$connectRemoteCouchDB(
|
||||||
|
uri: string,
|
||||||
|
auth: { username: string; password: string },
|
||||||
|
disableRequestURI: boolean,
|
||||||
|
passphrase: string | false,
|
||||||
|
useDynamicIterationCount: boolean,
|
||||||
|
performSetup: boolean,
|
||||||
|
skipInfo: boolean,
|
||||||
|
compression: boolean
|
||||||
|
): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
|
||||||
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
|
||||||
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
|
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
|
||||||
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
|
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
|
||||||
const userNameAndPassword = (auth.username && auth.password) ? `${auth.username}:${auth.password}` : "";
|
const userNameAndPassword = auth.username && auth.password ? `${auth.username}:${auth.password}` : "";
|
||||||
if (this.authHeaderSource.value != userNameAndPassword) {
|
if (this.authHeaderSource.value != userNameAndPassword) {
|
||||||
this.authHeaderSource.value = userNameAndPassword;
|
this.authHeaderSource.value = userNameAndPassword;
|
||||||
}
|
}
|
||||||
@@ -135,7 +149,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
if (Math.floor(response.status / 100) !== 2) {
|
if (Math.floor(response.status / 100) !== 2) {
|
||||||
if (method != "GET" && localURL.indexOf("/_local/") === -1 && !localURL.endsWith("/")) {
|
if (method != "GET" && localURL.indexOf("/_local/") === -1 && !localURL.endsWith("/")) {
|
||||||
const r = response.clone();
|
const r = response.clone();
|
||||||
this._log(`The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}`);
|
this._log(
|
||||||
|
`The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}`
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._log(await (await r.blob()).text(), LOG_LEVEL_VERBOSE);
|
this._log(await (await r.blob()).text(), LOG_LEVEL_VERBOSE);
|
||||||
@@ -144,7 +160,10 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
this._log(_, LOG_LEVEL_VERBOSE);
|
this._log(_, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._log(`Just checkpoint or some server information has been missing. The 404 error shown above is not an error.`, LOG_LEVEL_VERBOSE)
|
this._log(
|
||||||
|
`Just checkpoint or some server information has been missing. The 404 error shown above is not an error.`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
@@ -178,7 +197,8 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
} catch (ex: any) {
|
} catch (ex: any) {
|
||||||
let msg = `${ex?.name}:${ex?.message}`;
|
let msg = `${ex?.name}:${ex?.message}`;
|
||||||
if (ex?.name == "TypeError" && ex?.message == "Failed to fetch") {
|
if (ex?.name == "TypeError" && ex?.message == "Failed to fetch") {
|
||||||
msg += "\n**Note** This error caused by many reasons. The only sure thing is you didn't touch the server.\nTo check details, open inspector.";
|
msg +=
|
||||||
|
"\n**Note** This error caused by many reasons. The only sure thing is you didn't touch the server.\nTo check details, open inspector.";
|
||||||
}
|
}
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
return msg;
|
return msg;
|
||||||
@@ -194,7 +214,10 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
return this.app.vault.getName();
|
return this.app.vault.getName();
|
||||||
}
|
}
|
||||||
$$getVaultName(): string {
|
$$getVaultName(): string {
|
||||||
return this.core.$$vaultName() + (this.settings?.additionalSuffixOfDatabaseName ? ("-" + this.settings.additionalSuffixOfDatabaseName) : "");
|
return (
|
||||||
|
this.core.$$vaultName() +
|
||||||
|
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
||||||
const file = this.app.workspace.getActiveFile();
|
const file = this.app.workspace.getActiveFile();
|
||||||
@@ -205,7 +228,6 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
}
|
}
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> {
|
$anyGetAppId(): Promise<string | undefined> {
|
||||||
return Promise.resolve(`${("appId" in this.app ? this.app.appId : "")}`);
|
return Promise.resolve(`${"appId" in this.app ? this.app.appId : ""}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from '../AbstractObsidianModule.ts';
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from '../../common/events.js';
|
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from 'octagonal-wheels/common/logger';
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { scheduleTask } from 'octagonal-wheels/concurrency/task';
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { type TFile } from '../../deps.ts';
|
import { type TFile } from "../../deps.ts";
|
||||||
import { fireAndForget } from 'octagonal-wheels/promises';
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { type FilePathWithPrefix } from '../../lib/src/common/types.ts';
|
import { type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
import { reactive, reactiveSource } from 'octagonal-wheels/dataobject/reactive';
|
import { reactive, reactiveSource } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { collectingChunks, pluginScanningCount, hiddenFilesEventCount, hiddenFilesProcessingCount } from '../../lib/src/mock_and_interop/stores.ts';
|
import {
|
||||||
|
collectingChunks,
|
||||||
|
pluginScanningCount,
|
||||||
|
hiddenFilesEventCount,
|
||||||
|
hiddenFilesProcessingCount,
|
||||||
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
|
|
||||||
export class ModuleObsidianEvents extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianEvents extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
||||||
this.plugin.registerEvent(this.app.vault.on("rename", (file, oldPath) => {
|
this.plugin.registerEvent(
|
||||||
eventHub.emitEvent(EVENT_FILE_RENAMED, { newPath: file.path as FilePathWithPrefix, old: oldPath as FilePathWithPrefix });
|
this.app.vault.on("rename", (file, oldPath) => {
|
||||||
}));
|
eventHub.emitEvent(EVENT_FILE_RENAMED, {
|
||||||
this.plugin.registerEvent(this.app.workspace.on("active-leaf-change", () => eventHub.emitEvent(EVENT_LEAF_ACTIVE_CHANGED)));
|
newPath: file.path as FilePathWithPrefix,
|
||||||
|
old: oldPath as FilePathWithPrefix,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.plugin.registerEvent(
|
||||||
|
this.app.workspace.on("active-leaf-change", () => eventHub.emitEvent(EVENT_LEAF_ACTIVE_CHANGED))
|
||||||
|
);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$$performRestart(): void {
|
$$performRestart(): void {
|
||||||
this._performAppReload();
|
this._performAppReload();
|
||||||
}
|
}
|
||||||
@@ -33,9 +43,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
|
|
||||||
swapSaveCommand() {
|
swapSaveCommand() {
|
||||||
this._log("Modifying callback of the save command", LOG_LEVEL_VERBOSE);
|
this._log("Modifying callback of the save command", LOG_LEVEL_VERBOSE);
|
||||||
const saveCommandDefinition = (this.app as any).commands?.commands?.[
|
const saveCommandDefinition = (this.app as any).commands?.commands?.["editor:save-file"];
|
||||||
"editor:save-file"
|
|
||||||
];
|
|
||||||
const save = saveCommandDefinition?.callback;
|
const save = saveCommandDefinition?.callback;
|
||||||
if (typeof save === "function") {
|
if (typeof save === "function") {
|
||||||
this.initialCallback = save;
|
this.initialCallback = save;
|
||||||
@@ -60,7 +68,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
window.CodeMirrorAdapter.commands.save = () => {
|
window.CodeMirrorAdapter.commands.save = () => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
_this.app.commands.executeCommandById('editor:save-file')
|
_this.app.commands.executeCommandById("editor:save-file");
|
||||||
// _this.app.performCommand('editor:save-file');
|
// _this.app.performCommand('editor:save-file');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -158,7 +166,6 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$$askReload(message?: string) {
|
$$askReload(message?: string) {
|
||||||
if (this.core.$$isReloadingScheduled()) {
|
if (this.core.$$isReloadingScheduled()) {
|
||||||
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -168,16 +175,17 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
const RESTART_NOW = "Yes, restart immediately";
|
const RESTART_NOW = "Yes, restart immediately";
|
||||||
const RESTART_AFTER_STABLE = "Yes, schedule a restart after stabilisation";
|
const RESTART_AFTER_STABLE = "Yes, schedule a restart after stabilisation";
|
||||||
const RETRY_LATER = "No, Leave it to me";
|
const RETRY_LATER = "No, Leave it to me";
|
||||||
const ret = await this.core.confirm.askSelectStringDialogue(message || "Do you want to restart and reload Obsidian now?", [
|
const ret = await this.core.confirm.askSelectStringDialogue(
|
||||||
RESTART_AFTER_STABLE,
|
message || "Do you want to restart and reload Obsidian now?",
|
||||||
RESTART_NOW,
|
[RESTART_AFTER_STABLE, RESTART_NOW, RETRY_LATER],
|
||||||
RETRY_LATER], { defaultAction: RETRY_LATER });
|
{ defaultAction: RETRY_LATER }
|
||||||
|
);
|
||||||
if (ret == RESTART_NOW) {
|
if (ret == RESTART_NOW) {
|
||||||
this._performAppReload();
|
this._performAppReload();
|
||||||
} else if (ret == RESTART_AFTER_STABLE) {
|
} else if (ret == RESTART_AFTER_STABLE) {
|
||||||
this.core.$$scheduleAppReload();
|
this.core.$$scheduleAppReload();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
$$scheduleAppReload() {
|
$$scheduleAppReload() {
|
||||||
if (!this.core._totalProcessingCount) {
|
if (!this.core._totalProcessingCount) {
|
||||||
@@ -194,24 +202,39 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
const proc = this.core.processingFileEventCount.value;
|
const proc = this.core.processingFileEventCount.value;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const __ = __tick.value;
|
const __ = __tick.value;
|
||||||
return dbCount + replicationCount + storageApplyingCount + chunkCount + pluginScanCount + hiddenFilesCount + conflictProcessCount + e + proc;
|
return (
|
||||||
})
|
dbCount +
|
||||||
this.plugin.registerInterval(setInterval(() => {
|
replicationCount +
|
||||||
__tick.value++;
|
storageApplyingCount +
|
||||||
}, 1000) as unknown as number);
|
chunkCount +
|
||||||
|
pluginScanCount +
|
||||||
|
hiddenFilesCount +
|
||||||
|
conflictProcessCount +
|
||||||
|
e +
|
||||||
|
proc
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.plugin.registerInterval(
|
||||||
|
setInterval(() => {
|
||||||
|
__tick.value++;
|
||||||
|
}, 1000) as unknown as number
|
||||||
|
);
|
||||||
|
|
||||||
let stableCheck = 3;
|
let stableCheck = 3;
|
||||||
this.core._totalProcessingCount.onChanged(e => {
|
this.core._totalProcessingCount.onChanged((e) => {
|
||||||
if (e.value == 0) {
|
if (e.value == 0) {
|
||||||
if (stableCheck-- <= 0) {
|
if (stableCheck-- <= 0) {
|
||||||
this._performAppReload();
|
this._performAppReload();
|
||||||
}
|
}
|
||||||
this._log(`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`, LOG_LEVEL_NOTICE, "restart-notice");
|
this._log(
|
||||||
|
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
"restart-notice"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
stableCheck = 3;
|
stableCheck = 3;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
|
|
||||||
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
addIcon(
|
addIcon(
|
||||||
"replicate",
|
"replicate",
|
||||||
@@ -22,7 +20,6 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
await this.core.$$replicate(true);
|
await this.core.$$replicate(true);
|
||||||
}).addClass("livesync-ribbon-replicate");
|
}).addClass("livesync-ribbon-replicate");
|
||||||
|
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-replicate",
|
id: "livesync-replicate",
|
||||||
name: "Replicate now",
|
name: "Replicate now",
|
||||||
@@ -84,9 +81,9 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
id: "livesync-scan-files",
|
id: "livesync-scan-files",
|
||||||
name: "Scan storage and database again",
|
name: "Scan storage and database again",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$$performFullScan(true)
|
await this.core.$$performFullScan(true);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-runbatch",
|
id: "livesync-runbatch",
|
||||||
@@ -94,7 +91,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.core.$everyCommitPendingFileEvent();
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// TODO, Replicator is possibly one of features. It should be moved to features.
|
// TODO, Replicator is possibly one of features. It should be moved to features.
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
@@ -103,9 +100,8 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
callback: () => {
|
callback: () => {
|
||||||
this.core.replicator.terminateSync();
|
this.core.replicator.terminateSync();
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
|
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
|
||||||
@@ -124,13 +120,10 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
await leaves[0].setViewState({
|
await leaves[0].setViewState({
|
||||||
type: viewType,
|
type: viewType,
|
||||||
active: true,
|
active: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (leaves.length > 0) {
|
if (leaves.length > 0) {
|
||||||
this.app.workspace.revealLeaf(
|
this.app.workspace.revealLeaf(leaves[0]);
|
||||||
leaves[0]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from '../AbstractObsidianModule.ts';
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
|
|
||||||
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
|
||||||
deviceAndVaultName: string = "";
|
deviceAndVaultName: string = "";
|
||||||
@@ -9,5 +9,4 @@ export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements I
|
|||||||
$$setDeviceAndVaultName(name: string): void {
|
$$setDeviceAndVaultName(name: string): void {
|
||||||
this.deviceAndVaultName = name;
|
this.deviceAndVaultName = name;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
__onMissingTranslation(() => { });
|
__onMissingTranslation(() => {});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
@@ -18,70 +17,80 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
// eslint-disable-next-line no-unused-labels
|
// eslint-disable-next-line no-unused-labels
|
||||||
__onMissingTranslation((key) => {
|
__onMissingTranslation((key) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const filename = `missing-translation-`
|
const filename = `missing-translation-`;
|
||||||
const time = now.toISOString().split("T")[0];
|
const time = now.toISOString().split("T")[0];
|
||||||
const outFile = `${filename}${time}.jsonl`;
|
const outFile = `${filename}${time}.jsonl`;
|
||||||
const piece = JSON.stringify(
|
const piece = JSON.stringify({
|
||||||
{
|
[key]: {},
|
||||||
[key]: {}
|
});
|
||||||
}
|
|
||||||
)
|
|
||||||
const writePiece = piece.substring(1, piece.length - 1) + ",";
|
const writePiece = piece.substring(1, piece.length - 1) + ",";
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
try {
|
try {
|
||||||
await this.core.storageAccess.ensureDir(this.app.vault.configDir + "/ls-debug/");
|
await this.core.storageAccess.ensureDir(this.app.vault.configDir + "/ls-debug/");
|
||||||
await this.core.storageAccess.appendHiddenFile(this.app.vault.configDir + "/ls-debug/" + outFile, writePiece + "\n")
|
await this.core.storageAccess.appendHiddenFile(
|
||||||
|
this.app.vault.configDir + "/ls-debug/" + outFile,
|
||||||
|
writePiece + "\n"
|
||||||
|
);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this._log(`Could not write ${outFile}`, LOG_LEVEL_VERBOSE);
|
this._log(`Could not write ${outFile}`, LOG_LEVEL_VERBOSE);
|
||||||
this._log(`Missing translation: ${writePiece}`, LOG_LEVEL_VERBOSE);
|
this._log(`Missing translation: ${writePiece}`, LOG_LEVEL_VERBOSE);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
type STUB = {
|
type STUB = {
|
||||||
toc: Set<string>,
|
toc: Set<string>;
|
||||||
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } }
|
stub: { [key: string]: { [key: string]: Map<string, Record<string, string>> } };
|
||||||
};
|
};
|
||||||
eventHub.onEvent("document-stub-created", (detail: STUB) => {
|
eventHub.onEvent("document-stub-created", (detail: STUB) => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
const stub = detail.stub;
|
const stub = detail.stub;
|
||||||
const toc = detail.toc;
|
const toc = detail.toc;
|
||||||
|
|
||||||
const stubDocX =
|
const stubDocX = Object.entries(stub)
|
||||||
Object.entries(stub).map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
return [`## ${key}`, Object.entries(value).
|
return [
|
||||||
map(([key2, value2]) => {
|
`## ${key}`,
|
||||||
return [`### ${key2}`,
|
Object.entries(value)
|
||||||
([...(value2.entries())].map(([key3, value3]) => {
|
.map(([key2, value2]) => {
|
||||||
// return `#### ${key3}` + "\n" + JSON.stringify(value3);
|
return [
|
||||||
const isObsolete = value3["is_obsolete"] ? " (obsolete)" : "";
|
`### ${key2}`,
|
||||||
const desc = value3["desc"] ?? "";
|
[...value2.entries()].map(([key3, value3]) => {
|
||||||
const key = value3["key"] ? "Setting key: " + value3["key"] + "\n" : "";
|
// return `#### ${key3}` + "\n" + JSON.stringify(value3);
|
||||||
return `#### ${key3}${isObsolete}\n${key}${desc}\n`
|
const isObsolete = value3["is_obsolete"] ? " (obsolete)" : "";
|
||||||
}))].flat()
|
const desc = value3["desc"] ?? "";
|
||||||
}).flat()].flat()
|
const key = value3["key"] ? "Setting key: " + value3["key"] + "\n" : "";
|
||||||
}).flat();
|
return `#### ${key3}${isObsolete}\n${key}${desc}\n`;
|
||||||
const stubDocMD = `
|
}),
|
||||||
|
].flat();
|
||||||
|
})
|
||||||
|
.flat(),
|
||||||
|
].flat();
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
const stubDocMD =
|
||||||
|
`
|
||||||
| Icon | Description |
|
| Icon | Description |
|
||||||
| :---: | ----------------------------------------------------------------- |
|
| :---: | ----------------------------------------------------------------- |
|
||||||
` +
|
` +
|
||||||
[...toc.values()].map(e => `${e}`).join("\n") + "\n\n" +
|
[...toc.values()].map((e) => `${e}`).join("\n") +
|
||||||
|
"\n\n" +
|
||||||
stubDocX.join("\n");
|
stubDocX.join("\n");
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.app.vault.configDir + "/ls-debug/stub-doc.md", stubDocMD);
|
await this.core.storageAccess.writeHiddenFileAuto(
|
||||||
})
|
this.app.vault.configDir + "/ls-debug/stub-doc.md",
|
||||||
|
stubDocMD
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
enableTestFunction(this.plugin);
|
enableTestFunction(this.plugin);
|
||||||
this.registerView(
|
this.registerView(VIEW_TYPE_TEST, (leaf) => new TestPaneView(leaf, this.plugin, this));
|
||||||
VIEW_TYPE_TEST,
|
|
||||||
(leaf) => new TestPaneView(leaf, this.plugin, this)
|
|
||||||
);
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "view-test",
|
id: "view-test",
|
||||||
name: "Open Test dialogue",
|
name: "Open Test dialogue",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_TEST);
|
void this.core.$$showView(VIEW_TYPE_TEST);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -111,4 +120,4 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
// this.addTestResult("Test of test3", true);
|
// this.addTestResult("Test of test3", true);
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule";
|
||||||
|
|
||||||
export class ModuleIntegratedTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleIntegratedTest extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
||||||
await delay(100);
|
await delay(100);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
while (!await proc()) {
|
while (!(await proc())) {
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
if (Date.now() - start > timeout) {
|
if (Date.now() - start > timeout) {
|
||||||
this._log(`Timeout`);
|
this._log(`Timeout`);
|
||||||
@@ -40,25 +39,27 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async assert(proc: () => Promise<boolean>): Promise<boolean> {
|
async assert(proc: () => Promise<boolean>): Promise<boolean> {
|
||||||
if (!await proc()) {
|
if (!(await proc())) {
|
||||||
this._log(`Assertion failed`);
|
this._log(`Assertion failed`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
async _orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
||||||
if (!await this._test(key, proc)) {
|
if (!(await this._test(key, proc))) {
|
||||||
throw new Error(`${key}`);
|
throw new Error(`${key}`);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
tryReplicate() {
|
tryReplicate() {
|
||||||
if (!this.settings.liveSync) {
|
if (!this.settings.liveSync) {
|
||||||
return shareRunningResult("replicate-test", async () => { await this.core.$$replicate() });
|
return shareRunningResult("replicate-test", async () => {
|
||||||
|
await this.core.$$replicate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async readStorageContent(file: FilePathWithPrefix): Promise<string | undefined> {
|
async readStorageContent(file: FilePathWithPrefix): Promise<string | undefined> {
|
||||||
if (!await this.core.storageAccess.isExistsIncludeHidden(file)) {
|
if (!(await this.core.storageAccess.isExistsIncludeHidden(file))) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return await this.core.storageAccess.readHiddenFileText(file);
|
return await this.core.storageAccess.readHiddenFileText(file);
|
||||||
@@ -70,13 +71,14 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
await this.core.$anyResolveConflictByNewest(stepFile);
|
await this.core.$anyResolveConflictByNewest(stepFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
||||||
await this._orDie(`Wait for acknowledge ${no}`, async () => {
|
await this._orDie(`Wait for acknowledge ${no}`, async () => {
|
||||||
if (!await this.waitWithReplicating(
|
if (
|
||||||
async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepAckFile, stepContent)
|
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
||||||
}, 20000)
|
}, 20000))
|
||||||
) return false;
|
)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
})
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _join(no: number, title: string): Promise<boolean> {
|
async _join(no: number, title: string): Promise<boolean> {
|
||||||
@@ -86,14 +88,14 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
const stepContent = `Step ${no}`;
|
const stepContent = `Step ${no}`;
|
||||||
|
|
||||||
await this._orDie(`Wait for step ${no} (${title})`, async () => {
|
await this._orDie(`Wait for step ${no} (${title})`, async () => {
|
||||||
if (!await this.waitWithReplicating(
|
if (
|
||||||
async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepFile, stepContent)
|
return await this.storageContentIsEqual(stepFile, stepContent);
|
||||||
}, 20000)
|
}, 20000))
|
||||||
) return false;
|
)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
)
|
|
||||||
await this.core.$anyResolveConflictByNewest(stepAckFile);
|
await this.core.$anyResolveConflictByNewest(stepAckFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
||||||
await this.tryReplicate();
|
await this.tryReplicate();
|
||||||
@@ -105,13 +107,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
title,
|
title,
|
||||||
isGameChanger,
|
isGameChanger,
|
||||||
proc,
|
proc,
|
||||||
check
|
check,
|
||||||
}: {
|
}: {
|
||||||
step: number,
|
step: number;
|
||||||
title: string,
|
title: string;
|
||||||
isGameChanger: boolean,
|
isGameChanger: boolean;
|
||||||
proc: () => Promise<any>,
|
proc: () => Promise<any>;
|
||||||
check: () => Promise<boolean>,
|
check: () => Promise<boolean>;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
if (isGameChanger) {
|
if (isGameChanger) {
|
||||||
await this._proceed(step, title);
|
await this._proceed(step, title);
|
||||||
@@ -121,9 +123,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
this._log(`Error: ${e}`);
|
this._log(`Error: ${e}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await this._orDie(`Step ${step} - ${title}`,
|
return await this._orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
||||||
async () => await this.waitWithReplicating(check)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return await this._join(step, title);
|
return await this._join(step, title);
|
||||||
}
|
}
|
||||||
@@ -135,14 +135,21 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
// async testReceiver(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> {
|
async nonLiveTestRunner(
|
||||||
|
isLeader: boolean,
|
||||||
|
testMain: (testFileName: FilePathWithPrefix, isLeader: boolean) => Promise<boolean>
|
||||||
|
): Promise<boolean> {
|
||||||
const storage = this.core.storageAccess;
|
const storage = this.core.storageAccess;
|
||||||
// const database = this.core.databaseFileAccess;
|
// const database = this.core.databaseFileAccess;
|
||||||
// const _orDie = this._orDie.bind(this);
|
// const _orDie = this._orDie.bind(this);
|
||||||
const testCommandFile = "IT.md" as FilePathWithPrefix;
|
const testCommandFile = "IT.md" as FilePathWithPrefix;
|
||||||
const textCommandResponseFile = "ITx.md" as FilePathWithPrefix;
|
const textCommandResponseFile = "ITx.md" as FilePathWithPrefix;
|
||||||
let testFileName: FilePathWithPrefix;
|
let testFileName: FilePathWithPrefix;
|
||||||
this.addTestResult("-------Starting ... ", true, `Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`);
|
this.addTestResult(
|
||||||
|
"-------Starting ... ",
|
||||||
|
true,
|
||||||
|
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
||||||
|
);
|
||||||
if (isLeader) {
|
if (isLeader) {
|
||||||
await this._proceed(0, "start");
|
await this._proceed(0, "start");
|
||||||
}
|
}
|
||||||
@@ -154,14 +161,14 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => await storage.removeHidden(testCommandFile),
|
proc: async () => await storage.removeHidden(testCommandFile),
|
||||||
check: async () => !(await storage.isExistsIncludeHidden(testCommandFile)),
|
check: async () => !(await storage.isExistsIncludeHidden(testCommandFile)),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 1,
|
step: 1,
|
||||||
title: "Make sure that command File Not Exists On Receiver",
|
title: "Make sure that command File Not Exists On Receiver",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => await storage.removeHidden(textCommandResponseFile),
|
proc: async () => await storage.removeHidden(textCommandResponseFile),
|
||||||
check: async () => !(await storage.isExistsIncludeHidden(textCommandResponseFile)),
|
check: async () => !(await storage.isExistsIncludeHidden(textCommandResponseFile)),
|
||||||
})
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 2,
|
step: 2,
|
||||||
@@ -173,14 +180,14 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
await storage.writeFileAuto(testCommandFile, testFileName);
|
await storage.writeFileAuto(testCommandFile, testFileName);
|
||||||
},
|
},
|
||||||
check: () => Promise.resolve(true),
|
check: () => Promise.resolve(true),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 3,
|
step: 3,
|
||||||
title: "Wait for the command file to be arrived",
|
title: "Wait for the command file to be arrived",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await storage.isExistsIncludeHidden(testCommandFile),
|
check: async () => await storage.isExistsIncludeHidden(testCommandFile),
|
||||||
})
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 4,
|
step: 4,
|
||||||
@@ -190,34 +197,31 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
await storage.writeHiddenFileAuto(textCommandResponseFile, "!");
|
await storage.writeHiddenFileAuto(textCommandResponseFile, "!");
|
||||||
},
|
},
|
||||||
check: () => Promise.resolve(true),
|
check: () => Promise.resolve(true),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 5,
|
step: 5,
|
||||||
title: "Wait for the response file to be arrived",
|
title: "Wait for the response file to be arrived",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await storage.isExistsIncludeHidden(textCommandResponseFile),
|
check: async () => await storage.isExistsIncludeHidden(textCommandResponseFile),
|
||||||
})
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 6,
|
step: 6,
|
||||||
title: "Proceed to begin the test",
|
title: "Proceed to begin the test",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => {
|
proc: async () => {},
|
||||||
|
|
||||||
},
|
|
||||||
check: () => Promise.resolve(true),
|
check: () => Promise.resolve(true),
|
||||||
});
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 6,
|
step: 6,
|
||||||
title: "Begin the test",
|
title: "Begin the test",
|
||||||
isGameChanger: !false,
|
isGameChanger: !false,
|
||||||
proc: async () => {
|
proc: async () => {},
|
||||||
},
|
|
||||||
check: () => {
|
check: () => {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
// await this.step(0, isLeader, true);
|
// await this.step(0, isLeader, true);
|
||||||
try {
|
try {
|
||||||
this.addTestResult("** Main------", true, ``);
|
this.addTestResult("** Main------", true, ``);
|
||||||
@@ -234,15 +238,18 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
// Make sure the
|
// Make sure the
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async testBasic(filename: FilePathWithPrefix, isLeader: boolean): Promise<boolean> {
|
async testBasic(filename: FilePathWithPrefix, isLeader: boolean): Promise<boolean> {
|
||||||
const storage = this.core.storageAccess;
|
const storage = this.core.storageAccess;
|
||||||
const database = this.core.databaseFileAccess;
|
const database = this.core.databaseFileAccess;
|
||||||
|
|
||||||
await this.addTestResult(`---**Starting Basic Test**---`, true, `Test as ${isLeader ? "Leader" : "Receiver"} command file ${filename}`);
|
await this.addTestResult(
|
||||||
|
`---**Starting Basic Test**---`,
|
||||||
|
true,
|
||||||
|
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${filename}`
|
||||||
|
);
|
||||||
// if (isLeader) {
|
// if (isLeader) {
|
||||||
// await this._proceed(0);
|
// await this._proceed(0);
|
||||||
// }
|
// }
|
||||||
@@ -252,10 +259,9 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
step: 0,
|
step: 0,
|
||||||
title: "Make sure that file is not exist",
|
title: "Make sure that file is not exist",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => !(await storage.isExists(filename)),
|
check: async () => !(await storage.isExists(filename)),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -263,47 +269,47 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World"),
|
proc: async () => await storage.writeFileAuto(filename, "Hello World"),
|
||||||
check: async () => await storage.isExists(filename),
|
check: async () => await storage.isExists(filename),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 2,
|
step: 2,
|
||||||
title: "Make sure the file is arrived",
|
title: "Make sure the file is arrived",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await storage.isExists(filename),
|
check: async () => await storage.isExists(filename),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 3,
|
step: 3,
|
||||||
title: "Update to Hello World 2",
|
title: "Update to Hello World 2",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World 2"),
|
proc: async () => await storage.writeFileAuto(filename, "Hello World 2"),
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 4,
|
step: 4,
|
||||||
title: "Make sure the modified file is arrived",
|
title: "Make sure the modified file is arrived",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
check: async () => await this.storageContentIsEqual(filename, "Hello World 2"),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 5,
|
step: 5,
|
||||||
title: "Update to Hello World 3",
|
title: "Update to Hello World 3",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => await storage.writeFileAuto(filename, "Hello World 3"),
|
proc: async () => await storage.writeFileAuto(filename, "Hello World 3"),
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 6,
|
step: 6,
|
||||||
title: "Make sure the modified file is arrived",
|
title: "Make sure the modified file is arrived",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
check: async () => await this.storageContentIsEqual(filename, "Hello World 3"),
|
||||||
})
|
});
|
||||||
|
|
||||||
const multiLineContent = `Line1:A
|
const multiLineContent = `Line1:A
|
||||||
Line2:B
|
Line2:B
|
||||||
Line3:C
|
Line3:C
|
||||||
Line4:D`
|
Line4:D`;
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 7,
|
step: 7,
|
||||||
@@ -311,38 +317,35 @@ Line4:D`
|
|||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => await storage.writeFileAuto(filename, multiLineContent),
|
proc: async () => await storage.writeFileAuto(filename, multiLineContent),
|
||||||
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
||||||
})
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 8,
|
step: 8,
|
||||||
title: "Make sure the modified file is arrived",
|
title: "Make sure the modified file is arrived",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
check: async () => await this.storageContentIsEqual(filename, multiLineContent),
|
||||||
})
|
});
|
||||||
|
|
||||||
// While LiveSync, possibly cannot cause the conflict.
|
// While LiveSync, possibly cannot cause the conflict.
|
||||||
if (!this.settings.liveSync) {
|
if (!this.settings.liveSync) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Step 9 Make Conflict But Resolvable
|
// Step 9 Make Conflict But Resolvable
|
||||||
const multiLineContentL = `Line1:A
|
const multiLineContentL = `Line1:A
|
||||||
Line2:B
|
Line2:B
|
||||||
Line3:C!
|
Line3:C!
|
||||||
Line4:D`
|
Line4:D`;
|
||||||
const multiLineContentC = `Line1:A
|
const multiLineContentC = `Line1:A
|
||||||
Line2:bbbbb
|
Line2:bbbbb
|
||||||
Line3:C
|
Line3:C
|
||||||
Line4:D`
|
Line4:D`;
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 9,
|
step: 9,
|
||||||
title: "Progress to be conflicted",
|
title: "Progress to be conflicted",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: () => Promise.resolve(true),
|
check: () => Promise.resolve(true),
|
||||||
})
|
});
|
||||||
|
|
||||||
await storage.writeFileAuto(filename, isLeader ? multiLineContentL : multiLineContentC);
|
await storage.writeFileAuto(filename, isLeader ? multiLineContentL : multiLineContentC);
|
||||||
|
|
||||||
@@ -350,62 +353,62 @@ Line4:D`
|
|||||||
step: 10,
|
step: 10,
|
||||||
title: "Update As Conflicted",
|
title: "Update As Conflicted",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: () => Promise.resolve(true),
|
check: () => Promise.resolve(true),
|
||||||
})
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 10,
|
step: 10,
|
||||||
title: "Make sure Automatically resolved",
|
title: "Make sure Automatically resolved",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 11,
|
step: 11,
|
||||||
title: "Make sure Automatically resolved",
|
title: "Make sure Automatically resolved",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
check: async () => (await database.getConflictedRevs(filename)).length === 0,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const sensiblyMergedContent = `Line1:A
|
const sensiblyMergedContent = `Line1:A
|
||||||
Line2:bbbbb
|
Line2:bbbbb
|
||||||
Line3:C!
|
Line3:C!
|
||||||
Line4:D`
|
Line4:D`;
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 12,
|
step: 12,
|
||||||
title: "Make sure Sensibly Merged on Leader",
|
title: "Make sure Sensibly Merged on Leader",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
||||||
})
|
});
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 13,
|
step: 13,
|
||||||
title: "Make sure Sensibly Merged on Receiver",
|
title: "Make sure Sensibly Merged on Receiver",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
check: async () => await this.storageContentIsEqual(filename, sensiblyMergedContent),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 14,
|
step: 14,
|
||||||
title: "Delete File",
|
title: "Delete File",
|
||||||
isGameChanger: isLeader,
|
isGameChanger: isLeader,
|
||||||
proc: async () => { await storage.removeHidden(filename) },
|
proc: async () => {
|
||||||
check: async () => !await storage.isExists(filename),
|
await storage.removeHidden(filename);
|
||||||
})
|
},
|
||||||
|
check: async () => !(await storage.isExists(filename)),
|
||||||
|
});
|
||||||
|
|
||||||
await this.performStep({
|
await this.performStep({
|
||||||
step: 15,
|
step: 15,
|
||||||
title: "Make sure File is deleted",
|
title: "Make sure File is deleted",
|
||||||
isGameChanger: !isLeader,
|
isGameChanger: !isLeader,
|
||||||
proc: async () => { },
|
proc: async () => {},
|
||||||
check: async () => !await storage.isExists(filename),
|
check: async () => !(await storage.isExists(filename)),
|
||||||
})
|
});
|
||||||
this._log(`The Basic Test has been completed`, LOG_LEVEL_NOTICE);
|
this._log(`The Basic Test has been completed`, LOG_LEVEL_NOTICE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -429,13 +432,12 @@ Line4:D`
|
|||||||
this._log(`Starting Test`);
|
this._log(`Starting Test`);
|
||||||
await this.testBasicEvent(isLeader);
|
await this.testBasicEvent(isLeader);
|
||||||
if (this.settings.remoteType == REMOTE_MINIO) await this.testBasicLive(isLeader);
|
if (this.settings.remoteType == REMOTE_MINIO) await this.testBasicLive(isLeader);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log(e)
|
this._log(e);
|
||||||
this._log(`Error: ${e}`);
|
this._log(`Error: ${e}`);
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
|
|
||||||
testRootPath = "_test/";
|
testRootPath = "_test/";
|
||||||
testInfoPath = "_testinfo/";
|
testInfoPath = "_testinfo/";
|
||||||
|
|
||||||
@@ -24,7 +22,6 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
return this.core.$$getVaultName().indexOf("dev") >= 0 && this.core.$$vaultName().indexOf("recv") < 0;
|
return this.core.$$getVaultName().indexOf("dev") >= 0 && this.core.$$vaultName().indexOf("recv") < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get nameByKind() {
|
get nameByKind() {
|
||||||
if (!this.isLeader) {
|
if (!this.isLeader) {
|
||||||
return "RECV";
|
return "RECV";
|
||||||
@@ -51,7 +48,6 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async dumpList() {
|
async dumpList() {
|
||||||
if (this.settings.syncInternalFiles) {
|
if (this.settings.syncInternalFiles) {
|
||||||
this._log("Write file list (Include Hidden)");
|
this._log("Write file list (Include Hidden)");
|
||||||
@@ -75,39 +71,38 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
void this._dumpFileList("files.md").finally(() => {
|
void this._dumpFileList("files.md").finally(() => {
|
||||||
void this.refreshSyncStatus();
|
void this.refreshSyncStatus();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
}
|
});
|
||||||
})
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "dump-file-structure-ih",
|
id: "dump-file-structure-ih",
|
||||||
name: "Dump Structure (Include Hidden)",
|
name: "Dump Structure (Include Hidden)",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const d = "files.md";
|
const d = "files.md";
|
||||||
void this._dumpFileListIncludeHidden(d);
|
void this._dumpFileListIncludeHidden(d);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "dump-file-structure-auto",
|
id: "dump-file-structure-auto",
|
||||||
name: "Dump Structure",
|
name: "Dump Structure",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.dumpList();
|
void this.dumpList();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "dump-file-test",
|
id: "dump-file-test",
|
||||||
name: `Perform Test (Dev) ${this.isLeader ? "(Leader)" : "(Recv)"}`,
|
name: `Perform Test (Dev) ${this.isLeader ? "(Leader)" : "(Recv)"}`,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.performTestManually();
|
void this.performTestManually();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "watch-sync-result",
|
id: "watch-sync-result",
|
||||||
name: `Watch sync result is matched between devices`,
|
name: `Watch sync result is matched between devices`,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.watchIsSynchronised = !this.watchIsSynchronised;
|
this.watchIsSynchronised = !this.watchIsSynchronised;
|
||||||
void this.refreshSyncStatus();
|
void this.refreshSyncStatus();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
this.app.vault.on("modify", async (file) => {
|
this.app.vault.on("modify", async (file) => {
|
||||||
if (file.path.startsWith(this.testInfoPath)) {
|
if (file.path.startsWith(this.testInfoPath)) {
|
||||||
await this.refreshSyncStatus();
|
await this.refreshSyncStatus();
|
||||||
@@ -117,7 +112,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
this.statusBarSyncStatus = this.plugin.addStatusBarItem();
|
this.statusBarSyncStatus = this.plugin.addStatusBarItem();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -164,12 +159,11 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async _dumpFileList(outFile?: string) {
|
async _dumpFileList(outFile?: string) {
|
||||||
const files = this.core.storageAccess.getFiles();
|
const files = this.core.storageAccess.getFiles();
|
||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (!await this.core.$$isTargetFile(file.path)) {
|
if (!(await this.core.$$isTargetFile(file.path))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (file.path.startsWith(this.testInfoPath)) continue;
|
if (file.path.startsWith(this.testInfoPath)) continue;
|
||||||
@@ -183,8 +177,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
name: file.name,
|
name: file.name,
|
||||||
size: stat.size,
|
size: stat.size,
|
||||||
mtime: stat.mtime,
|
mtime: stat.mtime,
|
||||||
hash: hashStr
|
hash: hashStr,
|
||||||
}
|
};
|
||||||
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
||||||
out.push(item);
|
out.push(item);
|
||||||
}
|
}
|
||||||
@@ -203,7 +197,9 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
async _dumpFileListIncludeHidden(outFile?: string) {
|
async _dumpFileListIncludeHidden(outFile?: string) {
|
||||||
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
|
const ignorePatterns = this.settings.syncInternalFilesIgnorePatterns
|
||||||
.replace(/\n| /g, "")
|
.replace(/\n| /g, "")
|
||||||
.split(",").filter(e => e).map(e => new RegExp(e, "i"));
|
.split(",")
|
||||||
|
.filter((e) => e)
|
||||||
|
.map((e) => new RegExp(e, "i"));
|
||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
const files = await this.core.storageAccess.getFilesIncludeHidden("", undefined, ignorePatterns);
|
const files = await this.core.storageAccess.getFilesIncludeHidden("", undefined, ignorePatterns);
|
||||||
console.dir(files);
|
console.dir(files);
|
||||||
@@ -222,8 +218,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
name: file.split("/").pop(),
|
name: file.split("/").pop(),
|
||||||
size: stat.size,
|
size: stat.size,
|
||||||
mtime: stat.mtime,
|
mtime: stat.mtime,
|
||||||
hash: hashStr
|
hash: hashStr,
|
||||||
}
|
};
|
||||||
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
// const fileLine = `-${file.path}:${stat.size}:${stat.mtime}:${hashStr}`;
|
||||||
out.push(item);
|
out.push(item);
|
||||||
}
|
}
|
||||||
@@ -263,27 +259,27 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
"docs/tech_info.md",
|
"docs/tech_info.md",
|
||||||
"docs/terms.md",
|
"docs/terms.md",
|
||||||
"docs/troubleshooting.md",
|
"docs/troubleshooting.md",
|
||||||
'images/1.png',
|
"images/1.png",
|
||||||
'images/2.png',
|
"images/2.png",
|
||||||
'images/corrupted_data.png',
|
"images/corrupted_data.png",
|
||||||
'images/hatch.png',
|
"images/hatch.png",
|
||||||
'images/lock_pattern1.png',
|
"images/lock_pattern1.png",
|
||||||
'images/lock_pattern2.png',
|
"images/lock_pattern2.png",
|
||||||
'images/quick_setup_1.png',
|
"images/quick_setup_1.png",
|
||||||
'images/quick_setup_2.png',
|
"images/quick_setup_2.png",
|
||||||
'images/quick_setup_3.png',
|
"images/quick_setup_3.png",
|
||||||
'images/quick_setup_3b.png',
|
"images/quick_setup_3b.png",
|
||||||
'images/quick_setup_4.png',
|
"images/quick_setup_4.png",
|
||||||
'images/quick_setup_5.png',
|
"images/quick_setup_5.png",
|
||||||
'images/quick_setup_6.png',
|
"images/quick_setup_6.png",
|
||||||
'images/quick_setup_7.png',
|
"images/quick_setup_7.png",
|
||||||
'images/quick_setup_8.png',
|
"images/quick_setup_8.png",
|
||||||
'images/quick_setup_9_1.png',
|
"images/quick_setup_9_1.png",
|
||||||
'images/quick_setup_9_2.png',
|
"images/quick_setup_9_2.png",
|
||||||
'images/quick_setup_10.png',
|
"images/quick_setup_10.png",
|
||||||
'images/remote_db_setting.png',
|
"images/remote_db_setting.png",
|
||||||
'images/write_logs_into_the_file.png',
|
"images/write_logs_into_the_file.png",
|
||||||
]
|
];
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const remote = remoteTopDir + file;
|
const remote = remoteTopDir + file;
|
||||||
const local = this.testRootPath + file;
|
const local = this.testRootPath + file;
|
||||||
@@ -303,7 +299,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
||||||
await delay(100);
|
await delay(100);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
while (!await proc()) {
|
while (!(await proc())) {
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
if (Date.now() - start > timeout) {
|
if (Date.now() - start > timeout) {
|
||||||
this._log(`Timeout`);
|
this._log(`Timeout`);
|
||||||
@@ -316,7 +312,6 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually1() {
|
async testConflictedManually1() {
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve!
|
const commonFile = `Resolve!
|
||||||
@@ -328,18 +323,20 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
|
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
if (await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", { timeout: 30, defaultOption: "Yes" }) == "no") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
||||||
|
timeout: 30,
|
||||||
|
defaultOption: "Yes",
|
||||||
|
})) == "no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const fileA = `Resolve to KEEP THIS
|
const fileA = `Resolve to KEEP THIS
|
||||||
Willy Wonka, Willy Wonka, the amazing chocolatier!!`
|
Willy Wonka, Willy Wonka, the amazing chocolatier!!`;
|
||||||
|
|
||||||
const fileB = `Resolve to DISCARD THIS
|
const fileB = `Resolve to DISCARD THIS
|
||||||
Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`
|
Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (this.isLeader) {
|
if (this.isLeader) {
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileA);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileA);
|
||||||
@@ -347,25 +344,37 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", fileB);
|
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") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to check the result of Manually 1?", {
|
||||||
|
timeout: 30,
|
||||||
|
defaultOption: "Yes",
|
||||||
|
})) == "no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
|
|
||||||
|
if (
|
||||||
if (!await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
return await this.__assertStorageContent(this.testRootPath + "wonka.md" as FilePath, fileA, false, true) == true;
|
return (
|
||||||
}, 30000)) {
|
(await this.__assertStorageContent(
|
||||||
return await this.__assertStorageContent(this.testRootPath + "wonka.md" as FilePath, fileA, false, true);
|
(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;
|
return true;
|
||||||
// We have to check the result
|
// We have to check the result
|
||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually2() {
|
async testConflictedManually2() {
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve To concatenate
|
const commonFile = `Resolve To concatenate
|
||||||
@@ -377,56 +386,82 @@ ABCDEFG`;
|
|||||||
|
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
if (await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", { timeout: 30, defaultOption: "Yes" }) == "no") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
||||||
|
timeout: 30,
|
||||||
|
defaultOption: "Yes",
|
||||||
|
})) == "no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileA = `Resolve to Concatenate
|
const fileA = `Resolve to Concatenate
|
||||||
ABCDEFGHIJKLMNOPQRSTYZ`
|
ABCDEFGHIJKLMNOPQRSTYZ`;
|
||||||
|
|
||||||
const fileB = `Resolve to Concatenate
|
const fileB = `Resolve to Concatenate
|
||||||
AJKLMNOPQRSTUVWXYZ`
|
AJKLMNOPQRSTUVWXYZ`;
|
||||||
|
|
||||||
const concatenated = `Resolve to Concatenate
|
const concatenated = `Resolve to Concatenate
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
||||||
if (this.isLeader) {
|
if (this.isLeader) {
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileA);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileA);
|
||||||
} else {
|
} else {
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", fileB);
|
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") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to test conflict Manually 2?", {
|
||||||
|
timeout: 30,
|
||||||
|
defaultOption: "Yes",
|
||||||
|
})) == "no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
|
|
||||||
|
if (
|
||||||
if (!await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
return await this.__assertStorageContent(this.testRootPath + "concat.md" as FilePath, concatenated, false, true) == true;
|
return (
|
||||||
}, 30000)) {
|
(await this.__assertStorageContent(
|
||||||
return await this.__assertStorageContent(this.testRootPath + "concat.md" as FilePath, concatenated, false, true);
|
(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;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async testConflictAutomatic() {
|
async testConflictAutomatic() {
|
||||||
|
|
||||||
if (this.isLeader) {
|
if (this.isLeader) {
|
||||||
const baseDoc = `Tasks!
|
const baseDoc = `Tasks!
|
||||||
- [ ] Task 1
|
- [ ] Task 1
|
||||||
- [ ] Task 2
|
- [ ] Task 2
|
||||||
- [ ] Task 3
|
- [ ] Task 3
|
||||||
- [ ] Task 4
|
- [ ] Task 4
|
||||||
`
|
`;
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
||||||
}
|
}
|
||||||
await delay(100)
|
await delay(100);
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
|
|
||||||
if (await this.core.confirm.askYesNoDialog("Ready to test conflict?", { timeout: 30, defaultOption: "Yes" }) == "no") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
||||||
|
timeout: 30,
|
||||||
|
defaultOption: "Yes",
|
||||||
|
})) == "no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mod1Doc = `Tasks!
|
const mod1Doc = `Tasks!
|
||||||
@@ -434,14 +469,14 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
|||||||
- [v] Task 2
|
- [v] Task 2
|
||||||
- [ ] Task 3
|
- [ ] Task 3
|
||||||
- [ ] Task 4
|
- [ ] Task 4
|
||||||
`
|
`;
|
||||||
|
|
||||||
const mod2Doc = `Tasks!
|
const mod2Doc = `Tasks!
|
||||||
- [ ] Task 1
|
- [ ] Task 1
|
||||||
- [ ] Task 2
|
- [ ] Task 2
|
||||||
- [v] Task 3
|
- [v] Task 3
|
||||||
- [ ] Task 4
|
- [ ] Task 4
|
||||||
`
|
`;
|
||||||
if (this.isLeader) {
|
if (this.isLeader) {
|
||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod1Doc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod1Doc);
|
||||||
} else {
|
} else {
|
||||||
@@ -451,7 +486,10 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
|||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" }) == "no") {
|
if (
|
||||||
|
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
||||||
|
"no"
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
@@ -461,8 +499,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
|||||||
- [v] Task 2
|
- [v] Task 2
|
||||||
- [v] Task 3
|
- [v] Task 3
|
||||||
- [ ] Task 4
|
- [ ] Task 4
|
||||||
`
|
`;
|
||||||
return this.__assertStorageContent(this.testRootPath + "task.md" as FilePath, mergedDoc, false, true);
|
return this.__assertStorageContent((this.testRootPath + "task.md") as FilePath, mergedDoc, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkConflictResolution() {
|
async checkConflictResolution() {
|
||||||
@@ -471,24 +509,27 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
|||||||
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
||||||
await this.core.$$replicate();
|
await this.core.$$replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (!await this.testConflictAutomatic()) {
|
if (!(await this.testConflictAutomatic())) {
|
||||||
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!await this.testConflictedManually1()) {
|
if (!(await this.testConflictedManually1())) {
|
||||||
this._log("Conflict resolution (Manual1) failed", LOG_LEVEL_NOTICE);
|
this._log("Conflict resolution (Manual1) failed", LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!await this.testConflictedManually2()) {
|
if (!(await this.testConflictedManually2())) {
|
||||||
this._log("Conflict resolution (Manual2) failed", LOG_LEVEL_NOTICE);
|
this._log("Conflict resolution (Manual2) failed", LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async __assertStorageContent(
|
||||||
async __assertStorageContent(fileName: FilePath, content: string, inverted = false, showResult = false): Promise<boolean | string> {
|
fileName: FilePath,
|
||||||
|
content: string,
|
||||||
|
inverted = false,
|
||||||
|
showResult = false
|
||||||
|
): Promise<boolean | string> {
|
||||||
try {
|
try {
|
||||||
const fileContent = await this.core.storageAccess.readHiddenFileText(fileName);
|
const fileContent = await this.core.storageAccess.readHiddenFileText(fileName);
|
||||||
let result = fileContent === content;
|
let result = fileContent === content;
|
||||||
@@ -531,4 +572,4 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`
|
|||||||
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import {
|
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||||
ItemView,
|
import TestPaneComponent from "./TestPane.svelte";
|
||||||
WorkspaceLeaf
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
} from "obsidian";
|
|
||||||
import TestPaneComponent from "./TestPane.svelte"
|
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts"
|
|
||||||
import type { ModuleDev } from "../ModuleDev.ts";
|
import type { ModuleDev } from "../ModuleDev.ts";
|
||||||
export const VIEW_TYPE_TEST = "ols-pane-test";
|
export const VIEW_TYPE_TEST = "ols-pane-test";
|
||||||
//Log view
|
//Log view
|
||||||
export class TestPaneView extends ItemView {
|
export class TestPaneView extends ItemView {
|
||||||
|
|
||||||
component?: TestPaneComponent;
|
component?: TestPaneComponent;
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
moduleDev: ModuleDev;
|
moduleDev: ModuleDev;
|
||||||
icon = "view-log";
|
icon = "view-log";
|
||||||
title: string = "Self-hosted LiveSync Test and Results"
|
title: string = "Self-hosted LiveSync Test and Results";
|
||||||
navigation = true;
|
navigation = true;
|
||||||
|
|
||||||
getIcon(): string {
|
getIcon(): string {
|
||||||
@@ -26,7 +22,6 @@ export class TestPaneView extends ItemView {
|
|||||||
this.moduleDev = moduleDev;
|
this.moduleDev = moduleDev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getViewType() {
|
getViewType() {
|
||||||
return VIEW_TYPE_TEST;
|
return VIEW_TYPE_TEST;
|
||||||
}
|
}
|
||||||
@@ -41,7 +36,7 @@ export class TestPaneView extends ItemView {
|
|||||||
target: this.contentEl,
|
target: this.contentEl,
|
||||||
props: {
|
props: {
|
||||||
plugin: this.plugin,
|
plugin: this.plugin,
|
||||||
moduleDev: this.moduleDev
|
moduleDev: this.moduleDev,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|||||||
@@ -7,40 +7,44 @@ export function enableTestFunction(plugin_: ObsidianLiveSyncPlugin) {
|
|||||||
plugin = plugin_;
|
plugin = plugin_;
|
||||||
}
|
}
|
||||||
export function addDebugFileLog(message: any, stackLog = false) {
|
export function addDebugFileLog(message: any, stackLog = false) {
|
||||||
fireAndForget(serialized("debug-log", async () => {
|
fireAndForget(
|
||||||
const now = new Date();
|
serialized("debug-log", async () => {
|
||||||
const filename = `debug-log`
|
const now = new Date();
|
||||||
const time = now.toISOString().split("T")[0];
|
const filename = `debug-log`;
|
||||||
const outFile = `${filename}${time}.jsonl`;
|
const time = now.toISOString().split("T")[0];
|
||||||
// const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
|
const outFile = `${filename}${time}.jsonl`;
|
||||||
const timestamp = now.toLocaleString();
|
// const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
|
||||||
const timestampEpoch = now;
|
const timestamp = now.toLocaleString();
|
||||||
let out = { "timestamp": timestamp, epoch: timestampEpoch, } as Record<string, any>;
|
const timestampEpoch = now;
|
||||||
if (message instanceof Error) {
|
let out = { timestamp: timestamp, epoch: timestampEpoch } as Record<string, any>;
|
||||||
// debugger;
|
if (message instanceof Error) {
|
||||||
// console.dir(message.stack);
|
// debugger;
|
||||||
out = { ...out, message };
|
// console.dir(message.stack);
|
||||||
} else if (stackLog) {
|
out = { ...out, message };
|
||||||
if (stackLog) {
|
} else if (stackLog) {
|
||||||
const stackE = new Error();
|
if (stackLog) {
|
||||||
const stack = stackE.stack;
|
const stackE = new Error();
|
||||||
out = { ...out, stack }
|
const stack = stackE.stack;
|
||||||
|
out = { ...out, stack };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (typeof message == "object") {
|
||||||
if (typeof message == "object") {
|
out = { ...out, ...message };
|
||||||
out = { ...out, ...message, }
|
} else {
|
||||||
} else {
|
out = {
|
||||||
out = {
|
result: message,
|
||||||
result: message
|
};
|
||||||
}
|
}
|
||||||
}
|
// const out = "--" + timestamp + "--\n" + messageContent + " " + (stack || "");
|
||||||
// const out = "--" + timestamp + "--\n" + messageContent + " " + (stack || "");
|
// const out
|
||||||
// const out
|
try {
|
||||||
try {
|
await plugin.storageAccess.appendHiddenFile(
|
||||||
await plugin.storageAccess.appendHiddenFile(plugin.app.vault.configDir + "/ls-debug/" + outFile, JSON.stringify(out) + "\n")
|
plugin.app.vault.configDir + "/ls-debug/" + outFile,
|
||||||
} catch {
|
JSON.stringify(out) + "\n"
|
||||||
|
);
|
||||||
//NO OP
|
} catch {
|
||||||
}
|
//NO OP
|
||||||
}));
|
}
|
||||||
}
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const measures = new Map<string, MeasureResult>();
|
|||||||
function clearResult(name: string) {
|
function clearResult(name: string) {
|
||||||
measures.set(name, [0, 0]);
|
measures.set(name, [0, 0]);
|
||||||
}
|
}
|
||||||
async function measureEach(name: string, proc: () => (void | Promise<void>)) {
|
async function measureEach(name: string, proc: () => void | Promise<void>) {
|
||||||
const [times, spent] = measures.get(name) ?? [0, 0];
|
const [times, spent] = measures.get(name) ?? [0, 0];
|
||||||
|
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
@@ -15,57 +15,76 @@ async function measureEach(name: string, proc: () => (void | Promise<void>)) {
|
|||||||
if (result instanceof Promise) await result;
|
if (result instanceof Promise) await result;
|
||||||
const end = performance.now();
|
const end = performance.now();
|
||||||
measures.set(name, [times + 1, spent + (end - start)]);
|
measures.set(name, [times + 1, spent + (end - start)]);
|
||||||
|
|
||||||
}
|
}
|
||||||
function formatNumber(num: number) {
|
function formatNumber(num: number) {
|
||||||
return num.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
return num.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
||||||
}
|
}
|
||||||
async function measure(name: string, proc: () => (void | Promise<void>), times: number = 10000, duration: number = 1000): Promise<NamedMeasureResult> {
|
async function measure(
|
||||||
|
name: string,
|
||||||
|
proc: () => void | Promise<void>,
|
||||||
|
times: number = 10000,
|
||||||
|
duration: number = 1000
|
||||||
|
): Promise<NamedMeasureResult> {
|
||||||
const from = Date.now();
|
const from = Date.now();
|
||||||
let last = times;
|
let last = times;
|
||||||
clearResult(name);
|
clearResult(name);
|
||||||
do {
|
do {
|
||||||
await measureEach(name, proc);
|
await measureEach(name, proc);
|
||||||
} while (last-- > 0 && (Date.now() - from) < duration)
|
} while (last-- > 0 && Date.now() - from < duration);
|
||||||
return [name, measures.get(name) as MeasureResult];
|
return [name, measures.get(name) as MeasureResult];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line require-await, @typescript-eslint/require-await
|
// eslint-disable-next-line require-await, @typescript-eslint/require-await
|
||||||
async function formatPerfResults(items: NamedMeasureResult[]) {
|
async function formatPerfResults(items: NamedMeasureResult[]) {
|
||||||
return `| Name | Runs | Each | Total |\n| --- | --- | --- | --- | \n` + items.map(e => `| ${e[0]} | ${e[1][0]} | ${e[1][0] != 0 ? formatNumber(e[1][1] / e[1][0]) : "-"} | ${formatNumber(e[1][0])} |`).join("\n");
|
return (
|
||||||
|
`| Name | Runs | Each | Total |\n| --- | --- | --- | --- | \n` +
|
||||||
|
items
|
||||||
|
.map(
|
||||||
|
(e) =>
|
||||||
|
`| ${e[0]} | ${e[1][0]} | ${e[1][0] != 0 ? formatNumber(e[1][1] / e[1][0]) : "-"} | ${formatNumber(e[1][0])} |`
|
||||||
|
)
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
export async function perf_trench(plugin: ObsidianLiveSyncPlugin) {
|
export async function perf_trench(plugin: ObsidianLiveSyncPlugin) {
|
||||||
clearResult("trench");
|
clearResult("trench");
|
||||||
const trench = new Trench(plugin.simpleStore);
|
const trench = new Trench(plugin.simpleStore);
|
||||||
const result = [] as NamedMeasureResult[];
|
const result = [] as NamedMeasureResult[];
|
||||||
result.push(await measure("trench-short-string", async () => {
|
result.push(
|
||||||
const p = trench.evacuate("string");
|
await measure("trench-short-string", async () => {
|
||||||
await p();
|
const p = trench.evacuate("string");
|
||||||
}));
|
await p();
|
||||||
|
})
|
||||||
|
);
|
||||||
{
|
{
|
||||||
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/10kb.png");
|
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/10kb.png");
|
||||||
const uint8Array = new Uint8Array(testBinary);
|
const uint8Array = new Uint8Array(testBinary);
|
||||||
result.push(await measure("trench-binary-10kb", async () => {
|
result.push(
|
||||||
const p = trench.evacuate(uint8Array);
|
await measure("trench-binary-10kb", async () => {
|
||||||
await p();
|
const p = trench.evacuate(uint8Array);
|
||||||
}));
|
await p();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/100kb.jpeg");
|
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/100kb.jpeg");
|
||||||
const uint8Array = new Uint8Array(testBinary);
|
const uint8Array = new Uint8Array(testBinary);
|
||||||
result.push(await measure("trench-binary-100kb", async () => {
|
result.push(
|
||||||
const p = trench.evacuate(uint8Array);
|
await measure("trench-binary-100kb", async () => {
|
||||||
await p();
|
const p = trench.evacuate(uint8Array);
|
||||||
}));
|
await p();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/1mb.png");
|
const testBinary = await plugin.storageAccess.readHiddenFileBinary("testdata/1mb.png");
|
||||||
const uint8Array = new Uint8Array(testBinary);
|
const uint8Array = new Uint8Array(testBinary);
|
||||||
result.push(await measure("trench-binary-1mb", async () => {
|
result.push(
|
||||||
const p = trench.evacuate(uint8Array);
|
await measure("trench-binary-1mb", async () => {
|
||||||
await p();
|
const p = trench.evacuate(uint8Array);
|
||||||
}));
|
await p();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return formatPerfResults(result);
|
return formatPerfResults(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import { TFile, Modal, App, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_pat
|
|||||||
import { getPathFromTFile, isValidPath } from "../../../common/utils.ts";
|
import { getPathFromTFile, isValidPath } from "../../../common/utils.ts";
|
||||||
import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts";
|
import { decodeBinary, escapeStringToHTML, readString } from "../../../lib/src/string_and_binary/convert.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { type DocumentID, type FilePathWithPrefix, type LoadedEntry, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../../lib/src/common/types.ts";
|
import {
|
||||||
|
type DocumentID,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type LoadedEntry,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
} from "../../../lib/src/common/types.ts";
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { isErrorOfMissingDoc } from "../../../lib/src/pouchdb/utils_couchdb.ts";
|
import { isErrorOfMissingDoc } from "../../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
import { fireAndForget, getDocData, readContent } from "../../../lib/src/common/utils.ts";
|
import { fireAndForget, getDocData, readContent } from "../../../lib/src/common/utils.ts";
|
||||||
@@ -19,7 +26,7 @@ function isComparableText(path: string) {
|
|||||||
}
|
}
|
||||||
function isComparableTextDecode(path: string) {
|
function isComparableTextDecode(path: string) {
|
||||||
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
const ext = path.split(".").splice(-1)[0].toLowerCase();
|
||||||
return ["json"].includes(ext)
|
return ["json"].includes(ext);
|
||||||
}
|
}
|
||||||
function readDocument(w: LoadedEntry) {
|
function readDocument(w: LoadedEntry) {
|
||||||
if (w.data.length == 0) return "";
|
if (w.data.length == 0) return "";
|
||||||
@@ -54,10 +61,16 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
currentDeleted = false;
|
currentDeleted = false;
|
||||||
initialRev?: string;
|
initialRev?: string;
|
||||||
|
|
||||||
constructor(app: App, plugin: ObsidianLiveSyncPlugin, file: TFile | FilePathWithPrefix, id?: DocumentID, revision?: string) {
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: ObsidianLiveSyncPlugin,
|
||||||
|
file: TFile | FilePathWithPrefix,
|
||||||
|
id?: DocumentID,
|
||||||
|
revision?: string
|
||||||
|
) {
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.file = (file instanceof TFile) ? getPathFromTFile(file) : file;
|
this.file = file instanceof TFile ? getPathFromTFile(file) : file;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.initialRev = revision;
|
this.initialRev = revision;
|
||||||
if (!file && id) {
|
if (!file && id) {
|
||||||
@@ -95,7 +108,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
async loadRevs(initialRev?: string) {
|
async loadRevs(initialRev?: string) {
|
||||||
if (this.revs_info.length == 0) return;
|
if (this.revs_info.length == 0) return;
|
||||||
if (initialRev) {
|
if (initialRev) {
|
||||||
const rIndex = this.revs_info.findIndex(e => e.rev == initialRev);
|
const rIndex = this.revs_info.findIndex((e) => e.rev == initialRev);
|
||||||
if (rIndex >= 0) {
|
if (rIndex >= 0) {
|
||||||
this.range.value = `${this.revs_info.length - 1 - rIndex}`;
|
this.range.value = `${this.revs_info.length - 1 - rIndex}`;
|
||||||
}
|
}
|
||||||
@@ -163,8 +176,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
} else if (isImage(this.file)) {
|
} else if (isImage(this.file)) {
|
||||||
const src = this.generateBlobURL("base", w1data);
|
const src = this.generateBlobURL("base", w1data);
|
||||||
const overlay = this.generateBlobURL("overlay", readDocument(w2) as Uint8Array);
|
const overlay = this.generateBlobURL("overlay", readDocument(w2) as Uint8Array);
|
||||||
result =
|
result = `<div class='ls-imgdiff-wrap'>
|
||||||
`<div class='ls-imgdiff-wrap'>
|
|
||||||
<div class='overlay'>
|
<div class='overlay'>
|
||||||
<img class='img-base' src="${src}">
|
<img class='img-base' src="${src}">
|
||||||
<img class='img-overlay' src='${overlay}'>
|
<img class='img-overlay' src='${overlay}'>
|
||||||
@@ -174,14 +186,12 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (result == undefined) {
|
if (result == undefined) {
|
||||||
if (typeof w1data != "string") {
|
if (typeof w1data != "string") {
|
||||||
if (isImage(this.file)) {
|
if (isImage(this.file)) {
|
||||||
const src = this.generateBlobURL("base", w1data);
|
const src = this.generateBlobURL("base", w1data);
|
||||||
result =
|
result = `<div class='ls-imgdiff-wrap'>
|
||||||
`<div class='ls-imgdiff-wrap'>
|
|
||||||
<div class='overlay'>
|
<div class='overlay'>
|
||||||
<img class='img-base' src="${src}">
|
<img class='img-base' src="${src}">
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +203,8 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file";
|
if (result == undefined) result = typeof w1data == "string" ? escapeStringToHTML(w1data) : "Binary file";
|
||||||
this.contentView.innerHTML = (this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result;
|
this.contentView.innerHTML =
|
||||||
|
(this.currentDeleted ? "(At this revision, the file has been deleted)\n" : "") + result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,9 +268,9 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
const leaf = this.plugin.app.workspace.getLeaf(false);
|
const leaf = this.plugin.app.workspace.getLeaf(false);
|
||||||
await leaf.openFile(targetFile);
|
await leaf.openFile(targetFile);
|
||||||
} else {
|
} else {
|
||||||
Logger("The file could not view on the editor", LOG_LEVEL_NOTICE)
|
Logger("The file could not view on the editor", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
buttons.createEl("button", { text: "Back to this revision" }, (e) => {
|
buttons.createEl("button", { text: "Back to this revision" }, (e) => {
|
||||||
e.addClass("mod-cta");
|
e.addClass("mod-cta");
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
@@ -285,9 +296,9 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
onClose() {
|
onClose() {
|
||||||
const { contentEl } = this;
|
const { contentEl } = this;
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
this.BlobURLs.forEach(value => {
|
this.BlobURLs.forEach((value) => {
|
||||||
console.log(value);
|
console.log(value);
|
||||||
if (value) URL.revokeObjectURL(value);
|
if (value) URL.revokeObjectURL(value);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import {
|
import { ItemView, WorkspaceLeaf } from "../../../deps.ts";
|
||||||
ItemView,
|
|
||||||
WorkspaceLeaf
|
|
||||||
} from "../../../deps.ts";
|
|
||||||
import GlobalHistoryComponent from "./GlobalHistory.svelte";
|
import GlobalHistoryComponent from "./GlobalHistory.svelte";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
|
||||||
export const VIEW_TYPE_GLOBAL_HISTORY = "global-history";
|
export const VIEW_TYPE_GLOBAL_HISTORY = "global-history";
|
||||||
export class GlobalHistoryView extends ItemView {
|
export class GlobalHistoryView extends ItemView {
|
||||||
|
|
||||||
component?: GlobalHistoryComponent;
|
component?: GlobalHistoryComponent;
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
icon = "clock";
|
icon = "clock";
|
||||||
@@ -23,7 +19,6 @@ export class GlobalHistoryView extends ItemView {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getViewType() {
|
getViewType() {
|
||||||
return VIEW_TYPE_GLOBAL_HISTORY;
|
return VIEW_TYPE_GLOBAL_HISTORY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class ConflictResolveModal extends Modal {
|
|||||||
if (this.pluginPickMode) {
|
if (this.pluginPickMode) {
|
||||||
this.title = "Pick a version";
|
this.title = "Pick a version";
|
||||||
this.remoteName = `Use ${remoteName || "Remote"}`;
|
this.remoteName = `Use ${remoteName || "Remote"}`;
|
||||||
this.localName = "Use Local"
|
this.localName = "Use Local";
|
||||||
}
|
}
|
||||||
// Send cancel signal for the previous merge dialogue
|
// Send cancel signal for the previous merge dialogue
|
||||||
// if not there, simply be ignored.
|
// if not there, simply be ignored.
|
||||||
@@ -48,7 +48,7 @@ export class ConflictResolveModal extends Modal {
|
|||||||
this.sendResponse(CANCELLED);
|
this.sendResponse(CANCELLED);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 10)
|
}, 10);
|
||||||
// sendValue("close-resolve-conflict:" + this.filename, false);
|
// sendValue("close-resolve-conflict:" + this.filename, false);
|
||||||
this.titleEl.setText(this.title);
|
this.titleEl.setText(this.title);
|
||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
@@ -60,28 +60,47 @@ export class ConflictResolveModal extends Modal {
|
|||||||
const x1 = v[0];
|
const x1 = v[0];
|
||||||
const x2 = v[1];
|
const x2 = v[1];
|
||||||
if (x1 == DIFF_DELETE) {
|
if (x1 == DIFF_DELETE) {
|
||||||
diff += "<span class='deleted'>" + escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") + "</span>";
|
diff +=
|
||||||
|
"<span class='deleted'>" +
|
||||||
|
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||||
|
"</span>";
|
||||||
} else if (x1 == DIFF_EQUAL) {
|
} else if (x1 == DIFF_EQUAL) {
|
||||||
diff += "<span class='normal'>" + escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") + "</span>";
|
diff +=
|
||||||
|
"<span class='normal'>" +
|
||||||
|
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||||
|
"</span>";
|
||||||
} else if (x1 == DIFF_INSERT) {
|
} else if (x1 == DIFF_INSERT) {
|
||||||
diff += "<span class='added'>" + escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") + "</span>";
|
diff +=
|
||||||
|
"<span class='added'>" +
|
||||||
|
escapeStringToHTML(x2).replace(/\n/g, "<span class='ls-mark-cr'></span>\n") +
|
||||||
|
"</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diff = diff.replace(/\n/g, "<br>");
|
diff = diff.replace(/\n/g, "<br>");
|
||||||
div.innerHTML = diff;
|
div.innerHTML = diff;
|
||||||
const div2 = contentEl.createDiv("");
|
const div2 = contentEl.createDiv("");
|
||||||
const date1 = new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
|
const date1 =
|
||||||
const date2 = new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
|
||||||
|
const date2 =
|
||||||
|
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
||||||
div2.innerHTML = `
|
div2.innerHTML = `
|
||||||
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
|
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
|
||||||
`;
|
`;
|
||||||
contentEl.createEl("button", { text: this.localName }, (e) => e.addEventListener("click", () => this.sendResponse(this.result.right.rev))).style.marginRight = "4px";
|
contentEl.createEl("button", { text: this.localName }, (e) =>
|
||||||
contentEl.createEl("button", { text: this.remoteName }, (e) => e.addEventListener("click", () => this.sendResponse(this.result.left.rev))).style.marginRight = "4px";
|
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
||||||
|
).style.marginRight = "4px";
|
||||||
|
contentEl.createEl("button", { text: this.remoteName }, (e) =>
|
||||||
|
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
||||||
|
).style.marginRight = "4px";
|
||||||
if (!this.pluginPickMode) {
|
if (!this.pluginPickMode) {
|
||||||
contentEl.createEl("button", { text: "Concat both" }, (e) => e.addEventListener("click", () => this.sendResponse(LEAVE_TO_SUBSEQUENT))).style.marginRight = "4px";
|
contentEl.createEl("button", { text: "Concat both" }, (e) =>
|
||||||
|
e.addEventListener("click", () => this.sendResponse(LEAVE_TO_SUBSEQUENT))
|
||||||
|
).style.marginRight = "4px";
|
||||||
}
|
}
|
||||||
contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) => e.addEventListener("click", () => this.sendResponse(CANCELLED))).style.marginRight = "4px";
|
contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) =>
|
||||||
|
e.addEventListener("click", () => this.sendResponse(CANCELLED))
|
||||||
|
).style.marginRight = "4px";
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResponse(result: MergeDialogResult) {
|
sendResponse(result: MergeDialogResult) {
|
||||||
@@ -106,4 +125,4 @@ export class ConflictResolveModal extends Modal {
|
|||||||
if (r === RESULT_TIMED_OUT) return CANCELLED;
|
if (r === RESULT_TIMED_OUT) return CANCELLED;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import {
|
import { ItemView, WorkspaceLeaf } from "obsidian";
|
||||||
ItemView,
|
|
||||||
WorkspaceLeaf
|
|
||||||
} from "obsidian";
|
|
||||||
import LogPaneComponent from "./LogPane.svelte";
|
import LogPaneComponent from "./LogPane.svelte";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
export const VIEW_TYPE_LOG = "log-log";
|
export const VIEW_TYPE_LOG = "log-log";
|
||||||
//Log view
|
//Log view
|
||||||
export class LogPaneView extends ItemView {
|
export class LogPaneView extends ItemView {
|
||||||
|
|
||||||
component?: LogPaneComponent;
|
component?: LogPaneComponent;
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
icon = "view-log";
|
icon = "view-log";
|
||||||
@@ -23,7 +19,6 @@ export class LogPaneView extends ItemView {
|
|||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getViewType() {
|
getViewType() {
|
||||||
return VIEW_TYPE_LOG;
|
return VIEW_TYPE_LOG;
|
||||||
}
|
}
|
||||||
@@ -35,8 +30,7 @@ export class LogPaneView extends ItemView {
|
|||||||
async onOpen() {
|
async onOpen() {
|
||||||
this.component = new LogPaneComponent({
|
this.component = new LogPaneComponent({
|
||||||
target: this.contentEl,
|
target: this.contentEl,
|
||||||
props: {
|
props: {},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
||||||
|
|
||||||
|
|
||||||
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-global-history",
|
id: "livesync-global-history",
|
||||||
name: "Show vault history",
|
name: "Show vault history",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.showGlobalHistory()
|
this.showGlobalHistory();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
this.registerView(
|
this.registerView(VIEW_TYPE_GLOBAL_HISTORY, (leaf) => new GlobalHistoryView(leaf, this.plugin));
|
||||||
VIEW_TYPE_GLOBAL_HISTORY,
|
|
||||||
(leaf) => new GlobalHistoryView(leaf, this.plugin)
|
|
||||||
);
|
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -25,5 +19,4 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implemen
|
|||||||
showGlobalHistory() {
|
showGlobalHistory() {
|
||||||
void this.core.$$showView(VIEW_TYPE_GLOBAL_HISTORY);
|
void this.core.$$showView(VIEW_TYPE_GLOBAL_HISTORY);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { CANCELLED, LEAVE_TO_SUBSEQUENT, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MISSING_OR_ERROR, type DocumentID, type FilePathWithPrefix, type diff_result } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
CANCELLED,
|
||||||
|
LEAVE_TO_SUBSEQUENT,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
MISSING_OR_ERROR,
|
||||||
|
type DocumentID,
|
||||||
|
type FilePathWithPrefix,
|
||||||
|
type diff_result,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
|
|
||||||
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-conflictcheck",
|
id: "livesync-conflictcheck",
|
||||||
@@ -13,14 +22,14 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.pickFileForResolve();
|
await this.pickFileForResolve();
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-all-conflictcheck",
|
id: "livesync-all-conflictcheck",
|
||||||
name: "Resolve all conflicted files",
|
name: "Resolve all conflicted files",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.allConflictCheck();
|
await this.allConflictCheck();
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,18 +59,26 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
// Create a new file by concatenating both conflicted revisions.
|
// Create a new file by concatenating both conflicted revisions.
|
||||||
const p = conflictCheckResult.diff.map((e) => e[1]).join("");
|
const p = conflictCheckResult.diff.map((e) => e[1]).join("");
|
||||||
const delRev = testDoc._conflicts[0];
|
const delRev = testDoc._conflicts[0];
|
||||||
if (!await this.core.databaseFileAccess.storeContent(filename, p)) {
|
if (!(await this.core.databaseFileAccess.storeContent(filename, p))) {
|
||||||
this._log(`Concatenated content cannot be stored:${filename}`, LOG_LEVEL_NOTICE);
|
this._log(`Concatenated content cannot be stored:${filename}`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
||||||
if (await this.core.$$resolveConflictByDeletingRev(filename, delRev, "UI Concatenated") == MISSING_OR_ERROR) {
|
if (
|
||||||
this._log(`Concatenated saved, but cannot delete conflicted revisions: ${filename}, (${displayRev(delRev)})`, LOG_LEVEL_NOTICE);
|
(await this.core.$$resolveConflictByDeletingRev(filename, delRev, "UI Concatenated")) ==
|
||||||
|
MISSING_OR_ERROR
|
||||||
|
) {
|
||||||
|
this._log(
|
||||||
|
`Concatenated saved, but cannot delete conflicted revisions: ${filename}, (${displayRev(delRev)})`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (typeof toDelete === "string") {
|
} else if (typeof toDelete === "string") {
|
||||||
// Select one of the conflicted revision to delete.
|
// Select one of the conflicted revision to delete.
|
||||||
if (await this.core.$$resolveConflictByDeletingRev(filename, toDelete, "UI Selected") == MISSING_OR_ERROR) {
|
if (
|
||||||
|
(await this.core.$$resolveConflictByDeletingRev(filename, toDelete, "UI Selected")) == MISSING_OR_ERROR
|
||||||
|
) {
|
||||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -83,22 +100,21 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
while (await this.pickFileForResolve());
|
while (await this.pickFileForResolve());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async pickFileForResolve() {
|
async pickFileForResolve() {
|
||||||
const notes: { id: DocumentID, path: FilePathWithPrefix, dispPath: string, mtime: number }[] = [];
|
const notes: { id: DocumentID; path: FilePathWithPrefix; dispPath: string; mtime: number }[] = [];
|
||||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
if (!("_conflicts" in doc)) continue;
|
if (!("_conflicts" in doc)) continue;
|
||||||
notes.push({ id: doc._id, path: getPath(doc), dispPath: getPathWithoutPrefix(doc), mtime: doc.mtime });
|
notes.push({ id: doc._id, path: getPath(doc), dispPath: getPathWithoutPrefix(doc), mtime: doc.mtime });
|
||||||
}
|
}
|
||||||
notes.sort((a, b) => b.mtime - a.mtime);
|
notes.sort((a, b) => b.mtime - a.mtime);
|
||||||
const notesList = notes.map(e => e.dispPath);
|
const notesList = notes.map((e) => e.dispPath);
|
||||||
if (notesList.length == 0) {
|
if (notesList.length == 0) {
|
||||||
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
|
this._log("There are no conflicted documents", LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
||||||
if (target) {
|
if (target) {
|
||||||
const targetItem = notes.find(e => e.dispPath == target)!;
|
const targetItem = notes.find((e) => e.dispPath == target)!;
|
||||||
await this.core.$$queueConflictCheck(targetItem.path);
|
await this.core.$$queueConflictCheck(targetItem.path);
|
||||||
await this.core.$$waitForAllConflictProcessed();
|
await this.core.$$waitForAllConflictProcessed();
|
||||||
return true;
|
return true;
|
||||||
@@ -107,21 +123,27 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $allScanStat(): Promise<boolean> {
|
async $allScanStat(): Promise<boolean> {
|
||||||
const notes: { path: string, mtime: number }[] = [];
|
const notes: { path: string; mtime: number }[] = [];
|
||||||
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
||||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
if (!("_conflicts" in doc)) continue;
|
if (!("_conflicts" in doc)) continue;
|
||||||
notes.push({ path: getPath(doc), mtime: doc.mtime });
|
notes.push({ path: getPath(doc), mtime: doc.mtime });
|
||||||
}
|
}
|
||||||
if (notes.length > 0) {
|
if (notes.length > 0) {
|
||||||
this.core.confirm.askInPopup(`conflicting-detected-on-safety`, `Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`, (anchor) => {
|
this.core.confirm.askInPopup(
|
||||||
anchor.text = "HERE";
|
`conflicting-detected-on-safety`,
|
||||||
anchor.addEventListener("click", () => {
|
`Some files have been left conflicted! Press {HERE} to resolve them, or you can do it later by "Pick a file to resolve conflict`,
|
||||||
fireAndForget(() => this.allConflictCheck())
|
(anchor) => {
|
||||||
});
|
anchor.text = "HERE";
|
||||||
}
|
anchor.addEventListener("click", () => {
|
||||||
|
fireAndForget(() => this.allConflictCheck());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._log(
|
||||||
|
`Some files have been left conflicted! Please resolve them by "Pick a file to resolve conflict". The list is written in the log.`,
|
||||||
|
LOG_LEVEL_VERBOSE
|
||||||
);
|
);
|
||||||
this._log(`Some files have been left conflicted! Please resolve them by "Pick a file to resolve conflict". The list is written in the log.`, LOG_LEVEL_VERBOSE);
|
|
||||||
for (const note of notes) {
|
for (const note of notes) {
|
||||||
this._log(`Conflicted: ${note.path}`);
|
this._log(`Conflicted: ${note.path}`);
|
||||||
}
|
}
|
||||||
@@ -130,5 +152,4 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,23 @@
|
|||||||
import { computed, reactive, reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive";
|
import { computed, reactive, reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type LOG_LEVEL } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
LOG_LEVEL_DEBUG,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
PREFIXMD_LOGFILE,
|
||||||
|
type DatabaseConnectingStatus,
|
||||||
|
type LOG_LEVEL,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { cancelTask, scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { cancelTask, scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { fireAndForget, isDirty, throttle } from "../../lib/src/common/utils.ts";
|
import { fireAndForget, isDirty, throttle } from "../../lib/src/common/utils.ts";
|
||||||
import { collectingChunks, pluginScanningCount, hiddenFilesEventCount, hiddenFilesProcessingCount, type LogEntry, logStore, logMessages } from "../../lib/src/mock_and_interop/stores.ts";
|
import {
|
||||||
|
collectingChunks,
|
||||||
|
pluginScanningCount,
|
||||||
|
hiddenFilesEventCount,
|
||||||
|
hiddenFilesProcessingCount,
|
||||||
|
type LogEntry,
|
||||||
|
logStore,
|
||||||
|
logMessages,
|
||||||
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import { eventHub } from "../../lib/src/hub/hub.ts";
|
import { eventHub } from "../../lib/src/hub/hub.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
@@ -22,15 +37,16 @@ setGlobalLogFunction((message: any, level?: number, key?: string) => {
|
|||||||
let recentLogs = [] as string[];
|
let recentLogs = [] as string[];
|
||||||
|
|
||||||
// Recent log splicer
|
// Recent log splicer
|
||||||
const recentLogProcessor = new QueueProcessor((logs: string[]) => {
|
const recentLogProcessor = new QueueProcessor(
|
||||||
recentLogs = [...recentLogs, ...logs].splice(-200);
|
(logs: string[]) => {
|
||||||
logMessages.value = recentLogs;
|
recentLogs = [...recentLogs, ...logs].splice(-200);
|
||||||
}, { batchSize: 25, delay: 10, suspended: false, concurrentLimit: 1 }).resumePipeLine();
|
logMessages.value = recentLogs;
|
||||||
|
},
|
||||||
|
{ batchSize: 25, delay: 10, suspended: false, concurrentLimit: 1 }
|
||||||
|
).resumePipeLine();
|
||||||
// logStore.intercept(e => e.slice(Math.min(e.length - 200, 0)));
|
// logStore.intercept(e => e.slice(Math.min(e.length - 200, 0)));
|
||||||
|
|
||||||
|
|
||||||
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
registerView = this.plugin.registerView.bind(this.plugin);
|
registerView = this.plugin.registerView.bind(this.plugin);
|
||||||
|
|
||||||
statusBar?: HTMLElement;
|
statusBar?: HTMLElement;
|
||||||
@@ -41,7 +57,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
logHistory?: HTMLDivElement;
|
logHistory?: HTMLDivElement;
|
||||||
messageArea?: HTMLDivElement;
|
messageArea?: HTMLDivElement;
|
||||||
|
|
||||||
statusBarLabels!: ReactiveValue<{ message: string, status: string }>;
|
statusBarLabels!: ReactiveValue<{ message: string; status: string }>;
|
||||||
statusLog = reactiveSource("");
|
statusLog = reactiveSource("");
|
||||||
notifies: { [key: string]: { notice: Notice; count: number } } = {};
|
notifies: { [key: string]: { notice: Notice; count: number } } = {};
|
||||||
|
|
||||||
@@ -52,7 +68,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const formatted = reactiveSource("");
|
const formatted = reactiveSource("");
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
|
let timer: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
let maxLen = 1;
|
let maxLen = 1;
|
||||||
numI.onChanged(numX => {
|
numI.onChanged((numX) => {
|
||||||
const num = numX.value;
|
const num = numX.value;
|
||||||
const numLen = `${Math.abs(num)}`.length + 1;
|
const numLen = `${Math.abs(num)}`.length + 1;
|
||||||
maxLen = maxLen < numLen ? numLen : maxLen;
|
maxLen = maxLen < numLen ? numLen : maxLen;
|
||||||
@@ -63,8 +79,8 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
maxLen = 1;
|
maxLen = 1;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
formatted.value = ` ${mark}${`${padSpaces}${num}`.slice(-(maxLen))}`;
|
formatted.value = ` ${mark}${`${padSpaces}${num}`.slice(-maxLen)}`;
|
||||||
})
|
});
|
||||||
return computed(() => formatted.value);
|
return computed(() => formatted.value);
|
||||||
}
|
}
|
||||||
const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`);
|
const labelReplication = padLeftSpComputed(this.core.replicationResultCount, `📥`);
|
||||||
@@ -74,16 +90,16 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
|
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
|
||||||
const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`);
|
const labelConflictProcessCount = padLeftSpComputed(this.core.conflictProcessQueueCount, `🔩`);
|
||||||
const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value);
|
const hiddenFilesCount = reactive(() => hiddenFilesEventCount.value + hiddenFilesProcessingCount.value);
|
||||||
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`)
|
const labelHiddenFilesCount = padLeftSpComputed(hiddenFilesCount, `⚙️`);
|
||||||
const queueCountLabelX = reactive(() => {
|
const queueCountLabelX = reactive(() => {
|
||||||
return `${labelReplication()}${labelDBCount()}${labelStorageCount()}${labelChunkCount()}${labelPluginScanCount()}${labelHiddenFilesCount()}${labelConflictProcessCount()}`;
|
return `${labelReplication()}${labelDBCount()}${labelStorageCount()}${labelChunkCount()}${labelPluginScanCount()}${labelHiddenFilesCount()}${labelConflictProcessCount()}`;
|
||||||
})
|
});
|
||||||
const queueCountLabel = () => queueCountLabelX.value;
|
const queueCountLabel = () => queueCountLabelX.value;
|
||||||
|
|
||||||
const requestingStatLabel = computed(() => {
|
const requestingStatLabel = computed(() => {
|
||||||
const diff = this.core.requestCount.value - this.core.responseCount.value;
|
const diff = this.core.requestCount.value - this.core.responseCount.value;
|
||||||
return diff != 0 ? "📲 " : "";
|
return diff != 0 ? "📲 " : "";
|
||||||
})
|
});
|
||||||
|
|
||||||
const replicationStatLabel = computed(() => {
|
const replicationStatLabel = computed(() => {
|
||||||
const e = this.core.replicationStat.value;
|
const e = this.core.replicationStat.value;
|
||||||
@@ -97,10 +113,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
let pullLast = "";
|
let pullLast = "";
|
||||||
let w = "";
|
let w = "";
|
||||||
const labels: Partial<Record<DatabaseConnectingStatus, string>> = {
|
const labels: Partial<Record<DatabaseConnectingStatus, string>> = {
|
||||||
"CONNECTED": "⚡",
|
CONNECTED: "⚡",
|
||||||
"JOURNAL_SEND": "📦↑",
|
JOURNAL_SEND: "📦↑",
|
||||||
"JOURNAL_RECEIVE": "📦↓",
|
JOURNAL_RECEIVE: "📦↓",
|
||||||
}
|
};
|
||||||
switch (e.syncStatus) {
|
switch (e.syncStatus) {
|
||||||
case "CLOSED":
|
case "CLOSED":
|
||||||
case "COMPLETED":
|
case "COMPLETED":
|
||||||
@@ -117,8 +133,18 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
case "JOURNAL_SEND":
|
case "JOURNAL_SEND":
|
||||||
case "JOURNAL_RECEIVE":
|
case "JOURNAL_RECEIVE":
|
||||||
w = labels[e.syncStatus] || "⚡";
|
w = labels[e.syncStatus] || "⚡";
|
||||||
pushLast = ((lastSyncPushSeq == 0) ? "" : (lastSyncPushSeq >= maxPushSeq ? " (LIVE)" : ` (${maxPushSeq - lastSyncPushSeq})`));
|
pushLast =
|
||||||
pullLast = ((lastSyncPullSeq == 0) ? "" : (lastSyncPullSeq >= maxPullSeq ? " (LIVE)" : ` (${maxPullSeq - lastSyncPullSeq})`));
|
lastSyncPushSeq == 0
|
||||||
|
? ""
|
||||||
|
: lastSyncPushSeq >= maxPushSeq
|
||||||
|
? " (LIVE)"
|
||||||
|
: ` (${maxPushSeq - lastSyncPushSeq})`;
|
||||||
|
pullLast =
|
||||||
|
lastSyncPullSeq == 0
|
||||||
|
? ""
|
||||||
|
: lastSyncPullSeq >= maxPullSeq
|
||||||
|
? " (LIVE)"
|
||||||
|
: ` (${maxPullSeq - lastSyncPullSeq})`;
|
||||||
break;
|
break;
|
||||||
case "ERRORED":
|
case "ERRORED":
|
||||||
w = "⚠";
|
w = "⚠";
|
||||||
@@ -127,13 +153,13 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
w = "?";
|
w = "?";
|
||||||
}
|
}
|
||||||
return { w, sent, pushLast, arrived, pullLast };
|
return { w, sent, pushLast, arrived, pullLast };
|
||||||
})
|
});
|
||||||
const labelProc = padLeftSpComputed(this.core.processing, `⏳`);
|
const labelProc = padLeftSpComputed(this.core.processing, `⏳`);
|
||||||
const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`);
|
const labelPend = padLeftSpComputed(this.core.totalQueued, `🛫`);
|
||||||
const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`);
|
const labelInBatchDelay = padLeftSpComputed(this.core.batched, `📬`);
|
||||||
const waitingLabel = computed(() => {
|
const waitingLabel = computed(() => {
|
||||||
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
|
return `${labelProc()}${labelPend()}${labelInBatchDelay()}`;
|
||||||
})
|
});
|
||||||
const statusLineLabel = computed(() => {
|
const statusLineLabel = computed(() => {
|
||||||
const { w, sent, pushLast, arrived, pullLast } = replicationStatLabel();
|
const { w, sent, pushLast, arrived, pullLast } = replicationStatLabel();
|
||||||
const queued = queueCountLabel();
|
const queued = queueCountLabel();
|
||||||
@@ -142,24 +168,26 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
return {
|
return {
|
||||||
message: `${networkActivity}Sync: ${w} ↑ ${sent}${pushLast} ↓ ${arrived}${pullLast}${waiting}${queued}`,
|
message: `${networkActivity}Sync: ${w} ↑ ${sent}${pushLast} ↓ ${arrived}${pullLast}${waiting}${queued}`,
|
||||||
};
|
};
|
||||||
})
|
});
|
||||||
const statusBarLabels = reactive(() => {
|
const statusBarLabels = reactive(() => {
|
||||||
const scheduleMessage = this.core.$$isReloadingScheduled() ? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n` : "";
|
const scheduleMessage = this.core.$$isReloadingScheduled()
|
||||||
|
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
|
||||||
|
: "";
|
||||||
const { message } = statusLineLabel();
|
const { message } = statusLineLabel();
|
||||||
const status = scheduleMessage + this.statusLog.value;
|
const status = scheduleMessage + this.statusLog.value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message, status
|
message,
|
||||||
}
|
status,
|
||||||
})
|
};
|
||||||
|
});
|
||||||
this.statusBarLabels = statusBarLabels;
|
this.statusBarLabels = statusBarLabels;
|
||||||
|
|
||||||
const applyToDisplay = throttle((label: typeof statusBarLabels.value) => {
|
const applyToDisplay = throttle((label: typeof statusBarLabels.value) => {
|
||||||
// const v = label;
|
// const v = label;
|
||||||
this.applyStatusBarText();
|
this.applyStatusBarText();
|
||||||
|
|
||||||
}, 20);
|
}, 20);
|
||||||
statusBarLabels.onChanged(label => applyToDisplay(label.value))
|
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
@@ -182,10 +210,13 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
if (!thisFile) return "";
|
if (!thisFile) return "";
|
||||||
// Case Sensitivity
|
// Case Sensitivity
|
||||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
if (this.core.$$shouldCheckCaseInsensitive()) {
|
||||||
const f = this.core.storageAccess.getFiles().map(e => e.path).filter(e => e.toLowerCase() == thisFile.path.toLowerCase());
|
const f = this.core.storageAccess
|
||||||
|
.getFiles()
|
||||||
|
.map((e) => e.path)
|
||||||
|
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
|
||||||
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
||||||
}
|
}
|
||||||
if (!await this.core.$$isTargetFile(thisFile.path)) return "Not synchronised: not a target file";
|
if (!(await this.core.$$isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
|
||||||
if (this.core.$$isFileSizeExceeded(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
if (this.core.$$isFileSizeExceeded(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -197,11 +228,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
this.adjustStatusDivPosition();
|
this.adjustStatusDivPosition();
|
||||||
await this.setFileStatus();
|
await this.setFileStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextFrameQueue: ReturnType<typeof requestAnimationFrame> | undefined = undefined;
|
nextFrameQueue: ReturnType<typeof requestAnimationFrame> | undefined = undefined;
|
||||||
logLines: { ttl: number, message: string }[] = [];
|
logLines: { ttl: number; message: string }[] = [];
|
||||||
|
|
||||||
applyStatusBarText() {
|
applyStatusBarText() {
|
||||||
if (this.nextFrameQueue) {
|
if (this.nextFrameQueue) {
|
||||||
@@ -222,10 +252,13 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
if (this.settings?.showStatusOnEditor && this.statusDiv) {
|
if (this.settings?.showStatusOnEditor && this.statusDiv) {
|
||||||
if (this.settings.showLongerLogInsideEditor) {
|
if (this.settings.showLongerLogInsideEditor) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
this.logLines = this.logLines.filter(e => e.ttl > now);
|
this.logLines = this.logLines.filter((e) => e.ttl > now);
|
||||||
const minimumNext = this.logLines.reduce((a, b) => a < b.ttl ? a : b.ttl, Number.MAX_SAFE_INTEGER);
|
const minimumNext = this.logLines.reduce(
|
||||||
|
(a, b) => (a < b.ttl ? a : b.ttl),
|
||||||
|
Number.MAX_SAFE_INTEGER
|
||||||
|
);
|
||||||
if (this.logLines.length > 0) setTimeout(() => this.applyStatusBarText(), minimumNext - now);
|
if (this.logLines.length > 0) setTimeout(() => this.applyStatusBarText(), minimumNext - now);
|
||||||
const recent = this.logLines.map(e => e.message);
|
const recent = this.logLines.map((e) => e.message);
|
||||||
const recentLogs = recent.reverse().join("\n");
|
const recentLogs = recent.reverse().join("\n");
|
||||||
if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs;
|
if (isDirty("recentLogs", recentLogs)) this.logHistory!.innerText = recentLogs;
|
||||||
}
|
}
|
||||||
@@ -237,14 +270,16 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduleTask("log-hide", 3000, () => { this.statusLog.value = "" });
|
scheduleTask("log-hide", 3000, () => {
|
||||||
|
this.statusLog.value = "";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> {
|
$allStartOnUnload(): Promise<boolean> {
|
||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.remove();
|
this.statusDiv.remove();
|
||||||
}
|
}
|
||||||
document.querySelectorAll(`.livesync-status`)?.forEach(e => e.remove());
|
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
@@ -264,22 +299,28 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
name: "Show log",
|
name: "Show log",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
void this.core.$$showView(VIEW_TYPE_LOG);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
this.registerView(
|
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
||||||
VIEW_TYPE_LOG,
|
|
||||||
(leaf) => new LogPaneView(leaf, this.plugin)
|
|
||||||
);
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.core.$$addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline();
|
logStore
|
||||||
|
.pipeTo(
|
||||||
|
new QueueProcessor((logs) => logs.forEach((e) => this.core.$$addLog(e.message, e.level, e.key)), {
|
||||||
|
suspended: false,
|
||||||
|
batchSize: 20,
|
||||||
|
concurrentLimit: 1,
|
||||||
|
delay: 0,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.startPipeline();
|
||||||
eventHub.onEvent(EVENT_FILE_RENAMED, (data) => {
|
eventHub.onEvent(EVENT_FILE_RENAMED, (data) => {
|
||||||
void this.setFileStatus();
|
void this.setFileStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
const w = document.querySelectorAll(`.livesync-status`);
|
const w = document.querySelectorAll(`.livesync-status`);
|
||||||
w.forEach(e => e.remove());
|
w.forEach((e) => e.remove());
|
||||||
|
|
||||||
this.observeForLogs();
|
this.observeForLogs();
|
||||||
|
|
||||||
@@ -298,15 +339,20 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeLogToTheFile(now: Date, vaultName: string, newMessage: string) {
|
writeLogToTheFile(now: Date, vaultName: string, newMessage: string) {
|
||||||
fireAndForget(() => serialized("writeLog", async () => {
|
fireAndForget(() =>
|
||||||
const time = now.toISOString().split("T")[0];
|
serialized("writeLog", async () => {
|
||||||
const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
|
const time = now.toISOString().split("T")[0];
|
||||||
const file = await this.core.storageAccess.isExists(normalizePath(logDate));
|
const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
|
||||||
if (!file) {
|
const file = await this.core.storageAccess.isExists(normalizePath(logDate));
|
||||||
await this.core.storageAccess.appendHiddenFile(normalizePath(logDate), "```\n");
|
if (!file) {
|
||||||
}
|
await this.core.storageAccess.appendHiddenFile(normalizePath(logDate), "```\n");
|
||||||
await this.core.storageAccess.appendHiddenFile(normalizePath(logDate), vaultName + ":" + newMessage + "\n");
|
}
|
||||||
}));
|
await this.core.storageAccess.appendHiddenFile(
|
||||||
|
normalizePath(logDate),
|
||||||
|
vaultName + ":" + newMessage + "\n"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
||||||
if (level == LOG_LEVEL_DEBUG) {
|
if (level == LOG_LEVEL_DEBUG) {
|
||||||
@@ -321,7 +367,12 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const vaultName = this.core.$$getVaultName();
|
const vaultName = this.core.$$getVaultName();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timestamp = now.toLocaleString();
|
const timestamp = now.toLocaleString();
|
||||||
const messageContent = typeof message == "string" ? message : message instanceof Error ? `${message.name}:${message.message}` : JSON.stringify(message, null, 2);
|
const messageContent =
|
||||||
|
typeof message == "string"
|
||||||
|
? message
|
||||||
|
: message instanceof Error
|
||||||
|
? `${message.name}:${message.message}`
|
||||||
|
: JSON.stringify(message, null, 2);
|
||||||
if (message instanceof Error) {
|
if (message instanceof Error) {
|
||||||
// debugger;
|
// debugger;
|
||||||
console.dir(message.stack);
|
console.dir(message.stack);
|
||||||
@@ -342,7 +393,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
if (!key) key = messageContent;
|
if (!key) key = messageContent;
|
||||||
if (key in this.notifies) {
|
if (key in this.notifies) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const isShown = this.notifies[key].notice.noticeEl?.isShown()
|
const isShown = this.notifies[key].notice.noticeEl?.isShown();
|
||||||
if (!isShown) {
|
if (!isShown) {
|
||||||
this.notifies[key].notice = new Notice(messageContent, 0);
|
this.notifies[key].notice = new Notice(messageContent, 0);
|
||||||
}
|
}
|
||||||
@@ -369,9 +420,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
} catch {
|
} catch {
|
||||||
// NO OP
|
// NO OP
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,15 @@ import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts"
|
|||||||
import { getPath } from "../../common/utils.ts";
|
import { getPath } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-history",
|
id: "livesync-history",
|
||||||
name: "Show history",
|
name: "Show history",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const file = this.core.$$getActiveFilePath();
|
const file = this.core.$$getActiveFilePath();
|
||||||
if (file) this.showHistory(file, undefined);
|
if (file) this.showHistory(file, undefined);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
@@ -30,9 +25,12 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
|
|||||||
fireAndForget(async () => await this.fileHistory());
|
fireAndForget(async () => await this.fileHistory());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_REQUEST_SHOW_HISTORY, ({ file, fileOnDB }: { file: TFile | FilePathWithPrefix, fileOnDB: LoadedEntry }) => {
|
eventHub.onEvent(
|
||||||
this.showHistory(file, fileOnDB._id);
|
EVENT_REQUEST_SHOW_HISTORY,
|
||||||
})
|
({ file, fileOnDB }: { file: TFile | FilePathWithPrefix; fileOnDB: LoadedEntry }) => {
|
||||||
|
this.showHistory(file, fileOnDB._id);
|
||||||
|
}
|
||||||
|
);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +39,16 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fileHistory() {
|
async fileHistory() {
|
||||||
const notes: { id: DocumentID, path: FilePathWithPrefix, dispPath: string, mtime: number }[] = [];
|
const notes: { id: DocumentID; path: FilePathWithPrefix; dispPath: string; mtime: number }[] = [];
|
||||||
for await (const doc of this.localDatabase.findAllDocs()) {
|
for await (const doc of this.localDatabase.findAllDocs()) {
|
||||||
notes.push({ id: doc._id, path: getPath(doc), dispPath: getPath(doc), mtime: doc.mtime });
|
notes.push({ id: doc._id, path: getPath(doc), dispPath: getPath(doc), mtime: doc.mtime });
|
||||||
}
|
}
|
||||||
notes.sort((a, b) => b.mtime - a.mtime);
|
notes.sort((a, b) => b.mtime - a.mtime);
|
||||||
const notesList = notes.map(e => e.dispPath);
|
const notesList = notes.map((e) => e.dispPath);
|
||||||
const target = await this.core.confirm.askSelectString("File to view History", notesList);
|
const target = await this.core.confirm.askSelectString("File to view History", notesList);
|
||||||
if (target) {
|
if (target) {
|
||||||
const targetId = notes.find(e => e.dispPath == target)!;
|
const targetId = notes.find((e) => e.dispPath == target)!;
|
||||||
this.showHistory(targetId.path, targetId.id);
|
this.showHistory(targetId.path, targetId.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
import { type BucketSyncSetting, type ConfigPassphraseStore, type CouchDBConnection, DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, SALT_OF_PASSPHRASE } from "../../lib/src/common/types";
|
import {
|
||||||
|
type BucketSyncSetting,
|
||||||
|
type ConfigPassphraseStore,
|
||||||
|
type CouchDBConnection,
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
SALT_OF_PASSPHRASE,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
||||||
import { encrypt, tryDecrypt } from "octagonal-wheels/encryption";
|
import { encrypt, tryDecrypt } from "octagonal-wheels/encryption";
|
||||||
import { setLang } from "../../lib/src/common/i18n";
|
import { setLang } from "../../lib/src/common/i18n";
|
||||||
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb";
|
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb";
|
||||||
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
getPassphrase(settings: ObsidianLiveSyncSettings) {
|
getPassphrase(settings: ObsidianLiveSyncSettings) {
|
||||||
const methods: Record<ConfigPassphraseStore, (() => Promise<string | false>)> = {
|
const methods: Record<ConfigPassphraseStore, () => Promise<string | false>> = {
|
||||||
"": () => Promise.resolve("*"),
|
"": () => Promise.resolve("*"),
|
||||||
"LOCALSTORAGE": () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false),
|
LOCALSTORAGE: () => Promise.resolve(localStorage.getItem("ls-setting-passphrase") ?? false),
|
||||||
"ASK_AT_LAUNCH": () => this.core.confirm.askString("Passphrase", "passphrase", "")
|
ASK_AT_LAUNCH: () => this.core.confirm.askString("Passphrase", "passphrase", ""),
|
||||||
}
|
};
|
||||||
const method = settings.configPassphraseStore;
|
const method = settings.configPassphraseStore;
|
||||||
const methodFunc = method in methods ? methods[method] : methods[""];
|
const methodFunc = method in methods ? methods[method] : methods[""];
|
||||||
return methodFunc();
|
return methodFunc();
|
||||||
@@ -29,7 +35,6 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async decryptConfigurationItem(encrypted: string, passphrase: string) {
|
async decryptConfigurationItem(encrypted: string, passphrase: string) {
|
||||||
const dec = await tryDecrypt(encrypted, passphrase + SALT_OF_PASSPHRASE, false);
|
const dec = await tryDecrypt(encrypted, passphrase + SALT_OF_PASSPHRASE, false);
|
||||||
if (dec) {
|
if (dec) {
|
||||||
@@ -39,7 +44,6 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) {
|
async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) {
|
||||||
if (this.usedPassphrase != "") {
|
if (this.usedPassphrase != "") {
|
||||||
return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE, false);
|
return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE, false);
|
||||||
@@ -47,7 +51,10 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
|
|
||||||
const passphrase = await this.getPassphrase(settings);
|
const passphrase = await this.getPassphrase(settings);
|
||||||
if (passphrase === false) {
|
if (passphrase === false) {
|
||||||
this._log("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL_URGENT);
|
this._log(
|
||||||
|
"Could not determine passphrase to save data.json! You probably make the configuration sure again!",
|
||||||
|
LOG_LEVEL_URGENT
|
||||||
|
);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE, false);
|
const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE, false);
|
||||||
@@ -60,17 +67,25 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
|
|
||||||
get appId() {
|
get appId() {
|
||||||
return `${("appId" in this.app ? this.app.appId : "")}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$saveSettingData() {
|
async $$saveSettingData() {
|
||||||
this.core.$$saveDeviceAndVaultName();
|
this.core.$$saveDeviceAndVaultName();
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
settings.deviceAndVaultName = "";
|
settings.deviceAndVaultName = "";
|
||||||
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
|
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
||||||
this._log("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"Could not determine passphrase for saving data.json! Our data.json have insecure items!",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (settings.couchDB_PASSWORD != "" || settings.couchDB_URI != "" || settings.couchDB_USER != "" || settings.couchDB_DBNAME) {
|
if (
|
||||||
|
settings.couchDB_PASSWORD != "" ||
|
||||||
|
settings.couchDB_URI != "" ||
|
||||||
|
settings.couchDB_USER != "" ||
|
||||||
|
settings.couchDB_DBNAME
|
||||||
|
) {
|
||||||
const connectionSetting: CouchDBConnection & BucketSyncSetting = {
|
const connectionSetting: CouchDBConnection & BucketSyncSetting = {
|
||||||
couchDB_DBNAME: settings.couchDB_DBNAME,
|
couchDB_DBNAME: settings.couchDB_DBNAME,
|
||||||
couchDB_PASSWORD: settings.couchDB_PASSWORD,
|
couchDB_PASSWORD: settings.couchDB_PASSWORD,
|
||||||
@@ -81,9 +96,12 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
endpoint: settings.endpoint,
|
endpoint: settings.endpoint,
|
||||||
region: settings.region,
|
region: settings.region,
|
||||||
secretKey: settings.secretKey,
|
secretKey: settings.secretKey,
|
||||||
useCustomRequestHandler: settings.useCustomRequestHandler
|
useCustomRequestHandler: settings.useCustomRequestHandler,
|
||||||
};
|
};
|
||||||
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(JSON.stringify(connectionSetting), settings);
|
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
||||||
|
JSON.stringify(connectionSetting),
|
||||||
|
settings
|
||||||
|
);
|
||||||
settings.couchDB_PASSWORD = "";
|
settings.couchDB_PASSWORD = "";
|
||||||
settings.couchDB_DBNAME = "";
|
settings.couchDB_DBNAME = "";
|
||||||
settings.couchDB_URI = "";
|
settings.couchDB_URI = "";
|
||||||
@@ -98,7 +116,6 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings);
|
settings.encryptedPassphrase = await this.encryptConfigurationItem(settings.passphrase, settings);
|
||||||
settings.passphrase = "";
|
settings.passphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
await this.core.saveData(settings);
|
await this.core.saveData(settings);
|
||||||
eventHub.emitEvent(EVENT_SETTING_SAVED, settings);
|
eventHub.emitEvent(EVENT_SETTING_SAVED, settings);
|
||||||
@@ -127,7 +144,10 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
const passphrase = await this.getPassphrase(settings);
|
const passphrase = await this.getPassphrase(settings);
|
||||||
if (passphrase === false) {
|
if (passphrase === false) {
|
||||||
this._log("Could not determine passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL_URGENT);
|
this._log(
|
||||||
|
"Could not determine passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!",
|
||||||
|
LOG_LEVEL_URGENT
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (settings.encryptedCouchDBConnection) {
|
if (settings.encryptedCouchDBConnection) {
|
||||||
const keys = [
|
const keys = [
|
||||||
@@ -139,17 +159,23 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
"bucket",
|
"bucket",
|
||||||
"endpoint",
|
"endpoint",
|
||||||
"region",
|
"region",
|
||||||
"secretKey"] as (keyof CouchDBConnection | keyof BucketSyncSetting)[];
|
"secretKey",
|
||||||
const decrypted = this.tryDecodeJson(await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase)) as (CouchDBConnection & BucketSyncSetting);
|
] as (keyof CouchDBConnection | keyof BucketSyncSetting)[];
|
||||||
|
const decrypted = this.tryDecodeJson(
|
||||||
|
await this.decryptConfigurationItem(settings.encryptedCouchDBConnection, passphrase)
|
||||||
|
) as CouchDBConnection & BucketSyncSetting;
|
||||||
if (decrypted) {
|
if (decrypted) {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
if (key in decrypted) {
|
if (key in decrypted) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
settings[key] = decrypted[key]
|
settings[key] = decrypted[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._log("Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL_URGENT);
|
this._log(
|
||||||
|
"Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!",
|
||||||
|
LOG_LEVEL_URGENT
|
||||||
|
);
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
settings[key] = "";
|
settings[key] = "";
|
||||||
@@ -162,11 +188,13 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
if (decrypted) {
|
if (decrypted) {
|
||||||
settings.passphrase = decrypted;
|
settings.passphrase = decrypted;
|
||||||
} else {
|
} else {
|
||||||
this._log("Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!", LOG_LEVEL_URGENT);
|
this._log(
|
||||||
|
"Could not decrypt passphrase for reading data.json! DO NOT synchronize with the remote before making sure your configuration is!",
|
||||||
|
LOG_LEVEL_URGENT
|
||||||
|
);
|
||||||
settings.passphrase = "";
|
settings.passphrase = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
@@ -191,7 +219,10 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isCloudantURI(this.settings.couchDB_URI) && this.settings.customChunkSize != 0) {
|
if (isCloudantURI(this.settings.couchDB_URI) && this.settings.customChunkSize != 0) {
|
||||||
this._log("Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.", LOG_LEVEL_NOTICE)
|
this._log(
|
||||||
|
"Configuration verification founds problems with your configuration. This has been fixed automatically. But you may already have data that cannot be synchronised. If this is the case, please rebuild everything.",
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
this.settings.customChunkSize = 0;
|
this.settings.customChunkSize = 0;
|
||||||
}
|
}
|
||||||
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
||||||
@@ -204,4 +235,4 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
||||||
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE }
|
|||||||
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
||||||
const SETTING_FOOTER = "\n````";
|
const SETTING_FOOTER = "\n````";
|
||||||
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-export-config",
|
id: "livesync-export-config",
|
||||||
@@ -21,8 +20,8 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.core.$$saveSettingData();
|
await this.core.$$saveSettingData();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-import-config",
|
id: "livesync-import-config",
|
||||||
name: "Parse setting file",
|
name: "Parse setting file",
|
||||||
@@ -33,32 +32,33 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
return ret.body != "";
|
return ret.body != "";
|
||||||
}
|
}
|
||||||
if (ctx.file) {
|
if (ctx.file) {
|
||||||
const file = ctx.file
|
const file = ctx.file;
|
||||||
fireAndForget(async () => await this.checkAndApplySettingFromMarkdown(file.path, false));
|
fireAndForget(async () => await this.checkAndApplySettingFromMarkdown(file.path, false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
eventHub.onEvent("event-file-changed", (info: {
|
eventHub.onEvent("event-file-changed", (info: { file: FilePathWithPrefix; automated: boolean }) => {
|
||||||
file: FilePathWithPrefix, automated: boolean
|
|
||||||
}) => {
|
|
||||||
fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated));
|
fireAndForget(() => this.checkAndApplySettingFromMarkdown(info.file, info.automated));
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
if (settings.settingSyncFile != "") {
|
if (settings.settingSyncFile != "") {
|
||||||
fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile));
|
fireAndForget(() => this.saveSettingToMarkdown(settings.settingSyncFile));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extractSettingFromWholeText(data: string): {
|
extractSettingFromWholeText(data: string): {
|
||||||
preamble: string, body: string, postscript: string
|
preamble: string;
|
||||||
|
body: string;
|
||||||
|
postscript: string;
|
||||||
} {
|
} {
|
||||||
if (data.indexOf(SETTING_HEADER) === -1) {
|
if (data.indexOf(SETTING_HEADER) === -1) {
|
||||||
return {
|
return {
|
||||||
preamble: data, body: "", postscript: ""
|
preamble: data,
|
||||||
}
|
body: "",
|
||||||
|
postscript: "",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const startMarkerPos = data.indexOf(SETTING_HEADER);
|
const startMarkerPos = data.indexOf(SETTING_HEADER);
|
||||||
const dataStartPos = startMarkerPos == -1 ? data.length : startMarkerPos;
|
const dataStartPos = startMarkerPos == -1 ? data.length : startMarkerPos;
|
||||||
@@ -68,27 +68,33 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
const ret = {
|
const ret = {
|
||||||
preamble: data.substring(0, dataStartPos),
|
preamble: data.substring(0, dataStartPos),
|
||||||
body,
|
body,
|
||||||
postscript: data.substring(dataEndPos + SETTING_FOOTER.length + 1)
|
postscript: data.substring(dataEndPos + SETTING_FOOTER.length + 1),
|
||||||
}
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async parseSettingFromMarkdown(filename: string, data?: string) {
|
async parseSettingFromMarkdown(filename: string, data?: string) {
|
||||||
const file = await this.core.storageAccess.isExists(filename);
|
const file = await this.core.storageAccess.isExists(filename);
|
||||||
if (!file) return {
|
if (!file)
|
||||||
preamble: "", body: "", postscript: "",
|
return {
|
||||||
};
|
preamble: "",
|
||||||
|
body: "",
|
||||||
|
postscript: "",
|
||||||
|
};
|
||||||
if (data) {
|
if (data) {
|
||||||
return this.extractSettingFromWholeText(data);
|
return this.extractSettingFromWholeText(data);
|
||||||
}
|
}
|
||||||
const parseData = data ?? await this.core.storageAccess.readFileText(filename);
|
const parseData = data ?? (await this.core.storageAccess.readFileText(filename));
|
||||||
return this.extractSettingFromWholeText(parseData);
|
return this.extractSettingFromWholeText(parseData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkAndApplySettingFromMarkdown(filename: string, automated?: boolean) {
|
async checkAndApplySettingFromMarkdown(filename: string, automated?: boolean) {
|
||||||
if (automated && !this.settings.notifyAllSettingSyncFile) {
|
if (automated && !this.settings.notifyAllSettingSyncFile) {
|
||||||
if (!this.settings.settingSyncFile || this.settings.settingSyncFile != filename) {
|
if (!this.settings.settingSyncFile || this.settings.settingSyncFile != filename) {
|
||||||
this._log(`Setting file (${filename}) is not matched to the current configuration. skipped.`, LOG_LEVEL_DEBUG);
|
this._log(
|
||||||
|
`Setting file (${filename}) is not matched to the current configuration. skipped.`,
|
||||||
|
LOG_LEVEL_DEBUG
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,61 +109,80 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("settingSyncFile" in newSetting && newSetting.settingSyncFile != filename) {
|
if ("settingSyncFile" in newSetting && newSetting.settingSyncFile != filename) {
|
||||||
this._log("This setting file seems to backed up one. Please fix the filename or settingSyncFile value.", automated ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
|
"This setting file seems to backed up one. Please fix the filename or settingSyncFile value.",
|
||||||
|
automated ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let settingToApply = { ...DEFAULT_SETTINGS } as ObsidianLiveSyncSettings;
|
let settingToApply = { ...DEFAULT_SETTINGS } as ObsidianLiveSyncSettings;
|
||||||
settingToApply = { ...settingToApply, ...newSetting }
|
settingToApply = { ...settingToApply, ...newSetting };
|
||||||
if (!(settingToApply?.writeCredentialsForSettingSync)) {
|
if (!settingToApply?.writeCredentialsForSettingSync) {
|
||||||
//New setting does not contains credentials.
|
//New setting does not contains credentials.
|
||||||
settingToApply.couchDB_USER = this.settings.couchDB_USER;
|
settingToApply.couchDB_USER = this.settings.couchDB_USER;
|
||||||
settingToApply.couchDB_PASSWORD = this.settings.couchDB_PASSWORD;
|
settingToApply.couchDB_PASSWORD = this.settings.couchDB_PASSWORD;
|
||||||
settingToApply.passphrase = this.settings.passphrase;
|
settingToApply.passphrase = this.settings.passphrase;
|
||||||
}
|
}
|
||||||
const oldSetting = this.generateSettingForMarkdown(this.settings, settingToApply.writeCredentialsForSettingSync);
|
const oldSetting = this.generateSettingForMarkdown(
|
||||||
|
this.settings,
|
||||||
|
settingToApply.writeCredentialsForSettingSync
|
||||||
|
);
|
||||||
if (!isObjectDifferent(oldSetting, this.generateSettingForMarkdown(settingToApply))) {
|
if (!isObjectDifferent(oldSetting, this.generateSettingForMarkdown(settingToApply))) {
|
||||||
this._log("Setting markdown has been detected, but not changed.", automated ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE);
|
this._log(
|
||||||
return
|
"Setting markdown has been detected, but not changed.",
|
||||||
|
automated ? LOG_LEVEL_INFO : LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const addMsg = this.settings.settingSyncFile != filename ? " (This is not-active file)" : "";
|
const addMsg = this.settings.settingSyncFile != filename ? " (This is not-active file)" : "";
|
||||||
this.core.confirm.askInPopup("apply-setting-from-md", `Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`, (anchor) => {
|
this.core.confirm.askInPopup(
|
||||||
anchor.text = "HERE";
|
"apply-setting-from-md",
|
||||||
anchor.addEventListener("click", () => {
|
`Setting markdown ${filename}${addMsg} has been detected. Apply this from {HERE}.`,
|
||||||
fireAndForget(async () => {
|
(anchor) => {
|
||||||
const APPLY_ONLY = "Apply settings";
|
anchor.text = "HERE";
|
||||||
const APPLY_AND_RESTART = "Apply settings and restart obsidian";
|
anchor.addEventListener("click", () => {
|
||||||
const APPLY_AND_REBUILD = "Apply settings and restart obsidian with red_flag_rebuild.md";
|
fireAndForget(async () => {
|
||||||
const APPLY_AND_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md";
|
const APPLY_ONLY = "Apply settings";
|
||||||
const CANCEL = "Cancel";
|
const APPLY_AND_RESTART = "Apply settings and restart obsidian";
|
||||||
const result = await this.core.confirm.askSelectStringDialogue("Ready for apply the setting.", [
|
const APPLY_AND_REBUILD = "Apply settings and restart obsidian with red_flag_rebuild.md";
|
||||||
APPLY_AND_RESTART,
|
const APPLY_AND_FETCH = "Apply settings and restart obsidian with red_flag_fetch.md";
|
||||||
APPLY_ONLY,
|
const CANCEL = "Cancel";
|
||||||
APPLY_AND_FETCH,
|
const result = await this.core.confirm.askSelectStringDialogue(
|
||||||
APPLY_AND_REBUILD,
|
"Ready for apply the setting.",
|
||||||
CANCEL], { defaultAction: APPLY_AND_RESTART });
|
[APPLY_AND_RESTART, APPLY_ONLY, APPLY_AND_FETCH, APPLY_AND_REBUILD, CANCEL],
|
||||||
if (result == APPLY_ONLY || result == APPLY_AND_RESTART || result == APPLY_AND_REBUILD || result == APPLY_AND_FETCH) {
|
{ defaultAction: APPLY_AND_RESTART }
|
||||||
this.core.settings = settingToApply;
|
);
|
||||||
await this.core.$$saveSettingData();
|
if (
|
||||||
if (result == APPLY_ONLY) {
|
result == APPLY_ONLY ||
|
||||||
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
result == APPLY_AND_RESTART ||
|
||||||
return;
|
result == APPLY_AND_REBUILD ||
|
||||||
|
result == APPLY_AND_FETCH
|
||||||
|
) {
|
||||||
|
this.core.settings = settingToApply;
|
||||||
|
await this.core.$$saveSettingData();
|
||||||
|
if (result == APPLY_ONLY) {
|
||||||
|
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result == APPLY_AND_REBUILD) {
|
||||||
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
|
}
|
||||||
|
if (result == APPLY_AND_FETCH) {
|
||||||
|
await this.core.rebuilder.scheduleFetch();
|
||||||
|
}
|
||||||
|
this.core.$$performRestart();
|
||||||
}
|
}
|
||||||
if (result == APPLY_AND_REBUILD) {
|
});
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
});
|
||||||
}
|
}
|
||||||
if (result == APPLY_AND_FETCH) {
|
);
|
||||||
await this.core.rebuilder.scheduleFetch();
|
|
||||||
}
|
|
||||||
this.core.$$performRestart();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generateSettingForMarkdown(settings?: ObsidianLiveSyncSettings, keepCredential?: boolean): Partial<ObsidianLiveSyncSettings> {
|
generateSettingForMarkdown(
|
||||||
|
settings?: ObsidianLiveSyncSettings,
|
||||||
|
keepCredential?: boolean
|
||||||
|
): Partial<ObsidianLiveSyncSettings> {
|
||||||
const saveData = { ...(settings ? settings : this.settings) } as Partial<ObsidianLiveSyncSettings>;
|
const saveData = { ...(settings ? settings : this.settings) } as Partial<ObsidianLiveSyncSettings>;
|
||||||
delete saveData.encryptedCouchDBConnection;
|
delete saveData.encryptedCouchDBConnection;
|
||||||
delete saveData.encryptedPassphrase;
|
delete saveData.encryptedPassphrase;
|
||||||
@@ -174,7 +199,6 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
const saveData = this.generateSettingForMarkdown();
|
const saveData = this.generateSettingForMarkdown();
|
||||||
const file = await this.core.storageAccess.isExists(filename);
|
const file = await this.core.storageAccess.isExists(filename);
|
||||||
|
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
await this.core.storageAccess.ensureDir(filename);
|
await this.core.storageAccess.ensureDir(filename);
|
||||||
const initialContent = `This file contains Self-hosted LiveSync settings as YAML.
|
const initialContent = `This file contains Self-hosted LiveSync settings as YAML.
|
||||||
@@ -188,8 +212,11 @@ We can perform a command in this file.
|
|||||||
**Note** Please handle it with all of your care if you have configured to write credentials in.
|
**Note** Please handle it with all of your care if you have configured to write credentials in.
|
||||||
|
|
||||||
|
|
||||||
`
|
`;
|
||||||
await this.core.storageAccess.writeFileAuto(filename, initialContent + SETTING_HEADER + "\n" + SETTING_FOOTER);
|
await this.core.storageAccess.writeFileAuto(
|
||||||
|
filename,
|
||||||
|
initialContent + SETTING_HEADER + "\n" + SETTING_FOOTER
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// if (!(file instanceof TFile)) {
|
// if (!(file instanceof TFile)) {
|
||||||
// this._log(`Markdown Setting: ${filename} already exists as a folder`, LOG_LEVEL_NOTICE);
|
// this._log(`Markdown Setting: ${filename} already exists as a folder`, LOG_LEVEL_NOTICE);
|
||||||
@@ -203,9 +230,11 @@ We can perform a command in this file.
|
|||||||
if (newBody == body) {
|
if (newBody == body) {
|
||||||
this._log("Markdown setting: Nothing had been changed", LOG_LEVEL_VERBOSE);
|
this._log("Markdown setting: Nothing had been changed", LOG_LEVEL_VERBOSE);
|
||||||
} else {
|
} else {
|
||||||
await this.core.storageAccess.writeFileAuto(filename, preamble + SETTING_HEADER + newBody + SETTING_FOOTER + postscript);
|
await this.core.storageAccess.writeFileAuto(
|
||||||
|
filename,
|
||||||
|
preamble + SETTING_HEADER + newBody + SETTING_FOOTER + postscript
|
||||||
|
);
|
||||||
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidia
|
|||||||
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
||||||
|
|
||||||
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
|
||||||
|
|
||||||
settingTab!: ObsidianLiveSyncSettingTab;
|
settingTab!: ObsidianLiveSyncSettingTab;
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
@@ -28,8 +27,6 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
get appId() {
|
get appId() {
|
||||||
return `${("appId" in this.app ? this.app.appId : "")}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { type ObsidianLiveSyncSettings, DEFAULT_SETTINGS, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../lib/src/common/types.ts";
|
import {
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
} from "../../lib/src/common/types.ts";
|
||||||
import { configURIBase } from "../../common/types.ts";
|
import { configURIBase } from "../../common/types.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.js";
|
||||||
import { decrypt, encrypt } from "../../lib/src/encryption/e2ee_v2.ts";
|
import { decrypt, encrypt } from "../../lib/src/encryption/e2ee_v2.ts";
|
||||||
@@ -6,10 +11,12 @@ import { fireAndForget } from "../../lib/src/common/utils.ts";
|
|||||||
import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_SETUP_URI, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_COPY_SETUP_URI, EVENT_REQUEST_OPEN_SETUP_URI, eventHub } from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
|
|
||||||
|
|
||||||
export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsidianModule {
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => await this.setupWizard(conf.settings));
|
this.registerObsidianProtocolHandler(
|
||||||
|
"setuplivesync",
|
||||||
|
async (conf: any) => await this.setupWizard(conf.settings)
|
||||||
|
);
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-copysetupuri",
|
id: "livesync-copysetupuri",
|
||||||
@@ -39,30 +46,55 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async command_copySetupURI(stripExtra = true) {
|
async command_copySetupURI(stripExtra = true) {
|
||||||
const encryptingPassphrase = await this.core.confirm.askString("Encrypt your settings", "The passphrase to encrypt the setup URI", "", true);
|
const encryptingPassphrase = await this.core.confirm.askString(
|
||||||
if (encryptingPassphrase === false)
|
"Encrypt your settings",
|
||||||
return;
|
"The passphrase to encrypt the setup URI",
|
||||||
const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" } as Partial<ObsidianLiveSyncSettings>;
|
"",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (encryptingPassphrase === false) return;
|
||||||
|
const setting = {
|
||||||
|
...this.settings,
|
||||||
|
configPassphraseStore: "",
|
||||||
|
encryptedCouchDBConnection: "",
|
||||||
|
encryptedPassphrase: "",
|
||||||
|
} as Partial<ObsidianLiveSyncSettings>;
|
||||||
if (stripExtra) {
|
if (stripExtra) {
|
||||||
delete setting.pluginSyncExtendedSetting;
|
delete setting.pluginSyncExtendedSetting;
|
||||||
}
|
}
|
||||||
const keys = Object.keys(setting) as (keyof ObsidianLiveSyncSettings)[];
|
const keys = Object.keys(setting) as (keyof ObsidianLiveSyncSettings)[];
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
if (JSON.stringify(k in setting ? setting[k] : "") == JSON.stringify(k in DEFAULT_SETTINGS ? DEFAULT_SETTINGS[k] : "*")) {
|
if (
|
||||||
|
JSON.stringify(k in setting ? setting[k] : "") ==
|
||||||
|
JSON.stringify(k in DEFAULT_SETTINGS ? DEFAULT_SETTINGS[k] : "*")
|
||||||
|
) {
|
||||||
delete setting[k];
|
delete setting[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false));
|
const encryptedSetting = encodeURIComponent(
|
||||||
|
await encrypt(JSON.stringify(setting), encryptingPassphrase, false)
|
||||||
|
);
|
||||||
const uri = `${configURIBase}${encryptedSetting}`;
|
const uri = `${configURIBase}${encryptedSetting}`;
|
||||||
await navigator.clipboard.writeText(uri);
|
await navigator.clipboard.writeText(uri);
|
||||||
this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
async command_copySetupURIFull() {
|
async command_copySetupURIFull() {
|
||||||
const encryptingPassphrase = await this.core.confirm.askString("Encrypt your settings", "The passphrase to encrypt the setup URI", "", true);
|
const encryptingPassphrase = await this.core.confirm.askString(
|
||||||
if (encryptingPassphrase === false)
|
"Encrypt your settings",
|
||||||
return;
|
"The passphrase to encrypt the setup URI",
|
||||||
const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" };
|
"",
|
||||||
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false));
|
true
|
||||||
|
);
|
||||||
|
if (encryptingPassphrase === false) return;
|
||||||
|
const setting = {
|
||||||
|
...this.settings,
|
||||||
|
configPassphraseStore: "",
|
||||||
|
encryptedCouchDBConnection: "",
|
||||||
|
encryptedPassphrase: "",
|
||||||
|
};
|
||||||
|
const encryptedSetting = encodeURIComponent(
|
||||||
|
await encrypt(JSON.stringify(setting), encryptingPassphrase, false)
|
||||||
|
);
|
||||||
const uri = `${configURIBase}${encryptedSetting}`;
|
const uri = `${configURIBase}${encryptedSetting}`;
|
||||||
await navigator.clipboard.writeText(uri);
|
await navigator.clipboard.writeText(uri);
|
||||||
this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
this._log("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
|
||||||
@@ -72,8 +104,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
async command_openSetupURI() {
|
async command_openSetupURI() {
|
||||||
const setupURI = await this.core.confirm.askString("Easy setup", "Set up URI", `${configURIBase}aaaaa`);
|
const setupURI = await this.core.confirm.askString("Easy setup", "Set up URI", `${configURIBase}aaaaa`);
|
||||||
if (setupURI === false)
|
if (setupURI === false) return;
|
||||||
return;
|
|
||||||
if (!setupURI.startsWith(`${configURIBase}`)) {
|
if (!setupURI.startsWith(`${configURIBase}`)) {
|
||||||
this._log("Set up URI looks wrong.", LOG_LEVEL_NOTICE);
|
this._log("Set up URI looks wrong.", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -85,12 +116,19 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
async setupWizard(confString: string) {
|
async setupWizard(confString: string) {
|
||||||
try {
|
try {
|
||||||
const oldConf = JSON.parse(JSON.stringify(this.settings));
|
const oldConf = JSON.parse(JSON.stringify(this.settings));
|
||||||
const encryptingPassphrase = await this.core.confirm.askString("Passphrase", "The passphrase to decrypt your setup URI", "", true);
|
const encryptingPassphrase = await this.core.confirm.askString(
|
||||||
if (encryptingPassphrase === false)
|
"Passphrase",
|
||||||
return;
|
"The passphrase to decrypt your setup URI",
|
||||||
|
"",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (encryptingPassphrase === false) return;
|
||||||
const newConf = await JSON.parse(await decrypt(confString, encryptingPassphrase, false));
|
const newConf = await JSON.parse(await decrypt(confString, encryptingPassphrase, false));
|
||||||
if (newConf) {
|
if (newConf) {
|
||||||
const result = await this.core.confirm.askYesNoDialog("Importing Configuration from the Setup-URI. Are you sure to proceed?", {});
|
const result = await this.core.confirm.askYesNoDialog(
|
||||||
|
"Importing Configuration from the Setup-URI. Are you sure to proceed?",
|
||||||
|
{}
|
||||||
|
);
|
||||||
if (result == "yes") {
|
if (result == "yes") {
|
||||||
const newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf) as ObsidianLiveSyncSettings;
|
const newSettingW = Object.assign({}, DEFAULT_SETTINGS, newConf) as ObsidianLiveSyncSettings;
|
||||||
this.core.replicator.closeReplication();
|
this.core.replicator.closeReplication();
|
||||||
@@ -100,7 +138,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
newSettingW.configPassphraseStore = "";
|
newSettingW.configPassphraseStore = "";
|
||||||
newSettingW.encryptedPassphrase = "";
|
newSettingW.encryptedPassphrase = "";
|
||||||
newSettingW.encryptedCouchDBConnection = "";
|
newSettingW.encryptedCouchDBConnection = "";
|
||||||
newSettingW.additionalSuffixOfDatabaseName = `${("appId" in this.app ? this.app.appId : "")}`
|
newSettingW.additionalSuffixOfDatabaseName = `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
const setupJustImport = "Just import setting";
|
const setupJustImport = "Just import setting";
|
||||||
const setupAsNew = "Set it up as secondary or subsequent device";
|
const setupAsNew = "Set it up as secondary or subsequent device";
|
||||||
const setupAsMerge = "Secondary device but try keeping local changes";
|
const setupAsMerge = "Secondary device but try keeping local changes";
|
||||||
@@ -114,7 +152,11 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
newSettingW.useIndexedDBAdapter = true;
|
newSettingW.useIndexedDBAdapter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupType = await this.core.confirm.askSelectStringDialogue("How would you like to set it up?", [setupAsNew, setupAgain, setupAsMerge, setupJustImport, setupManually], { defaultAction: setupAsNew });
|
const setupType = await this.core.confirm.askSelectStringDialogue(
|
||||||
|
"How would you like to set it up?",
|
||||||
|
[setupAsNew, setupAgain, setupAsMerge, setupJustImport, setupManually],
|
||||||
|
{ defaultAction: setupAsNew }
|
||||||
|
);
|
||||||
if (setupType == setupJustImport) {
|
if (setupType == setupJustImport) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.core.$$clearUsedPassphrase();
|
||||||
@@ -128,16 +170,27 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
this.core.$$clearUsedPassphrase();
|
this.core.$$clearUsedPassphrase();
|
||||||
await this.core.rebuilder.$fetchLocal(true);
|
await this.core.rebuilder.$fetchLocal(true);
|
||||||
} else if (setupType == setupAgain) {
|
} else if (setupType == setupAgain) {
|
||||||
const confirm = "I know this operation will rebuild all my databases with files on this device, and files that are on the remote database and I didn't synchronize to any other devices will be lost and want to proceed indeed.";
|
const confirm =
|
||||||
if (await this.core.confirm.askSelectStringDialogue("Do you really want to do this?", ["Cancel", confirm], { defaultAction: "Cancel" }) != confirm) {
|
"I know this operation will rebuild all my databases with files on this device, and files that are on the remote database and I didn't synchronize to any other devices will be lost and want to proceed indeed.";
|
||||||
|
if (
|
||||||
|
(await this.core.confirm.askSelectStringDialogue(
|
||||||
|
"Do you really want to do this?",
|
||||||
|
["Cancel", confirm],
|
||||||
|
{ defaultAction: "Cancel" }
|
||||||
|
)) != confirm
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.core.$$clearUsedPassphrase();
|
||||||
await this.core.rebuilder.$rebuildEverything();
|
await this.core.rebuilder.$rebuildEverything();
|
||||||
} else if (setupType == setupManually) {
|
} else if (setupType == setupManually) {
|
||||||
const keepLocalDB = await this.core.confirm.askYesNoDialog("Keep local DB?", { defaultOption: "No" });
|
const keepLocalDB = await this.core.confirm.askYesNoDialog("Keep local DB?", {
|
||||||
const keepRemoteDB = await this.core.confirm.askYesNoDialog("Keep remote DB?", { defaultOption: "No" });
|
defaultOption: "No",
|
||||||
|
});
|
||||||
|
const keepRemoteDB = await this.core.confirm.askYesNoDialog("Keep remote DB?", {
|
||||||
|
defaultOption: "No",
|
||||||
|
});
|
||||||
if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
|
if (keepLocalDB == "yes" && keepRemoteDB == "yes") {
|
||||||
// nothing to do. so peaceful.
|
// nothing to do. so peaceful.
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
@@ -145,7 +198,9 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
await this.core.$allSuspendAllSync();
|
await this.core.$allSuspendAllSync();
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.core.$allSuspendExtraSync();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
const replicate = await this.core.confirm.askYesNoDialog("Unlock and replicate?", { defaultOption: "Yes" });
|
const replicate = await this.core.confirm.askYesNoDialog("Unlock and replicate?", {
|
||||||
|
defaultOption: "Yes",
|
||||||
|
});
|
||||||
if (replicate == "yes") {
|
if (replicate == "yes") {
|
||||||
await this.core.$$replicate(true);
|
await this.core.$$replicate(true);
|
||||||
await this.core.$$markRemoteUnlocked();
|
await this.core.$$markRemoteUnlocked();
|
||||||
@@ -154,7 +209,9 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (keepLocalDB == "no" && keepRemoteDB == "no") {
|
if (keepLocalDB == "no" && keepRemoteDB == "no") {
|
||||||
const reset = await this.core.confirm.askYesNoDialog("Drop everything?", { defaultOption: "No" });
|
const reset = await this.core.confirm.askYesNoDialog("Drop everything?", {
|
||||||
|
defaultOption: "No",
|
||||||
|
});
|
||||||
if (reset != "yes") {
|
if (reset != "yes") {
|
||||||
this._log("Cancelled", LOG_LEVEL_NOTICE);
|
this._log("Cancelled", LOG_LEVEL_NOTICE);
|
||||||
this.core.settings = oldConf;
|
this.core.settings = oldConf;
|
||||||
@@ -168,7 +225,9 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
if (keepLocalDB == "no") {
|
if (keepLocalDB == "no") {
|
||||||
await this.core.$$resetLocalDatabase();
|
await this.core.$$resetLocalDatabase();
|
||||||
await this.core.localDatabase.initializeDatabase();
|
await this.core.localDatabase.initializeDatabase();
|
||||||
const rebuild = await this.core.confirm.askYesNoDialog("Rebuild the database?", { defaultOption: "Yes" });
|
const rebuild = await this.core.confirm.askYesNoDialog("Rebuild the database?", {
|
||||||
|
defaultOption: "Yes",
|
||||||
|
});
|
||||||
if (rebuild == "yes") {
|
if (rebuild == "yes") {
|
||||||
initDB = this.core.$$initializeDatabase(true);
|
initDB = this.core.$$initializeDatabase(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +239,9 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
await this.core.$$markRemoteLocked();
|
await this.core.$$markRemoteLocked();
|
||||||
}
|
}
|
||||||
if (keepLocalDB == "no" || keepRemoteDB == "no") {
|
if (keepLocalDB == "no" || keepRemoteDB == "no") {
|
||||||
const replicate = await this.core.confirm.askYesNoDialog("Replicate once?", { defaultOption: "Yes" });
|
const replicate = await this.core.confirm.askYesNoDialog("Replicate once?", {
|
||||||
|
defaultOption: "Yes",
|
||||||
|
});
|
||||||
if (replicate == "yes") {
|
if (replicate == "yes") {
|
||||||
if (initDB != null) {
|
if (initDB != null) {
|
||||||
await initDB;
|
await initDB;
|
||||||
@@ -200,6 +261,4 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
import { Setting, TextComponent, type ToggleComponent, type DropdownComponent, ButtonComponent, type TextAreaComponent, type ValueComponent } from "obsidian";
|
import {
|
||||||
|
Setting,
|
||||||
|
TextComponent,
|
||||||
|
type ToggleComponent,
|
||||||
|
type DropdownComponent,
|
||||||
|
ButtonComponent,
|
||||||
|
type TextAreaComponent,
|
||||||
|
type ValueComponent,
|
||||||
|
} from "obsidian";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { unique } from "octagonal-wheels/collection";
|
||||||
import { LEVEL_ADVANCED, LEVEL_POWER_USER, statusDisplay, type ConfigurationItem } from "../../../lib/src/common/types.ts";
|
import {
|
||||||
import { type ObsidianLiveSyncSettingTab, type AutoWireOption, wrapMemo, type OnUpdateResult, createStub, findAttrFromParent } from "./ObsidianLiveSyncSettingTab.ts";
|
LEVEL_ADVANCED,
|
||||||
import { type AllSettingItemKey, getConfig, type AllSettings, type AllStringItemKey, type AllNumericItemKey, type AllBooleanItemKey } from "./settingConstants.ts";
|
LEVEL_POWER_USER,
|
||||||
|
statusDisplay,
|
||||||
|
type ConfigurationItem,
|
||||||
|
} from "../../../lib/src/common/types.ts";
|
||||||
|
import {
|
||||||
|
type ObsidianLiveSyncSettingTab,
|
||||||
|
type AutoWireOption,
|
||||||
|
wrapMemo,
|
||||||
|
type OnUpdateResult,
|
||||||
|
createStub,
|
||||||
|
findAttrFromParent,
|
||||||
|
} from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
|
import {
|
||||||
|
type AllSettingItemKey,
|
||||||
|
getConfig,
|
||||||
|
type AllSettings,
|
||||||
|
type AllStringItemKey,
|
||||||
|
type AllNumericItemKey,
|
||||||
|
type AllBooleanItemKey,
|
||||||
|
} from "./settingConstants.ts";
|
||||||
|
|
||||||
export class LiveSyncSetting extends Setting {
|
export class LiveSyncSetting extends Setting {
|
||||||
autoWiredComponent?: TextComponent | ToggleComponent | DropdownComponent | ButtonComponent | TextAreaComponent;
|
autoWiredComponent?: TextComponent | ToggleComponent | DropdownComponent | ButtonComponent | TextAreaComponent;
|
||||||
@@ -29,8 +55,9 @@ export class LiveSyncSetting extends Setting {
|
|||||||
DEV: {
|
DEV: {
|
||||||
const paneName = findAttrFromParent(this.settingEl, "data-pane");
|
const paneName = findAttrFromParent(this.settingEl, "data-pane");
|
||||||
const panelName = findAttrFromParent(this.settingEl, "data-panel");
|
const panelName = findAttrFromParent(this.settingEl, "data-panel");
|
||||||
const itemName = typeof this.nameBuf == "string" ? this.nameBuf : this.nameBuf.textContent?.toString() ?? "";
|
const itemName =
|
||||||
const strValue = typeof value == "string" ? value : value.textContent?.toString() ?? "";
|
typeof this.nameBuf == "string" ? this.nameBuf : (this.nameBuf.textContent?.toString() ?? "");
|
||||||
|
const strValue = typeof value == "string" ? value : (value.textContent?.toString() ?? "");
|
||||||
|
|
||||||
createStub(itemName, key, strValue, panelName, paneName);
|
createStub(itemName, key, strValue, panelName, paneName);
|
||||||
}
|
}
|
||||||
@@ -111,9 +138,11 @@ export class LiveSyncSetting extends Setting {
|
|||||||
}
|
}
|
||||||
autoWireText(key: AllStringItemKey, opt?: AutoWireOption) {
|
autoWireText(key: AllStringItemKey, opt?: AutoWireOption) {
|
||||||
const conf = this.autoWireSetting(key, opt);
|
const conf = this.autoWireSetting(key, opt);
|
||||||
this.addText(text => {
|
this.addText((text) => {
|
||||||
this.autoWiredComponent = text;
|
this.autoWiredComponent = text;
|
||||||
const setValue = wrapMemo((value: string) => { text.setValue(value) });
|
const setValue = wrapMemo((value: string) => {
|
||||||
|
text.setValue(value);
|
||||||
|
});
|
||||||
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||||
this.invalidateValue();
|
this.invalidateValue();
|
||||||
text.onChange(async (value) => {
|
text.onChange(async (value) => {
|
||||||
@@ -129,9 +158,11 @@ export class LiveSyncSetting extends Setting {
|
|||||||
}
|
}
|
||||||
autoWireTextArea(key: AllStringItemKey, opt?: AutoWireOption) {
|
autoWireTextArea(key: AllStringItemKey, opt?: AutoWireOption) {
|
||||||
const conf = this.autoWireSetting(key, opt);
|
const conf = this.autoWireSetting(key, opt);
|
||||||
this.addTextArea(text => {
|
this.addTextArea((text) => {
|
||||||
this.autoWiredComponent = text;
|
this.autoWiredComponent = text;
|
||||||
const setValue = wrapMemo((value: string) => { text.setValue(value) });
|
const setValue = wrapMemo((value: string) => {
|
||||||
|
text.setValue(value);
|
||||||
|
});
|
||||||
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
this.invalidateValue = () => setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||||
this.invalidateValue();
|
this.invalidateValue();
|
||||||
text.onChange(async (value) => {
|
text.onChange(async (value) => {
|
||||||
@@ -145,9 +176,12 @@ export class LiveSyncSetting extends Setting {
|
|||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
autoWireNumeric(key: AllNumericItemKey, opt: AutoWireOption & { clampMin?: number; clampMax?: number; acceptZero?: boolean; }) {
|
autoWireNumeric(
|
||||||
|
key: AllNumericItemKey,
|
||||||
|
opt: AutoWireOption & { clampMin?: number; clampMax?: number; acceptZero?: boolean }
|
||||||
|
) {
|
||||||
const conf = this.autoWireSetting(key, opt);
|
const conf = this.autoWireSetting(key, opt);
|
||||||
this.addText(text => {
|
this.addText((text) => {
|
||||||
this.autoWiredComponent = text;
|
this.autoWiredComponent = text;
|
||||||
if (opt.clampMin) {
|
if (opt.clampMin) {
|
||||||
text.inputEl.setAttribute("min", `${opt.clampMin}`);
|
text.inputEl.setAttribute("min", `${opt.clampMin}`);
|
||||||
@@ -156,7 +190,9 @@ export class LiveSyncSetting extends Setting {
|
|||||||
text.inputEl.setAttribute("max", `${opt.clampMax}`);
|
text.inputEl.setAttribute("max", `${opt.clampMax}`);
|
||||||
}
|
}
|
||||||
let lastError = false;
|
let lastError = false;
|
||||||
const setValue = wrapMemo((value: string) => { text.setValue(value) });
|
const setValue = wrapMemo((value: string) => {
|
||||||
|
text.setValue(value);
|
||||||
|
});
|
||||||
this.invalidateValue = () => {
|
this.invalidateValue = () => {
|
||||||
if (!lastError) setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
if (!lastError) setValue(`${LiveSyncSetting.env.editingSettings[key]}`);
|
||||||
};
|
};
|
||||||
@@ -193,9 +229,11 @@ export class LiveSyncSetting extends Setting {
|
|||||||
}
|
}
|
||||||
autoWireToggle(key: AllBooleanItemKey, opt?: AutoWireOption) {
|
autoWireToggle(key: AllBooleanItemKey, opt?: AutoWireOption) {
|
||||||
const conf = this.autoWireSetting(key, opt);
|
const conf = this.autoWireSetting(key, opt);
|
||||||
this.addToggle(toggle => {
|
this.addToggle((toggle) => {
|
||||||
this.autoWiredComponent = toggle;
|
this.autoWiredComponent = toggle;
|
||||||
const setValue = wrapMemo((value: boolean) => { toggle.setValue(opt?.invert ? !value : value) });
|
const setValue = wrapMemo((value: boolean) => {
|
||||||
|
toggle.setValue(opt?.invert ? !value : value);
|
||||||
|
});
|
||||||
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] ?? false);
|
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] ?? false);
|
||||||
this.invalidateValue();
|
this.invalidateValue();
|
||||||
|
|
||||||
@@ -207,16 +245,15 @@ export class LiveSyncSetting extends Setting {
|
|||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
autoWireDropDown<T extends string>(key: AllStringItemKey, opt: AutoWireOption & { options: Record<T, string>; }) {
|
autoWireDropDown<T extends string>(key: AllStringItemKey, opt: AutoWireOption & { options: Record<T, string> }) {
|
||||||
const conf = this.autoWireSetting(key, opt);
|
const conf = this.autoWireSetting(key, opt);
|
||||||
this.addDropdown(dropdown => {
|
this.addDropdown((dropdown) => {
|
||||||
this.autoWiredComponent = dropdown;
|
this.autoWiredComponent = dropdown;
|
||||||
const setValue = wrapMemo((value: string) => {
|
const setValue = wrapMemo((value: string) => {
|
||||||
dropdown.setValue(value);
|
dropdown.setValue(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
dropdown
|
dropdown.addOptions(opt.options);
|
||||||
.addOptions(opt.options);
|
|
||||||
|
|
||||||
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] || "");
|
this.invalidateValue = () => setValue(LiveSyncSetting.env.editingSettings[key] || "");
|
||||||
this.invalidateValue();
|
this.invalidateValue();
|
||||||
@@ -264,7 +301,6 @@ export class LiveSyncSetting extends Setting {
|
|||||||
const newConf = this._getComputedStatus();
|
const newConf = this._getComputedStatus();
|
||||||
const keys = Object.keys(newConf) as [keyof OnUpdateResult];
|
const keys = Object.keys(newConf) as [keyof OnUpdateResult];
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
|
|
||||||
if (k in this.prevStatus && this.prevStatus[k] == newConf[k]) {
|
if (k in this.prevStatus && this.prevStatus[k] == newConf[k]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -277,8 +313,8 @@ export class LiveSyncSetting extends Setting {
|
|||||||
case "classes":
|
case "classes":
|
||||||
break;
|
break;
|
||||||
case "disabled":
|
case "disabled":
|
||||||
this.setDisabled((newConf[k] || false));
|
this.setDisabled(newConf[k] || false);
|
||||||
this.settingEl.toggleClass("sls-setting-disabled", (newConf[k] || false));
|
this.settingEl.toggleClass("sls-setting-disabled", newConf[k] || false);
|
||||||
this.prevStatus[k] = newConf[k];
|
this.prevStatus[k] = newConf[k];
|
||||||
break;
|
break;
|
||||||
case "isCta":
|
case "isCta":
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,21 @@
|
|||||||
import { $t } from "../../../lib/src/common/i18n.ts";
|
import { $t } from "../../../lib/src/common/i18n.ts";
|
||||||
import { DEFAULT_SETTINGS, configurationNames, type ConfigurationItem, type FilterBooleanKeys, type FilterNumberKeys, type FilterStringKeys, type ObsidianLiveSyncSettings } from "../../../lib/src/common/types.ts";
|
import {
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
configurationNames,
|
||||||
|
type ConfigurationItem,
|
||||||
|
type FilterBooleanKeys,
|
||||||
|
type FilterNumberKeys,
|
||||||
|
type FilterStringKeys,
|
||||||
|
type ObsidianLiveSyncSettings,
|
||||||
|
} from "../../../lib/src/common/types.ts";
|
||||||
|
|
||||||
export type OnDialogSettings = {
|
export type OnDialogSettings = {
|
||||||
configPassphrase: string,
|
configPassphrase: string;
|
||||||
preset: "" | "PERIODIC" | "LIVESYNC" | "DISABLE",
|
preset: "" | "PERIODIC" | "LIVESYNC" | "DISABLE";
|
||||||
syncMode: "ONEVENTS" | "PERIODIC" | "LIVESYNC"
|
syncMode: "ONEVENTS" | "PERIODIC" | "LIVESYNC";
|
||||||
dummy: number,
|
dummy: number;
|
||||||
deviceAndVaultName: string,
|
deviceAndVaultName: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const OnDialogSettingsDefault: OnDialogSettings = {
|
export const OnDialogSettingsDefault: OnDialogSettings = {
|
||||||
configPassphrase: "",
|
configPassphrase: "",
|
||||||
@@ -15,9 +23,8 @@ export const OnDialogSettingsDefault: OnDialogSettings = {
|
|||||||
syncMode: "ONEVENTS",
|
syncMode: "ONEVENTS",
|
||||||
dummy: 0,
|
dummy: 0,
|
||||||
deviceAndVaultName: "",
|
deviceAndVaultName: "",
|
||||||
}
|
};
|
||||||
export const AllSettingDefault =
|
export const AllSettingDefault = { ...DEFAULT_SETTINGS, ...OnDialogSettingsDefault };
|
||||||
{ ...DEFAULT_SETTINGS, ...OnDialogSettingsDefault }
|
|
||||||
|
|
||||||
export type AllSettings = ObsidianLiveSyncSettings & OnDialogSettings;
|
export type AllSettings = ObsidianLiveSyncSettings & OnDialogSettings;
|
||||||
export type AllStringItemKey = FilterStringKeys<AllSettings>;
|
export type AllStringItemKey = FilterStringKeys<AllSettings>;
|
||||||
@@ -25,324 +32,326 @@ export type AllNumericItemKey = FilterNumberKeys<AllSettings>;
|
|||||||
export type AllBooleanItemKey = FilterBooleanKeys<AllSettings>;
|
export type AllBooleanItemKey = FilterBooleanKeys<AllSettings>;
|
||||||
export type AllSettingItemKey = AllStringItemKey | AllNumericItemKey | AllBooleanItemKey;
|
export type AllSettingItemKey = AllStringItemKey | AllNumericItemKey | AllBooleanItemKey;
|
||||||
|
|
||||||
export type ValueOf<T extends AllSettingItemKey> =
|
export type ValueOf<T extends AllSettingItemKey> = T extends AllStringItemKey
|
||||||
T extends AllStringItemKey ? string :
|
? string
|
||||||
T extends AllNumericItemKey ? number :
|
: T extends AllNumericItemKey
|
||||||
T extends AllBooleanItemKey ? boolean :
|
? number
|
||||||
AllSettings[T];
|
: T extends AllBooleanItemKey
|
||||||
|
? boolean
|
||||||
|
: AllSettings[T];
|
||||||
|
|
||||||
export const SettingInformation: Partial<Record<keyof AllSettings, ConfigurationItem>> = {
|
export const SettingInformation: Partial<Record<keyof AllSettings, ConfigurationItem>> = {
|
||||||
"liveSync": {
|
liveSync: {
|
||||||
"name": "Sync Mode"
|
|
||||||
},
|
|
||||||
"couchDB_URI": {
|
|
||||||
"name": "URI",
|
|
||||||
"placeHolder": "https://........"
|
|
||||||
},
|
|
||||||
"couchDB_USER": {
|
|
||||||
"name": "Username",
|
|
||||||
"desc": "username"
|
|
||||||
},
|
|
||||||
"couchDB_PASSWORD": {
|
|
||||||
"name": "Password",
|
|
||||||
"desc": "password"
|
|
||||||
},
|
|
||||||
"couchDB_DBNAME": {
|
|
||||||
"name": "Database name"
|
|
||||||
},
|
|
||||||
"passphrase": {
|
|
||||||
"name": "Passphrase",
|
|
||||||
"desc": "Encrypting passphrase. If you change the passphrase of an existing database, overwriting the remote database is strongly recommended."
|
|
||||||
},
|
|
||||||
"showStatusOnEditor": {
|
|
||||||
"name": "Show status inside the editor",
|
|
||||||
"desc": "Reflected after reboot"
|
|
||||||
},
|
|
||||||
"showOnlyIconsOnEditor": {
|
|
||||||
"name": "Show status as icons only"
|
|
||||||
},
|
|
||||||
"showStatusOnStatusbar": {
|
|
||||||
"name": "Show status on the status bar",
|
|
||||||
"desc": "Reflected after reboot."
|
|
||||||
},
|
|
||||||
"lessInformationInLog": {
|
|
||||||
"name": "Show only notifications",
|
|
||||||
"desc": "Prevent logging and show only notification. Please disable when you report the logs"
|
|
||||||
},
|
|
||||||
"showVerboseLog": {
|
|
||||||
"name": "Verbose Log",
|
|
||||||
"desc": "Show verbose log. Please enable when you report the logs"
|
|
||||||
},
|
|
||||||
"hashCacheMaxCount": {
|
|
||||||
"name": "Memory cache size (by total items)"
|
|
||||||
},
|
|
||||||
"hashCacheMaxAmount": {
|
|
||||||
"name": "Memory cache size (by total characters)",
|
|
||||||
"desc": "(Mega chars)"
|
|
||||||
},
|
|
||||||
"writeCredentialsForSettingSync": {
|
|
||||||
"name": "Write credentials in the file",
|
|
||||||
"desc": "(Not recommended) If set, credentials will be stored in the file."
|
|
||||||
},
|
|
||||||
"notifyAllSettingSyncFile": {
|
|
||||||
"name": "Notify all setting files"
|
|
||||||
},
|
|
||||||
"configPassphrase": {
|
|
||||||
"name": "Passphrase of sensitive configuration items",
|
|
||||||
"desc": "This passphrase will not be copied to another device. It will be set to `Default` until you configure it again."
|
|
||||||
},
|
|
||||||
"configPassphraseStore": {
|
|
||||||
"name": "Encrypting sensitive configuration items"
|
|
||||||
},
|
|
||||||
"syncOnSave": {
|
|
||||||
"name": "Sync on Save",
|
|
||||||
"desc": "When you save a file, sync automatically"
|
|
||||||
},
|
|
||||||
"syncOnEditorSave": {
|
|
||||||
"name": "Sync on Editor Save",
|
|
||||||
"desc": "When you save a file in the editor, sync automatically"
|
|
||||||
},
|
|
||||||
"syncOnFileOpen": {
|
|
||||||
"name": "Sync on File Open",
|
|
||||||
"desc": "When you open a file, sync automatically"
|
|
||||||
},
|
|
||||||
"syncOnStart": {
|
|
||||||
"name": "Sync on Start",
|
|
||||||
"desc": "Start synchronization after launching Obsidian."
|
|
||||||
},
|
|
||||||
"syncAfterMerge": {
|
|
||||||
"name": "Sync after merging file",
|
|
||||||
"desc": "Sync automatically after merging files"
|
|
||||||
},
|
|
||||||
"trashInsteadDelete": {
|
|
||||||
"name": "Use the trash bin",
|
|
||||||
"desc": "Do not delete files that are deleted in remote, just move to trash."
|
|
||||||
},
|
|
||||||
"doNotDeleteFolder": {
|
|
||||||
"name": "Keep empty folder",
|
|
||||||
"desc": "Normally, a folder is deleted when it becomes empty after a synchronization. Enabling this will prevent it from getting deleted"
|
|
||||||
},
|
|
||||||
"resolveConflictsByNewerFile": {
|
|
||||||
"name": "Always overwrite with a newer file (beta)",
|
|
||||||
"desc": "(Def off) Resolve conflicts by newer files automatically."
|
|
||||||
},
|
|
||||||
"checkConflictOnlyOnOpen": {
|
|
||||||
"name": "Postpone resolution of inactive files"
|
|
||||||
},
|
|
||||||
"showMergeDialogOnlyOnActive": {
|
|
||||||
"name": "Postpone manual resolution of inactive files"
|
|
||||||
},
|
|
||||||
"disableMarkdownAutoMerge": {
|
|
||||||
"name": "Always resolve conflicts manually",
|
|
||||||
"desc": "If this switch is turned on, a merge dialog will be displayed, even if the sensible-merge is possible automatically. (Turn on to previous behavior)"
|
|
||||||
},
|
|
||||||
"writeDocumentsIfConflicted": {
|
|
||||||
"name": "Always reflect synchronized changes even if the note has a conflict",
|
|
||||||
"desc": "Turn on to previous behavior"
|
|
||||||
},
|
|
||||||
"syncInternalFilesInterval": {
|
|
||||||
"name": "Scan hidden files periodically",
|
|
||||||
"desc": "Seconds, 0 to disable"
|
|
||||||
},
|
|
||||||
"batchSave": {
|
|
||||||
"name": "Batch database update",
|
|
||||||
"desc": "Reducing the frequency with which on-disk changes are reflected into the DB"
|
|
||||||
},
|
|
||||||
"readChunksOnline": {
|
|
||||||
"name": "Fetch chunks on demand",
|
|
||||||
"desc": "(ex. Read chunks online) If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended."
|
|
||||||
},
|
|
||||||
"syncMaxSizeInMB": {
|
|
||||||
"name": "Maximum file size",
|
|
||||||
"desc": "(MB) If this is set, changes to local and remote files that are larger than this will be skipped. If the file becomes smaller again, a newer one will be used."
|
|
||||||
},
|
|
||||||
"useIgnoreFiles": {
|
|
||||||
"name": "(Beta) Use ignore files",
|
|
||||||
"desc": "If this is set, changes to local files which are matched by the ignore files will be skipped. Remote changes are determined using local ignore files."
|
|
||||||
},
|
|
||||||
"ignoreFiles": {
|
|
||||||
"name": "Ignore files",
|
|
||||||
"desc": "We can use multiple ignore files, e.g.) `.gitignore, .dockerignore`"
|
|
||||||
},
|
|
||||||
"batch_size": {
|
|
||||||
"name": "Batch size",
|
|
||||||
"desc": "Number of change feed items to process at a time. Defaults to 50. Minimum is 2."
|
|
||||||
},
|
|
||||||
"batches_limit": {
|
|
||||||
"name": "Batch limit",
|
|
||||||
"desc": "Number of batches to process at a time. Defaults to 40. Minimum is 2. This along with batch size controls how many docs are kept in memory at a time."
|
|
||||||
},
|
|
||||||
"useTimeouts": {
|
|
||||||
"name": "Use timeouts instead of heartbeats",
|
|
||||||
"desc": "If this option is enabled, PouchDB will hold the connection open for 60 seconds, and if no change arrives in that time, close and reopen the socket, instead of holding it open indefinitely. Useful when a proxy limits request duration but can increase resource usage."
|
|
||||||
},
|
|
||||||
"concurrencyOfReadChunksOnline": {
|
|
||||||
"name": "Batch size of on-demand fetching"
|
|
||||||
},
|
|
||||||
"minimumIntervalOfReadChunksOnline": {
|
|
||||||
"name": "The delay for consecutive on-demand fetches"
|
|
||||||
},
|
|
||||||
"suspendFileWatching": {
|
|
||||||
"name": "Suspend file watching",
|
|
||||||
"desc": "Stop watching for file change."
|
|
||||||
},
|
|
||||||
"suspendParseReplicationResult": {
|
|
||||||
"name": "Suspend database reflecting",
|
|
||||||
"desc": "Stop reflecting database changes to storage files."
|
|
||||||
},
|
|
||||||
"writeLogToTheFile": {
|
|
||||||
"name": "Write logs into the file",
|
|
||||||
"desc": "Warning! This will have a serious impact on performance. And the logs will not be synchronised under the default name. Please be careful with logs; they often contain your confidential information."
|
|
||||||
},
|
|
||||||
"deleteMetadataOfDeletedFiles": {
|
|
||||||
"name": "Do not keep metadata of deleted files."
|
|
||||||
},
|
|
||||||
"useIndexedDBAdapter": {
|
|
||||||
"name": "(Obsolete) Use an old adapter for compatibility",
|
|
||||||
"desc": "Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this.",
|
|
||||||
"obsolete": true
|
|
||||||
},
|
|
||||||
"watchInternalFileChanges": {
|
|
||||||
"name": "Scan changes on customization sync",
|
|
||||||
"desc": "Do not use internal API"
|
|
||||||
},
|
|
||||||
"doNotSuspendOnFetching": {
|
|
||||||
"name": "Fetch database with previous behaviour"
|
|
||||||
},
|
|
||||||
"disableCheckingConfigMismatch": {
|
|
||||||
"name": "Do not check configuration mismatch before replication"
|
|
||||||
},
|
|
||||||
"usePluginSync": {
|
|
||||||
"name": "Enable customization sync"
|
|
||||||
},
|
|
||||||
"autoSweepPlugins": {
|
|
||||||
"name": "Scan customization automatically",
|
|
||||||
"desc": "Scan customization before replicating."
|
|
||||||
},
|
|
||||||
"autoSweepPluginsPeriodic": {
|
|
||||||
"name": "Scan customization periodically",
|
|
||||||
"desc": "Scan customization every 1 minute."
|
|
||||||
},
|
|
||||||
"notifyPluginOrSettingUpdated": {
|
|
||||||
"name": "Notify customized",
|
|
||||||
"desc": "Notify when other device has newly customized."
|
|
||||||
},
|
|
||||||
"remoteType": {
|
|
||||||
"name": "Remote Type",
|
|
||||||
"desc": "Remote server type"
|
|
||||||
},
|
|
||||||
"endpoint": {
|
|
||||||
"name": "Endpoint URL",
|
|
||||||
"placeHolder": "https://........"
|
|
||||||
},
|
|
||||||
"accessKey": {
|
|
||||||
"name": "Access Key"
|
|
||||||
},
|
|
||||||
"secretKey": {
|
|
||||||
"name": "Secret Key"
|
|
||||||
},
|
|
||||||
"region": {
|
|
||||||
"name": "Region",
|
|
||||||
"placeHolder": "auto"
|
|
||||||
},
|
|
||||||
"bucket": {
|
|
||||||
"name": "Bucket Name"
|
|
||||||
},
|
|
||||||
"useCustomRequestHandler": {
|
|
||||||
"name": "Use Custom HTTP Handler",
|
|
||||||
"desc": "If your Object Storage could not configured accepting CORS, enable this."
|
|
||||||
},
|
|
||||||
"maxChunksInEden": {
|
|
||||||
"name": "Maximum Incubating Chunks",
|
|
||||||
"desc": "The maximum number of chunks that can be incubated within the document. Chunks exceeding this number will immediately graduate to independent chunks."
|
|
||||||
},
|
|
||||||
"maxTotalLengthInEden": {
|
|
||||||
"name": "Maximum Incubating Chunk Size",
|
|
||||||
"desc": "The maximum total size of chunks that can be incubated within the document. Chunks exceeding this size will immediately graduate to independent chunks."
|
|
||||||
},
|
|
||||||
"maxAgeInEden": {
|
|
||||||
"name": "Maximum Incubation Period",
|
|
||||||
"desc": "The maximum duration for which chunks can be incubated within the document. Chunks exceeding this period will graduate to independent chunks."
|
|
||||||
},
|
|
||||||
"settingSyncFile": {
|
|
||||||
"name": "Filename",
|
|
||||||
"desc": "If you set this, all settings are saved in a markdown file. You will be notified when new settings arrive. You can set different files by the platform."
|
|
||||||
},
|
|
||||||
"preset": {
|
|
||||||
"name": "Presets",
|
|
||||||
"desc": "Apply preset configuration"
|
|
||||||
},
|
|
||||||
"syncMode": {
|
|
||||||
name: "Sync Mode",
|
name: "Sync Mode",
|
||||||
},
|
},
|
||||||
"periodicReplicationInterval": {
|
couchDB_URI: {
|
||||||
"name": "Periodic Sync interval",
|
name: "URI",
|
||||||
"desc": "Interval (sec)"
|
placeHolder: "https://........",
|
||||||
},
|
},
|
||||||
"syncInternalFilesBeforeReplication": {
|
couchDB_USER: {
|
||||||
"name": "Scan for hidden files before replication"
|
name: "Username",
|
||||||
|
desc: "username",
|
||||||
},
|
},
|
||||||
"automaticallyDeleteMetadataOfDeletedFiles": {
|
couchDB_PASSWORD: {
|
||||||
"name": "Delete old metadata of deleted files on start-up",
|
name: "Password",
|
||||||
"desc": "(Days passed, 0 to disable automatic-deletion)"
|
desc: "password",
|
||||||
},
|
},
|
||||||
"additionalSuffixOfDatabaseName": {
|
couchDB_DBNAME: {
|
||||||
"name": "Database suffix",
|
name: "Database name",
|
||||||
"desc": "LiveSync could not handle multiple vaults which have same name without different prefix, This should be automatically configured."
|
|
||||||
},
|
},
|
||||||
"hashAlg": {
|
passphrase: {
|
||||||
"name": configurationNames["hashAlg"]?.name || "",
|
name: "Passphrase",
|
||||||
"desc": "xxhash64 is the current default."
|
desc: "Encrypting passphrase. If you change the passphrase of an existing database, overwriting the remote database is strongly recommended.",
|
||||||
},
|
},
|
||||||
"deviceAndVaultName": {
|
showStatusOnEditor: {
|
||||||
"name": "Device name",
|
name: "Show status inside the editor",
|
||||||
"desc": "Unique name between all synchronized devices. To edit this setting, please disable customization sync once."
|
desc: "Reflected after reboot",
|
||||||
},
|
},
|
||||||
"displayLanguage": {
|
showOnlyIconsOnEditor: {
|
||||||
"name": "Display Language",
|
name: "Show status as icons only",
|
||||||
"desc": "Not all messages have been translated. And, please revert to \"Default\" when reporting errors."
|
},
|
||||||
|
showStatusOnStatusbar: {
|
||||||
|
name: "Show status on the status bar",
|
||||||
|
desc: "Reflected after reboot.",
|
||||||
|
},
|
||||||
|
lessInformationInLog: {
|
||||||
|
name: "Show only notifications",
|
||||||
|
desc: "Prevent logging and show only notification. Please disable when you report the logs",
|
||||||
|
},
|
||||||
|
showVerboseLog: {
|
||||||
|
name: "Verbose Log",
|
||||||
|
desc: "Show verbose log. Please enable when you report the logs",
|
||||||
|
},
|
||||||
|
hashCacheMaxCount: {
|
||||||
|
name: "Memory cache size (by total items)",
|
||||||
|
},
|
||||||
|
hashCacheMaxAmount: {
|
||||||
|
name: "Memory cache size (by total characters)",
|
||||||
|
desc: "(Mega chars)",
|
||||||
|
},
|
||||||
|
writeCredentialsForSettingSync: {
|
||||||
|
name: "Write credentials in the file",
|
||||||
|
desc: "(Not recommended) If set, credentials will be stored in the file.",
|
||||||
|
},
|
||||||
|
notifyAllSettingSyncFile: {
|
||||||
|
name: "Notify all setting files",
|
||||||
|
},
|
||||||
|
configPassphrase: {
|
||||||
|
name: "Passphrase of sensitive configuration items",
|
||||||
|
desc: "This passphrase will not be copied to another device. It will be set to `Default` until you configure it again.",
|
||||||
|
},
|
||||||
|
configPassphraseStore: {
|
||||||
|
name: "Encrypting sensitive configuration items",
|
||||||
|
},
|
||||||
|
syncOnSave: {
|
||||||
|
name: "Sync on Save",
|
||||||
|
desc: "When you save a file, sync automatically",
|
||||||
|
},
|
||||||
|
syncOnEditorSave: {
|
||||||
|
name: "Sync on Editor Save",
|
||||||
|
desc: "When you save a file in the editor, sync automatically",
|
||||||
|
},
|
||||||
|
syncOnFileOpen: {
|
||||||
|
name: "Sync on File Open",
|
||||||
|
desc: "When you open a file, sync automatically",
|
||||||
|
},
|
||||||
|
syncOnStart: {
|
||||||
|
name: "Sync on Start",
|
||||||
|
desc: "Start synchronization after launching Obsidian.",
|
||||||
|
},
|
||||||
|
syncAfterMerge: {
|
||||||
|
name: "Sync after merging file",
|
||||||
|
desc: "Sync automatically after merging files",
|
||||||
|
},
|
||||||
|
trashInsteadDelete: {
|
||||||
|
name: "Use the trash bin",
|
||||||
|
desc: "Do not delete files that are deleted in remote, just move to trash.",
|
||||||
|
},
|
||||||
|
doNotDeleteFolder: {
|
||||||
|
name: "Keep empty folder",
|
||||||
|
desc: "Normally, a folder is deleted when it becomes empty after a synchronization. Enabling this will prevent it from getting deleted",
|
||||||
|
},
|
||||||
|
resolveConflictsByNewerFile: {
|
||||||
|
name: "Always overwrite with a newer file (beta)",
|
||||||
|
desc: "(Def off) Resolve conflicts by newer files automatically.",
|
||||||
|
},
|
||||||
|
checkConflictOnlyOnOpen: {
|
||||||
|
name: "Postpone resolution of inactive files",
|
||||||
|
},
|
||||||
|
showMergeDialogOnlyOnActive: {
|
||||||
|
name: "Postpone manual resolution of inactive files",
|
||||||
|
},
|
||||||
|
disableMarkdownAutoMerge: {
|
||||||
|
name: "Always resolve conflicts manually",
|
||||||
|
desc: "If this switch is turned on, a merge dialog will be displayed, even if the sensible-merge is possible automatically. (Turn on to previous behavior)",
|
||||||
|
},
|
||||||
|
writeDocumentsIfConflicted: {
|
||||||
|
name: "Always reflect synchronized changes even if the note has a conflict",
|
||||||
|
desc: "Turn on to previous behavior",
|
||||||
|
},
|
||||||
|
syncInternalFilesInterval: {
|
||||||
|
name: "Scan hidden files periodically",
|
||||||
|
desc: "Seconds, 0 to disable",
|
||||||
|
},
|
||||||
|
batchSave: {
|
||||||
|
name: "Batch database update",
|
||||||
|
desc: "Reducing the frequency with which on-disk changes are reflected into the DB",
|
||||||
|
},
|
||||||
|
readChunksOnline: {
|
||||||
|
name: "Fetch chunks on demand",
|
||||||
|
desc: "(ex. Read chunks online) If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended.",
|
||||||
|
},
|
||||||
|
syncMaxSizeInMB: {
|
||||||
|
name: "Maximum file size",
|
||||||
|
desc: "(MB) If this is set, changes to local and remote files that are larger than this will be skipped. If the file becomes smaller again, a newer one will be used.",
|
||||||
|
},
|
||||||
|
useIgnoreFiles: {
|
||||||
|
name: "(Beta) Use ignore files",
|
||||||
|
desc: "If this is set, changes to local files which are matched by the ignore files will be skipped. Remote changes are determined using local ignore files.",
|
||||||
|
},
|
||||||
|
ignoreFiles: {
|
||||||
|
name: "Ignore files",
|
||||||
|
desc: "We can use multiple ignore files, e.g.) `.gitignore, .dockerignore`",
|
||||||
|
},
|
||||||
|
batch_size: {
|
||||||
|
name: "Batch size",
|
||||||
|
desc: "Number of change feed items to process at a time. Defaults to 50. Minimum is 2.",
|
||||||
|
},
|
||||||
|
batches_limit: {
|
||||||
|
name: "Batch limit",
|
||||||
|
desc: "Number of batches to process at a time. Defaults to 40. Minimum is 2. This along with batch size controls how many docs are kept in memory at a time.",
|
||||||
|
},
|
||||||
|
useTimeouts: {
|
||||||
|
name: "Use timeouts instead of heartbeats",
|
||||||
|
desc: "If this option is enabled, PouchDB will hold the connection open for 60 seconds, and if no change arrives in that time, close and reopen the socket, instead of holding it open indefinitely. Useful when a proxy limits request duration but can increase resource usage.",
|
||||||
|
},
|
||||||
|
concurrencyOfReadChunksOnline: {
|
||||||
|
name: "Batch size of on-demand fetching",
|
||||||
|
},
|
||||||
|
minimumIntervalOfReadChunksOnline: {
|
||||||
|
name: "The delay for consecutive on-demand fetches",
|
||||||
|
},
|
||||||
|
suspendFileWatching: {
|
||||||
|
name: "Suspend file watching",
|
||||||
|
desc: "Stop watching for file change.",
|
||||||
|
},
|
||||||
|
suspendParseReplicationResult: {
|
||||||
|
name: "Suspend database reflecting",
|
||||||
|
desc: "Stop reflecting database changes to storage files.",
|
||||||
|
},
|
||||||
|
writeLogToTheFile: {
|
||||||
|
name: "Write logs into the file",
|
||||||
|
desc: "Warning! This will have a serious impact on performance. And the logs will not be synchronised under the default name. Please be careful with logs; they often contain your confidential information.",
|
||||||
|
},
|
||||||
|
deleteMetadataOfDeletedFiles: {
|
||||||
|
name: "Do not keep metadata of deleted files.",
|
||||||
|
},
|
||||||
|
useIndexedDBAdapter: {
|
||||||
|
name: "(Obsolete) Use an old adapter for compatibility",
|
||||||
|
desc: "Before v0.17.16, we used an old adapter for the local database. Now the new adapter is preferred. However, it needs local database rebuilding. Please disable this toggle when you have enough time. If leave it enabled, also while fetching from the remote database, you will be asked to disable this.",
|
||||||
|
obsolete: true,
|
||||||
|
},
|
||||||
|
watchInternalFileChanges: {
|
||||||
|
name: "Scan changes on customization sync",
|
||||||
|
desc: "Do not use internal API",
|
||||||
|
},
|
||||||
|
doNotSuspendOnFetching: {
|
||||||
|
name: "Fetch database with previous behaviour",
|
||||||
|
},
|
||||||
|
disableCheckingConfigMismatch: {
|
||||||
|
name: "Do not check configuration mismatch before replication",
|
||||||
|
},
|
||||||
|
usePluginSync: {
|
||||||
|
name: "Enable customization sync",
|
||||||
|
},
|
||||||
|
autoSweepPlugins: {
|
||||||
|
name: "Scan customization automatically",
|
||||||
|
desc: "Scan customization before replicating.",
|
||||||
|
},
|
||||||
|
autoSweepPluginsPeriodic: {
|
||||||
|
name: "Scan customization periodically",
|
||||||
|
desc: "Scan customization every 1 minute.",
|
||||||
|
},
|
||||||
|
notifyPluginOrSettingUpdated: {
|
||||||
|
name: "Notify customized",
|
||||||
|
desc: "Notify when other device has newly customized.",
|
||||||
|
},
|
||||||
|
remoteType: {
|
||||||
|
name: "Remote Type",
|
||||||
|
desc: "Remote server type",
|
||||||
|
},
|
||||||
|
endpoint: {
|
||||||
|
name: "Endpoint URL",
|
||||||
|
placeHolder: "https://........",
|
||||||
|
},
|
||||||
|
accessKey: {
|
||||||
|
name: "Access Key",
|
||||||
|
},
|
||||||
|
secretKey: {
|
||||||
|
name: "Secret Key",
|
||||||
|
},
|
||||||
|
region: {
|
||||||
|
name: "Region",
|
||||||
|
placeHolder: "auto",
|
||||||
|
},
|
||||||
|
bucket: {
|
||||||
|
name: "Bucket Name",
|
||||||
|
},
|
||||||
|
useCustomRequestHandler: {
|
||||||
|
name: "Use Custom HTTP Handler",
|
||||||
|
desc: "If your Object Storage could not configured accepting CORS, enable this.",
|
||||||
|
},
|
||||||
|
maxChunksInEden: {
|
||||||
|
name: "Maximum Incubating Chunks",
|
||||||
|
desc: "The maximum number of chunks that can be incubated within the document. Chunks exceeding this number will immediately graduate to independent chunks.",
|
||||||
|
},
|
||||||
|
maxTotalLengthInEden: {
|
||||||
|
name: "Maximum Incubating Chunk Size",
|
||||||
|
desc: "The maximum total size of chunks that can be incubated within the document. Chunks exceeding this size will immediately graduate to independent chunks.",
|
||||||
|
},
|
||||||
|
maxAgeInEden: {
|
||||||
|
name: "Maximum Incubation Period",
|
||||||
|
desc: "The maximum duration for which chunks can be incubated within the document. Chunks exceeding this period will graduate to independent chunks.",
|
||||||
|
},
|
||||||
|
settingSyncFile: {
|
||||||
|
name: "Filename",
|
||||||
|
desc: "If you set this, all settings are saved in a markdown file. You will be notified when new settings arrive. You can set different files by the platform.",
|
||||||
|
},
|
||||||
|
preset: {
|
||||||
|
name: "Presets",
|
||||||
|
desc: "Apply preset configuration",
|
||||||
|
},
|
||||||
|
syncMode: {
|
||||||
|
name: "Sync Mode",
|
||||||
|
},
|
||||||
|
periodicReplicationInterval: {
|
||||||
|
name: "Periodic Sync interval",
|
||||||
|
desc: "Interval (sec)",
|
||||||
|
},
|
||||||
|
syncInternalFilesBeforeReplication: {
|
||||||
|
name: "Scan for hidden files before replication",
|
||||||
|
},
|
||||||
|
automaticallyDeleteMetadataOfDeletedFiles: {
|
||||||
|
name: "Delete old metadata of deleted files on start-up",
|
||||||
|
desc: "(Days passed, 0 to disable automatic-deletion)",
|
||||||
|
},
|
||||||
|
additionalSuffixOfDatabaseName: {
|
||||||
|
name: "Database suffix",
|
||||||
|
desc: "LiveSync could not handle multiple vaults which have same name without different prefix, This should be automatically configured.",
|
||||||
|
},
|
||||||
|
hashAlg: {
|
||||||
|
name: configurationNames["hashAlg"]?.name || "",
|
||||||
|
desc: "xxhash64 is the current default.",
|
||||||
|
},
|
||||||
|
deviceAndVaultName: {
|
||||||
|
name: "Device name",
|
||||||
|
desc: "Unique name between all synchronized devices. To edit this setting, please disable customization sync once.",
|
||||||
|
},
|
||||||
|
displayLanguage: {
|
||||||
|
name: "Display Language",
|
||||||
|
desc: 'Not all messages have been translated. And, please revert to "Default" when reporting errors.',
|
||||||
},
|
},
|
||||||
enableChunkSplitterV2: {
|
enableChunkSplitterV2: {
|
||||||
name: "Use splitting-limit-capped chunk splitter",
|
name: "Use splitting-limit-capped chunk splitter",
|
||||||
desc: "If enabled, chunks will be split into no more than 100 items. However, dedupe is slightly weaker."
|
desc: "If enabled, chunks will be split into no more than 100 items. However, dedupe is slightly weaker.",
|
||||||
},
|
},
|
||||||
disableWorkerForGeneratingChunks: {
|
disableWorkerForGeneratingChunks: {
|
||||||
name: "Do not split chunks in the background",
|
name: "Do not split chunks in the background",
|
||||||
desc: "If disabled(toggled), chunks will be split on the UI thread (Previous behaviour)."
|
desc: "If disabled(toggled), chunks will be split on the UI thread (Previous behaviour).",
|
||||||
},
|
},
|
||||||
processSmallFilesInUIThread: {
|
processSmallFilesInUIThread: {
|
||||||
name: "Process small files in the foreground",
|
name: "Process small files in the foreground",
|
||||||
desc: "If enabled, the file under 1kb will be processed in the UI thread."
|
desc: "If enabled, the file under 1kb will be processed in the UI thread.",
|
||||||
},
|
},
|
||||||
batchSaveMinimumDelay: {
|
batchSaveMinimumDelay: {
|
||||||
name: "Minimum delay for batch database updating",
|
name: "Minimum delay for batch database updating",
|
||||||
desc: "Seconds. Saving to the local database will be delayed until this value after we stop typing or saving."
|
desc: "Seconds. Saving to the local database will be delayed until this value after we stop typing or saving.",
|
||||||
},
|
},
|
||||||
batchSaveMaximumDelay: {
|
batchSaveMaximumDelay: {
|
||||||
name: "Maximum delay for batch database updating",
|
name: "Maximum delay for batch database updating",
|
||||||
desc: "Saving will be performed forcefully after this number of seconds."
|
desc: "Saving will be performed forcefully after this number of seconds.",
|
||||||
},
|
},
|
||||||
"notifyThresholdOfRemoteStorageSize": {
|
notifyThresholdOfRemoteStorageSize: {
|
||||||
name: "Notify when the estimated remote storage size exceeds on start up",
|
name: "Notify when the estimated remote storage size exceeds on start up",
|
||||||
desc: "MB (0 to disable)."
|
desc: "MB (0 to disable).",
|
||||||
},
|
},
|
||||||
"usePluginSyncV2": {
|
usePluginSyncV2: {
|
||||||
name: "Enable per-file customization sync",
|
name: "Enable per-file customization sync",
|
||||||
desc: "If enabled, efficient per-file customization sync will be used. A minor migration is required when enabling this feature, and all devices must be updated to v0.23.18. Enabling this feature will result in losing compatibility with older versions."
|
desc: "If enabled, efficient per-file customization sync will be used. A minor migration is required when enabling this feature, and all devices must be updated to v0.23.18. Enabling this feature will result in losing compatibility with older versions.",
|
||||||
},
|
},
|
||||||
"handleFilenameCaseSensitive": {
|
handleFilenameCaseSensitive: {
|
||||||
name: "Handle files as Case-Sensitive",
|
name: "Handle files as Case-Sensitive",
|
||||||
desc: "If this enabled, All files are handled as case-Sensitive (Previous behaviour)."
|
desc: "If this enabled, All files are handled as case-Sensitive (Previous behaviour).",
|
||||||
},
|
},
|
||||||
"doNotUseFixedRevisionForChunks": {
|
doNotUseFixedRevisionForChunks: {
|
||||||
name: "Compute revisions for chunks (Previous behaviour)",
|
name: "Compute revisions for chunks (Previous behaviour)",
|
||||||
desc: "If this enabled, all chunks will be stored with the revision made from its content. (Previous behaviour)"
|
desc: "If this enabled, all chunks will be stored with the revision made from its content. (Previous behaviour)",
|
||||||
},
|
},
|
||||||
"sendChunksBulkMaxSize": {
|
sendChunksBulkMaxSize: {
|
||||||
name: "Maximum size of chunks to send in one request",
|
name: "Maximum size of chunks to send in one request",
|
||||||
desc: "MB"
|
desc: "MB",
|
||||||
},
|
},
|
||||||
"useAdvancedMode": {
|
useAdvancedMode: {
|
||||||
name: "Enable advanced features",
|
name: "Enable advanced features",
|
||||||
// desc: "Enable advanced mode"
|
// desc: "Enable advanced mode"
|
||||||
},
|
},
|
||||||
@@ -354,11 +363,11 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
|
|||||||
useEdgeCaseMode: {
|
useEdgeCaseMode: {
|
||||||
name: "Enable edge case treatment features",
|
name: "Enable edge case treatment features",
|
||||||
},
|
},
|
||||||
"enableDebugTools": {
|
enableDebugTools: {
|
||||||
name: "Enable Developers' Debug Tools.",
|
name: "Enable Developers' Debug Tools.",
|
||||||
desc: "You need a restart to apply this setting."
|
desc: "You need a restart to apply this setting.",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
|
function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
|
||||||
if (!infoSrc) return false;
|
if (!infoSrc) return false;
|
||||||
const info = { ...infoSrc };
|
const info = { ...infoSrc };
|
||||||
@@ -369,7 +378,6 @@ function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
function _getConfig(key: AllSettingItemKey) {
|
function _getConfig(key: AllSettingItemKey) {
|
||||||
|
|
||||||
if (key in configurationNames) {
|
if (key in configurationNames) {
|
||||||
return configurationNames[key as keyof ObsidianLiveSyncSettings];
|
return configurationNames[key as keyof ObsidianLiveSyncSettings];
|
||||||
}
|
}
|
||||||
@@ -385,4 +393,4 @@ export function getConfName(key: AllSettingItemKey) {
|
|||||||
const conf = getConfig(key);
|
const conf = getConfig(key);
|
||||||
if (!conf) return `${key} (No info)`;
|
if (!conf) return `${key} (No info)`;
|
||||||
return conf.name;
|
return conf.name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,26 @@ export interface Confirm {
|
|||||||
askYesNo(message: string): Promise<"yes" | "no">;
|
askYesNo(message: string): Promise<"yes" | "no">;
|
||||||
askString(title: string, key: string, placeholder: string, isPassword?: boolean): Promise<string | false>;
|
askString(title: string, key: string, placeholder: string, isPassword?: boolean): Promise<string | false>;
|
||||||
|
|
||||||
askYesNoDialog(message: string, opt: { title?: string, defaultOption?: "Yes" | "No", timeout?: number }): Promise<"yes" | "no">;
|
askYesNoDialog(
|
||||||
|
message: string,
|
||||||
|
opt: { title?: string; defaultOption?: "Yes" | "No"; timeout?: number }
|
||||||
|
): Promise<"yes" | "no">;
|
||||||
|
|
||||||
askSelectString(message: string, items: string[]): Promise<string>
|
askSelectString(message: string, items: string[]): Promise<string>;
|
||||||
|
|
||||||
askSelectStringDialogue(message: string, buttons: string[], opt: { title?: string, defaultAction: (typeof buttons)[number], timeout?: number }): Promise<(typeof buttons)[number] | false>;
|
askSelectStringDialogue(
|
||||||
|
message: string,
|
||||||
|
buttons: string[],
|
||||||
|
opt: { title?: string; defaultAction: (typeof buttons)[number]; timeout?: number }
|
||||||
|
): Promise<(typeof buttons)[number] | false>;
|
||||||
|
|
||||||
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void): void;
|
askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void): void;
|
||||||
|
|
||||||
confirmWithMessage(title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false>;
|
confirmWithMessage(
|
||||||
}
|
title: string,
|
||||||
|
contentMd: string,
|
||||||
|
buttons: string[],
|
||||||
|
defaultAction: (typeof buttons)[number],
|
||||||
|
timeout?: number
|
||||||
|
): Promise<(typeof buttons)[number] | false>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
import type { FilePathWithPrefix, LoadedEntry, MetaEntry, UXFileInfo, UXFileInfoStub } from "../../lib/src/common/types";
|
import type {
|
||||||
|
FilePathWithPrefix,
|
||||||
|
LoadedEntry,
|
||||||
|
MetaEntry,
|
||||||
|
UXFileInfo,
|
||||||
|
UXFileInfoStub,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
|
|
||||||
export interface DatabaseFileAccess {
|
export interface DatabaseFileAccess {
|
||||||
delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise<boolean>;
|
delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise<boolean>;
|
||||||
store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
||||||
storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
|
storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
|
||||||
createChunks: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
createChunks: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
||||||
fetch: (file: UXFileInfoStub | FilePathWithPrefix,
|
fetch: (
|
||||||
rev?: string, waitForReady?: boolean, skipCheck?: boolean) => Promise<UXFileInfo | false>;
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
fetchEntryFromMeta: (meta: MetaEntry,
|
rev?: string,
|
||||||
waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
|
waitForReady?: boolean,
|
||||||
fetchEntryMeta: (file: UXFileInfoStub | FilePathWithPrefix,
|
skipCheck?: boolean
|
||||||
rev?: string, skipCheck?: boolean) => Promise<MetaEntry | false>;
|
) => Promise<UXFileInfo | false>;
|
||||||
fetchEntry: (file: UXFileInfoStub | FilePathWithPrefix,
|
fetchEntryFromMeta: (meta: MetaEntry, waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
|
||||||
rev?: string, waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
|
fetchEntryMeta: (
|
||||||
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
|
rev?: string,
|
||||||
|
skipCheck?: boolean
|
||||||
|
) => Promise<MetaEntry | false>;
|
||||||
|
fetchEntry: (
|
||||||
|
file: UXFileInfoStub | FilePathWithPrefix,
|
||||||
|
rev?: string,
|
||||||
|
waitForReady?: boolean,
|
||||||
|
skipCheck?: boolean
|
||||||
|
) => Promise<LoadedEntry | false>;
|
||||||
getConflictedRevs: (file: UXFileInfoStub | FilePathWithPrefix) => Promise<string[]>;
|
getConflictedRevs: (file: UXFileInfoStub | FilePathWithPrefix) => Promise<string[]>;
|
||||||
// storeFromStorage: (file: UXFileInfoStub | FilePathWithPrefix, force?: boolean) => Promise<boolean>;
|
// storeFromStorage: (file: UXFileInfoStub | FilePathWithPrefix, force?: boolean) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export interface Rebuilder {
|
export interface Rebuilder {
|
||||||
$performRebuildDB(method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"): Promise<void>;
|
$performRebuildDB(
|
||||||
|
method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice" | "localOnlyWithChunks"
|
||||||
|
): Promise<void>;
|
||||||
$rebuildRemote(): Promise<void>;
|
$rebuildRemote(): Promise<void>;
|
||||||
$rebuildEverything(): Promise<void>;
|
$rebuildEverything(): Promise<void>;
|
||||||
$fetchLocal(makeLocalChunkBeforeSync?: boolean): Promise<void>;
|
$fetchLocal(makeLocalChunkBeforeSync?: boolean): Promise<void>;
|
||||||
@@ -7,5 +9,4 @@ export interface Rebuilder {
|
|||||||
scheduleRebuild(): Promise<void>;
|
scheduleRebuild(): Promise<void>;
|
||||||
scheduleFetch(): Promise<void>;
|
scheduleFetch(): Promise<void>;
|
||||||
resolveAllConflictedFilesByNewerOnes(): Promise<void>;
|
resolveAllConflictedFilesByNewerOnes(): Promise<void>;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,43 +1,50 @@
|
|||||||
import type { FilePath, FilePathWithPrefix, UXDataWriteOptions, UXFileInfo, UXFileInfoStub, UXFolderInfo, UXStat } from "../../lib/src/common/types"
|
import type {
|
||||||
|
FilePath,
|
||||||
|
FilePathWithPrefix,
|
||||||
|
UXDataWriteOptions,
|
||||||
|
UXFileInfo,
|
||||||
|
UXFileInfoStub,
|
||||||
|
UXFolderInfo,
|
||||||
|
UXStat,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
|
|
||||||
export interface StorageAccess {
|
export interface StorageAccess {
|
||||||
|
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
||||||
|
|
||||||
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>
|
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
|
||||||
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>
|
readFileAuto(path: string): Promise<string | ArrayBuffer>;
|
||||||
|
readFileText(path: string): Promise<string>;
|
||||||
|
isExists(path: string): Promise<boolean>;
|
||||||
|
writeHiddenFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
|
||||||
readFileAuto(path: string): Promise<string | ArrayBuffer>
|
stat(path: string): Promise<UXStat | null>;
|
||||||
readFileText(path: string): Promise<string>
|
statHidden(path: string): Promise<UXStat | null>;
|
||||||
isExists(path: string): Promise<boolean>
|
removeHidden(path: string): Promise<boolean>;
|
||||||
writeHiddenFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>
|
readHiddenFileAuto(path: string): Promise<string | ArrayBuffer>;
|
||||||
appendHiddenFile(path: string, data: string, opt?: UXDataWriteOptions): Promise<boolean>
|
readHiddenFileBinary(path: string): Promise<ArrayBuffer>;
|
||||||
|
readHiddenFileText(path: string): Promise<string>;
|
||||||
stat(path: string): Promise<UXStat | null>
|
isExistsIncludeHidden(path: string): Promise<boolean>;
|
||||||
statHidden(path: string): Promise<UXStat | null>
|
|
||||||
removeHidden(path: string): Promise<boolean>
|
|
||||||
readHiddenFileAuto(path: string): Promise<string | ArrayBuffer>
|
|
||||||
readHiddenFileBinary(path: string): Promise<ArrayBuffer>
|
|
||||||
readHiddenFileText(path: string): Promise<string>
|
|
||||||
isExistsIncludeHidden(path: string): Promise<boolean>
|
|
||||||
// This could be work also for the hidden files.
|
// This could be work also for the hidden files.
|
||||||
ensureDir(path: string): Promise<boolean>
|
ensureDir(path: string): Promise<boolean>;
|
||||||
triggerFileEvent(event: string, path: string): void
|
triggerFileEvent(event: string, path: string): void;
|
||||||
triggerHiddenFile(path: string): Promise<void>
|
triggerHiddenFile(path: string): Promise<void>;
|
||||||
|
|
||||||
getFileStub(path: string): UXFileInfoStub | null
|
getFileStub(path: string): UXFileInfoStub | null;
|
||||||
readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false>;
|
readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false>;
|
||||||
getStub(path: string): UXFileInfoStub | UXFolderInfo | null
|
getStub(path: string): UXFileInfoStub | UXFolderInfo | null;
|
||||||
|
|
||||||
getFiles(): UXFileInfoStub[]
|
getFiles(): UXFileInfoStub[];
|
||||||
getFileNames(): FilePathWithPrefix[]
|
getFileNames(): FilePathWithPrefix[];
|
||||||
|
|
||||||
touched(file: UXFileInfoStub | FilePathWithPrefix): void
|
touched(file: UXFileInfoStub | FilePathWithPrefix): void;
|
||||||
recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean
|
recentlyTouched(file: UXFileInfoStub | FilePathWithPrefix): boolean;
|
||||||
clearTouched(): void
|
clearTouched(): void;
|
||||||
|
|
||||||
// -- Low-Level
|
// -- Low-Level
|
||||||
delete(file: FilePathWithPrefix | UXFileInfoStub | string, force: boolean): Promise<void>
|
delete(file: FilePathWithPrefix | UXFileInfoStub | string, force: boolean): Promise<void>;
|
||||||
trash(file: FilePathWithPrefix | UXFileInfoStub | string, system: boolean): Promise<void>
|
trash(file: FilePathWithPrefix | UXFileInfoStub | string, system: boolean): Promise<void>;
|
||||||
|
|
||||||
getFilesIncludeHidden(
|
getFilesIncludeHidden(
|
||||||
basePath: string,
|
basePath: string,
|
||||||
@@ -45,4 +52,4 @@ export interface StorageAccess {
|
|||||||
excludeFilter?: RegExp[],
|
excludeFilter?: RegExp[],
|
||||||
skipFolder?: string[]
|
skipFolder?: string[]
|
||||||
): Promise<FilePath[]>;
|
): Promise<FilePath[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings } from "../../lib/src/common/types.ts";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, VER, type ObsidianLiveSyncSettings } from "../../lib/src/common/types.ts";
|
||||||
import { EVENT_LAYOUT_READY, EVENT_PLUGIN_LOADED, EVENT_PLUGIN_UNLOADED, EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
import {
|
||||||
|
EVENT_LAYOUT_READY,
|
||||||
|
EVENT_PLUGIN_LOADED,
|
||||||
|
EVENT_PLUGIN_UNLOADED,
|
||||||
|
EVENT_REQUEST_RELOAD_SETTING_TAB,
|
||||||
|
EVENT_SETTING_SAVED,
|
||||||
|
eventHub,
|
||||||
|
} from "../../common/events.ts";
|
||||||
import { $f, setLang } from "../../lib/src/common/i18n.ts";
|
import { $f, setLang } from "../../lib/src/common/i18n.ts";
|
||||||
import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts";
|
import { versionNumberString2Number } from "../../lib/src/string_and_binary/convert.ts";
|
||||||
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||||
@@ -9,9 +16,8 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { ICoreModule } from "../ModuleTypes.ts";
|
||||||
|
|
||||||
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
||||||
|
|
||||||
async $$onLiveSyncReady() {
|
async $$onLiveSyncReady() {
|
||||||
if (!await this.core.$everyOnLayoutReady()) return;
|
if (!(await this.core.$everyOnLayoutReady())) return;
|
||||||
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
||||||
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
||||||
const ANSWER_KEEP = "Keep this plug-in suspended";
|
const ANSWER_KEEP = "Keep this plug-in suspended";
|
||||||
@@ -29,7 +35,12 @@ Do you want to resume them and restart Obsidian?
|
|||||||
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
|
> These flags are set by the plug-in while rebuilding, or fetching. If the process ends abnormally, it may be kept unintended.
|
||||||
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
|
> If you are not sure, you can try to rerun these processes. Make sure to back your vault up.
|
||||||
`;
|
`;
|
||||||
if (await this.core.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], { defaultAction: ANSWER_KEEP, title: "Scram Enabled" }) == ANSWER_RESUME) {
|
if (
|
||||||
|
(await this.core.confirm.askSelectStringDialogue(message, [ANSWER_KEEP, ANSWER_RESUME], {
|
||||||
|
defaultAction: ANSWER_KEEP,
|
||||||
|
title: "Scram Enabled",
|
||||||
|
})) == ANSWER_RESUME
|
||||||
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
this.settings.suspendParseReplicationResult = false;
|
this.settings.suspendParseReplicationResult = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
@@ -42,11 +53,11 @@ Do you want to resume them and restart Obsidian?
|
|||||||
//TODO:stop all sync.
|
//TODO:stop all sync.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!await this.core.$everyOnFirstInitialize()) return;
|
if (!(await this.core.$everyOnFirstInitialize())) return;
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.core.$$realizeSettingSyncMode();
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
this._log(`Additional safety scan..`, LOG_LEVEL_VERBOSE);
|
this._log(`Additional safety scan..`, LOG_LEVEL_VERBOSE);
|
||||||
if (!await this.core.$allScanStat()) {
|
if (!(await this.core.$allScanStat())) {
|
||||||
this._log(`Additional safety scan has been failed on some module`, LOG_LEVEL_NOTICE);
|
this._log(`Additional safety scan has been failed on some module`, LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
this._log(`Additional safety scan done`, LOG_LEVEL_VERBOSE);
|
this._log(`Additional safety scan done`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -62,7 +73,7 @@ Do you want to resume them and restart Obsidian?
|
|||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
fireAndForget(() => this.core.$$realizeSettingSyncMode());
|
fireAndForget(() => this.core.$$realizeSettingSyncMode());
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncLoad(): Promise<void> {
|
async $$onLiveSyncLoad(): Promise<void> {
|
||||||
@@ -70,7 +81,7 @@ Do you want to resume them and restart Obsidian?
|
|||||||
// debugger;
|
// debugger;
|
||||||
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
||||||
this._log("loading plugin");
|
this._log("loading plugin");
|
||||||
if (!await this.core.$everyOnloadStart()) {
|
if (!(await this.core.$everyOnloadStart())) {
|
||||||
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
|
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,7 +93,7 @@ Do you want to resume them and restart Obsidian?
|
|||||||
|
|
||||||
this._log($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`);
|
this._log($f`Self-hosted LiveSync${" v"}${manifestVersion} ${packageVersion}`);
|
||||||
await this.core.$$loadSettings();
|
await this.core.$$loadSettings();
|
||||||
if (!await this.core.$everyOnloadAfterLoadSettings()) {
|
if (!(await this.core.$everyOnloadAfterLoadSettings())) {
|
||||||
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
|
this._log("Plugin initialising has been cancelled by some module", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,7 +127,7 @@ Do you want to resume them and restart Obsidian?
|
|||||||
// this.$$replicate = this.$$replicate.bind(this);
|
// this.$$replicate = this.$$replicate.bind(this);
|
||||||
this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
||||||
await this.core.$everyOnload();
|
await this.core.$everyOnload();
|
||||||
await Promise.all(this.core.addOns.map(e => e.onload()));
|
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncUnload(): Promise<void> {
|
async $$onLiveSyncUnload(): Promise<void> {
|
||||||
@@ -159,12 +170,17 @@ Do you want to resume them and restart Obsidian?
|
|||||||
|
|
||||||
isReady = false;
|
isReady = false;
|
||||||
|
|
||||||
$$isReady(): boolean { return this.isReady; }
|
$$isReady(): boolean {
|
||||||
|
return this.isReady;
|
||||||
|
}
|
||||||
|
|
||||||
$$markIsReady(): void { this.isReady = true; }
|
$$markIsReady(): void {
|
||||||
|
this.isReady = true;
|
||||||
$$resetIsReady(): void { this.isReady = false; }
|
}
|
||||||
|
|
||||||
|
$$resetIsReady(): void {
|
||||||
|
this.isReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
_suspended = false;
|
_suspended = false;
|
||||||
$$isSuspended(): boolean {
|
$$isSuspended(): boolean {
|
||||||
@@ -178,5 +194,4 @@ Do you want to resume them and restart Obsidian?
|
|||||||
$$isUnloaded(): boolean {
|
$$isUnloaded(): boolean {
|
||||||
return this._unloaded;
|
return this._unloaded;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +1,171 @@
|
|||||||
import { encrypt } from "npm:octagonal-wheels@0.1.11/encryption/encryption.js";
|
import { encrypt } from "npm:octagonal-wheels@0.1.11/encryption/encryption.js";
|
||||||
|
|
||||||
const noun = ["waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning", "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter", "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook", "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly", "feather", "grass", "haze", "mountain", "night", "pond", "darkness", "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder", "violet", "water", "wildflower", "wave", "water", "resonance", "sun", "log", "dream", "cherry", "tree", "fog", "frost", "voice", "paper", "frog", "smoke", "star"];
|
const noun = [
|
||||||
const adjectives = ["autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark", "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter", "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue", "billowing", "broken", "cold", "damp", "falling", "frosty", "green", "long", "late", "lingering", "bold", "little", "morning", "muddy", "old", "red", "rough", "still", "small", "sparkling", "thrumming", "shy", "wandering", "withered", "wild", "black", "young", "holy", "solitary", "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine", "polished", "ancient", "purple", "lively", "nameless"];
|
"waterfall",
|
||||||
|
"river",
|
||||||
|
"breeze",
|
||||||
|
"moon",
|
||||||
|
"rain",
|
||||||
|
"wind",
|
||||||
|
"sea",
|
||||||
|
"morning",
|
||||||
|
"snow",
|
||||||
|
"lake",
|
||||||
|
"sunset",
|
||||||
|
"pine",
|
||||||
|
"shadow",
|
||||||
|
"leaf",
|
||||||
|
"dawn",
|
||||||
|
"glitter",
|
||||||
|
"forest",
|
||||||
|
"hill",
|
||||||
|
"cloud",
|
||||||
|
"meadow",
|
||||||
|
"sun",
|
||||||
|
"glade",
|
||||||
|
"bird",
|
||||||
|
"brook",
|
||||||
|
"butterfly",
|
||||||
|
"bush",
|
||||||
|
"dew",
|
||||||
|
"dust",
|
||||||
|
"field",
|
||||||
|
"fire",
|
||||||
|
"flower",
|
||||||
|
"firefly",
|
||||||
|
"feather",
|
||||||
|
"grass",
|
||||||
|
"haze",
|
||||||
|
"mountain",
|
||||||
|
"night",
|
||||||
|
"pond",
|
||||||
|
"darkness",
|
||||||
|
"snowflake",
|
||||||
|
"silence",
|
||||||
|
"sound",
|
||||||
|
"sky",
|
||||||
|
"shape",
|
||||||
|
"surf",
|
||||||
|
"thunder",
|
||||||
|
"violet",
|
||||||
|
"water",
|
||||||
|
"wildflower",
|
||||||
|
"wave",
|
||||||
|
"water",
|
||||||
|
"resonance",
|
||||||
|
"sun",
|
||||||
|
"log",
|
||||||
|
"dream",
|
||||||
|
"cherry",
|
||||||
|
"tree",
|
||||||
|
"fog",
|
||||||
|
"frost",
|
||||||
|
"voice",
|
||||||
|
"paper",
|
||||||
|
"frog",
|
||||||
|
"smoke",
|
||||||
|
"star",
|
||||||
|
];
|
||||||
|
const adjectives = [
|
||||||
|
"autumn",
|
||||||
|
"hidden",
|
||||||
|
"bitter",
|
||||||
|
"misty",
|
||||||
|
"silent",
|
||||||
|
"empty",
|
||||||
|
"dry",
|
||||||
|
"dark",
|
||||||
|
"summer",
|
||||||
|
"icy",
|
||||||
|
"delicate",
|
||||||
|
"quiet",
|
||||||
|
"white",
|
||||||
|
"cool",
|
||||||
|
"spring",
|
||||||
|
"winter",
|
||||||
|
"patient",
|
||||||
|
"twilight",
|
||||||
|
"dawn",
|
||||||
|
"crimson",
|
||||||
|
"wispy",
|
||||||
|
"weathered",
|
||||||
|
"blue",
|
||||||
|
"billowing",
|
||||||
|
"broken",
|
||||||
|
"cold",
|
||||||
|
"damp",
|
||||||
|
"falling",
|
||||||
|
"frosty",
|
||||||
|
"green",
|
||||||
|
"long",
|
||||||
|
"late",
|
||||||
|
"lingering",
|
||||||
|
"bold",
|
||||||
|
"little",
|
||||||
|
"morning",
|
||||||
|
"muddy",
|
||||||
|
"old",
|
||||||
|
"red",
|
||||||
|
"rough",
|
||||||
|
"still",
|
||||||
|
"small",
|
||||||
|
"sparkling",
|
||||||
|
"thrumming",
|
||||||
|
"shy",
|
||||||
|
"wandering",
|
||||||
|
"withered",
|
||||||
|
"wild",
|
||||||
|
"black",
|
||||||
|
"young",
|
||||||
|
"holy",
|
||||||
|
"solitary",
|
||||||
|
"fragrant",
|
||||||
|
"aged",
|
||||||
|
"snowy",
|
||||||
|
"proud",
|
||||||
|
"floral",
|
||||||
|
"restless",
|
||||||
|
"divine",
|
||||||
|
"polished",
|
||||||
|
"ancient",
|
||||||
|
"purple",
|
||||||
|
"lively",
|
||||||
|
"nameless",
|
||||||
|
];
|
||||||
function friendlyString() {
|
function friendlyString() {
|
||||||
return `${adjectives[Math.floor(Math.random() * adjectives.length)]}-${noun[Math.floor(Math.random() * noun.length)]}`;
|
return `${adjectives[Math.floor(Math.random() * adjectives.length)]}-${noun[Math.floor(Math.random() * noun.length)]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri_passphrase = `${Deno.env.get("uri_passphrase") ?? friendlyString()}`;
|
const uri_passphrase = `${Deno.env.get("uri_passphrase") ?? friendlyString()}`;
|
||||||
|
|
||||||
|
|
||||||
const URIBASE = "obsidian://setuplivesync?settings=";
|
const URIBASE = "obsidian://setuplivesync?settings=";
|
||||||
async function main() {
|
async function main() {
|
||||||
const conf = {
|
const conf = {
|
||||||
"couchDB_URI": `${Deno.env.get("hostname")}`,
|
couchDB_URI: `${Deno.env.get("hostname")}`,
|
||||||
"couchDB_USER": `${Deno.env.get("username")}`,
|
couchDB_USER: `${Deno.env.get("username")}`,
|
||||||
"couchDB_PASSWORD": `${Deno.env.get("password")}`,
|
couchDB_PASSWORD: `${Deno.env.get("password")}`,
|
||||||
"couchDB_DBNAME": `${Deno.env.get("database")}`,
|
couchDB_DBNAME: `${Deno.env.get("database")}`,
|
||||||
"syncOnStart": true,
|
syncOnStart: true,
|
||||||
"gcDelay": 0,
|
gcDelay: 0,
|
||||||
"periodicReplication": true,
|
periodicReplication: true,
|
||||||
"syncOnFileOpen": true,
|
syncOnFileOpen: true,
|
||||||
"encrypt": true,
|
encrypt: true,
|
||||||
"passphrase": `${Deno.env.get("passphrase")}`,
|
passphrase: `${Deno.env.get("passphrase")}`,
|
||||||
"usePathObfuscation": true,
|
usePathObfuscation: true,
|
||||||
"batchSave": true,
|
batchSave: true,
|
||||||
"batch_size": 50,
|
batch_size: 50,
|
||||||
"batches_limit": 50,
|
batches_limit: 50,
|
||||||
"useHistory": true,
|
useHistory: true,
|
||||||
"disableRequestURI": true,
|
disableRequestURI: true,
|
||||||
"customChunkSize": 50,
|
customChunkSize: 50,
|
||||||
"syncAfterMerge": false,
|
syncAfterMerge: false,
|
||||||
"concurrencyOfReadChunksOnline": 100,
|
concurrencyOfReadChunksOnline: 100,
|
||||||
"minimumIntervalOfReadChunksOnline": 100,
|
minimumIntervalOfReadChunksOnline: 100,
|
||||||
}
|
};
|
||||||
const encryptedConf = encodeURIComponent(await encrypt(JSON.stringify(conf), uri_passphrase, false));
|
const encryptedConf = encodeURIComponent(await encrypt(JSON.stringify(conf), uri_passphrase, false));
|
||||||
const theURI = `${URIBASE}${encryptedConf}`;
|
const theURI = `${URIBASE}${encryptedConf}`;
|
||||||
console.log("\nYour passphrase of Setup-URI is: ", uri_passphrase);
|
console.log("\nYour passphrase of Setup-URI is: ", uri_passphrase);
|
||||||
console.log("This passphrase is never shown again, so please note it in a safe place.")
|
console.log("This passphrase is never shown again, so please note it in a safe place.");
|
||||||
console.log(theURI);
|
console.log(theURI);
|
||||||
}
|
}
|
||||||
await main();
|
await main();
|
||||||
|
|||||||
Reference in New Issue
Block a user