Compare commits

..

3 Commits

Author SHA1 Message Date
vorotamoroz
cbcfdc453e update default vault and bump for release 2025-02-27 13:39:28 +00:00
vorotamoroz
a4eb21593c bump 2025-02-27 13:24:51 +00:00
vorotamoroz
05eb2c8262 ## 0.24.16
### Improved

#### Peer-to-Peer

- Now peer-to-peer synchronisation checks the settings are compatible with each other.
- Peer-to-peer synchronisation now handles the platform and detects pseudo-clients.

#### General

- New migration method has been implemented, that called `Doctor`.

- The minimum interval for replication to be caused when an event occurs can now be configurable.
- Some detail note has been added and change nuance about the `Report` in the setting dialogue, which had less informative.

### Behaviour and default changed

- `Compute revisions for chunks` are backed into enabled again. it is necessary for garbage collection of chunks.

### Refactored

- Platform specific codes are more separated. No longer `node` modules were used in the browser and Obsidian.
2025-02-27 13:23:11 +00:00
18 changed files with 496 additions and 218 deletions

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.24.15",
"version": "0.24.17",
"minAppVersion": "0.9.12",
"description": "Community implementation of self-hosted livesync. Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"author": "vorotamoroz",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.24.15",
"version": "0.24.17",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.24.15",
"version": "0.24.17",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.645.0",

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.24.15",
"version": "0.24.17",
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
"main": "main.js",
"type": "module",

View File

@@ -18,6 +18,8 @@ export const EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG = "request-open-plugin-sync-d
export const EVENT_REQUEST_OPEN_P2P = "request-open-p2p";
export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p";
export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor";
// export const EVENT_FILE_CHANGED = "file-changed";
declare global {
@@ -33,6 +35,7 @@ declare global {
[EVENT_REQUEST_OPEN_P2P]: undefined;
[EVENT_REQUEST_OPEN_SETUP_URI]: undefined;
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
[EVENT_REQUEST_RUN_DOCTOR]: string;
}
}

View File

@@ -30,6 +30,7 @@ import { sameChangePairs } from "./stores.ts";
import type { KeyValueDatabase } from "./KeyValueDB.ts";
import { scheduleTask } from "octagonal-wheels/concurrency/task";
import { EVENT_PLUGIN_UNLOADED, eventHub } from "./events.ts";
import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels/promises";
export { scheduleTask, cancelTask, cancelAllTasks } from "../lib/src/concurrency/task.ts";
@@ -493,3 +494,47 @@ export function onlyInNTimes(n: number, proc: (progress: number) => any) {
}
};
}
const waitingTasks = {} as Record<string, { task?: PromiseWithResolvers<any>; previous: number; leastNext: number }>;
export function rateLimitedSharedExecution<T>(key: string, interval: number, proc: () => Promise<T>): Promise<T> {
if (!(key in waitingTasks)) {
waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 };
}
if (waitingTasks[key].task) {
// Extend the previous execution time.
waitingTasks[key].leastNext = Date.now() + interval;
return waitingTasks[key].task.promise;
}
const previous = waitingTasks[key].previous;
const delay = previous == 0 ? 0 : Math.max(interval - (Date.now() - previous), 0);
const task = promiseWithResolver<T>();
void task.promise.finally(() => {
if (waitingTasks[key].task === task) {
waitingTasks[key].task = undefined;
waitingTasks[key].previous = Math.max(Date.now(), waitingTasks[key].leastNext);
}
});
waitingTasks[key] = {
task,
previous: Date.now(),
leastNext: Date.now() + interval,
};
void scheduleTask("thin-out-" + key, delay, async () => {
try {
task.resolve(await proc());
} catch (ex) {
task.reject(ex);
}
});
return task.promise;
}
export function updatePreviousExecutionTime(key: string, timeDelta: number = 0) {
if (!(key in waitingTasks)) {
waitingTasks[key] = { task: undefined, previous: 0, leastNext: 0 };
}
waitingTasks[key].leastNext = Math.max(Date.now() + timeDelta, waitingTasks[key].leastNext);
}

View File

