mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2025-12-21 05:31:28 +00:00
### Fixed
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710). - Just using the fetched chunk temporarily. - Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712). - It no longer detects files that the plug-in has modified. - It may reduce unnecessary file comparisons and unexpected file states. ### Improved - Now checking the remote database configuration respecting the CouchDB version (#714).
This commit is contained in:
2
src/lib
2
src/lib
Submodule src/lib updated: f021a9a6f4...21ca077163
@@ -15,10 +15,40 @@ import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/uti
|
|||||||
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
||||||
import type { StorageAccess } from "../interfaces/StorageAccess";
|
import type { StorageAccess } from "../interfaces/StorageAccess";
|
||||||
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||||
|
|
||||||
|
const fileLockPrefix = "file-lock:";
|
||||||
|
|
||||||
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
||||||
|
processingFiles: Set<FilePathWithPrefix> = new Set();
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return this.processingFiles.has(path);
|
||||||
|
}
|
||||||
vaultAccess!: SerializedFileAccess;
|
vaultAccess!: SerializedFileAccess;
|
||||||
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core);
|
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core, this);
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.core.storageAccess = this;
|
this.core.storageAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -42,7 +72,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin);
|
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
||||||
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
||||||
import type { InternalFileInfo } from "../../../common/types.ts";
|
import type { InternalFileInfo } from "../../../common/types.ts";
|
||||||
import { markChangesAreSame } from "../../../common/utils.ts";
|
import { markChangesAreSame } from "../../../common/utils.ts";
|
||||||
import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
|
|
||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
|
||||||
return `fl:${typeof file == "string" ? file : file.path}`;
|
|
||||||
}
|
|
||||||
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
return arr.buffer;
|
return arr.buffer;
|
||||||
@@ -21,60 +16,55 @@ function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<Arr
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// function isFile(file: TFile | TFolder | string | UXFileInfo): boolean {
|
|
||||||
// file instanceof TFile;
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function processReadFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
async function processWriteFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SerializedFileAccess {
|
export class SerializedFileAccess {
|
||||||
app: App;
|
app: App;
|
||||||
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
||||||
constructor(app: App, plugin: (typeof this)["plugin"]) {
|
storageAccess: StorageAccess;
|
||||||
|
constructor(app: App, plugin: SerializedFileAccess["plugin"], storageAccess: StorageAccess) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryAdapterStat(file: TFile | string) {
|
async tryAdapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, async () => {
|
return await this.storageAccess.processReadFile(path as FilePath, async () => {
|
||||||
if (!(await this.app.vault.adapter.exists(path))) return null;
|
if (!(await this.app.vault.adapter.exists(path))) return null;
|
||||||
return this.app.vault.adapter.stat(path);
|
return this.app.vault.adapter.stat(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async adapterStat(file: TFile | string) {
|
async adapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.stat(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.stat(path));
|
||||||
}
|
}
|
||||||
async adapterExists(file: TFile | string) {
|
async adapterExists(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.exists(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.exists(path));
|
||||||
}
|
}
|
||||||
async adapterRemove(file: TFile | string) {
|
async adapterRemove(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.remove(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.remove(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterRead(file: TFile | string) {
|
async adapterRead(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
}
|
}
|
||||||
async adapterReadBinary(file: TFile | string) {
|
async adapterReadBinary(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterReadAuto(file: TFile | string) {
|
async adapterReadAuto(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterWrite(
|
async adapterWrite(
|
||||||
@@ -84,35 +74,39 @@ export class SerializedFileAccess {
|
|||||||
) {
|
) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.write(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, () =>
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultCacheRead(file: TFile) {
|
async vaultCacheRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.cachedRead(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.cachedRead(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultRead(file: TFile) {
|
async vaultRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.read(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.read(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadBinary(file: TFile) {
|
async vaultReadBinary(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadAuto(file: TFile) {
|
async vaultReadAuto(file: TFile) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.read(file));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.read(file));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.read(file);
|
const oldData = await this.app.vault.read(file);
|
||||||
if (data === oldData) {
|
if (data === oldData) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -122,7 +116,7 @@ export class SerializedFileAccess {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.readBinary(file);
|
const oldData = await this.app.vault.readBinary(file);
|
||||||
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -139,9 +133,13 @@ export class SerializedFileAccess {
|
|||||||
options?: DataWriteOptions
|
options?: DataWriteOptions
|
||||||
): Promise<TFile> {
|
): Promise<TFile> {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(path, () => this.app.vault.create(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.create(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(path, () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.createBinary(path, toArrayBuffer(data), options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +152,14 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: TFile | TFolder, force = false) {
|
async delete(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.delete(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.delete(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async trash(file: TFile | TFolder, force = false) {
|
async trash(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.trash(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isStorageInsensitive(): boolean {
|
isStorageInsensitive(): boolean {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
|||||||
import type { LiveSyncCore } from "../../../main.ts";
|
import type { LiveSyncCore } from "../../../main.ts";
|
||||||
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
||||||
|
|
||||||
export type FileEvent = {
|
export type FileEvent = {
|
||||||
@@ -46,6 +47,7 @@ export abstract class StorageEventManager {
|
|||||||
export class StorageEventManagerObsidian extends StorageEventManager {
|
export class StorageEventManagerObsidian extends StorageEventManager {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
core: LiveSyncCore;
|
core: LiveSyncCore;
|
||||||
|
storageAccess: StorageAccess;
|
||||||
|
|
||||||
get shouldBatchSave() {
|
get shouldBatchSave() {
|
||||||
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
||||||
@@ -56,8 +58,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
get batchSaveMaximumDelay(): number {
|
get batchSaveMaximumDelay(): number {
|
||||||
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
||||||
}
|
}
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccess: StorageAccess) {
|
||||||
super();
|
super();
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
@@ -88,6 +91,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
const file = info?.file as TFile;
|
const file = info?.file as TFile;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.isWaiting(file.path as FilePath)) {
|
if (!this.isWaiting(file.path as FilePath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,22 +109,35 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
|
|
||||||
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file, true);
|
const fileInfo = TFileToUXFileInfoStub(file, true);
|
||||||
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||||
|
// vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename').
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue(
|
void this.appendQueue(
|
||||||
@@ -145,6 +165,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
// Watch raw events (Internal API)
|
// Watch raw events (Internal API)
|
||||||
watchVaultRawEvents(path: FilePath) {
|
watchVaultRawEvents(path: FilePath) {
|
||||||
|
if (this.storageAccess.isFileProcessing(path)) {
|
||||||
|
// Logger(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Only for internal files.
|
// Only for internal files.
|
||||||
if (!this.plugin.settings) return;
|
if (!this.plugin.settings) return;
|
||||||
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
||||||
|
|||||||
@@ -101,6 +101,23 @@ export function paneRemoteConfig(
|
|||||||
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
||||||
|
|
||||||
|
const serverBanner = r.headers["server"] ?? r.headers["Server"] ?? "unknown";
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.serverVersion", { info: serverBanner }));
|
||||||
|
const versionMatch = serverBanner.match(/CouchDB(\/([0-9.]+))?/);
|
||||||
|
const versionStr = versionMatch ? versionMatch[2] : "0.0.0";
|
||||||
|
const versionParts = `${versionStr}.0.0.0`.split(".");
|
||||||
|
// Compare version string with the target version.
|
||||||
|
// version must be a string like "3.2.1" or "3.10.2", and must be two or three parts.
|
||||||
|
function isGreaterThanOrEqual(version: string) {
|
||||||
|
const targetParts = version.split(".");
|
||||||
|
for (let i = 0; i < targetParts.length; i++) {
|
||||||
|
// compare as number if possible (so 3.10 > 3.2, 3.10.1b > 3.10.1a)
|
||||||
|
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
|
||||||
|
if (result > 0) return true;
|
||||||
|
if (result < 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Admin check
|
// Admin check
|
||||||
// for database creation and deletion
|
// for database creation and deletion
|
||||||
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||||
@@ -108,28 +125,31 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
||||||
}
|
}
|
||||||
// HTTP user-authorization check
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
// HTTP user-authorization check
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
isSuccessful = false;
|
||||||
addConfigFixButton(
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
addConfigFixButton(
|
||||||
"chttpd/require_valid_user",
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
||||||
"true"
|
"chttpd/require_valid_user",
|
||||||
);
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||||
}
|
isSuccessful = false;
|
||||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
||||||
isSuccessful = false;
|
addConfigFixButton(
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
||||||
addConfigFixButton(
|
"chttpd_auth/require_valid_user",
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
"true"
|
||||||
"chttpd_auth/require_valid_user",
|
);
|
||||||
"true"
|
} else {
|
||||||
);
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
||||||
} else {
|
}
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
|
||||||
}
|
}
|
||||||
// HTTPD check
|
// HTTPD check
|
||||||
// Check Authentication header
|
// Check Authentication header
|
||||||
@@ -144,12 +164,26 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
||||||
}
|
}
|
||||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.enable_cors != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
isSuccessful = false;
|
||||||
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"));
|
||||||
|
addConfigFixButton(
|
||||||
|
$msg("obsidianLiveSyncSettingTab.msgEnableCorsChttpd"),
|
||||||
|
"chttpd/enable_cors",
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCorsChttpd"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
||||||
|
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the server is not cloudant, configure request size
|
// If the server is not cloudant, configure request size
|
||||||
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import type {
|
|||||||
import type { CustomRegExp } from "../../lib/src/common/utils";
|
import type { CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
|
||||||
export interface StorageAccess {
|
export interface StorageAccess {
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean;
|
||||||
|
|
||||||
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
||||||
|
|
||||||
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
|||||||
Reference in New Issue
Block a user