mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-13 21:02:30 +00:00
Fixed:
- Now we *surely* can set the device name and enable customised synchronisation. - Unnecessary dialogue update processes have been eliminated. - Customisation sync no longer stores half-collected files. - No longer hangs up when removing or renaming files with the `Sync on Save` toggle enabled. Improved: - Customisation sync now performs data deserialization more smoothly. - New translations have been merged.
This commit is contained in:
@@ -4,7 +4,7 @@ import { Notice, type PluginManifest, parseYaml, normalizePath, type ListedFiles
|
|||||||
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "../lib/src/common/types.ts";
|
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "../lib/src/common/types.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "../lib/src/common/types.ts";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "../lib/src/common/types.ts";
|
||||||
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../common/types.ts";
|
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "../common/types.ts";
|
||||||
import { createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocDataAsArray, isDocContentSame, throttle } from "../lib/src/common/utils.ts";
|
import { createSavingEntryFromLoadedEntry, createTextBlob, delay, fireAndForget, getDocDataAsArray, isDocContentSame } from "../lib/src/common/utils.ts";
|
||||||
import { Logger } from "../lib/src/common/logger.ts";
|
import { Logger } from "../lib/src/common/logger.ts";
|
||||||
import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../lib/src/string_and_binary/strbin.ts";
|
import { readString, decodeBinary, arrayBufferToBase64, digestHash } from "../lib/src/string_and_binary/strbin.ts";
|
||||||
import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts";
|
import { serialized, shareRunningResult } from "../lib/src/concurrency/lock.ts";
|
||||||
@@ -19,7 +19,6 @@ import type ObsidianLiveSyncPlugin from '../main.ts';
|
|||||||
|
|
||||||
const d = "\u200b";
|
const d = "\u200b";
|
||||||
const d2 = "\n";
|
const d2 = "\n";
|
||||||
const delimiters = /(?<=[\n|\u200b])/g;
|
|
||||||
|
|
||||||
|
|
||||||
function serialize(data: PluginDataEx): string {
|
function serialize(data: PluginDataEx): string {
|
||||||
@@ -42,8 +41,45 @@ function serialize(data: PluginDataEx): string {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function splitWithDelimiters(sources: string[]): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const str of sources) {
|
||||||
|
let startIndex = 0;
|
||||||
|
const maxLen = str.length;
|
||||||
|
let i = -1;
|
||||||
|
let i1;
|
||||||
|
let i2;
|
||||||
|
do {
|
||||||
|
i1 = str.indexOf(d, startIndex);
|
||||||
|
i2 = str.indexOf(d2, startIndex);
|
||||||
|
if (i1 == -1 && i2 == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i1 == -1) {
|
||||||
|
i = i2;
|
||||||
|
} else if (i2 == -1) {
|
||||||
|
i = i1;
|
||||||
|
} else {
|
||||||
|
i = i1 < i2 ? i1 : i2;
|
||||||
|
}
|
||||||
|
result.push(str.slice(startIndex, i + 1));
|
||||||
|
startIndex = i + 1;
|
||||||
|
} while (i < maxLen);
|
||||||
|
if (startIndex < maxLen) {
|
||||||
|
result.push(str.slice(startIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To keep compatibilities
|
||||||
|
if (sources[sources.length - 1] == "") {
|
||||||
|
result.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function getTokenizer(source: string[]) {
|
function getTokenizer(source: string[]) {
|
||||||
const sources = source.flatMap(e => e.split(delimiters))
|
const sources = splitWithDelimiters(source);
|
||||||
sources[0] = sources[0].substring(1);
|
sources[0] = sources[0].substring(1);
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
let lineRunOut = false;
|
let lineRunOut = false;
|
||||||
@@ -318,8 +354,7 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
createMissingConfigurationEntry = throttle(() => this._createMissingConfigurationEntry(), 1000);
|
async createMissingConfigurationEntry() {
|
||||||
_createMissingConfigurationEntry() {
|
|
||||||
let saveRequired = false;
|
let saveRequired = false;
|
||||||
for (const v of this.pluginList) {
|
for (const v of this.pluginList) {
|
||||||
const key = `${v.category}/${v.name}`;
|
const key = `${v.category}/${v.name}`;
|
||||||
@@ -337,7 +372,7 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saveRequired) {
|
if (saveRequired) {
|
||||||
this.plugin.saveSettingData();
|
await this.plugin.saveSettingData();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -365,7 +400,11 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline().root.onUpdateProgress(() => {
|
}, { suspended: false, batchSize: 1, concurrentLimit: 10, delay: 100, yieldThreshold: 10, maintainDelay: false, totalRemainingReactiveSource: pluginScanningCount }).startPipeline().root.onUpdateProgress(() => {
|
||||||
this.createMissingConfigurationEntry();
|
scheduleTask("checkMissingConfigurations", 250, async () => {
|
||||||
|
if (this.pluginScanProcessor.isIdle()) {
|
||||||
|
await this.createMissingConfigurationEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -654,7 +693,7 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
for (const target of fileTargets) {
|
for (const target of fileTargets) {
|
||||||
const data = await this.makeEntryFromFile(target);
|
const data = await this.makeEntryFromFile(target);
|
||||||
if (data == false) {
|
if (data == false) {
|
||||||
// Logger(`Config: skipped: ${target} `, LOG_LEVEL_VERBOSE);
|
Logger(`Config: skipped (Possibly is not exist): ${target} `, LOG_LEVEL_VERBOSE);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (data.version) {
|
if (data.version) {
|
||||||
@@ -703,13 +742,15 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
const oldC = await this.localDatabase.getDBEntryFromMeta(old, {}, false, false);
|
||||||
if (oldC) {
|
if (oldC) {
|
||||||
const d = await deserialize(getDocDataAsArray(oldC.data), {}) as PluginDataEx;
|
const d = await deserialize(getDocDataAsArray(oldC.data), {}) as PluginDataEx;
|
||||||
const diffs = (d.files.map(previous => ({ prev: previous, curr: dt.files.find(e => e.filename == previous.filename) })).map(async e => {
|
if (d.files.length == dt.files.length) {
|
||||||
try { return await isDocContentSame(e.curr?.data ?? [], e.prev.data) } catch (_) { return false }
|
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 }
|
||||||
const isSame = (await Promise.all(diffs)).every(e => e == true);
|
}))
|
||||||
if (isSame) {
|
const isSame = (await Promise.all(diffs)).every(e => e == true);
|
||||||
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
|
if (isSame) {
|
||||||
return true;
|
Logger(`STORAGE --> DB:${prefixedFileName}: (config) Skipped (Same content)`, LOG_LEVEL_VERBOSE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveData =
|
saveData =
|
||||||
@@ -757,9 +798,11 @@ export class ConfigSync extends LiveSyncCommands {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
|
this.recentProcessedInternalFiles = [key, ...this.recentProcessedInternalFiles].slice(0, 100);
|
||||||
|
// To prevent saving half-collected file sets.
|
||||||
this.storeCustomizationFiles(path).then(() => {/* Fire and forget */ });
|
const keySchedule = this.filenameToUnifiedKey(path);
|
||||||
|
scheduleTask(keySchedule, 100, async () => {
|
||||||
|
await this.storeCustomizationFiles(path);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule src/lib updated: 302a2e7c0b...9825b64d17
+15
-12
@@ -878,6 +878,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
|
|||||||
async onload() {
|
async onload() {
|
||||||
logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline();
|
logStore.pipeTo(new QueueProcessor(logs => logs.forEach(e => this.addLog(e.message, e.level, e.key)), { suspended: false, batchSize: 20, concurrentLimit: 1, delay: 0 })).startPipeline();
|
||||||
Logger("loading plugin");
|
Logger("loading plugin");
|
||||||
|
__onMissingTranslation(() => { });
|
||||||
// eslint-disable-next-line no-unused-labels
|
// eslint-disable-next-line no-unused-labels
|
||||||
DEV: {
|
DEV: {
|
||||||
__onMissingTranslation((key) => {
|
__onMissingTranslation((key) => {
|
||||||
@@ -1137,12 +1138,14 @@ Note: We can always able to read V1 format. It will be progressively converted.
|
|||||||
this.settingTab.requestReload()
|
this.settingTab.requestReload()
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSettingData() {
|
saveDeviceAndVaultName() {
|
||||||
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
|
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.getVaultName();
|
||||||
|
|
||||||
localStorage.setItem(lsKey, this.deviceAndVaultName || "");
|
localStorage.setItem(lsKey, this.deviceAndVaultName || "");
|
||||||
|
}
|
||||||
|
async saveSettingData() {
|
||||||
|
this.saveDeviceAndVaultName();
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
|
settings.deviceAndVaultName = "";
|
||||||
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
|
if (this.usedPassphrase == "" && !await this.getPassphrase(settings)) {
|
||||||
Logger("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL_NOTICE);
|
Logger("Could not determine passphrase for saving data.json! Our data.json have insecure items!", LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
@@ -3060,28 +3063,28 @@ Or if you are sure know what had been happened, we can unlock the database from
|
|||||||
const ret = await this.localDatabase.putDBEntry(d);
|
const ret = await this.localDatabase.putDBEntry(d);
|
||||||
if (ret !== false) {
|
if (ret !== false) {
|
||||||
Logger(msg + fullPath);
|
Logger(msg + fullPath);
|
||||||
if (this.settings.syncOnSave && !this.suspended) {
|
this.scheduleReplicateIfSyncOnSave();
|
||||||
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return ret != false;
|
return ret != false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduleReplicateIfSyncOnSave() {
|
||||||
|
if (this.settings.syncOnSave && !this.suspended) {
|
||||||
|
scheduleTask("perform-replicate-after-save", 250, () => this.replicate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async deleteFromDB(file: TFile) {
|
async deleteFromDB(file: TFile) {
|
||||||
if (!await this.isTargetFile(file)) return;
|
if (!await this.isTargetFile(file)) return;
|
||||||
const fullPath = getPathFromTFile(file);
|
const fullPath = getPathFromTFile(file);
|
||||||
Logger(`deleteDB By path:${fullPath}`);
|
Logger(`deleteDB By path:${fullPath}`);
|
||||||
await this.deleteFromDBbyPath(fullPath);
|
await this.deleteFromDBbyPath(fullPath);
|
||||||
if (this.settings.syncOnSave && !this.suspended) {
|
this.scheduleReplicateIfSyncOnSave();
|
||||||
await this.replicate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteFromDBbyPath(fullPath: FilePath) {
|
async deleteFromDBbyPath(fullPath: FilePath) {
|
||||||
await this.localDatabase.deleteDBEntry(fullPath);
|
await this.localDatabase.deleteDBEntry(fullPath);
|
||||||
if (this.settings.syncOnSave && !this.suspended) {
|
this.scheduleReplicateIfSyncOnSave();
|
||||||
await this.replicate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetLocalDatabase() {
|
async resetLocalDatabase() {
|
||||||
|
|||||||
@@ -439,6 +439,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
if (key == "deviceAndVaultName") {
|
if (key == "deviceAndVaultName") {
|
||||||
this.plugin.deviceAndVaultName = this.editingSettings?.[key];
|
this.plugin.deviceAndVaultName = this.editingSettings?.[key];
|
||||||
|
this.plugin.saveDeviceAndVaultName();
|
||||||
return await Promise.resolve();
|
return await Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,12 +520,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
/**
|
/**
|
||||||
* Reread all settings and request invalidate
|
* Reread all settings and request invalidate
|
||||||
*/
|
*/
|
||||||
reloadAllSettings() {
|
reloadAllSettings(skipUpdate: boolean = false) {
|
||||||
const localSetting = this.reloadAllLocalSettings();
|
const localSetting = this.reloadAllLocalSettings();
|
||||||
this._editingSettings = { ...this.plugin.settings, ...localSetting };
|
this._editingSettings = { ...this.plugin.settings, ...localSetting };
|
||||||
this._editingSettings = { ...this.editingSettings, ...this.computeAllLocalSettings() };
|
this._editingSettings = { ...this.editingSettings, ...this.computeAllLocalSettings() };
|
||||||
this.initialSettings = { ...this.editingSettings, };
|
this.initialSettings = { ...this.editingSettings, };
|
||||||
this.requestUpdate();
|
if (!skipUpdate) this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -667,9 +668,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger(`reread: all! hidden`, LOG_LEVEL_VERBOSE)
|
this.reloadAllSettings(true);
|
||||||
this.reloadAllSettings();
|
|
||||||
this.display();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user