@@ -23,6 +23,7 @@ import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
import type ObsidianLiveSyncPlugin from "../../main.ts";
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase {
storeP2PStatusLine = reactiveSource("");
@@ -79,6 +80,9 @@ export class P2PReplicator
}
return undefined!;
}
override getPlatform(): string {
return getPlatformName();
}
override onunload(): void {
removeP2PReplicatorInstance();

View File

@@ -299,6 +299,8 @@
placeholder="anything-you-like"
bind:value={eRoomId}
autocomplete="off"
spellcheck="false"
autocorrect="off"
/>
<button onclick={() => chooseRandom()}> Use Random Number </button>
</label>
@@ -327,6 +329,12 @@
<label class={{ "is-dirty": isDeviceNameModified }}>
<input type="text" placeholder="iphone-16" bind:value={eDeviceName} autocomplete="off" />
</label>
<span>
<small>
Device name to identify the device. Please use shorter one for the stable peer
detection, i.e., "iphone-16" or "macbook-2021".
</small>
</span>
</td>
</tr>
<tr>

Submodule src/lib updated: 2a0dd3c3ac...9888ee8859

View File

@@ -570,6 +570,9 @@ export default class ObsidianLiveSyncPlugin
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
throwShouldBeOverridden();
}
$$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
throwShouldBeOverridden();
}
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
throwShouldBeOverridden();
@@ -636,10 +639,6 @@ export default class ObsidianLiveSyncPlugin
throwShouldBeOverridden();
}
$$waitForReplicationOnce(): Promise<boolean | void> {
throwShouldBeOverridden();
}
$$resetLocalDatabase(): Promise<void> {
throwShouldBeOverridden();
}

View File

@@ -18,17 +18,26 @@ import {
type MetaEntry,
} from "../../lib/src/common/types";
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
import { getPath, isChunk, isValidPath, scheduleTask } from "../../common/utils";
import {
getPath,
isChunk,
isValidPath,
rateLimitedSharedExecution,
scheduleTask,
updatePreviousExecutionTime,
} from "../../common/utils";
import { isAnyNote } from "../../lib/src/common/utils";
import { EVENT_FILE_SAVED, eventHub } from "../../common/events";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import { globalSlipBoard } from "../../lib/src/bureau/bureau";
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
export class ModuleReplicator extends AbstractModule implements ICoreModule {
$everyOnloadAfterLoadSettings(): Promise<boolean> {
eventHub.onEvent(EVENT_FILE_SAVED, () => {
if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$waitForReplicationOnce());
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent());
}
});
return Promise.resolve(true);
@@ -61,7 +70,16 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
await this.loadQueuedFiles();
return true;
}
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
try {
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
return await this.$$_replicate(showMessage);
} finally {
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT);
}
}
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
//--?
if (!this.core.$$isReady()) return;
if (isLockAcquired("cleanup")) {
@@ -192,6 +210,15 @@ Or if you are sure know what had been happened, we can unlock the database from
return ret;
}
async $$replicateByEvent(): Promise<boolean | void> {
const least = this.settings.syncMinimumInterval;
if (least > 0) {
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
return await this.$$replicate();
});
}
return await shareRunningResult(`replication`, () => this.core.$$replicate());
}
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
this.replicationResultProcessor.suspend();
@@ -416,8 +443,4 @@ Or if you are sure know what had been happened, we can unlock the database from
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllFromServer(showingNotice);
return !checkResult;
}
async $$waitForReplicationOnce(): Promise<boolean | void> {
return await shareRunningResult(`replication`, () => this.core.$$replicate());
}
}

View File

@@ -140,7 +140,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
//auto resolved, but need check again;
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
//Wait for the running replication, if not running replication, run it once.
await this.core.$$waitForReplicationOnce();
await this.core.$$replicateByEvent();
}
this._log("[conflict] Automatically merged, but we have to check it again");
await this.core.$$queueConflictCheck(filename);

View File

