mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2024-12-03 08:45:34 +02:00
v0.24.2
Rewritten - Hidden File Sync is now respects the file changes on the storage. Not simply comparing modified times. - This makes hidden file sync more robust and reliable. Fixed - `Scan hidden files before replication` is now configurable again. - Some unexpected errors are now handled more gracefully. - Meaningless event passing during boot sequence is now prevented. - Error handling for non-existing files has been fixed. - Hidden files will not be batched to avoid the potential error. - This behaviour had been causing the error in the previous versions in specific situations. - The log which checking automatic conflict resolution is now in verbose level. - Replication log (skipping non-targetting files) shows the correct information. - The dialogue that asking enabling optional feature during `Rebuild Everything` now prevents to show the `overwrite` option. - The rebuilding device is the first, meaningless. - Files with different modified time but identical content are no longer processed repeatedly. - Some unexpected errors which caused after terminating plug-in are now avoided. - Improved - JSON files are now more transferred efficiently. - Now the JSON files are transferred in more fine chunks, which makes the transfer more efficient.
This commit is contained in:
parent
ed5cb3e043
commit
9d304b3233
@ -11,6 +11,8 @@ import {
|
||||
|
||||
import { Logger } from "../lib/src/common/logger.ts";
|
||||
import {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
type AnyEntry,
|
||||
type DocumentID,
|
||||
@ -25,15 +27,11 @@ import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||
import { writeString } from "../lib/src/string_and_binary/convert.ts";
|
||||
import { fireAndForget } from "../lib/src/common/utils.ts";
|
||||
import { sameChangePairs } from "./stores.ts";
|
||||
import type { KeyValueDatabase } from "./KeyValueDB.ts";
|
||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||
import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts";
|
||||
|
||||
export {
|
||||
scheduleTask,
|
||||
setPeriodicTask,
|
||||
cancelTask,
|
||||
cancelAllTasks,
|
||||
cancelPeriodicTask,
|
||||
cancelAllPeriodicTask,
|
||||
} from "../lib/src/concurrency/task.ts";
|
||||
export { scheduleTask, cancelTask, cancelAllTasks } from "../lib/src/concurrency/task.ts";
|
||||
|
||||
// For backward compatibility, using the path for determining id.
|
||||
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
||||
@ -72,10 +70,25 @@ export function getPathWithoutPrefix(entry: AnyEntry) {
|
||||
export function getPathFromTFile(file: TAbstractFile) {
|
||||
return file.path as FilePath;
|
||||
}
|
||||
|
||||
export function isInternalFile(file: UXFileInfoStub | string | FilePathWithPrefix) {
|
||||
if (typeof file == "string") return file.startsWith(ICHeader);
|
||||
if (file.isInternal) return true;
|
||||
return false;
|
||||
}
|
||||
export function getPathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) {
|
||||
if (typeof file == "string") return file as FilePathWithPrefix;
|
||||
return file.path;
|
||||
}
|
||||
export function getStoragePathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) {
|
||||
if (typeof file == "string") return stripAllPrefixes(file as FilePathWithPrefix);
|
||||
return stripAllPrefixes(file.path);
|
||||
}
|
||||
export function getDatabasePathFromUXFileInfo(file: UXFileInfoStub | string | FilePathWithPrefix) {
|
||||
const prefix = isInternalFile(file) ? ICHeader : "";
|
||||
if (typeof file == "string") return (prefix + stripAllPrefixes(file as FilePathWithPrefix)) as FilePathWithPrefix;
|
||||
return (prefix + stripAllPrefixes(file.path)) as FilePathWithPrefix;
|
||||
}
|
||||
|
||||
const memos: { [key: string]: any } = {};
|
||||
export function memoObject<T>(key: string, obj: T): T {
|
||||
@ -148,11 +161,14 @@ export function isCustomisationSyncMetadata(str: string): boolean {
|
||||
|
||||
export class PeriodicProcessor {
|
||||
_process: () => Promise<any>;
|
||||
_timer?: number;
|
||||
_timer?: number = undefined;
|
||||
_plugin: ObsidianLiveSyncPlugin;
|
||||
constructor(plugin: ObsidianLiveSyncPlugin, process: () => Promise<any>) {
|
||||
this._plugin = plugin;
|
||||
this._process = process;
|
||||
eventHub.onceEvent(EVENT_PLUGIN_UNLOADED, () => {
|
||||
this.disable();
|
||||
});
|
||||
}
|
||||
async process() {
|
||||
try {
|
||||
@ -265,9 +281,14 @@ export function compareMTime(
|
||||
throw new Error("Unexpected error");
|
||||
}
|
||||
|
||||
function getKey(file: AnyEntry | string | UXFileInfoStub) {
|
||||
const key = typeof file == "string" ? file : stripAllPrefixes(file.path);
|
||||
return key;
|
||||
}
|
||||
|
||||
export function markChangesAreSame(file: AnyEntry | string | UXFileInfoStub, mtime1: number, mtime2: number) {
|
||||
if (mtime1 === mtime2) return true;
|
||||
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
||||
const key = getKey(file);
|
||||
const pairs = sameChangePairs.get(key, []) || [];
|
||||
if (pairs.some((e) => e == mtime1 || e == mtime2)) {
|
||||
sameChangePairs.set(key, [...new Set([...pairs, mtime1, mtime2])]);
|
||||
@ -275,8 +296,13 @@ export function markChangesAreSame(file: AnyEntry | string | UXFileInfoStub, mti
|
||||
sameChangePairs.set(key, [mtime1, mtime2]);
|
||||
}
|
||||
}
|
||||
|
||||
export function unmarkChanges(file: AnyEntry | string | UXFileInfoStub) {
|
||||
const key = getKey(file);
|
||||
sameChangePairs.delete(key);
|
||||
}
|
||||
export function isMarkedAsSameChanges(file: UXFileInfoStub | AnyEntry | string, mtimes: number[]) {
|
||||
const key = typeof file == "string" ? file : "_id" in file ? file._id : file.path;
|
||||
const key = getKey(file);
|
||||
const pairs = sameChangePairs.get(key, []) || [];
|
||||
if (mtimes.every((e) => pairs.indexOf(e) !== -1)) {
|
||||
return EVEN;
|
||||
@ -374,6 +400,95 @@ export function displayRev(rev: string) {
|
||||
return `${number}-${hash.substring(0, 6)}`;
|
||||
}
|
||||
|
||||
// export function getPathFromUXFileInfo(file: UXFileInfoStub | UXFileInfo | string) {
|
||||
// return (typeof file == "string" ? file : file.path) as FilePathWithPrefix;
|
||||
// }
|
||||
type DocumentProps = {
|
||||
id: DocumentID;
|
||||
rev?: string;
|
||||
prefixedPath: FilePathWithPrefix;
|
||||
path: FilePath;
|
||||
isDeleted: boolean;
|
||||
revDisplay: string;
|
||||
shortenedId: string;
|
||||
shortenedPath: string;
|
||||
};
|
||||
|
||||
export function getDocProps(doc: AnyEntry): DocumentProps {
|
||||
const id = doc._id;
|
||||
const shortenedId = id.substring(0, 10);
|
||||
const prefixedPath = getPath(doc);
|
||||
const path = stripAllPrefixes(prefixedPath);
|
||||
const rev = doc._rev;
|
||||
const revDisplay = rev ? displayRev(rev) : "0-NOREVS";
|
||||
// const prefix = prefixedPath.substring(0, prefixedPath.length - path.length);
|
||||
const shortenedPath = path.substring(0, 10);
|
||||
const isDeleted = doc._deleted || doc.deleted || false;
|
||||
return { id, rev, revDisplay, prefixedPath, path, isDeleted, shortenedId, shortenedPath };
|
||||
}
|
||||
|
||||
export function getLogLevel(showNotice: boolean) {
|
||||
return showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||
}
|
||||
|
||||
export type MapLike<K, V> = {
|
||||
set(key: K, value: V): Map<K, V>;
|
||||
clear(): void;
|
||||
delete(key: K): boolean;
|
||||
get(key: K): V | undefined;
|
||||
has(key: K): boolean;
|
||||
keys: () => IterableIterator<K>;
|
||||
get size(): number;
|
||||
};
|
||||
|
||||
export async function autosaveCache<K, V>(db: KeyValueDatabase, mapKey: string): Promise<MapLike<K, V>> {
|
||||
const savedData = (await db.get<Map<K, V>>(mapKey)) ?? new Map<K, V>();
|
||||
const _commit = () => {
|
||||
try {
|
||||
scheduleTask("commit-map-save-" + mapKey, 250, async () => {
|
||||
await db.set(mapKey, savedData);
|
||||
});
|
||||
} catch {
|
||||
// NO OP.
|
||||
}
|
||||
};
|
||||
return {
|
||||
set(key: K, value: V) {
|
||||
const modified = savedData.get(key) !== value;
|
||||
const result = savedData.set(key, value);
|
||||
if (modified) {
|
||||
_commit();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
clear(): void {
|
||||
savedData.clear();
|
||||
_commit();
|
||||
},
|
||||
delete(key: K): boolean {
|
||||
const result = savedData.delete(key);
|
||||
if (result) {
|
||||
_commit();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
get(key: K): V | undefined {
|
||||
return savedData.get(key);
|
||||
},
|
||||
has(key) {
|
||||
return savedData.has(key);
|
||||
},
|
||||
keys() {
|
||||
return savedData.keys();
|
||||
},
|
||||
get size() {
|
||||
return savedData.size;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function onlyInNTimes(n: number, proc: (progress: number) => any) {
|
||||
let counter = 0;
|
||||
return function () {
|
||||
if (counter++ % n == 0) {
|
||||
proc(counter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import ObsidianLiveSyncPlugin from "../../main";
|
||||
import { ConfigSync, type IPluginDataExDisplay, pluginIsEnumerating, pluginList, pluginManifestStore, pluginV2Progress } from "./CmdConfigSync.ts";
|
||||
import {
|
||||
ConfigSync,
|
||||
type IPluginDataExDisplay,
|
||||
pluginIsEnumerating,
|
||||
pluginList,
|
||||
pluginManifestStore,
|
||||
pluginV2Progress,
|
||||
} from "./CmdConfigSync.ts";
|
||||
import PluginCombo from "./PluginCombo.svelte";
|
||||
import { Menu, type PluginManifest } from "obsidian";
|
||||
import { unique } from "../../lib/src/common/utils";
|
||||
import { MODE_SELECTIVE, MODE_AUTOMATIC, MODE_PAUSED, type SYNC_MODE, type PluginSyncSettingEntry, MODE_SHINY } from "../../lib/src/common/types";
|
||||
import {
|
||||
MODE_SELECTIVE,
|
||||
MODE_AUTOMATIC,
|
||||
MODE_PAUSED,
|
||||
type SYNC_MODE,
|
||||
MODE_SHINY,
|
||||
} from "../../lib/src/common/types";
|
||||
import { normalizePath } from "../../deps";
|
||||
import { HiddenFileSync } from "../HiddenFileSync/CmdHiddenFileSync.ts";
|
||||
import { LOG_LEVEL_NOTICE, Logger } from "octagonal-wheels/common/logger";
|
||||
@ -16,13 +29,15 @@
|
||||
|
||||
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
|
||||
if (!addOn) {
|
||||
const msg = "AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue.";
|
||||
const msg =
|
||||
"AddOn Module (ConfigSync) has not been loaded. This is very unexpected situation. Please report this issue.";
|
||||
Logger(msg, LOG_LEVEL_NOTICE);
|
||||
throw new Error(msg);
|
||||
}
|
||||
const addOnHiddenFileSync = plugin.getAddOn(HiddenFileSync.name) as HiddenFileSync;
|
||||
if (!addOnHiddenFileSync) {
|
||||
const msg = "AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue.";
|
||||
const msg =
|
||||
"AddOn Module (HiddenFileSync) has not been loaded. This is very unexpected situation. Please report this issue.";
|
||||
Logger(msg, LOG_LEVEL_NOTICE);
|
||||
throw new Error(msg);
|
||||
}
|
||||
@ -99,7 +114,11 @@
|
||||
async function applyData(data: IPluginDataExDisplay): Promise<boolean> {
|
||||
return await addOn.applyData(data);
|
||||
}
|
||||
async function compareData(docA: IPluginDataExDisplay, docB: IPluginDataExDisplay, compareEach = false): Promise<boolean> {
|
||||
async function compareData(
|
||||
docA: IPluginDataExDisplay,
|
||||
docB: IPluginDataExDisplay,
|
||||
compareEach = false
|
||||
): Promise<boolean> {
|
||||
return await addOn.compareUsingDisplayData(docA, docB, compareEach);
|
||||
}
|
||||
async function deleteData(data: IPluginDataExDisplay): Promise<boolean> {
|
||||
@ -130,7 +149,7 @@
|
||||
setMode(key, MODE_AUTOMATIC);
|
||||
const configDir = normalizePath(plugin.app.vault.configDir);
|
||||
const files = (plugin.settings.pluginSyncExtendedSetting[key]?.files ?? []).map((e) => `${configDir}/${e}`);
|
||||
addOnHiddenFileSync.syncInternalFilesAndDatabase(direction, true, false, files);
|
||||
addOnHiddenFileSync.initialiseInternalFileSync(direction, true, files);
|
||||
}
|
||||
function askOverwriteModeForAutomatic(evt: MouseEvent, key: string) {
|
||||
const menu = new Menu();
|
||||
@ -199,7 +218,7 @@
|
||||
.filter((e) => `${e.category}/${e.name}` == key)
|
||||
.map((e) => e.files)
|
||||
.flat()
|
||||
.map((e) => e.filename),
|
||||
.map((e) => e.filename)
|
||||
);
|
||||
if (mode == MODE_SELECTIVE) {
|
||||
automaticList.delete(key);
|
||||
@ -249,7 +268,15 @@
|
||||
.map((e) => ({ category: e[0], name: e[1], displayName: e[1] })),
|
||||
]
|
||||
.sort((a, b) => (a.displayName ?? a.name).localeCompare(b.displayName ?? b.name))
|
||||
.reduce((p, c) => ({ ...p, [c.category]: unique(c.category in p ? [...p[c.category], c.displayName ?? c.name] : [c.displayName ?? c.name]) }), {} as Record<string, string[]>);
|
||||
.reduce(
|
||||
(p, c) => ({
|
||||
...p,
|
||||
[c.category]: unique(
|
||||
c.category in p ? [...p[c.category], c.displayName ?? c.name] : [c.displayName ?? c.name]
|
||||
),
|
||||
}),
|
||||
{} as Record<string, string[]>
|
||||
);
|
||||
}
|
||||
$: {
|
||||
displayKeys = computeDisplayKeys(list);
|
||||
@ -337,7 +364,12 @@
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if mode == MODE_SELECTIVE || mode == MODE_SHINY}
|
||||
<PluginCombo {...options} isFlagged={mode == MODE_SHINY} list={list.filter((e) => e.category == key && e.name == name)} hidden={false} />
|
||||
<PluginCombo
|
||||
{...options}
|
||||
isFlagged={mode == MODE_SHINY}
|
||||
list={list.filter((e) => e.category == key && e.name == name)}
|
||||
hidden={false}
|
||||
/>
|
||||
{:else}
|
||||
<div class="statusnote">{TITLES[mode]}</div>
|
||||
{/if}
|
||||
@ -359,7 +391,10 @@
|
||||
{@const modeEtc = automaticListDisp.get(bindKeyETC) ?? MODE_SELECTIVE}
|
||||
<div class="labelrow {hideEven ? 'hideeven' : ''}">
|
||||
<div class="title">
|
||||
<button class="status" on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_ALL}/${name}`, bindKeyAll)}>
|
||||
<button
|
||||
class="status"
|
||||
on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_ALL}/${name}`, bindKeyAll)}
|
||||
>
|
||||
{getIcon(modeAll)}
|
||||
</button>
|
||||
<span class="name">{nameMap.get(`plugins/${name}`) || name}</span>
|
||||
@ -373,14 +408,22 @@
|
||||
{#if modeAll == MODE_SELECTIVE || modeAll == MODE_SHINY}
|
||||
<div class="filerow {hideEven ? 'hideeven' : ''}">
|
||||
<div class="filetitle">
|
||||
<button class="status" on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_MAIN}/${name}/MAIN`, bindKeyMain)}>
|
||||
<button
|
||||
class="status"
|
||||
on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_MAIN}/${name}/MAIN`, bindKeyMain)}
|
||||
>
|
||||
{getIcon(modeMain)}
|
||||
</button>
|
||||
<span class="name">MAIN</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if modeMain == MODE_SELECTIVE || modeMain == MODE_SHINY}
|
||||
<PluginCombo {...options} isFlagged={modeMain == MODE_SHINY} list={filterList(listX, ["PLUGIN_MAIN"])} hidden={false} />
|
||||
<PluginCombo
|
||||
{...options}
|
||||
isFlagged={modeMain == MODE_SHINY}
|
||||
list={filterList(listX, ["PLUGIN_MAIN"])}
|
||||
hidden={false}
|
||||
/>
|
||||
{:else}
|
||||
<div class="statusnote">{TITLES[modeMain]}</div>
|
||||
{/if}
|
||||
@ -388,14 +431,22 @@
|
||||
</div>
|
||||
<div class="filerow {hideEven ? 'hideeven' : ''}">
|
||||
<div class="filetitle">
|
||||
<button class="status" on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_DATA}/${name}`, bindKeyData)}>
|
||||
<button
|
||||
class="status"
|
||||
on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_DATA}/${name}`, bindKeyData)}
|
||||
>
|
||||
{getIcon(modeData)}
|
||||
</button>
|
||||
<span class="name">DATA</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if modeData == MODE_SELECTIVE || modeData == MODE_SHINY}
|
||||
<PluginCombo {...options} isFlagged={modeData == MODE_SHINY} list={filterList(listX, ["PLUGIN_DATA"])} hidden={false} />
|
||||
<PluginCombo
|
||||
{...options}
|
||||
isFlagged={modeData == MODE_SHINY}
|
||||
list={filterList(listX, ["PLUGIN_DATA"])}
|
||||
hidden={false}
|
||||
/>
|
||||
{:else}
|
||||
<div class="statusnote">{TITLES[modeData]}</div>
|
||||
{/if}
|
||||
@ -404,14 +455,22 @@
|
||||
{#if useSyncPluginEtc}
|
||||
<div class="filerow {hideEven ? 'hideeven' : ''}">
|
||||
<div class="filetitle">
|
||||
<button class="status" on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_ETC}/${name}`, bindKeyETC)}>
|
||||
<button
|
||||
class="status"
|
||||
on:click={(evt) => askMode(evt, `${PREFIX_PLUGIN_ETC}/${name}`, bindKeyETC)}
|
||||
>
|
||||
{getIcon(modeEtc)}
|
||||
</button>
|
||||
<span class="name">Other files</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
{#if modeEtc == MODE_SELECTIVE || modeEtc == MODE_SHINY}
|
||||
<PluginCombo {...options} isFlagged={modeEtc == MODE_SHINY} list={filterList(listX, ["PLUGIN_ETC"])} hidden={false} />
|
||||
<PluginCombo
|
||||
{...options}
|
||||
isFlagged={modeEtc == MODE_SHINY}
|
||||
list={filterList(listX, ["PLUGIN_ETC"])}
|
||||
hidden={false}
|
||||
/>
|
||||
{:else}
|
||||
<div class="statusnote">{TITLES[modeEtc]}</div>
|
||||
{/if}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
import { Logger } from "octagonal-wheels/common/logger";
|
||||
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||
import { getPath } from "../common/utils.ts";
|
||||
import {
|
||||
LOG_LEVEL_INFO,
|
||||
@ -11,7 +11,9 @@ import {
|
||||
type LOG_LEVEL,
|
||||
} from "../lib/src/common/types.ts";
|
||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
||||
|
||||
let noticeIndex = 0;
|
||||
export abstract class LiveSyncCommands {
|
||||
plugin: ObsidianLiveSyncPlugin;
|
||||
get app() {
|
||||
@ -57,4 +59,34 @@ export abstract class LiveSyncCommands {
|
||||
// console.log(msg);
|
||||
Logger(msg, level, key);
|
||||
};
|
||||
|
||||
_verbose = (msg: any, key?: string) => {
|
||||
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
||||
};
|
||||
|
||||
_info = (msg: any, key?: string) => {
|
||||
this._log(msg, LOG_LEVEL_INFO, key);
|
||||
};
|
||||
|
||||
_notice = (msg: any, key?: string) => {
|
||||
this._log(msg, LOG_LEVEL_NOTICE, key);
|
||||
};
|
||||
_progress = (prefix: string = "", level: LOG_LEVEL = LOG_LEVEL_NOTICE) => {
|
||||
const key = `keepalive-progress-${noticeIndex++}`;
|
||||
return {
|
||||
log: (msg: any) => {
|
||||
this._log(prefix + msg, level, key);
|
||||
},
|
||||
once: (msg: any) => {
|
||||
this._log(prefix + msg, level);
|
||||
},
|
||||
done: (msg: string = "Done") => {
|
||||
this._log(prefix + msg + MARK_DONE, level, key);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
_debug = (msg: any, key?: string) => {
|
||||
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
||||
};
|
||||
}
|
||||
|
2
src/lib
2
src/lib
@ -1 +1 @@
|
||||
Subproject commit e1688acb0f28abdcc84191d06e386052a9dff989
|
||||
Subproject commit 2e156a988bdce705878f24483c9efc2a69fb24b4
|
@ -1,6 +1,11 @@
|
||||
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||
import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
|
||||
import { getPathFromUXFileInfo, isInternalMetadata, markChangesAreSame } from "../../common/utils";
|
||||
import {
|
||||
getDatabasePathFromUXFileInfo,
|
||||
getStoragePathFromUXFileInfo,
|
||||
isInternalMetadata,
|
||||
markChangesAreSame,
|
||||
} from "../../common/utils";
|
||||
import type {
|
||||
UXFileInfoStub,
|
||||
FilePathWithPrefix,
|
||||
@ -9,10 +14,11 @@ import type {
|
||||
LoadedEntry,
|
||||
FilePath,
|
||||
SavingEntry,
|
||||
DocumentID,
|
||||
} from "../../lib/src/common/types";
|
||||
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
||||
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||
import { isPlainText, shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
||||
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
||||
import {
|
||||
createBlob,
|
||||
createTextBlob,
|
||||
@ -23,6 +29,7 @@ import {
|
||||
} from "../../lib/src/common/utils";
|
||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||
import { AbstractModule } from "../AbstractModule.ts";
|
||||
import { ICHeader } from "../../common/types.ts";
|
||||
|
||||
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
|
||||
$everyOnload(): Promise<boolean> {
|
||||
@ -67,7 +74,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
}
|
||||
|
||||
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
||||
const path = getPathFromUXFileInfo(file);
|
||||
const path = getStoragePathFromUXFileInfo(file);
|
||||
if (!(await this.core.$$isTargetFile(path))) {
|
||||
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
@ -83,7 +90,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
if (!(await this.checkIsTargetFile(file))) {
|
||||
return true;
|
||||
}
|
||||
const fullPath = getPathFromUXFileInfo(file);
|
||||
const fullPath = getDatabasePathFromUXFileInfo(file);
|
||||
try {
|
||||
this._log(`deleteDB By path:${fullPath}`);
|
||||
return await this.deleteFromDBbyPath(fullPath, rev);
|
||||
@ -104,6 +111,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
|
||||
const blob = createTextBlob(content);
|
||||
const bytes = (await blob.arrayBuffer()).byteLength;
|
||||
const isInternal = path.startsWith(".") ? true : undefined;
|
||||
const dummyUXFileInfo: UXFileInfo = {
|
||||
name: path.split("/").pop() as string,
|
||||
path: path,
|
||||
@ -114,6 +122,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
type: "file",
|
||||
},
|
||||
body: blob,
|
||||
isInternal,
|
||||
};
|
||||
return await this._store(dummyUXFileInfo, true, false, false);
|
||||
}
|
||||
@ -133,18 +142,47 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
this._log("File seems bad", LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
const path = getPathFromUXFileInfo(file);
|
||||
|
||||
// const path = getPathFromUXFileInfo(file);
|
||||
const isPlain = isPlainText(file.name);
|
||||
const possiblyLarge = !isPlain;
|
||||
const content = file.body;
|
||||
if (possiblyLarge) this._log(`Processing: ${path}`, LOG_LEVEL_VERBOSE);
|
||||
|
||||
const datatype = determineTypeFromBlob(content);
|
||||
const fullPath = file.path;
|
||||
const id = await this.core.$$path2id(fullPath);
|
||||
const idPrefix = file.isInternal ? ICHeader : "";
|
||||
const fullPath = getStoragePathFromUXFileInfo(file);
|
||||
const fullPathOnDB = getDatabasePathFromUXFileInfo(file);
|
||||
|
||||
if (possiblyLarge) this._log(`Processing: ${fullPath}`, LOG_LEVEL_VERBOSE);
|
||||
|
||||
// if (isInternalMetadata(fullPath)) {
|
||||
// this._log(`Internal file: ${fullPath}`, LOG_LEVEL_VERBOSE);
|
||||
// return false;
|
||||
// }
|
||||
if (file.isInternal) {
|
||||
if (file.deleted) {
|
||||
file.stat = {
|
||||
size: 0,
|
||||
ctime: Date.now(),
|
||||
mtime: Date.now(),
|
||||
type: "file",
|
||||
};
|
||||
} else if (file.stat == undefined) {
|
||||
const stat = await this.core.storageAccess.statHidden(file.path);
|
||||
if (!stat) {
|
||||
// We stored actually deleted or not since here, so this is an unexpected case. we should raise an error.
|
||||
this._log(`Internal file not found: ${fullPath}`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
file.stat = stat;
|
||||
}
|
||||
}
|
||||
|
||||
const idMain = await this.core.$$path2id(fullPath);
|
||||
|
||||
const id = (idPrefix + idMain) as DocumentID;
|
||||
const d: SavingEntry = {
|
||||
_id: id,
|
||||
path: file.path,
|
||||
path: fullPathOnDB,
|
||||
data: content,
|
||||
ctime: file.stat.ctime,
|
||||
mtime: file.stat.mtime,
|
||||
@ -166,7 +204,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
// return true;
|
||||
// }
|
||||
try {
|
||||
const old = await this.localDatabase.getDBEntry(fullPath, undefined, false, true, false);
|
||||
const old = await this.localDatabase.getDBEntry(d.path, undefined, false, true, false);
|
||||
if (old !== false) {
|
||||
const oldData = { data: old.data, deleted: old._deleted || old.deleted };
|
||||
const newData = { data: d.data, deleted: d._deleted || d.deleted };
|
||||
@ -181,24 +219,14 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
// d._rev = old._rev;
|
||||
}
|
||||
} catch (ex) {
|
||||
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
|
||||
);
|
||||
} 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." +
|
||||
(force ? "force writing." : "") +
|
||||
fullPath +
|
||||
(d._deleted || d.deleted ? " (deleted)" : ""),
|
||||
LOG_LEVEL_VERBOSE
|
||||
);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
return !force;
|
||||
}
|
||||
@ -220,7 +248,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
if (!(await this.checkIsTargetFile(file))) {
|
||||
return [];
|
||||
}
|
||||
const filename = getPathFromUXFileInfo(file);
|
||||
const filename = getDatabasePathFromUXFileInfo(file);
|
||||
const doc = await this.localDatabase.getDBEntryMeta(filename, { conflicts: true }, true);
|
||||
if (doc === false) {
|
||||
return [];
|
||||
@ -243,9 +271,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
return false;
|
||||
}
|
||||
const data = createBlob(readContent(entry));
|
||||
const path = stripAllPrefixes(entry.path);
|
||||
const fileInfo: UXFileInfo = {
|
||||
name: entry.path.split("/").pop() as string,
|
||||
path: entry.path,
|
||||
name: path.split("/").pop() as string,
|
||||
path: path,
|
||||
stat: {
|
||||
size: entry.size,
|
||||
ctime: entry.ctime,
|
||||
@ -265,12 +294,12 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
||||
rev?: string,
|
||||
skipCheck = false
|
||||
): Promise<MetaEntry | false> {
|
||||
const filename = getPathFromUXFileInfo(file);
|
||||
const dbFileName = getDatabasePathFromUXFileInfo(file);
|
||||
if (skipCheck && !(await this.checkIsTargetFile(file))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const doc = await this.localDatabase.getDBEntryMeta(filename, rev ? { rev: rev } : undefined, true);
|
||||
const doc = await this.localDatabase.getDBEntryMeta(dbFileName, rev ? { rev: rev } : undefined, true);
|
||||
if (doc === false) {
|
||||
return false;
|
||||
}
|
||||
|
@ -14,8 +14,8 @@ import {
|
||||
compareFileFreshness,
|
||||
EVEN,
|
||||
getPath,
|
||||
getPathFromUXFileInfo,
|
||||
getPathWithoutPrefix,
|
||||
getStoragePathFromUXFileInfo,
|
||||
markChangesAreSame,
|
||||
} from "../../common/utils";
|
||||
import { getDocDataAsArray, isDocContentSame, readContent } from "../../lib/src/common/utils";
|
||||
@ -94,11 +94,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||
if (!shouldApplied) {
|
||||
readFile = await this.readFileFromStub(file);
|
||||
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
|
||||
if (shouldApplied) {
|
||||
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
||||
// So, mark the changes are same.
|
||||
markChangesAreSame(file, file.stat.mtime, entry.mtime);
|
||||
}
|
||||
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
||||
// So, mark the changes are same.
|
||||
markChangesAreSame(file, file.stat.mtime, entry.mtime);
|
||||
} else {
|
||||
shouldApplied = true;
|
||||
}
|
||||
@ -170,18 +168,13 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
||||
info: UXFileInfoStub | FilePath,
|
||||
rev: string
|
||||
): Promise<boolean | undefined> {
|
||||
const path = getStoragePathFromUXFileInfo(info);
|
||||
if (!(await this.deleteRevisionFromDB(info, rev))) {
|
||||
this._log(
|
||||
`Failed to delete the conflicted revision ${rev} of ${getPathFromUXFileInfo(info)}`,
|
||||
LOG_LEVEL_VERBOSE
|
||||
);
|
||||
this._log(`Failed to delete the conflicted revision ${rev} of ${path}`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
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 ${path} to the storage`, LOG_LEVEL_VERBOSE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
||||
await this.core.$$tryResetRemoteDatabase();
|
||||
await this.core.$$markRemoteLocked();
|
||||
await delay(500);
|
||||
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
||||
// We do not have any other devices' data, so we do not need to ask for overwriting.
|
||||
await this.askUsingOptionalFeature({ enableOverwrite: false });
|
||||
await delay(1000);
|
||||
await this.core.$$replicateAllToServer(true);
|
||||
await delay(1000);
|
||||
|
@ -329,7 +329,7 @@ Or if you are sure know what had been happened, we can unlock the database from
|
||||
} else if (isValidPath(getPath(doc))) {
|
||||
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
||||
} else {
|
||||
Logger(`Skipped: ${doc._id.substring(0, 8)}`, LOG_LEVEL_VERBOSE);
|
||||
Logger(`Skipped: ${path} (${doc._id.substring(0, 8)})`, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LRUCache } from "octagonal-wheels/memory/LRUCache";
|
||||
import {
|
||||
getPathFromUXFileInfo,
|
||||
getStoragePathFromUXFileInfo,
|
||||
id2path,
|
||||
isInternalMetadata,
|
||||
path2id,
|
||||
@ -16,7 +16,7 @@ import {
|
||||
type ObsidianLiveSyncSettings,
|
||||
type UXFileInfoStub,
|
||||
} from "../../lib/src/common/types";
|
||||
import { addPrefix, isAcceptedAll, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
||||
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
|
||||
import { AbstractModule } from "../AbstractModule";
|
||||
import type { ICoreModule } from "../ModuleTypes";
|
||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||
@ -107,14 +107,14 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
||||
}
|
||||
);
|
||||
|
||||
const filepath = getPathFromUXFileInfo(file);
|
||||
const filepath = getStoragePathFromUXFileInfo(file);
|
||||
const lc = filepath.toLowerCase();
|
||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
||||
if (lc in fileCount && fileCount[lc] > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const fileNameLC = getPathFromUXFileInfo(file).split("/").pop()?.toLowerCase();
|
||||
const fileNameLC = getStoragePathFromUXFileInfo(file).split("/").pop()?.toLowerCase();
|
||||
if (this.settings.useIgnoreFiles) {
|
||||
if (this.ignoreFiles.some((e) => e.toLowerCase() == fileNameLC)) {
|
||||
// We must reload ignore files due to the its change.
|
||||
@ -154,16 +154,12 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
||||
if (!this.settings.useIgnoreFiles) {
|
||||
return false;
|
||||
}
|
||||
const filepath = getPathFromUXFileInfo(file);
|
||||
const filepath = getStoragePathFromUXFileInfo(file);
|
||||
if (this.ignoreFileCache.has(filepath)) {
|
||||
// Renew
|
||||
await this.readIgnoreFile(filepath);
|
||||
}
|
||||
if (
|
||||
!(await isAcceptedAll(stripAllPrefixes(filepath), this.ignoreFiles, (filename) =>
|
||||
this.getIgnoreFile(filename)
|
||||
))
|
||||
) {
|
||||
if (!(await isAcceptedAll(filepath, this.ignoreFiles, (filename) => this.getIgnoreFile(filename)))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
CANCELLED,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_NOTICE,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
MISSING_OR_ERROR,
|
||||
NOT_CONFLICTED,
|
||||
type diff_check_result,
|
||||
@ -114,7 +115,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
||||
conflictCheckResult === CANCELLED
|
||||
) {
|
||||
// nothing to do.
|
||||
this._log(`conflict:Nothing to do:${filename}`);
|
||||
this._log(`[conflict] Not conflicted or cancelled: ${filename}`, LOG_LEVEL_VERBOSE);
|
||||
return;
|
||||
}
|
||||
if (conflictCheckResult === AUTO_MERGED) {
|
||||
@ -123,7 +124,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
||||
//Wait for the running replication, if not running replication, run it once.
|
||||
await this.core.$$waitForReplicationOnce();
|
||||
}
|
||||
this._log("conflict:Automatically merged, but we have to check it again");
|
||||
this._log("[conflict] Automatically merged, but we have to check it again");
|
||||
await this.core.$$queueConflictCheck(filename);
|
||||
return;
|
||||
}
|
||||
@ -131,13 +132,13 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
||||
const af = this.core.$$getActiveFilePath();
|
||||
if (af && af != filename) {
|
||||
this._log(
|
||||
`${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
||||
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
||||
LOG_LEVEL_NOTICE
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._log("conflict:Manual merge required!");
|
||||
this._log("[conflict] Manual merge required!");
|
||||
await this.core.$anyResolveConflictByUI(filename, conflictCheckResult);
|
||||
});
|
||||
}
|
||||
|
@ -120,12 +120,12 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
||||
}
|
||||
}
|
||||
statHidden(path: string): Promise<UXStat | null> {
|
||||
return this.vaultAccess.adapterStat(path);
|
||||
return this.vaultAccess.tryAdapterStat(path);
|
||||
}
|
||||
async removeHidden(path: string): Promise<boolean> {
|
||||
try {
|
||||
await this.vaultAccess.adapterRemove(path);
|
||||
if (this.vaultAccess.adapterStat(path) !== null) {
|
||||
if (this.vaultAccess.tryAdapterStat(path) !== null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -145,7 +145,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
||||
return await this.vaultAccess.adapterReadBinary(path);
|
||||
}
|
||||
async isExistsIncludeHidden(path: string): Promise<boolean> {
|
||||
return (await this.vaultAccess.adapterStat(path)) !== null;
|
||||
return (await this.vaultAccess.tryAdapterStat(path)) !== null;
|
||||
}
|
||||
async ensureDir(path: string): Promise<boolean> {
|
||||
try {
|
||||
|
@ -42,6 +42,13 @@ export class SerializedFileAccess {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
async tryAdapterStat(file: TFile | string) {
|
||||
const path = file instanceof TFile ? file.path : file;
|
||||
return await processReadFile(file, async () => {
|
||||
if (!(await this.app.vault.adapter.exists(path))) return null;
|
||||
return this.app.vault.adapter.stat(path);
|
||||
});
|
||||
}
|
||||
async adapterStat(file: TFile | string) {
|
||||
const path = file instanceof TFile ? file.path : file;
|
||||
return await processReadFile(file, () => this.app.vault.adapter.stat(path));
|
||||
|
@ -171,11 +171,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
||||
// Folder
|
||||
return;
|
||||
}
|
||||
|
||||
void this.appendQueue(
|
||||
[
|
||||
{
|
||||
type: "INTERNAL",
|
||||
file: InternalFileToUXFileInfoStub(path),
|
||||
skipBatchWait: true, // Internal files should be processed immediately.
|
||||
},
|
||||
],
|
||||
null
|
||||
@ -212,11 +214,14 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
||||
// Stop cache using to prevent the corruption;
|
||||
// let cache: null | string | ArrayBuffer;
|
||||
// new file or something changed, cache the changes.
|
||||
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
|
||||
// Wait for a bit while to let the writer has marked `touched` at the file.
|
||||
await delay(10);
|
||||
if (this.core.storageAccess.recentlyTouched(file)) {
|
||||
continue;
|
||||
// if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
|
||||
if (file instanceof TFile || !file.isFolder) {
|
||||
if (type == "CREATE" || type == "CHANGED") {
|
||||
// Wait for a bit while to let the writer has marked `touched` at the file.
|
||||
await delay(10);
|
||||
if (this.core.storageAccess.recentlyTouched(file.path)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,13 +385,11 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
||||
const file = queue.args.file;
|
||||
const lockKey = `handleFile:${file.path}`;
|
||||
return await serialized(lockKey, async () => {
|
||||
// TODO CHECK
|
||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||
const last = Number((await this.core.kvDB.get(key)) || 0);
|
||||
if (queue.type == "INTERNAL" || file.isInternal) {
|
||||
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
|
||||
} else {
|
||||
// let mtime = file.stat.mtime;
|
||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||
const last = Number((await this.core.kvDB.get(key)) || 0);
|
||||
if (queue.type == "DELETE") {
|
||||
await this.core.$anyHandlerProcessesFileEvent(queue);
|
||||
} else {
|
||||
|
@ -55,7 +55,7 @@ export async function InternalFileToUXFileInfo(
|
||||
prefix: string = ICHeader
|
||||
): Promise<UXFileInfo> {
|
||||
const name = fullPath.split("/").pop() as string;
|
||||
const stat = await vaultAccess.adapterStat(fullPath);
|
||||
const stat = await vaultAccess.tryAdapterStat(fullPath);
|
||||
if (stat == null) throw new Error(`File not found: ${fullPath}`);
|
||||
if (stat.type == "folder") throw new Error(`File not found: ${fullPath}`);
|
||||
const file = await vaultAccess.adapterReadAuto(fullPath);
|
||||
|
@ -238,10 +238,10 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
||||
await this.syncFileBetweenDBandStorage(file, doc);
|
||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(getPath(doc), true));
|
||||
eventHub.emitEvent("event-file-changed", {
|
||||
file: getPath(doc),
|
||||
automated: true,
|
||||
});
|
||||
// eventHub.emitEvent("event-file-changed", {
|
||||
// file: getPath(doc),
|
||||
// automated: true,
|
||||
// });
|
||||
} else {
|
||||
this._log(
|
||||
`SYNC DATABASE AND STORAGE: ${getPath(doc)} has been skipped due to file size exceeding the limit`,
|
||||
@ -282,10 +282,6 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
||||
this._log("STORAGE -> DB :" + file.path);
|
||||
await this.core.fileHandler.storeFileToDB(file);
|
||||
eventHub.emitEvent("event-file-changed", {
|
||||
file: file.path,
|
||||
automated: true,
|
||||
});
|
||||
} else {
|
||||
this._log(
|
||||
`STORAGE -> DB : ${file.path} has been skipped due to file size exceeding the limit`,
|
||||
@ -296,7 +292,12 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
||||
case TARGET_IS_NEW:
|
||||
if (!this.core.$$isFileSizeExceeded(doc.size)) {
|
||||
this._log("STORAGE <- DB :" + file.path);
|
||||
if (!(await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true))) {
|
||||
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
||||
eventHub.emitEvent("event-file-changed", {
|
||||
file: file.path,
|
||||
automated: true,
|
||||
});
|
||||
} else {
|
||||
this._log(`STORAGE <- DB : Cloud not read ${file.path}, possibly deleted`, LOG_LEVEL_NOTICE);
|
||||
}
|
||||
return caches;
|
||||
|
@ -12,31 +12,34 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
||||
__onMissingTranslation(() => {});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
async onMissingTranslation(key: string): Promise<void> {
|
||||
const now = new Date();
|
||||
const filename = `missing-translation-`;
|
||||
const time = now.toISOString().split("T")[0];
|
||||
const outFile = `${filename}${time}.jsonl`;
|
||||
const piece = JSON.stringify({
|
||||
[key]: {},
|
||||
});
|
||||
const writePiece = piece.substring(1, piece.length - 1) + ",";
|
||||
try {
|
||||
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"
|
||||
);
|
||||
} catch (ex) {
|
||||
this._log(`Could not write ${outFile}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(`Missing translation: ${writePiece}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
}
|
||||
|
||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||
// eslint-disable-next-line no-unused-labels
|
||||
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
||||
__onMissingTranslation((key) => {
|
||||
const now = new Date();
|
||||
const filename = `missing-translation-`;
|
||||
const time = now.toISOString().split("T")[0];
|
||||
const outFile = `${filename}${time}.jsonl`;
|
||||
const piece = JSON.stringify({
|
||||
[key]: {},
|
||||
});
|
||||
const writePiece = piece.substring(1, piece.length - 1) + ",";
|
||||
fireAndForget(async () => {
|
||||
try {
|
||||
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"
|
||||
);
|
||||
} catch (ex) {
|
||||
this._log(`Could not write ${outFile}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(`Missing translation: ${writePiece}`, LOG_LEVEL_VERBOSE);
|
||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||
}
|
||||
});
|
||||
void this.onMissingTranslation(key);
|
||||
});
|
||||
type STUB = {
|
||||
toc: Set<string>;
|
||||
|
@ -46,6 +46,8 @@ const recentLogProcessor = new QueueProcessor(
|
||||
).resumePipeLine();
|
||||
// logStore.intercept(e => e.slice(Math.min(e.length - 200, 0)));
|
||||
|
||||
const showDebugLog = false;
|
||||
export const MARK_DONE = "\u{2009}\u{2009}";
|
||||
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
|
||||
registerView = this.plugin.registerView.bind(this.plugin);
|
||||
|
||||
@ -89,7 +91,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
||||
const labelChunkCount = padLeftSpComputed(collectingChunks, `🧩`);
|
||||
const labelPluginScanCount = padLeftSpComputed(pluginScanningCount, `🔌`);
|
||||
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 queueCountLabelX = reactive(() => {
|
||||
return `${labelReplication()}${labelDBCount()}${labelStorageCount()}${labelChunkCount()}${labelPluginScanCount()}${labelHiddenFilesCount()}${labelConflictProcessCount()}`;
|
||||
@ -355,7 +357,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
||||
);
|
||||
}
|
||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
||||
if (level == LOG_LEVEL_DEBUG) {
|
||||
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
||||
return;
|
||||
}
|
||||
if (level < LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
||||
@ -412,15 +414,17 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
||||
};
|
||||
}
|
||||
const timeout = 5000;
|
||||
scheduleTask(`notify-${key}`, timeout, () => {
|
||||
const notify = this.notifies[key].notice;
|
||||
delete this.notifies[key];
|
||||
try {
|
||||
notify.hide();
|
||||
} catch {
|
||||
// NO OP
|
||||
}
|
||||
});
|
||||
if (!key.startsWith("keepalive-") || messageContent.indexOf(MARK_DONE) !== -1) {
|
||||
scheduleTask(`notify-${key}`, timeout, () => {
|
||||
const notify = this.notifies[key].notice;
|
||||
delete this.notifies[key];
|
||||
try {
|
||||
notify.hide();
|
||||
} catch {
|
||||
// NO OP
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1905,7 +1905,7 @@ However, your report is needed to stabilise this. I appreciate you for your grea
|
||||
}
|
||||
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("syncInternalFilesBeforeReplication", {
|
||||
onUpdate: visibleOnly(() => this.isConfiguredAs("watchInternalFileChanges", false)),
|
||||
onUpdate: visibleOnly(() => this.isConfiguredAs("watchInternalFileChanges", true)),
|
||||
});
|
||||
|
||||
new Setting(paneEl).setClass("wizardHidden").autoWireNumeric("syncInternalFilesInterval", {
|
||||
|
Loading…
Reference in New Issue
Block a user