- New feature:

- We can disable the status bar in the setting dialogue.
- Improved:
  - Now some files are handled as correct data type.
  - Customisation sync now uses the digest of each file for better performance.
  - The status in the Editor now works performant.
- Refactored:
  - Common functions have been ready and the codebase has been organised.
  - Stricter type checking following TypeScript updates.
  - Remove old iOS workaround for simplicity and performance.
This commit is contained in:
vorotamoroz
2024-03-19 17:58:55 +01:00
parent 0313443b29
commit f7fbe85d65
12 changed files with 251 additions and 245 deletions

View File

@@ -4,10 +4,9 @@ import { Notice, type PluginManifest, parseYaml, normalizePath, type ListedFiles
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "./lib/src/types"; import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "./lib/src/types";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "./lib/src/types"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "./lib/src/types";
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types"; import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types";
import { createTextBlob, delay, getDocData, isDocContentSame, sendSignal, waitForSignal } from "./lib/src/utils"; import { createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocData, isDocContentSame, sendSignal, waitForSignal } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { WrappedNotice } from "./lib/src/wrapper"; import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "./lib/src/strbin";
import { readString, decodeBinary, arrayBufferToBase64, sha1 } from "./lib/src/strbin";
import { serialized } from "./lib/src/lock"; import { serialized } from "./lib/src/lock";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { stripAllPrefixes } from "./lib/src/path"; import { stripAllPrefixes } from "./lib/src/path";
@@ -31,7 +30,8 @@ function serialize(data: PluginDataEx): string {
ret += data.mtime + d2; ret += data.mtime + d2;
for (const file of data.files) { for (const file of data.files) {
ret += file.filename + d + (file.displayName ?? "") + d + (file.version ?? "") + d2; ret += file.filename + d + (file.displayName ?? "") + d + (file.version ?? "") + d2;
ret += file.mtime + d + file.size + d2; const hash = digestHash((file.data ?? []).join());
ret += file.mtime + d + file.size + d + hash + d2;
for (const data of file.data ?? []) { for (const data of file.data ?? []) {
ret += data + d ret += data + d
} }
@@ -95,6 +95,7 @@ function deserialize2(str: string): PluginDataEx {
tokens.nextLine(); tokens.nextLine();
const mtime = Number(tokens.next()); const mtime = Number(tokens.next());
const size = Number(tokens.next()); const size = Number(tokens.next());
const hash = tokens.next();
tokens.nextLine(); tokens.nextLine();
const data = [] as string[]; const data = [] as string[];
let piece = ""; let piece = "";
@@ -110,7 +111,8 @@ function deserialize2(str: string): PluginDataEx {
version, version,
mtime, mtime,
size, size,
data data,
hash
} }
) )
tokens.nextLine(); tokens.nextLine();
@@ -137,10 +139,11 @@ export const pluginIsEnumerating = writable(false);
export type PluginDataExFile = { export type PluginDataExFile = {
filename: string, filename: string,
data?: string[], data: string[],
mtime: number, mtime: number,
size: number, size: number,
version?: string, version?: string,
hash?: string,
displayName?: string, displayName?: string,
} }
export type PluginDataExDisplay = { export type PluginDataExDisplay = {
@@ -169,17 +172,16 @@ export class ConfigSync extends LiveSyncCommands {
pluginScanningCount.onChanged((e) => { pluginScanningCount.onChanged((e) => {
const total = e.value; const total = e.value;
pluginIsEnumerating.set(total != 0); pluginIsEnumerating.set(total != 0);
if (total == 0) { // if (total == 0) {
Logger(`Processing configurations done`, LOG_LEVEL_INFO, "get-plugins"); // Logger(`Processing configurations done`, LOG_LEVEL_INFO, "get-plugins");
} // }
}) })
} }
confirmPopup: WrappedNotice = null;
get kvDB() { get kvDB() {
return this.plugin.kvDB; return this.plugin.kvDB;
} }
pluginDialog: PluginDialogModal = null; pluginDialog?: PluginDialogModal = undefined;
periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false)); periodicPluginSweepProcessor = new PeriodicProcessor(this.plugin, async () => await this.scanAllConfigFiles(false));
pluginList: PluginDataExDisplay[] = []; pluginList: PluginDataExDisplay[] = [];
@@ -187,7 +189,7 @@ export class ConfigSync extends LiveSyncCommands {
if (!this.settings.usePluginSync) { if (!this.settings.usePluginSync) {
return; return;
} }
if (this.pluginDialog != null) { if (this.pluginDialog) {
this.pluginDialog.open(); this.pluginDialog.open();
} else { } else {
this.pluginDialog = new PluginDialogModal(this.app, this.plugin); this.pluginDialog = new PluginDialogModal(this.app, this.plugin);
@@ -198,7 +200,7 @@ export class ConfigSync extends LiveSyncCommands {
hidePluginSyncModal() { hidePluginSyncModal() {
if (this.pluginDialog != null) { if (this.pluginDialog != null) {
this.pluginDialog.close(); this.pluginDialog.close();
this.pluginDialog = null; this.pluginDialog = undefined;
} }
} }
onunload() { onunload() {
@@ -273,16 +275,28 @@ export class ConfigSync extends LiveSyncCommands {
await this.updatePluginList(showMessage); await this.updatePluginList(showMessage);
} }
async loadPluginData(path: FilePathWithPrefix): Promise<PluginDataExDisplay | false> { async loadPluginData(path: FilePathWithPrefix): Promise<PluginDataExDisplay | false> {
const wx = await this.localDatabase.getDBEntry(path, null, false, false); const wx = await this.localDatabase.getDBEntry(path, undefined, false, false);
if (wx) { if (wx) {
const data = deserialize(getDocData(wx.data), {}) as PluginDataEx; const data = deserialize(getDocData(wx.data), {}) as PluginDataEx;
const xFiles = [] as PluginDataExFile[]; const xFiles = [] as PluginDataExFile[];
let missingHash = false;
for (const file of data.files) { for (const file of data.files) {
const work = { ...file }; const work = { ...file, data: [] as string[] };
const tempStr = getDocData(work.data); if (!file.hash) {
work.data = [await sha1(tempStr)]; // debugger;
const tempStr = getDocData(work.data);
const hash = digestHash(tempStr);
file.hash = hash;
missingHash = true;
}
work.data = [file.hash];
xFiles.push(work); xFiles.push(work);
} }
if (missingHash) {
Logger(`Digest created for ${path} to improve checking`, LOG_LEVEL_VERBOSE);
wx.data = serialize(data);
fireAndForget(() => this.localDatabase.putDBEntry(createSavingEntryFromLoadedEntry(wx)));
}
return ({ return ({
...data, ...data,
documentPath: this.getPath(wx), documentPath: this.getPath(wx),
@@ -317,21 +331,21 @@ export class ConfigSync extends LiveSyncCommands {
const plugin = v[0]; const plugin = v[0];
const path = plugin.path || this.getPath(plugin); const path = plugin.path || this.getPath(plugin);
const oldEntry = (this.pluginList.find(e => e.documentPath == path)); const oldEntry = (this.pluginList.find(e => e.documentPath == path));
if (oldEntry && oldEntry.mtime == plugin.mtime) return; if (oldEntry && oldEntry.mtime == plugin.mtime) return [];
try { try {
const pluginData = await this.loadPluginData(path); const pluginData = await this.loadPluginData(path);
if (pluginData) { if (pluginData) {
return [pluginData]; return [pluginData];
} }
// Failed to load // Failed to load
return; return [];
} catch (ex) { } catch (ex) {
Logger(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE); Logger(`Something happened at enumerating customization :${path}`, LOG_LEVEL_NOTICE);
Logger(ex, LOG_LEVEL_VERBOSE); Logger(ex, LOG_LEVEL_VERBOSE);
} }
return; return [];
}, { suspended: true, batchSize: 1, concurrentLimit: 5, delay: 300, yieldThreshold: 10 }).pipeTo( }, { suspended: false, batchSize: 1, concurrentLimit: 5, delay: 100, yieldThreshold: 10, maintainDelay: false }).pipeTo(
new QueueProcessor( new QueueProcessor(
async (pluginDataList) => { async (pluginDataList) => {
// Concurrency is two, therefore, we can unlock the previous awaiting. // Concurrency is two, therefore, we can unlock the previous awaiting.
@@ -349,8 +363,8 @@ export class ConfigSync extends LiveSyncCommands {
} }
return; return;
} }
, { suspended: true, batchSize: 10, concurrentLimit: 2, delay: 250, yieldThreshold: 25, totalRemainingReactiveSource: pluginScanningCount })).startPipeline().root.onIdle(() => { , { suspended: false, batchSize: 10, concurrentLimit: 2, delay: 100, yieldThreshold: 25, totalRemainingReactiveSource: pluginScanningCount, maintainDelay: false })).startPipeline().root.onIdle(() => {
Logger(`All files enumerated`, LOG_LEVEL_INFO, "get-plugins"); // Logger(`All files enumerated`, LOG_LEVEL_INFO, "get-plugins");
this.createMissingConfigurationEntry(); this.createMissingConfigurationEntry();
}); });
@@ -502,9 +516,9 @@ export class ConfigSync extends LiveSyncCommands {
if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) { if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) { if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
const fragment = createFragment((doc) => { const fragment = createFragment((doc) => {
doc.createEl("span", null, (a) => { doc.createEl("span", undefined, (a) => {
a.appendText(`Some configuration has been arrived, Press `); a.appendText(`Some configuration has been arrived, Press `);
a.appendChild(a.createEl("a", null, (anchor) => { a.appendChild(a.createEl("a", undefined, (anchor) => {
anchor.text = "HERE"; anchor.text = "HERE";
anchor.addEventListener("click", () => { anchor.addEventListener("click", () => {
this.showPluginSyncModal(); this.showPluginSyncModal();
@@ -670,7 +684,7 @@ export class ConfigSync extends LiveSyncCommands {
const content = createTextBlob(serialize(dt)); const content = createTextBlob(serialize(dt));
try { try {
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false); const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, false);
let saveData: SavingEntry; let saveData: SavingEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
@@ -694,7 +708,7 @@ export class ConfigSync extends LiveSyncCommands {
if (oldC) { if (oldC) {
const d = await deserialize(getDocData(oldC.data), {}) as PluginDataEx; const d = await deserialize(getDocData(oldC.data), {}) as PluginDataEx;
const diffs = (d.files.map(previous => ({ prev: previous, curr: dt.files.find(e => e.filename == previous.filename) })).map(async e => { const diffs = (d.files.map(previous => ({ prev: previous, curr: dt.files.find(e => e.filename == previous.filename) })).map(async e => {
try { return await isDocContentSame(e.curr.data, e.prev.data) } catch (_) { return false } try { return await isDocContentSame(e.curr?.data ?? [], e.prev.data) } catch (_) { return false }
})) }))
const isSame = (await Promise.all(diffs)).every(e => e == true); const isSame = (await Promise.all(diffs)).every(e => e == true);
if (isSame) { if (isSame) {
@@ -767,7 +781,11 @@ export class ConfigSync extends LiveSyncCommands {
const filesOnDB = ((await this.localDatabase.allDocsRaw({ startkey: ICXHeader + "", endkey: `${ICXHeader}\u{10ffff}`, include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]).filter(e => !e.deleted); const filesOnDB = ((await this.localDatabase.allDocsRaw({ startkey: ICXHeader + "", endkey: `${ICXHeader}\u{10ffff}`, include_docs: true })).rows.map(e => e.doc) as InternalFileEntry[]).filter(e => !e.deleted);
let deleteCandidate = filesOnDB.map(e => this.getPath(e)).filter(e => e.startsWith(`${ICXHeader}${term}/`)); let deleteCandidate = filesOnDB.map(e => this.getPath(e)).filter(e => e.startsWith(`${ICXHeader}${term}/`));
for (const vp of virtualPathsOfLocalFiles) { for (const vp of virtualPathsOfLocalFiles) {
const p = files.find(e => e.key == vp).file; const p = files.find(e => e.key == vp)?.file;
if (!p) {
Logger(`scanAllConfigFiles - File not found: ${vp}`, LOG_LEVEL_VERBOSE);
continue;
}
await this.storeCustomizationFiles(p); await this.storeCustomizationFiles(p);
deleteCandidate = deleteCandidate.filter(e => e != vp); deleteCandidate = deleteCandidate.filter(e => e != vp);
} }
@@ -782,10 +800,11 @@ export class ConfigSync extends LiveSyncCommands {
const mtime = new Date().getTime(); const mtime = new Date().getTime();
await serialized("file-x-" + prefixedFileName, async () => { await serialized("file-x-" + prefixedFileName, async () => {
try { try {
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false) as InternalFileEntry | false; const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, false) as InternalFileEntry | false;
let saveData: InternalFileEntry; let saveData: InternalFileEntry;
if (old === false) { if (old === false) {
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted (Not found on database)`); Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted (Not found on database)`);
return;
} else { } else {
if (old.deleted) { if (old.deleted) {
Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted`); Logger(`STORAGE -x> DB:${prefixedFileName}: (config) already deleted`);

View File

@@ -1,12 +1,10 @@
import { normalizePath, type PluginManifest, type ListedFiles } from "./deps"; import { normalizePath, type PluginManifest, type ListedFiles } from "./deps";
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry, type DocumentID } from "./lib/src/types";
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types"; import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types";
import { createBinaryBlob, isDocContentSame, sendSignal } from "./lib/src/utils"; import { readAsBlob, isDocContentSame, sendSignal, readContent, createBlob } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { isInternalMetadata, PeriodicProcessor } from "./utils"; import { isInternalMetadata, PeriodicProcessor } from "./utils";
import { WrappedNotice } from "./lib/src/wrapper";
import { decodeBinary, encodeBinary } from "./lib/src/strbin";
import { serialized } from "./lib/src/lock"; import { serialized } from "./lib/src/lock";
import { JsonResolveModal } from "./JsonResolveModal"; import { JsonResolveModal } from "./JsonResolveModal";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
@@ -16,7 +14,7 @@ import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "./lib/src/sto
export class HiddenFileSync extends LiveSyncCommands { export class HiddenFileSync extends LiveSyncCommands {
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(this.plugin, async () => this.settings.syncInternalFiles && this.localDatabase.isReady && await this.syncInternalFilesAndDatabase("push", false)); periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(this.plugin, async () => this.settings.syncInternalFiles && this.localDatabase.isReady && await this.syncInternalFilesAndDatabase("push", false));
confirmPopup: WrappedNotice = null;
get kvDB() { get kvDB() {
return this.plugin.kvDB; return this.plugin.kvDB;
} }
@@ -67,11 +65,11 @@ export class HiddenFileSync extends LiveSyncCommands {
realizeSettingSyncMode(): Promise<void> { realizeSettingSyncMode(): Promise<void> {
this.periodicInternalFileScanProcessor?.disable(); this.periodicInternalFileScanProcessor?.disable();
if (this.plugin.suspended) if (this.plugin.suspended)
return; return Promise.resolve();
if (!this.plugin.isReady) if (!this.plugin.isReady)
return; return Promise.resolve();
this.periodicInternalFileScanProcessor.enable(this.settings.syncInternalFiles && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0); this.periodicInternalFileScanProcessor.enable(this.settings.syncInternalFiles && this.settings.syncInternalFilesInterval ? (this.settings.syncInternalFilesInterval * 1000) : 0);
return; return Promise.resolve();
} }
procInternalFile(filename: string) { procInternalFile(filename: string) {
@@ -125,7 +123,7 @@ export class HiddenFileSync extends LiveSyncCommands {
if (storageMTime == 0) { if (storageMTime == 0) {
await this.deleteInternalFileOnDatabase(path); await this.deleteInternalFileOnDatabase(path);
} else { } else {
await this.storeInternalFileToDatabase({ path: path, ...stat }); await this.storeInternalFileToDatabase({ path: path, mtime, ctime: stat?.ctime ?? mtime, size: stat?.size ?? 0 });
} }
} }
@@ -162,7 +160,7 @@ export class HiddenFileSync extends LiveSyncCommands {
await this.localDatabase.removeRevision(id, delRev); await this.localDatabase.removeRevision(id, delRev);
Logger(`Older one has been deleted:${path}`); Logger(`Older one has been deleted:${path}`);
const cc = await this.localDatabase.getRaw(id, { conflicts: true }); const cc = await this.localDatabase.getRaw(id, { conflicts: true });
if (cc._conflicts.length == 0) { if (cc._conflicts?.length === 0) {
await this.extractInternalFileFromDatabase(stripAllPrefixes(path)) await this.extractInternalFileFromDatabase(stripAllPrefixes(path))
} else { } else {
this.conflictResolutionProcessor.enqueue(path); this.conflictResolutionProcessor.enqueue(path);
@@ -177,11 +175,12 @@ export class HiddenFileSync extends LiveSyncCommands {
// Retrieve data // Retrieve data
const id = await this.path2id(path, ICHeader); const id = await this.path2id(path, ICHeader);
const doc = await this.localDatabase.getRaw(id, { conflicts: true }); const doc = await this.localDatabase.getRaw(id, { conflicts: true });
// If there is no conflict, return with false. // if (!("_conflicts" in doc)){
if (!("_conflicts" in doc)) // return [];
return; // }
if (doc._conflicts === undefined) return [];
if (doc._conflicts.length == 0) if (doc._conflicts.length == 0)
return; return [];
Logger(`Hidden file conflicted:${path}`); Logger(`Hidden file conflicted:${path}`);
const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0])); const conflicts = doc._conflicts.sort((a, b) => Number(a.split("-")[0]) - Number(b.split("-")[0]));
const revA = doc._rev; const revA = doc._rev;
@@ -192,7 +191,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const conflictedRevNo = Number(conflictedRev.split("-")[0]); const conflictedRevNo = Number(conflictedRev.split("-")[0]);
//Search //Search
const revFrom = (await this.localDatabase.getRaw<EntryDoc>(id, { revs_info: true })); const revFrom = (await this.localDatabase.getRaw<EntryDoc>(id, { revs_info: true }));
const commonBase = revFrom._revs_info.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? ""; const commonBase = revFrom._revs_info?.filter(e => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo).first()?.rev ?? "";
const result = await this.plugin.mergeObject(path, commonBase, doc._rev, conflictedRev); const result = await this.plugin.mergeObject(path, commonBase, doc._rev, conflictedRev);
if (result) { if (result) {
Logger(`Object merge:${path}`, LOG_LEVEL_INFO); Logger(`Object merge:${path}`, LOG_LEVEL_INFO);
@@ -203,11 +202,14 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
await this.plugin.vaultAccess.adapterWrite(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.vaultAccess.adapterStat(filename); const stat = await this.vaultAccess.adapterStat(filename);
if (!stat) {
throw new Error(`conflictResolutionProcessor: Failed to stat file ${filename}`);
}
await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename); await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.removeRevision(id, revB); await this.localDatabase.removeRevision(id, revB);
this.conflictResolutionProcessor.enqueue(path); this.conflictResolutionProcessor.enqueue(path);
return; return [];
} else { } else {
Logger(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE); Logger(`Object merge is not applicable.`, LOG_LEVEL_VERBOSE);
} }
@@ -215,11 +217,11 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
// When not JSON file, resolve conflicts by choosing a newer one. // When not JSON file, resolve conflicts by choosing a newer one.
await this.resolveByNewerEntry(id, path, doc, revA, revB); await this.resolveByNewerEntry(id, path, doc, revA, revB);
return; return [];
} catch (ex) { } catch (ex) {
Logger(`Failed to resolve conflict (Hidden): ${path}`); Logger(`Failed to resolve conflict (Hidden): ${path}`);
Logger(ex, LOG_LEVEL_VERBOSE); Logger(ex, LOG_LEVEL_VERBOSE);
return; return [];
} }
}, { }, {
suspended: false, batchSize: 1, concurrentLimit: 5, delay: 10, keepResultUntilDownstreamConnected: true, yieldThreshold: 10, suspended: false, batchSize: 1, concurrentLimit: 5, delay: 10, keepResultUntilDownstreamConnected: true, yieldThreshold: 10,
@@ -312,11 +314,11 @@ export class HiddenFileSync extends LiveSyncCommands {
if (processed % 100 == 0) { if (processed % 100 == 0) {
Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal"); Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
} }
if (!filename) return; if (!filename) return [];
if (ignorePatterns.some(e => filename.match(e))) if (ignorePatterns.some(e => filename.match(e)))
return; return [];
if (await this.plugin.isIgnoredByIgnoreFiles(filename)) { if (await this.plugin.isIgnoredByIgnoreFiles(filename)) {
return; return [];
} }
const fileOnStorage = filename in filesMap ? filesMap[filename] : undefined; const fileOnStorage = filename in filesMap ? filesMap[filename] : undefined;
@@ -403,7 +405,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>; const enabledPlugins = this.app.plugins.enabledPlugins as Set<string>;
const enabledPluginManifests = manifests.filter(e => enabledPlugins.has(e.id)); const enabledPluginManifests = manifests.filter(e => enabledPlugins.has(e.id));
for (const manifest of enabledPluginManifests) { for (const manifest of enabledPluginManifests) {
if (manifest.dir in updatedFolders) { if (manifest.dir && manifest.dir in updatedFolders) {
// If notified about plug-ins, reloading Obsidian may not be necessary. // If notified about plug-ins, reloading Obsidian may not be necessary.
updatedCount -= updatedFolders[manifest.dir]; updatedCount -= updatedFolders[manifest.dir];
const updatePluginId = manifest.id; const updatePluginId = manifest.id;
@@ -451,19 +453,11 @@ export class HiddenFileSync extends LiveSyncCommands {
const id = await this.path2id(file.path, ICHeader); const id = await this.path2id(file.path, ICHeader);
const prefixedFileName = addPrefix(file.path, ICHeader); const prefixedFileName = addPrefix(file.path, ICHeader);
const contentBin = await this.plugin.vaultAccess.adapterReadBinary(file.path); const content = createBlob(await this.plugin.vaultAccess.adapterReadAuto(file.path));
let content: Blob;
try {
content = createBinaryBlob(contentBin);
} catch (ex) {
Logger(`The file ${file.path} could not be encoded`);
Logger(ex, LOG_LEVEL_VERBOSE);
return false;
}
const mtime = file.mtime; const mtime = file.mtime;
return await serialized("file-" + prefixedFileName, async () => { return await serialized("file-" + prefixedFileName, async () => {
try { try {
const old = await this.localDatabase.getDBEntry(prefixedFileName, null, false, false); const old = await this.localDatabase.getDBEntry(prefixedFileName, undefined, false, false);
let saveData: SavingEntry; let saveData: SavingEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
@@ -479,7 +473,7 @@ export class HiddenFileSync extends LiveSyncCommands {
type: "newnote", type: "newnote",
}; };
} else { } else {
if (await isDocContentSame(createBinaryBlob(decodeBinary(old.data)), content) && !forceWrite) { if (await isDocContentSame(readAsBlob(old), content) && !forceWrite) {
// Logger(`STORAGE --> DB:${file.path}: (hidden) Not changed`, LOG_LEVEL_VERBOSE); // Logger(`STORAGE --> DB:${file.path}: (hidden) Not changed`, LOG_LEVEL_VERBOSE);
return; return;
} }
@@ -489,10 +483,10 @@ export class HiddenFileSync extends LiveSyncCommands {
data: content, data: content,
mtime, mtime,
size: file.size, size: file.size,
datatype: "newnote", datatype: old.datatype,
children: [], children: [],
deleted: false, deleted: false,
type: "newnote", type: old.datatype,
}; };
} }
const ret = await this.localDatabase.putDBEntry(saveData); const ret = await this.localDatabase.putDBEntry(saveData);
@@ -515,7 +509,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
await serialized("file-" + prefixedFileName, async () => { await serialized("file-" + prefixedFileName, async () => {
try { try {
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, true) as InternalFileEntry | false; const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, undefined, true) as InternalFileEntry | false;
let saveData: InternalFileEntry; let saveData: InternalFileEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
@@ -531,7 +525,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} else { } else {
// Remove all conflicted before deleting. // Remove all conflicted before deleting.
const conflicts = await this.localDatabase.getRaw(old._id, { conflicts: true }); const conflicts = await this.localDatabase.getRaw(old._id, { conflicts: true });
if ("_conflicts" in conflicts) { if (conflicts._conflicts !== undefined) {
for (const conflictRev of conflicts._conflicts) { for (const conflictRev of conflicts._conflicts) {
await this.localDatabase.removeRevision(old._id, conflictRev); await this.localDatabase.removeRevision(old._id, conflictRev);
Logger(`STORAGE -x> DB:${filename}: (hidden) conflict removed ${old._rev} => ${conflictRev}`, LOG_LEVEL_VERBOSE); Logger(`STORAGE -x> DB:${filename}: (hidden) conflict removed ${old._rev} => ${conflictRev}`, LOG_LEVEL_VERBOSE);
@@ -581,7 +575,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const deleted = fileOnDB.deleted || fileOnDB._deleted || false; const deleted = fileOnDB.deleted || fileOnDB._deleted || false;
if (deleted) { if (deleted) {
if (!isExists) { if (!isExists) {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
} else { } else {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`);
await this.plugin.vaultAccess.adapterRemove(filename); await this.plugin.vaultAccess.adapterRemove(filename);
@@ -597,7 +591,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
if (!isExists) { if (!isExists) {
await this.vaultAccess.ensureDirectory(filename); await this.vaultAccess.ensureDirectory(filename);
await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, readContent(fileOnDB), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -608,13 +602,13 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`); Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`);
return true; return true;
} else { } else {
const contentBin = await this.plugin.vaultAccess.adapterReadBinary(filename); const content = await this.plugin.vaultAccess.adapterReadAuto(filename);
const content = await encodeBinary(contentBin); const docContent = readContent(fileOnDB);
if (await isDocContentSame(content, fileOnDB.data) && !force) { if (await isDocContentSame(content, docContent) && !force) {
// Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE); // Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE);
return true; return true;
} }
await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, docContent, { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -669,7 +663,11 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
await this.plugin.vaultAccess.adapterWrite(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.plugin.vaultAccess.adapterStat(filename); const stat = await this.plugin.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }, true); if (!stat) {
throw new Error("Stat failed");
}
const mtime = stat?.mtime ?? 0;
await this.storeInternalFileToDatabase({ path: filename, mtime, ctime: stat?.ctime ?? mtime, size: stat?.size ?? 0 }, true);
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -703,7 +701,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const root = this.app.vault.getRoot(); const root = this.app.vault.getRoot();
const findRoot = root.path; const findRoot = root.path;
const filenames = (await this.getFiles(findRoot, [], null, ignoreFilter)).filter(e => e.startsWith(".")).filter(e => !e.startsWith(".trash")); const filenames = (await this.getFiles(findRoot, [], undefined, ignoreFilter)).filter(e => e.startsWith(".")).filter(e => !e.startsWith(".trash"));
const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => { const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => {
return { return {
path: e as FilePath, path: e as FilePath,
@@ -716,9 +714,12 @@ export class HiddenFileSync extends LiveSyncCommands {
if (await this.plugin.isIgnoredByIgnoreFiles(w.path)) { if (await this.plugin.isIgnoredByIgnoreFiles(w.path)) {
continue continue
} }
const mtime = w.stat?.mtime ?? 0
const ctime = w.stat?.ctime ?? mtime;
const size = w.stat?.size ?? 0;
result.push({ result.push({
...w, ...w,
...w.stat mtime, ctime, size
}); });
} }
return result; return result;
@@ -729,8 +730,8 @@ export class HiddenFileSync extends LiveSyncCommands {
async getFiles( async getFiles(
path: string, path: string,
ignoreList: string[], ignoreList: string[],
filter: RegExp[], filter?: RegExp[],
ignoreFilter: RegExp[] ignoreFilter?: RegExp[]
) { ) {
let w: ListedFiles; let w: ListedFiles;
try { try {

View File

@@ -5,7 +5,7 @@ import ObsidianLiveSyncPlugin from "./main";
import { type DocumentID, type FilePathWithPrefix, type LoadedEntry, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "./lib/src/types"; import { type DocumentID, type FilePathWithPrefix, type LoadedEntry, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "./lib/src/types";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { isErrorOfMissingDoc } from "./lib/src/utils_couchdb"; import { isErrorOfMissingDoc } from "./lib/src/utils_couchdb";
import { getDocData } from "./lib/src/utils"; import { getDocData, readContent } from "./lib/src/utils";
import { isPlainText, stripPrefix } from "./lib/src/path"; import { isPlainText, stripPrefix } from "./lib/src/path";
function isImage(path: string) { function isImage(path: string) {
@@ -66,7 +66,7 @@ export class DocumentHistoryModal extends Modal {
} }
} }
async loadFile(initialRev: string) { async loadFile(initialRev?: string) {
if (!this.id) { if (!this.id) {
this.id = await this.plugin.path2id(this.file); this.id = await this.plugin.path2id(this.file);
} }
@@ -109,7 +109,7 @@ export class DocumentHistoryModal extends Modal {
if (v) { if (v) {
URL.revokeObjectURL(v); URL.revokeObjectURL(v);
} }
this.BlobURLs.set(key, undefined); this.BlobURLs.delete(key);
} }
generateBlobURL(key: string, data: Uint8Array) { generateBlobURL(key: string, data: Uint8Array) {
this.revokeURL(key); this.revokeURL(key);
@@ -247,12 +247,10 @@ export class DocumentHistoryModal extends Modal {
Logger(`Old content copied to clipboard`, LOG_LEVEL_NOTICE); Logger(`Old content copied to clipboard`, LOG_LEVEL_NOTICE);
}); });
}); });
async function focusFile(path: string) { const focusFile = async (path: string) => {
const targetFile = app.vault const targetFile = this.plugin.app.vault.getFileByPath(path);
.getFiles()
.find((f) => f.path === path);
if (targetFile) { if (targetFile) {
const leaf = 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)
@@ -265,19 +263,16 @@ export class DocumentHistoryModal extends Modal {
const pathToWrite = stripPrefix(this.file); const pathToWrite = stripPrefix(this.file);
if (!isValidPath(pathToWrite)) { if (!isValidPath(pathToWrite)) {
Logger("Path is not valid to write content.", LOG_LEVEL_INFO); Logger("Path is not valid to write content.", LOG_LEVEL_INFO);
return;
} }
if (this.currentDoc?.datatype == "plain") { if (!this.currentDoc) {
await this.plugin.vaultAccess.adapterWrite(pathToWrite, getDocData(this.currentDoc.data)); Logger("No active file loaded.", LOG_LEVEL_INFO);
await focusFile(pathToWrite); return;
this.close();
} else if (this.currentDoc?.datatype == "newnote") {
await this.plugin.vaultAccess.adapterWrite(pathToWrite, decodeBinary(this.currentDoc.data));
await focusFile(pathToWrite);
this.close();
} else {
Logger(`Could not parse entry`, LOG_LEVEL_NOTICE);
} }
const d = readContent(this.currentDoc);
await this.plugin.vaultAccess.adapterWrite(pathToWrite, d);
await focusFile(pathToWrite);
this.close();
}); });
}); });
} }

View File

@@ -2,12 +2,11 @@
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types"; import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types";
import { createBinaryBlob, getDocData, isDocContentSame } from "./lib/src/utils"; import { getDocData, isDocContentSame, readAsBlob } from "./lib/src/utils";
import { diff_match_patch } from "./deps"; import { diff_match_patch } from "./deps";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { isPlainText, stripAllPrefixes } from "./lib/src/path"; import { isPlainText, stripAllPrefixes } from "./lib/src/path";
import { TFile } from "./deps"; import { TFile } from "./deps";
import { decodeBinary } from "./lib/src/strbin";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
let showDiffInfo = false; let showDiffInfo = false;
@@ -107,15 +106,9 @@
if (checkStorageDiff) { if (checkStorageDiff) {
const abs = plugin.vaultAccess.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA))); const abs = plugin.vaultAccess.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA)));
if (abs instanceof TFile) { if (abs instanceof TFile) {
let result = false; const data = await plugin.vaultAccess.adapterReadAuto(abs);
if (isPlainText(docA.path)) { const d = readAsBlob(doc);
const data = await plugin.vaultAccess.adapterRead(abs); const result = await isDocContentSame(data, d);
result = await isDocContentSame(data, doc.data);
} else {
const data = await plugin.vaultAccess.adapterReadBinary(abs);
const dataEEncoded = createBinaryBlob(data);
result = await isDocContentSame(dataEEncoded, createBinaryBlob(decodeBinary(doc.data)));
}
if (result) { if (result) {
diffDetail += " ⚖️"; diffDetail += " ⚖️";
} else { } else {

View File

@@ -1,7 +1,7 @@
import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "./deps"; import { App, PluginSettingTab, Setting, sanitizeHTMLToDom, TextAreaComponent, MarkdownRenderer, stringifyYaml } from "./deps";
import { DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, type ConfigPassphraseStore, type RemoteDBSettings, type FilePathWithPrefix, type HashAlgorithm, type DocumentID, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, LOG_LEVEL_INFO, type LoadedEntry, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED } from "./lib/src/types"; import { DEFAULT_SETTINGS, type ObsidianLiveSyncSettings, type ConfigPassphraseStore, type RemoteDBSettings, type FilePathWithPrefix, type HashAlgorithm, type DocumentID, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, LOG_LEVEL_INFO, type LoadedEntry, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED } from "./lib/src/types";
import { createBinaryBlob, createTextBlob, delay, isDocContentSame } from "./lib/src/utils"; import { createBlob, delay, isDocContentSame, readAsBlob } from "./lib/src/utils";
import { decodeBinary, versionNumberString2Number } from "./lib/src/strbin"; import { versionNumberString2Number } from "./lib/src/strbin";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb"; import { checkSyncInfo, isCloudantURI } from "./lib/src/utils_couchdb";
import { testCrypt } from "./lib/src/e2ee_v2"; import { testCrypt } from "./lib/src/e2ee_v2";
@@ -86,11 +86,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
} }
w.querySelectorAll(`.sls-setting-label`).forEach((element) => { w.querySelectorAll(`.sls-setting-label`).forEach((element) => {
element.removeClass("selected"); element.removeClass("selected");
(element.querySelector<HTMLInputElement>("input[type=radio]")).checked = false; (element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = false;
}); });
w.querySelectorAll(`.sls-setting-label.c-${screen}`).forEach((element) => { w.querySelectorAll(`.sls-setting-label.c-${screen}`).forEach((element) => {
element.addClass("selected"); element.addClass("selected");
(element.querySelector<HTMLInputElement>("input[type=radio]")).checked = true; (element.querySelector<HTMLInputElement>("input[type=radio]"))!.checked = true;
}); });
this.selectedScreen = screen; this.selectedScreen = screen;
}; };
@@ -120,7 +120,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
tmpDiv.innerHTML = `<button> OK, I read all. </button>`; tmpDiv.innerHTML = `<button> OK, I read all. </button>`;
if (lastVersion > this.plugin.settings.lastReadUpdates) { if (lastVersion > this.plugin.settings.lastReadUpdates) {
const informationButtonDiv = h3El.appendChild(tmpDiv); const informationButtonDiv = h3El.appendChild(tmpDiv);
informationButtonDiv.querySelector("button").addEventListener("click", async () => { informationButtonDiv.querySelector("button")?.addEventListener("click", async () => {
this.plugin.settings.lastReadUpdates = lastVersion; this.plugin.settings.lastReadUpdates = lastVersion;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
informationButtonDiv.remove(); informationButtonDiv.remove();
@@ -230,23 +230,23 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
let remoteTroubleShootMDSrc = ""; let remoteTroubleShootMDSrc = "";
try { try {
remoteTroubleShootMDSrc = await request(`${rawRepoURI}${basePath}/${filename}`); remoteTroubleShootMDSrc = await request(`${rawRepoURI}${basePath}/${filename}`);
} catch (ex) { } catch (ex: any) {
remoteTroubleShootMDSrc = "Error Occurred!!\n" + ex.toString(); remoteTroubleShootMDSrc = "Error Occurred!!\n" + ex.toString();
} }
const remoteTroubleShootMD = remoteTroubleShootMDSrc.replace(/\((.*?(.png)|(.jpg))\)/g, `(${rawRepoURI}${basePath}/$1)`) const remoteTroubleShootMD = remoteTroubleShootMDSrc.replace(/\((.*?(.png)|(.jpg))\)/g, `(${rawRepoURI}${basePath}/$1)`)
// Render markdown // Render markdown
await MarkdownRenderer.render(this.plugin.app, `<a class='sls-troubleshoot-anchor'></a> [Tips and Troubleshooting](${topPath}) [PageTop](${filename})\n\n${remoteTroubleShootMD}`, troubleShootEl, `${rawRepoURI}`, this.plugin); await MarkdownRenderer.render(this.plugin.app, `<a class='sls-troubleshoot-anchor'></a> [Tips and Troubleshooting](${topPath}) [PageTop](${filename})\n\n${remoteTroubleShootMD}`, troubleShootEl, `${rawRepoURI}`, this.plugin);
// Menu // Menu
troubleShootEl.querySelector<HTMLAnchorElement>(".sls-troubleshoot-anchor") troubleShootEl.querySelector<HTMLAnchorElement>(".sls-troubleshoot-anchor")?.parentElement?.setCssStyles({ position: "sticky", top: "-1em", backgroundColor: "var(--modal-background)" });
.parentElement.setCssStyles({ position: "sticky", top: "-1em", backgroundColor: "var(--modal-background)" });
// Trap internal links. // Trap internal links.
troubleShootEl.querySelectorAll<HTMLAnchorElement>("a.internal-link").forEach((anchorEl) => { troubleShootEl.querySelectorAll<HTMLAnchorElement>("a.internal-link").forEach((anchorEl) => {
anchorEl.addEventListener("click", async (evt) => { anchorEl.addEventListener("click", async (evt) => {
const uri = anchorEl.getAttr("data-href"); const uri = anchorEl.getAttr("data-href");
if (!uri) return;
if (uri.startsWith("#")) { if (uri.startsWith("#")) {
evt.preventDefault(); evt.preventDefault();
const elements = Array.from(troubleShootEl.querySelectorAll<HTMLHeadingElement>("[data-heading]")) const elements = Array.from(troubleShootEl.querySelectorAll<HTMLHeadingElement>("[data-heading]"))
const p = elements.find(e => e.getAttr("data-heading").toLowerCase().split(" ").join("-") == uri.substring(1).toLowerCase()); const p = elements.find(e => e.getAttr("data-heading")?.toLowerCase().split(" ").join("-") == uri.substring(1).toLowerCase());
if (p) { if (p) {
p.setCssStyles({ scrollMargin: "3em" }); p.setCssStyles({ scrollMargin: "3em" });
p.scrollIntoView({ behavior: "instant", block: "start" }); p.scrollIntoView({ behavior: "instant", block: "start" });
@@ -414,7 +414,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
tmpDiv.addClass("ob-btn-config-fix"); tmpDiv.addClass("ob-btn-config-fix");
tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`; tmpDiv.innerHTML = `<label>${title}</label><button>Fix</button>`;
const x = checkResultDiv.appendChild(tmpDiv); const x = checkResultDiv.appendChild(tmpDiv);
x.querySelector("button").addEventListener("click", async () => { x.querySelector("button")?.addEventListener("click", async () => {
Logger(`CouchDB Configuration: ${title} -> Set ${key} to ${value}`) Logger(`CouchDB Configuration: ${title} -> Set ${key} to ${value}`)
const res = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, undefined, key, value); const res = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, undefined, key, value);
if (res.status == 200) { if (res.status == 200) {
@@ -510,15 +510,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"]; const origins = ["app://obsidian.md", "capacitor://localhost", "http://localhost"];
for (const org of origins) { for (const org of origins) {
const rr = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, org); const rr = await requestToCouchDB(this.plugin.settings.couchDB_URI, this.plugin.settings.couchDB_USER, this.plugin.settings.couchDB_PASSWORD, org);
const responseHeaders = Object.entries(rr.headers) const responseHeaders = Object.fromEntries(Object.entries(rr.headers)
.map((e) => { .map((e) => {
e[0] = (e[0] + "").toLowerCase(); e[0] = `${e[0]}`.toLowerCase();
return e; return e;
}) }));
.reduce((obj, [key, val]) => {
obj[key] = val;
return obj;
}, {} as { [key: string]: string });
addResult(`Origin check:${org}`); addResult(`Origin check:${org}`);
if (responseHeaders["access-control-allow-credentials"] != "true") { if (responseHeaders["access-control-allow-credentials"] != "true") {
addResult("❗ CORS is not allowing credential"); addResult("❗ CORS is not allowing credential");
@@ -534,7 +530,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
addResult("--Done--", ["ob-btn-config-head"]); addResult("--Done--", ["ob-btn-config-head"]);
addResult("If you have some trouble with Connection-check even though all Config-check has been passed, Please check your reverse proxy's configuration.", ["ob-btn-config-info"]); addResult("If you have some trouble with Connection-check even though all Config-check has been passed, Please check your reverse proxy's configuration.", ["ob-btn-config-info"]);
Logger(`Checking configuration done`, LOG_LEVEL_INFO); Logger(`Checking configuration done`, LOG_LEVEL_INFO);
} catch (ex) { } catch (ex: any) {
if (ex?.status == 401) { if (ex?.status == 401) {
addResult(`❗ Access forbidden.`); addResult(`❗ Access forbidden.`);
addResult(`We could not continue the test.`); addResult(`We could not continue the test.`);
@@ -803,7 +799,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
new Setting(containerGeneralSettingsEl) new Setting(containerGeneralSettingsEl)
.setName("Show status inside the editor") .setName("Show status inside the editor")
.setDesc("") .setDesc("Reflected after reboot")
.addToggle((toggle) => .addToggle((toggle) =>
toggle.setValue(this.plugin.settings.showStatusOnEditor).onChange(async (value) => { toggle.setValue(this.plugin.settings.showStatusOnEditor).onChange(async (value) => {
this.plugin.settings.showStatusOnEditor = value; this.plugin.settings.showStatusOnEditor = value;
@@ -822,7 +818,16 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}) })
); );
} }
new Setting(containerGeneralSettingsEl)
.setName("Show status on the status bar")
.setDesc("Reflected after reboot.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.showStatusOnStatusbar).onChange(async (value) => {
this.plugin.settings.showStatusOnStatusbar = value;
await this.plugin.saveSettings();
this.display();
})
);
containerGeneralSettingsEl.createEl("h4", { text: "Logging" }); containerGeneralSettingsEl.createEl("h4", { text: "Logging" });
new Setting(containerGeneralSettingsEl) new Setting(containerGeneralSettingsEl)
.setName("Show only notifications") .setName("Show only notifications")
@@ -1337,7 +1342,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
}); });
text.inputEl.setAttribute("type", "number"); text.inputEl.setAttribute("type", "number");
}); });
let skipPatternTextArea: TextAreaComponent = null; let skipPatternTextArea: TextAreaComponent;
const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, \\/obsidian-livesync\\/"; const defaultSkipPattern = "\\/node_modules\\/, \\/\\.git\\/, \\/obsidian-livesync\\/";
const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$ ,\\/workspace.json$,\\/workspace-mobile.json$"; const defaultSkipPatternXPlat = defaultSkipPattern + ",\\/workspace$ ,\\/workspace.json$,\\/workspace-mobile.json$";
new Setting(containerSyncSettingEl) new Setting(containerSyncSettingEl)
@@ -1747,15 +1752,8 @@ ${stringifyYaml(pluginConfig)}`;
} }
const checkBetweenStorageAndDatabase = async (file: TFile, fileOnDB: LoadedEntry) => { const checkBetweenStorageAndDatabase = async (file: TFile, fileOnDB: LoadedEntry) => {
let content: Blob; const dataContent = readAsBlob(fileOnDB);
let dataContent: Blob; const content = createBlob(await this.plugin.vaultAccess.vaultReadAuto(file))
if (fileOnDB.type == "newnote") {
dataContent = createBinaryBlob(decodeBinary(fileOnDB.data));
content = createBinaryBlob(await this.plugin.vaultAccess.vaultReadBinary(file));
} else {
dataContent = createTextBlob(fileOnDB.data);
content = createTextBlob(await this.plugin.vaultAccess.vaultRead(file));
}
if (await isDocContentSame(content, dataContent)) { if (await isDocContentSame(content, dataContent)) {
Logger(`Compare: SAME: ${file.path}`) Logger(`Compare: SAME: ${file.path}`)
} else { } else {
@@ -1831,6 +1829,7 @@ ${stringifyYaml(pluginConfig)}`;
//Prepare converted data //Prepare converted data
newDoc._id = idEncoded; newDoc._id = idEncoded;
newDoc.path = docName as FilePathWithPrefix; newDoc.path = docName as FilePathWithPrefix;
// @ts-ignore
delete newDoc._rev; delete newDoc._rev;
try { try {
const obfuscatedDoc = await this.plugin.localDatabase.getRaw(idEncoded, { revs_info: true }); const obfuscatedDoc = await this.plugin.localDatabase.getRaw(idEncoded, { revs_info: true });
@@ -1856,7 +1855,7 @@ ${stringifyYaml(pluginConfig)}`;
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE); Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
Logger(ret, LOG_LEVEL_VERBOSE); Logger(ret, LOG_LEVEL_VERBOSE);
} }
} catch (ex) { } catch (ex: any) {
if (ex?.status == 404) { if (ex?.status == 404) {
// We can perform this safely // We can perform this safely
if ((await this.plugin.localDatabase.putRaw(newDoc)).ok) { if ((await this.plugin.localDatabase.putRaw(newDoc)).ok) {

View File

@@ -1,6 +1,7 @@
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "./deps"; import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "./deps";
import { serialized } from "./lib/src/lock"; import { serialized } from "./lib/src/lock";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { isPlainText } from "./lib/src/path";
import type { FilePath } from "./lib/src/types"; import type { FilePath } from "./lib/src/types";
import { createBinaryBlob, isDocContentSame } from "./lib/src/utils"; import { createBinaryBlob, isDocContentSame } from "./lib/src/utils";
import type { InternalFileInfo } from "./types"; import type { InternalFileInfo } from "./types";
@@ -56,6 +57,12 @@ export class SerializedFileAccess {
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path)); return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
} }
async adapterReadAuto(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.adapter.read(path));
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
}
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") {
@@ -77,12 +84,19 @@ export class SerializedFileAccess {
return await processReadFile(file, () => this.app.vault.readBinary(file)); return await processReadFile(file, () => this.app.vault.readBinary(file));
} }
async vaultReadAuto(file: TFile) {
const path = file.path;
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.read(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) {
markChangesAreSame(file, file.stat.mtime, options.mtime); if (options && options.mtime) markChangesAreSame(file, file.stat.mtime, options.mtime);
return false return false
} }
await this.app.vault.modify(file, data, options) await this.app.vault.modify(file, data, options)
@@ -93,7 +107,7 @@ export class SerializedFileAccess {
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);
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) { if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
markChangesAreSame(file, file.stat.mtime, options.mtime); if (options && options.mtime) markChangesAreSame(file, file.stat.mtime, options.mtime);
return false; return false;
} }
await this.app.vault.modifyBinary(file, toArrayBuffer(data), options) await this.app.vault.modifyBinary(file, toArrayBuffer(data), options)
@@ -149,10 +163,9 @@ export class SerializedFileAccess {
c += v; c += v;
try { try {
await this.app.vault.adapter.mkdir(c); await this.app.vault.adapter.mkdir(c);
} catch (ex) { } catch (ex: any) {
// basically skip exceptions. if (ex?.message == "Folder already exists.") {
if (ex.message && ex.message == "Folder already exists.") { // Skip if already exists.
// especially this message is.
} else { } else {
Logger("Folder Create Error"); Logger("Folder Create Error");
Logger(ex); Logger(ex);

View File

@@ -1,7 +1,7 @@
import type { SerializedFileAccess } from "./SerializedFileAccess"; import type { SerializedFileAccess } from "./SerializedFileAccess";
import { Plugin, TAbstractFile, TFile, TFolder } from "./deps"; import { Plugin, TAbstractFile, TFile, TFolder } from "./deps";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { isPlainText, shouldBeIgnored } from "./lib/src/path"; import { shouldBeIgnored } from "./lib/src/path";
import type { KeyedQueueProcessor } from "./lib/src/processor"; import type { KeyedQueueProcessor } from "./lib/src/processor";
import { LOG_LEVEL_NOTICE, type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types"; import { LOG_LEVEL_NOTICE, type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types";
import { delay } from "./lib/src/utils"; import { delay } from "./lib/src/utils";
@@ -109,7 +109,8 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (file instanceof TFolder) continue; if (file instanceof TFolder) continue;
if (!await this.plugin.isTargetFile(file.path)) continue; if (!await this.plugin.isTargetFile(file.path)) continue;
let cache: null | string | ArrayBuffer; // Stop cache using to prevent the corruption;
// let cache: null | string | ArrayBuffer;
// new file or something changed, cache the changes. // new file or something changed, cache the changes.
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
// Wait for a bit while to let the writer has marked `touched` at the file. // Wait for a bit while to let the writer has marked `touched` at the file.
@@ -117,12 +118,13 @@ export class StorageEventManagerObsidian extends StorageEventManager {
if (this.plugin.vaultAccess.recentlyTouched(file)) { if (this.plugin.vaultAccess.recentlyTouched(file)) {
continue; continue;
} }
if (!isPlainText(file.name)) { // cache = await this.plugin.vaultAccess.vaultReadAuto(file);
cache = await this.plugin.vaultAccess.vaultReadBinary(file); // if (!isPlainText(file.name)) {
} else { // cache = await this.plugin.vaultAccess.vaultReadBinary(file);
cache = await this.plugin.vaultAccess.vaultCacheRead(file); // } else {
if (!cache) cache = await this.plugin.vaultAccess.vaultRead(file); // cache = await this.plugin.vaultAccess.vaultCacheRead(file);
} // if (!cache) cache = await this.plugin.vaultAccess.vaultRead(file);
// }
} }
const fileInfo = file instanceof TFile ? { const fileInfo = file instanceof TFile ? {
ctime: file.stat.ctime, ctime: file.stat.ctime,
@@ -137,7 +139,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
args: { args: {
file: fileInfo, file: fileInfo,
oldPath, oldPath,
cache, // cache,
ctx ctx
}, },
key: atomicKey key: atomicKey

View File

@@ -7,10 +7,9 @@ import PluginPane from "./PluginPane.svelte";
export class PluginDialogModal extends Modal { export class PluginDialogModal extends Modal {
plugin: ObsidianLiveSyncPlugin; plugin: ObsidianLiveSyncPlugin;
logEl: HTMLDivElement; component: PluginPane | undefined;
component: PluginPane = null;
isOpened() { isOpened() {
return this.component != null; return this.component != undefined;
} }
constructor(app: App, plugin: ObsidianLiveSyncPlugin) { constructor(app: App, plugin: ObsidianLiveSyncPlugin) {
@@ -21,7 +20,7 @@ export class PluginDialogModal extends Modal {
onOpen() { onOpen() {
const { contentEl } = this; const { contentEl } = this;
this.titleEl.setText("Customization Sync (Beta2)") this.titleEl.setText("Customization Sync (Beta2)")
if (this.component == null) { if (!this.component) {
this.component = new PluginPane({ this.component = new PluginPane({
target: contentEl, target: contentEl,
props: { plugin: this.plugin }, props: { plugin: this.plugin },
@@ -30,9 +29,9 @@ export class PluginDialogModal extends Modal {
} }
onClose() { onClose() {
if (this.component != null) { if (this.component) {
this.component.$destroy(); this.component.$destroy();
this.component = null; this.component = undefined;
} }
} }
} }
@@ -94,13 +93,13 @@ export class InputStringDialog extends Modal {
} }
export class PopoverSelectString extends FuzzySuggestModal<string> { export class PopoverSelectString extends FuzzySuggestModal<string> {
app: App; app: App;
callback: (e: string) => void = () => { }; callback: ((e: string) => void) | undefined = () => { };
getItemsFun: () => string[] = () => { getItemsFun: () => string[] = () => {
return ["yes", "no"]; return ["yes", "no"];
} }
constructor(app: App, note: string, placeholder: string | null, getItemsFun: () => string[], callback: (e: string) => void) { constructor(app: App, 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);
@@ -118,13 +117,14 @@ export class PopoverSelectString extends FuzzySuggestModal<string> {
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void { onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void {
// debugger; // debugger;
this.callback(item); this.callback?.(item);
this.callback = null; this.callback = undefined;
} }
onClose(): void { onClose(): void {
setTimeout(() => { setTimeout(() => {
if (this.callback != null) { if (this.callback) {
this.callback(""); this.callback("");
this.callback = undefined;
} }
}, 100); }, 100);
} }
@@ -136,16 +136,16 @@ export class MessageBox extends Modal {
title: string; title: string;
contentMd: string; contentMd: string;
buttons: string[]; buttons: string[];
result: string; result: string | false = false;
isManuallyClosed = false; isManuallyClosed = false;
defaultAction: string | undefined; defaultAction: string | undefined;
timeout: number | undefined; timeout: number | undefined;
timer: ReturnType<typeof setInterval> = undefined; timer: ReturnType<typeof setInterval> | undefined = undefined;
defaultButtonComponent: ButtonComponent | undefined; defaultButtonComponent: ButtonComponent | undefined;
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, onSubmit: (result: (typeof buttons)[number] | false) => void) { constructor(plugin: Plugin, title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout: number | undefined, 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;
@@ -156,6 +156,7 @@ export class MessageBox extends Modal {
this.timeout = timeout; this.timeout = timeout;
if (this.timeout) { if (this.timeout) {
this.timer = setInterval(() => { this.timer = setInterval(() => {
if (this.timeout === undefined) return;
this.timeout--; this.timeout--;
if (this.timeout < 0) { if (this.timeout < 0) {
if (this.timer) { if (this.timer) {
@@ -166,7 +167,7 @@ export class MessageBox extends Modal {
this.isManuallyClosed = true; this.isManuallyClosed = true;
this.close(); this.close();
} else { } else {
this.defaultButtonComponent.setButtonText(`( ${this.timeout} ) ${defaultAction}`); this.defaultButtonComponent?.setButtonText(`( ${this.timeout} ) ${defaultAction}`);
} }
}, 1000); }, 1000);
} }
@@ -223,7 +224,7 @@ export class MessageBox extends Modal {
} }
export function confirmWithMessage(plugin: Plugin, title: string, contentMd: string, buttons: string[], defaultAction?: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false> { export function confirmWithMessage(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, (result) => res(result)); const dialog = new MessageBox(plugin, title, contentMd, buttons, defaultAction, timeout, (result) => res(result));
dialog.open(); dialog.open();

Submodule src/lib updated: b9b70535ed...29e23f5763

View File

@@ -4,7 +4,7 @@ import { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch, stri
import { Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, type RequestUrlParam, type RequestUrlResponse, requestUrl, type MarkdownFileInfo } from "./deps"; import { Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, type RequestUrlParam, type RequestUrlResponse, requestUrl, type MarkdownFileInfo } from "./deps";
import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type diff_check_result, type diff_result_leaf, type EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, type ConfigPassphraseStore, type CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, type AnyEntry, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT, LOG_LEVEL_VERBOSE, type SavingEntry, MISSING_OR_ERROR, NOT_CONFLICTED, AUTO_MERGED, CANCELLED, LEAVE_TO_SUBSEQUENT, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3_HR, } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type diff_check_result, type diff_result_leaf, type EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, type ConfigPassphraseStore, type CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, type AnyEntry, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT, LOG_LEVEL_VERBOSE, type SavingEntry, MISSING_OR_ERROR, NOT_CONFLICTED, AUTO_MERGED, CANCELLED, LEAVE_TO_SUBSEQUENT, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3_HR, } from "./lib/src/types";
import { type InternalFileInfo, type CacheData, type FileEventItem, FileWatchEventQueueMax } from "./types"; import { type InternalFileInfo, type CacheData, type FileEventItem, FileWatchEventQueueMax } from "./types";
import { arrayToChunkedArray, createBinaryBlob, createTextBlob, fireAndForget, getDocData, isDocContentSame, isObjectDifferent, sendValue } from "./lib/src/utils"; import { arrayToChunkedArray, createBlob, fireAndForget, getDocData, isDocContentSame, isObjectDifferent, readContent, sendValue } from "./lib/src/utils";
import { Logger, setGlobalLogFunction } from "./lib/src/logger"; import { Logger, setGlobalLogFunction } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { ConflictResolveModal } from "./ConflictResolveModal"; import { ConflictResolveModal } from "./ConflictResolveModal";
@@ -211,6 +211,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin
this.last_successful_post = true; this.last_successful_post = true;
} }
Logger(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG); Logger(`HTTP:${method}${size} to:${localURL} -> ${response.status}`, LOG_LEVEL_DEBUG);
if (Math.floor(response.status / 100) !== 2) {
const r = response.clone();
Logger(`The request may have failed. The reason sent by the server: ${r.status}: ${r.statusText}`);
try {
Logger(await (await r.blob()).text(), LOG_LEVEL_VERBOSE);
} catch (_) {
Logger("Cloud not parse response", LOG_LEVEL_VERBOSE);
}
}
return response; return response;
} catch (ex) { } catch (ex) {
Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE); Logger(`HTTP:${method}${size} to:${localURL} -> failed`, LOG_LEVEL_VERBOSE);
@@ -786,8 +795,10 @@ Note: We can always able to read V1 format. It will be progressively converted.
const lsKey = "obsidian-live-sync-ver" + this.getVaultName(); const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsKey); const last_version = localStorage.getItem(lsKey);
this.observeForLogs(); this.observeForLogs();
this.statusBar = this.addStatusBarItem(); if (this.settings.showStatusOnStatusbar) {
this.statusBar.addClass("syncstatusbar"); this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
}
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000); const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) { if (lastVersion > this.settings.lastReadUpdates && this.settings.isConfigured) {
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL_NOTICE); Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL_NOTICE);
@@ -1329,12 +1340,12 @@ We can perform a command in this file.
return; return;
} }
const cache = queue.args.cache; // const cache = queue.args.cache;
if (queue.type == "CREATE" || queue.type == "CHANGED") { if (queue.type == "CREATE" || queue.type == "CHANGED") {
fireAndForget(() => this.checkAndApplySettingFromMarkdown(queue.args.file.path, true)); fireAndForget(() => this.checkAndApplySettingFromMarkdown(queue.args.file.path, true));
const keyD1 = `file-last-proc-DELETED-${file.path}`; const keyD1 = `file-last-proc-DELETED-${file.path}`;
await this.kvDB.set(keyD1, mtime); await this.kvDB.set(keyD1, mtime);
if (!await this.updateIntoDB(targetFile, cache)) { if (!await this.updateIntoDB(targetFile, undefined)) {
Logger(`STORAGE -> DB: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL_INFO); Logger(`STORAGE -> DB: failed, cancel the relative operations: ${targetFile.path}`, LOG_LEVEL_INFO);
// cancel running queues and remove one of atomic operation // cancel running queues and remove one of atomic operation
this.cancelRelativeEvent(queue); this.cancelRelativeEvent(queue);
@@ -1551,7 +1562,7 @@ We can perform a command in this file.
Logger(msg + "ERROR, invalid path: " + path, LOG_LEVEL_NOTICE); Logger(msg + "ERROR, invalid path: " + path, LOG_LEVEL_NOTICE);
return; return;
} }
const writeData = doc.datatype == "newnote" ? decodeBinary(doc.data) : getDocData(doc.data); const writeData = readContent(doc);
await this.vaultAccess.ensureDirectory(path); await this.vaultAccess.ensureDirectory(path);
try { try {
let outFile; let outFile;
@@ -1658,13 +1669,13 @@ We can perform a command in this file.
storageApplyingProcessor = new KeyedQueueProcessor(async (docs: LoadedEntry[]) => { storageApplyingProcessor = new KeyedQueueProcessor(async (docs: LoadedEntry[]) => {
const entry = docs[0]; const entry = docs[0];
const path = this.getPath(entry); const path = this.getPath(entry);
Logger(`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) change...`, LOG_LEVEL_VERBOSE); Logger(`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`, LOG_LEVEL_VERBOSE);
const targetFile = this.vaultAccess.getAbstractFileByPath(this.getPathWithoutPrefix(entry)); const targetFile = this.vaultAccess.getAbstractFileByPath(this.getPathWithoutPrefix(entry));
if (targetFile instanceof TFolder) { if (targetFile instanceof TFolder) {
Logger(`${this.getPath(entry)} is already exist as the folder`); Logger(`${this.getPath(entry)} is already exist as the folder`);
} else { } else {
await this.processEntryDoc(entry, targetFile instanceof TFile ? targetFile : undefined); await this.processEntryDoc(entry, targetFile instanceof TFile ? targetFile : undefined);
Logger(`Processing ${path} (${entry._id.substring(0, 8)}:${entry._rev?.substring(0, 5)}) `); Logger(`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Done`);
} }
return; return;
@@ -1853,22 +1864,22 @@ We can perform a command in this file.
const newLog = log; const newLog = log;
// scheduleTask("update-display", 50, () => { // scheduleTask("update-display", 50, () => {
this.statusBar?.setText(newMsg.split("\n")[0]); this.statusBar?.setText(newMsg.split("\n")[0]);
const selector = `.CodeMirror-wrap,` + // const selector = `.CodeMirror-wrap,` +
`.markdown-preview-view.cm-s-obsidian,` + // `.markdown-preview-view.cm-s-obsidian,` +
`.markdown-source-view.cm-s-obsidian,` + // `.markdown-source-view.cm-s-obsidian,` +
`.canvas-wrapper,` + // `.canvas-wrapper,` +
`.empty-state` // `.empty-state`
; // ;
if (this.settings.showStatusOnEditor) { if (this.settings.showStatusOnEditor) {
const root = activeDocument.documentElement; const root = activeDocument.documentElement;
const q = root.querySelectorAll(selector); root.style.setProperty("--sls-log-text", "'" + (newMsg + "\\A " + newLog) + "'");
q.forEach(e => e.setAttr("data-log", '' + (newMsg + "\n" + newLog) + ''))
} else { } else {
const root = activeDocument.documentElement; // const root = activeDocument.documentElement;
const q = root.querySelectorAll(selector); // root.style.setProperty("--log-text", "'" + (newMsg + "\\A " + newLog) + "'");
q.forEach(e => e.setAttr("data-log", ''))
} }
// }, true); // }, true);
scheduleTask("log-hide", 3000, () => { this.statusLog.value = "" }); scheduleTask("log-hide", 3000, () => { this.statusLog.value = "" });
} }
@@ -2113,7 +2124,7 @@ Or if you are sure know what had been happened, we can unlock the database from
new QueueProcessor( new QueueProcessor(
async (pairs) => { async (pairs) => {
const docs = await this.localDatabase.allDocsRaw<EntryDoc>({ keys: pairs.map(e => e.id), include_docs: true }); const docs = await this.localDatabase.allDocsRaw<EntryDoc>({ keys: pairs.map(e => e.id), include_docs: true });
const docsMap = docs.rows.reduce((p, c) => ({ ...p, [c.id]: c.doc }), {} as Record<DocumentID, EntryDoc>); const docsMap = Object.fromEntries(docs.rows.map(e => [e.id, e.doc]));
const syncFilesToSync = pairs.map((e) => ({ file: e.file, doc: docsMap[e.id] as LoadedEntry })); const syncFilesToSync = pairs.map((e) => ({ file: e.file, doc: docsMap[e.id] as LoadedEntry }));
return syncFilesToSync; return syncFilesToSync;
} }
@@ -2675,41 +2686,24 @@ Or if you are sure know what had been happened, we can unlock the database from
if (shouldBeIgnored(file.path)) { if (shouldBeIgnored(file.path)) {
return true; return true;
} }
let content: Blob; // let content: Blob;
let datatype: "plain" | "newnote" = "newnote"; // let datatype: "plain" | "newnote" = "newnote";
if (!cache) { const isPlain = isPlainText(file.name);
if (!isPlainText(file.name)) { const possiblyLarge = !isPlain;
Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE); // if (!cache) {
const contentBin = await this.vaultAccess.vaultReadBinary(file); if (possiblyLarge) Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE);
Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); const content = createBlob(await this.vaultAccess.vaultReadAuto(file));
try { const datatype = isPlain ? "plain" : "newnote";
content = createBinaryBlob(contentBin); // }
} catch (ex) { // else if (cache instanceof ArrayBuffer) {
Logger(`The file ${file.path} could not be encoded`); // Logger(`Cache Reading: ${file.path}`, LOG_LEVEL_VERBOSE);
Logger(ex, LOG_LEVEL_VERBOSE); // content = createBinaryBlob(cache);
return false; // datatype = "newnote"
} // } else {
datatype = "newnote"; // content = createTextBlob(cache);
} else { // datatype = "plain";
content = createTextBlob(await this.vaultAccess.vaultRead(file)); // }
datatype = "plain"; if (possiblyLarge) Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
}
} else {
if (cache instanceof ArrayBuffer) {
Logger(`Cache Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
try {
content = createBinaryBlob(cache);
} catch (ex) {
Logger(`The file ${file.path} could not be encoded`);
Logger(ex, LOG_LEVEL_VERBOSE);
return false;
}
datatype = "newnote"
} else {
content = createTextBlob(cache);
datatype = "plain";
}
}
const fullPath = getPathFromTFile(file); const fullPath = getPathFromTFile(file);
const id = await this.path2id(fullPath); const id = await this.path2id(fullPath);
const d: SavingEntry = { const d: SavingEntry = {

View File

@@ -242,14 +242,11 @@ export function mergeObject(
ret[key] = v; ret[key] = v;
} }
} }
const retSorted = Object.fromEntries(Object.entries(ret).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
if (Array.isArray(objA) && Array.isArray(objB)) { if (Array.isArray(objA) && Array.isArray(objB)) {
return Object.values(Object.entries(ret) return Object.values(retSorted);
.sort()
.reduce((p, [key, value]) => ({ ...p, [key]: value }), {}));
} }
return Object.entries(ret) return retSorted;
.sort()
.reduce((p, [key, value]) => ({ ...p, [key]: value }), {});
} }
export function flattenObject(obj: Record<string | number | symbol, any>, path: string[] = []): [string, any][] { export function flattenObject(obj: Record<string | number | symbol, any>, path: string[] = []): [string, any][] {
@@ -313,7 +310,7 @@ export function isCustomisationSyncMetadata(str: string): boolean {
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, null, null, (result) => res(result as "yes" | "no")); const popover = new PopoverSelectString(app, message, undefined, undefined, (result) => res(result as "yes" | "no"));
popover.open(); popover.open();
}); });
}; };
@@ -327,7 +324,7 @@ export const askSelectString = (app: App, message: string, items: string[]): Pro
}; };
export const askString = (app: App, title: string, key: string, placeholder: string, isPassword?: boolean): Promise<string | false> => { export const askString = (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();
@@ -400,7 +397,7 @@ export const _requestToCouchDB = async (baseUri: string, username: string, passw
}; };
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);
}; };
@@ -440,7 +437,7 @@ export function compareMTime(baseMTime: number, targetMTime: number): typeof BAS
export function markChangesAreSame(file: TFile | AnyEntry | string, mtime1: number, mtime2: number) { export function markChangesAreSame(file: TFile | AnyEntry | string, mtime1: number, mtime2: number) {
if (mtime1 === mtime2) return true; if (mtime1 === mtime2) return true;
const key = typeof file == "string" ? file : file instanceof TFile ? file.path : file.path ?? file._id; const key = typeof file == "string" ? file : file instanceof TFile ? file.path : file.path ?? file._id;
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 {
@@ -449,7 +446,7 @@ export function markChangesAreSame(file: TFile | AnyEntry | string, mtime1: numb
} }
export function isMarkedAsSameChanges(file: TFile | AnyEntry | string, mtimes: number[]) { export function isMarkedAsSameChanges(file: TFile | AnyEntry | string, mtimes: number[]) {
const key = typeof file == "string" ? file : file instanceof TFile ? file.path : file.path ?? file._id; const key = typeof file == "string" ? file : file instanceof TFile ? file.path : file.path ?? file._id;
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;
} }

View File

@@ -77,14 +77,6 @@
border-top: 1px solid var(--background-modifier-border); border-top: 1px solid var(--background-modifier-border);
} }
/* .sls-table-head{
width:50%;
}
.sls-table-tail{
width:50%;
} */
.sls-header-button { .sls-header-button {
margin-left: 2em; margin-left: 2em;
} }
@@ -94,7 +86,7 @@
} }
:root { :root {
--slsmessage: ""; --sls-log-text: "";
} }
.sls-troubleshoot-preview { .sls-troubleshoot-preview {
@@ -110,7 +102,7 @@
.markdown-source-view.cm-s-obsidian::before, .markdown-source-view.cm-s-obsidian::before,
.canvas-wrapper::before, .canvas-wrapper::before,
.empty-state::before { .empty-state::before {
content: attr(data-log); content: var(--sls-log-text, "");
text-align: right; text-align: right;
white-space: pre-wrap; white-space: pre-wrap;
position: absolute; position: absolute;