@@ -1,17 +1,141 @@
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger.js";
import { SETTING_VERSION_SUPPORT_CASE_INSENSITIVE } from "../../lib/src/common/types.js";
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger.js";
import { type ObsidianLiveSyncSettings } from "../../lib/src/common/types.js";
import {
EVENT_REQUEST_OPEN_P2P,
EVENT_REQUEST_OPEN_SETTING_WIZARD,
EVENT_REQUEST_OPEN_SETTINGS,
EVENT_REQUEST_OPEN_SETUP_URI,
EVENT_REQUEST_RUN_DOCTOR,
eventHub,
} from "../../common/events.ts";
import { AbstractModule } from "../AbstractModule.ts";
import type { ICoreModule } from "../ModuleTypes.ts";
import { $msg } from "src/lib/src/common/i18n.ts";
import { checkUnsuitableValues, RuleLevel, type RuleForType } from "../../lib/src/common/configForDoc.ts";
import { getConfName, type AllSettingItemKey } from "../features/SettingDialogue/settingConstants.ts";
export class ModuleMigration extends AbstractModule implements ICoreModule {
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
const r = checkUnsuitableValues(this.core.settings);
if (!forceRescan && r.version == this.settings.doctorProcessedVersion) {
const isIssueFound = Object.keys(r.rules).length > 0;
const msg = isIssueFound ? "Issues found" : "No issues found";
this._log(`${msg} but marked as to be silent`, LOG_LEVEL_VERBOSE);
return;
}
const issues = Object.entries(r.rules);
if (issues.length == 0) {
this._log($msg("Doctor.Message.NoIssues"), LOG_LEVEL_NOTICE);
return;
} else {
const OPT_YES = `${$msg("Doctor.Button.Yes")}` as const;
const OPT_NO = `${$msg("Doctor.Button.No")}` as const;
const OPT_DISMISS = `${$msg("Doctor.Button.DismissThisVersion")}` as const;
// this._log(`Issues found in ${key}`, LOG_LEVEL_VERBOSE);
const issues = Object.keys(r.rules)
.map((key) => `- ${getConfName(key as AllSettingItemKey)}`)
.join("\n");
const msg = await this.core.confirm.askSelectStringDialogue(
$msg("Doctor.Dialogue.Main", { activateReason, issues }),
[OPT_YES, OPT_NO, OPT_DISMISS],
{
title: $msg("Doctor.Dialogue.Title"),
defaultAction: OPT_YES,
}
);
if (msg == OPT_DISMISS) {
this.settings.doctorProcessedVersion = r.version;
await this.core.saveSettings();
this._log("Marked as to be silent", LOG_LEVEL_VERBOSE);
return;
}
if (msg != OPT_YES) return;
let shouldRebuild = false;
let shouldRebuildLocal = false;
const issueItems = Object.entries(r.rules) as [keyof ObsidianLiveSyncSettings, RuleForType<any>][];
this._log(`${issueItems.length} Issue(s) found `, LOG_LEVEL_VERBOSE);
let idx = 0;
const applySettings = {} as Partial<ObsidianLiveSyncSettings>;
const OPT_FIX = `${$msg("Doctor.Button.Fix")}` as const;
const OPT_SKIP = `${$msg("Doctor.Button.Skip")}` as const;
const OPT_FIXBUTNOREBUILD = `${$msg("Doctor.Button.FixButNoRebuild")}` as const;
let skipped = 0;
for (const [key, value] of issueItems) {
const levelMap = {
[RuleLevel.Necessary]: $msg("Doctor.Level.Necessary"),
[RuleLevel.Recommended]: $msg("Doctor.Level.Recommended"),
[RuleLevel.Optional]: $msg("Doctor.Level.Optional"),
[RuleLevel.Must]: $msg("Doctor.Level.Must"),
};
const level = value.level ? levelMap[value.level] : "Unknown";
const options = [OPT_FIX];
if ((!skipRebuild && value.requireRebuild) || value.requireRebuildLocal) {
options.push(OPT_FIXBUTNOREBUILD);
}
options.push(OPT_SKIP);
const note = skipRebuild
? ""
: `${value.requireRebuild ? $msg("Doctor.Message.RebuildRequired") : ""}${value.requireRebuildLocal ? $msg("Doctor.Message.RebuildLocalRequired") : ""}`;
const ret = await this.core.confirm.askSelectStringDialogue(
$msg("Doctor.Dialogue.MainFix", {
name: getConfName(key as AllSettingItemKey),
current: `${this.settings[key]}`,
reason: value.reason ?? " N/A ",
ideal: `${value.value}`,
level: `${level}`,
note: note,
}),
options,
{
title: $msg("Doctor.Dialogue.TitleFix", { current: `${++idx}`, total: `${issueItems.length}` }),
defaultAction: OPT_FIX,
}
);
if (ret == OPT_FIX || ret == OPT_FIXBUTNOREBUILD) {
//@ts-ignore
applySettings[key] = value.value;
if (ret == OPT_FIX) {
shouldRebuild = shouldRebuild || value.requireRebuild || false;
shouldRebuildLocal = shouldRebuildLocal || value.requireRebuildLocal || false;
}
} else {
skipped++;
}
}
if (Object.keys(applySettings).length > 0) {
this.settings = {
...this.settings,
...applySettings,
};
}
if (skipped == 0) {
this.settings.doctorProcessedVersion = r.version;
} else {
if (
(await this.core.confirm.askYesNoDialog($msg("Doctor.Message.SomeSkipped"), {
title: $msg("Doctor.Dialogue.TitleAlmostDone"),
defaultOption: "No",
})) == "no"
) {
// Some skipped, and user wants
this.settings.doctorProcessedVersion = r.version;
}
}
await this.core.saveSettings();
if (!skipRebuild) {
if (shouldRebuild) {
await this.core.rebuilder.scheduleRebuild();
await this.core.$$performRestart();
} else if (shouldRebuildLocal) {
await this.core.rebuilder.scheduleFetch();
await this.core.$$performRestart();
}
}
}
}
async migrateDisableBulkSend() {
if (this.settings.sendChunksBulk) {
this._log($msg("moduleMigration.logBulkSendCorrupted"), LOG_LEVEL_NOTICE);
@@ -20,157 +144,157 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
await this.saveSettings();
}
}
async migrationCheck() {
const old = this.settings.settingVersion;
const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// Check each migrations(old -> current)
if (!(await this.migrateToCaseInsensitive(old, current))) {
this._log(
$msg("moduleMigration.logMigrationFailed", {
old: old.toString(),
current: current.toString(),
}),
LOG_LEVEL_NOTICE
);
return;
}
}
async migrateToCaseInsensitive(old: number, current: number) {
if (
this.settings.handleFilenameCaseSensitive !== undefined &&
this.settings.doNotUseFixedRevisionForChunks !== undefined
) {
if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) {
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
await this.saveSettings();
}
return true;
}
if (
old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE &&
this.settings.handleFilenameCaseSensitive !== undefined &&
this.settings.doNotUseFixedRevisionForChunks !== undefined
) {
return true;
}
// async migrationCheck() {
// const old = this.settings.settingVersion;
// const current = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// // Check each migrations(old -> current)
// if (!(await this.migrateToCaseInsensitive(old, current))) {
// this._log(
// $msg("moduleMigration.logMigrationFailed", {
// old: old.toString(),
// current: current.toString(),
// }),
// LOG_LEVEL_NOTICE
// );
// return;
// }
// }
// async migrateToCaseInsensitive(old: number, current: number) {
// if (
// this.settings.handleFilenameCaseSensitive !== undefined &&
// this.settings.doNotUseFixedRevisionForChunks !== undefined
// ) {
// if (current < SETTING_VERSION_SUPPORT_CASE_INSENSITIVE) {
// this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// await this.saveSettings();
// }
// return true;
// }
// if (
// old >= SETTING_VERSION_SUPPORT_CASE_INSENSITIVE &&
// this.settings.handleFilenameCaseSensitive !== undefined &&
// this.settings.doNotUseFixedRevisionForChunks !== undefined
// ) {
// return true;
// }
let remoteHandleFilenameCaseSensitive: undefined | boolean = undefined;
let remoteDoNotUseFixedRevisionForChunks: undefined | boolean = undefined;
let remoteChecked = false;
try {
const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings);
if (remoteInfo) {
remoteHandleFilenameCaseSensitive =
"handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false;
remoteDoNotUseFixedRevisionForChunks =
"doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false;
if (
remoteHandleFilenameCaseSensitive !== undefined ||
remoteDoNotUseFixedRevisionForChunks !== undefined
) {
remoteChecked = true;
}
} else {
this._log($msg("moduleMigration.logFetchRemoteTweakFailed"), LOG_LEVEL_INFO);
}
} catch (ex) {
this._log($msg("moduleMigration.logRemoteTweakUnavailable"), LOG_LEVEL_INFO);
this._log(ex, LOG_LEVEL_VERBOSE);
}
// let remoteHandleFilenameCaseSensitive: undefined | boolean = undefined;
// let remoteDoNotUseFixedRevisionForChunks: undefined | boolean = undefined;
// let remoteChecked = false;
// try {
// const remoteInfo = await this.core.replicator.getRemotePreferredTweakValues(this.settings);
// if (remoteInfo) {
// remoteHandleFilenameCaseSensitive =
// "handleFilenameCaseSensitive" in remoteInfo ? remoteInfo.handleFilenameCaseSensitive : false;
// remoteDoNotUseFixedRevisionForChunks =
// "doNotUseFixedRevisionForChunks" in remoteInfo ? remoteInfo.doNotUseFixedRevisionForChunks : false;
// if (
// remoteHandleFilenameCaseSensitive !== undefined ||
// remoteDoNotUseFixedRevisionForChunks !== undefined
// ) {
// remoteChecked = true;
// }
// } else {
// this._log($msg("moduleMigration.logFetchRemoteTweakFailed"), LOG_LEVEL_INFO);
// }
// } catch (ex) {
// this._log($msg("moduleMigration.logRemoteTweakUnavailable"), LOG_LEVEL_INFO);
// this._log(ex, LOG_LEVEL_VERBOSE);
// }
if (remoteChecked) {
// The case that the remote could be checked.
if (remoteHandleFilenameCaseSensitive && remoteDoNotUseFixedRevisionForChunks) {
// Migrated, but configured as same as old behaviour.
this.settings.handleFilenameCaseSensitive = true;
this.settings.doNotUseFixedRevisionForChunks = true;
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
this._log(
$msg("moduleMigration.logMigratedSameBehaviour", {
current: current.toString(),
}),
LOG_LEVEL_INFO
);
await this.saveSettings();
return true;
}
const message = $msg("moduleMigration.msgFetchRemoteAgain");
const OPTION_FETCH = $msg("moduleMigration.optionYesFetchAgain");
const DISMISS = $msg("moduleMigration.optionNoAskAgain");
const options = [OPTION_FETCH, DISMISS];
const ret = await this.core.confirm.confirmWithMessage(
$msg("moduleMigration.titleCaseSensitivity"),
message,
options,
DISMISS,
40
);
if (ret == OPTION_FETCH) {
this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false;
this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false;
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
await this.saveSettings();
try {
await this.core.rebuilder.scheduleFetch();
return;
} catch (ex) {
this._log($msg("moduleMigration.logRedflag2CreationFail"), LOG_LEVEL_VERBOSE);
this._log(ex, LOG_LEVEL_VERBOSE);
}
return false;
} else {
return false;
}
}
// if (remoteChecked) {
// // The case that the remote could be checked.
// if (remoteHandleFilenameCaseSensitive && remoteDoNotUseFixedRevisionForChunks) {
// // Migrated, but configured as same as old behaviour.
// this.settings.handleFilenameCaseSensitive = true;
// this.settings.doNotUseFixedRevisionForChunks = true;
// this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// this._log(
// $msg("moduleMigration.logMigratedSameBehaviour", {
// current: current.toString(),
// }),
// LOG_LEVEL_INFO
// );
// await this.saveSettings();
// return true;
// }
// const message = $msg("moduleMigration.msgFetchRemoteAgain");
// const OPTION_FETCH = $msg("moduleMigration.optionYesFetchAgain");
// const DISMISS = $msg("moduleMigration.optionNoAskAgain");
// const options = [OPTION_FETCH, DISMISS];
// const ret = await this.core.confirm.confirmWithMessage(
// $msg("moduleMigration.titleCaseSensitivity"),
// message,
// options,
// DISMISS,
// 40
// );
// if (ret == OPTION_FETCH) {
// this.settings.handleFilenameCaseSensitive = remoteHandleFilenameCaseSensitive || false;
// this.settings.doNotUseFixedRevisionForChunks = remoteDoNotUseFixedRevisionForChunks || false;
// this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// await this.saveSettings();
// try {
// await this.core.rebuilder.scheduleFetch();
// return;
// } catch (ex) {
// this._log($msg("moduleMigration.logRedflag2CreationFail"), LOG_LEVEL_VERBOSE);
// this._log(ex, LOG_LEVEL_VERBOSE);
// }
// return false;
// } else {
// return false;
// }
// }
const ENABLE_BOTH = $msg("moduleMigration.optionEnableBoth");
const ENABLE_FILENAME_CASE_INSENSITIVE = $msg("moduleMigration.optionEnableFilenameCaseInsensitive");
const ENABLE_FIXED_REVISION_FOR_CHUNKS = $msg("moduleMigration.optionEnableFixedRevisionForChunks");
const ADJUST_TO_REMOTE = $msg("moduleMigration.optionAdjustRemote");
const KEEP = $msg("moduleMigration.optionKeepPreviousBehaviour");
const DISMISS = $msg("moduleMigration.optionDecideLater");
const message = $msg("moduleMigration.msgSinceV02321");
const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS];
if (remoteChecked) {
options.push(ADJUST_TO_REMOTE);
}
options.push(KEEP, DISMISS);
const ret = await this.core.confirm.confirmWithMessage(
$msg("moduleMigration.titleCaseSensitivity"),
message,
options,
DISMISS,
40
);
console.dir(ret);
switch (ret) {
case ENABLE_BOTH:
this.settings.handleFilenameCaseSensitive = false;
this.settings.doNotUseFixedRevisionForChunks = false;
break;
case ENABLE_FILENAME_CASE_INSENSITIVE:
this.settings.handleFilenameCaseSensitive = false;
this.settings.doNotUseFixedRevisionForChunks = true;
break;
case ENABLE_FIXED_REVISION_FOR_CHUNKS:
this.settings.doNotUseFixedRevisionForChunks = false;
this.settings.handleFilenameCaseSensitive = true;
break;
case KEEP:
this.settings.handleFilenameCaseSensitive = true;
this.settings.doNotUseFixedRevisionForChunks = true;
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
await this.saveSettings();
return true;
case DISMISS:
default:
return false;
}
this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
await this.saveSettings();
await this.core.rebuilder.scheduleRebuild();
await this.core.$$performRestart();
}
// const ENABLE_BOTH = $msg("moduleMigration.optionEnableBoth");
// const ENABLE_FILENAME_CASE_INSENSITIVE = $msg("moduleMigration.optionEnableFilenameCaseInsensitive");
// const ENABLE_FIXED_REVISION_FOR_CHUNKS = $msg("moduleMigration.optionEnableFixedRevisionForChunks");
// const ADJUST_TO_REMOTE = $msg("moduleMigration.optionAdjustRemote");
// const KEEP = $msg("moduleMigration.optionKeepPreviousBehaviour");
// const DISMISS = $msg("moduleMigration.optionDecideLater");
// const message = $msg("moduleMigration.msgSinceV02321");
// const options = [ENABLE_BOTH, ENABLE_FILENAME_CASE_INSENSITIVE, ENABLE_FIXED_REVISION_FOR_CHUNKS];
// if (remoteChecked) {
// options.push(ADJUST_TO_REMOTE);
// }
// options.push(KEEP, DISMISS);
// const ret = await this.core.confirm.confirmWithMessage(
// $msg("moduleMigration.titleCaseSensitivity"),
// message,
// options,
// DISMISS,
// 40
// );
// console.dir(ret);
// switch (ret) {
// case ENABLE_BOTH:
// this.settings.handleFilenameCaseSensitive = false;
// this.settings.doNotUseFixedRevisionForChunks = false;
// break;
// case ENABLE_FILENAME_CASE_INSENSITIVE:
// this.settings.handleFilenameCaseSensitive = false;
// this.settings.doNotUseFixedRevisionForChunks = true;
// break;
// case ENABLE_FIXED_REVISION_FOR_CHUNKS:
// this.settings.doNotUseFixedRevisionForChunks = false;
// this.settings.handleFilenameCaseSensitive = true;
// break;
// case KEEP:
// this.settings.handleFilenameCaseSensitive = true;
// this.settings.doNotUseFixedRevisionForChunks = true;
// this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// await this.saveSettings();
// return true;
// case DISMISS:
// default:
// return false;
// }
// this.settings.settingVersion = SETTING_VERSION_SUPPORT_CASE_INSENSITIVE;
// await this.saveSettings();
// await this.core.rebuilder.scheduleRebuild();
// await this.core.$$performRestart();
// }
async initialMessage() {
const message = $msg("moduleMigration.msgInitialSetup", {
@@ -226,7 +350,8 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
return false;
}
if (this.settings.isConfigured) {
await this.migrationCheck();
await this.migrateUsingDoctor(false);
// await this.migrationCheck();
await this.migrateDisableBulkSend();
}
if (!this.settings.isConfigured) {
@@ -235,7 +360,14 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
this._log($msg("moduleMigration.logSetupCancelled"), LOG_LEVEL_NOTICE);
return false;
}
await this.migrateUsingDoctor(true);
}
return true;
}
$everyOnLayoutReady(): Promise<boolean> {
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
await this.migrateUsingDoctor(false, reason, true);
});
return Promise.resolve(true);
}
}

View File

@@ -56,7 +56,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
} else {
if (this.settings.syncOnEditorSave) {
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
fireAndForget(() => this.core.$$replicate());
fireAndForget(() => this.core.$$replicateByEvent());
}
}
});
@@ -155,7 +155,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
return;
}
if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) {
await this.core.$$replicate();
await this.core.$$replicateByEvent();
}
await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
}

View File

@@ -90,7 +90,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
// So we have to run replication if configured.
// TODO: Make this is as a event request
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
await this.core.$$waitForReplicationOnce();
await this.core.$$replicateByEvent();
}
// And, check it again.
await this.core.$$queueConflictCheck(filename);

View File

@@ -71,6 +71,7 @@ import {
EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG,
EVENT_REQUEST_OPEN_SETUP_URI,
EVENT_REQUEST_RELOAD_SETTING_TAB,
EVENT_REQUEST_RUN_DOCTOR,
eventHub,
} from "../../../common/events.ts";
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
@@ -1890,6 +1891,9 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
})
.setClass("wizardHidden");
new Setting(paneEl).autoWireNumeric("syncMinimumInterval", {
onUpdate: onlyOnNonLiveSync,
});
new Setting(paneEl)
.setClass("wizardHidden")
.autoWireToggle("syncOnSave", { onUpdate: onlyOnNonLiveSync });
@@ -2226,10 +2230,23 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
void addPane(containerEl, "Hatch", "🧰", 50, true).then((paneEl) => {
// const hatchWarn = this.createEl(paneEl, "div", { text: `To stop the boot up sequence for fixing problems on databases, you can put redflag.md on top of your vault (Rebooting obsidian is required).` });
// hatchWarn.addClass("op-warn-info");
void addPanel(paneEl, "Reporting Issue").then((paneEl) => {
new Setting(paneEl).setName("Make report to inform the issue").addButton((button) =>
void addPanel(paneEl, $msg("Setting.TroubleShooting")).then((paneEl) => {
new Setting(paneEl)
.setName($msg("Setting.TroubleShooting.Doctor"))
.setDesc($msg("Setting.TroubleShooting.Doctor.Desc"))
.addButton((button) =>
button
.setButtonText("Run Doctor")
.setCta()
.setDisabled(false)
.onClick(() => {
this.closeSetting();
eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, "you wanted(Thank you)!");
})
);
new Setting(paneEl).setName("Prepare the 'report' to create an issue").addButton((button) =>
button
.setButtonText("Make report")
.setButtonText("Copy Report to clipboard")
.setCta()
.setDisabled(false)
.onClick(async () => {
@@ -2310,7 +2327,10 @@ version:${manifestVersion}
${stringifyYaml(pluginConfig)}`;
console.log(msgConfig);
await navigator.clipboard.writeText(msgConfig);
Logger(`Information has been copied to clipboard`, LOG_LEVEL_NOTICE);
Logger(
`Generated report has been copied to clipboard. Please report the issue with this! Thank you for your cooperation!`,
LOG_LEVEL_NOTICE
);
})
);
new Setting(paneEl).autoWireToggle("writeLogToTheFile");

View File

@@ -346,8 +346,8 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
desc: "If this enabled, All files are handled as case-Sensitive (Previous behaviour).",
},
doNotUseFixedRevisionForChunks: {
name: "Compute revisions for chunks (Previous behaviour)",
desc: "If this enabled, all chunks will be stored with the revision made from its content. (Previous behaviour)",
name: "Compute revisions for chunks",
desc: "If this enabled, all chunks will be stored with the revision made from its content.",
},
sendChunksBulkMaxSize: {
name: "Maximum size of chunks to send in one request",
@@ -373,6 +373,10 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
name: "Suppress notification of hidden files change",
desc: "If enabled, the notification of hidden files change will be suppressed.",
},
syncMinimumInterval: {
name: "Minimum interval for syncing",
desc: "The minimum interval for automatic synchronisation on event.",
},
};
function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
if (!infoSrc) return false;

View File

@@ -10,6 +10,46 @@ Nevertheless, that being said, to be more honest, I still have not decided what
Note: Already you have noticed this, but let me mention it again, this is a significantly large update. If you have noticed anything, please let me know. I will try to fix it as soon as possible (Some address is on my [profile](https://github.com/vrtmrz)).
## 0.24.17
Confession. I got the default values wrong. So scary and sorry.
### Behaviour and default changed
- **NOW INDEED AND ACTUALLY** `Compute revisions for chunks` are backed into enabled again. it is necessary for garbage collection of chunks.
- As far as existing users are concerned, this will not automatically change, but the Doctor will inform us.
## 0.24.16
### Improved
#### Peer-to-Peer
- Now peer-to-peer synchronisation checks the settings are compatible with each other.
- No longer unexpected database broken, phew.
- Peer-to-peer synchronisation now handles the platform and detects pseudo-clients.
- Pseudo clients will not decrypt/encrypt anything, just relay the data. Hence, always settings are not compatible. Therefore, we have to accept the incompatibility for pseudo clients.
#### General
- New migration method has been implemented, that called `Doctor`.
- `Doctor` checks the difference between the ideal and actual values and encourages corrective action. To facilitate our decision, the reasons for this and the recommendations are also presented.
- This can be used not only during migration. We can invoke the doctor from the settings for trouble-shooting.
- The minimum interval for replication to be caused when an event occurs can now be configurable.
- Some detail note has been added and change nuance about the `Report` in the setting dialogue, which had less informative.
### Behaviour and default changed
- `Compute revisions for chunks` are backed into enabled again. it is necessary for garbage collection of chunks.
- As far as existing users are concerned, this will not automatically change, but the Doctor will inform us.
### Refactored
- Platform specific codes are more separated. No longer `node` modules were used in the browser and Obsidian.
## 0.24.15
### Fixed
@@ -111,46 +151,4 @@ And, this is just a single web page, without any server-side code. It is a stati
- Terser optimisation has slightly improved.
- During the build, analysis meta-file of the bundled codes will be generated.
## 0.24.10
### Fixed
- Fixed the issue which the filename is shown as `undefined`.
- Fixed the issue where files transferred at short intervals were not reflected.
### Improved
- Add more translations: `ja-JP` (Japanese) by @kohki-shikata (Thank you so much)!
### Internal
- Some files have been prettified.
## 0.24.9
Skipped.
## 0.24.8
### Fixed
- Some parallel-processing tasks are now performed more safely.
- Some error messages has been fixed.
### Improved
- Synchronisation is now more efficient and faster.
- Saving chunks is a bit more robust.
### New Feature
- We can remove orphaned chunks again, now!
- Without rebuilding the database!
- Note: Please synchronise devices completely before removing orphaned chunks.
- Note2: Deleted files are using chunks, if you want to remove them, please commit the deletion first. (`Commit File Deletion`)
- Note3: If you lost some chunks, do not worry. They will be resurrected if not so much time has passed. Try `Resurrect deleted chunks`.
- Note4: This feature is still beta. Please report any issues you encounter.
- Note5: Please disable `On demand chunk fetching`, and enable `Compute revisions for each chunk` before using this feature.
- These settings is going to be default in the future.
Older notes are in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).

View File

@@ -14,6 +14,48 @@ Thank you, and I hope your troubles will be resolved!
---
## 0.24.10
### Fixed
- Fixed the issue which the filename is shown as `undefined`.
- Fixed the issue where files transferred at short intervals were not reflected.
### Improved
- Add more translations: `ja-JP` (Japanese) by @kohki-shikata (Thank you so much)!
### Internal
- Some files have been prettified.
## 0.24.9
Skipped.
## 0.24.8
### Fixed
- Some parallel-processing tasks are now performed more safely.
- Some error messages has been fixed.
### Improved
- Synchronisation is now more efficient and faster.
- Saving chunks is a bit more robust.
### New Feature
- We can remove orphaned chunks again, now!
- Without rebuilding the database!
- Note: Please synchronise devices completely before removing orphaned chunks.
- Note2: Deleted files are using chunks, if you want to remove them, please commit the deletion first. (`Commit File Deletion`)
- Note3: If you lost some chunks, do not worry. They will be resurrected if not so much time has passed. Try `Resurrect deleted chunks`.
- Note4: This feature is still beta. Please report any issues you encounter.
- Note5: Please disable `On demand chunk fetching`, and enable `Compute revisions for each chunk` before using this feature.
- These settings is going to be default in the future.
## 0.24.7
### Fixed (Security)