Compare commits

...

5 Commits

Author SHA1 Message Date
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
vorotamoroz
fecefa3631 ### Fixed
- Now, even without WeakRef, Polyfill is used and the whole thing works without error. However, if you can switch WebView Engine, it is recommended to switch to a WebView Engine that supports WeakRef.

And bumped.
2025-02-20 10:40:18 +00:00
vorotamoroz
f8c4d5ccb0 Add a bit more. 2025-02-18 13:48:41 +00:00
vorotamoroz
e63e79bc8e Add note of flag files. 2025-02-18 13:36:15 +00:00
19 changed files with 546 additions and 245 deletions

View File

@@ -1,6 +1,6 @@
<!-- 2024-02-15 -->
# Tips and Troubleshooting
<!-- 2025-02-18 -->
# Tips and Troubleshooting
- [Tips and Troubleshooting](#tips-and-troubleshooting)
- [Notable bugs and fixes](#notable-bugs-and-fixes)
@@ -25,14 +25,16 @@
<!-- - -->
## Notable bugs and fixes
### Binary files get bigger on iOS
- Reported at: v0.20.x
- Fixed at: v0.21.2 (Fixed but not reviewed)
- Required action: larger files will not be fixed automatically, please perform `Verify and repair all files`. If our local database and storage are not matched, we will be asked to apply which one.
### Some setting name has been changed
- Fixed at: v0.22.6
| Previous name | New name |
@@ -58,13 +60,16 @@ Therefore, experienced users (especially those stable enough not to have to rebu
Please disable it when you have enough time.
### ZIP (or any extensions) files were not synchronised. Why?
It depends on Obsidian detects. May toggling `Detect all extensions` of `File and links` (setting of Obsidian) will help us.
### I hope to report the issue, but you said you needs `Report`. How to make it?
We can copy the report to the clipboard, by pressing the `Make report` button on the `Hatch` pane.
![Screenshot](../images/hatch.png)
### Where can I check the log?
We can launch the log pane by `Show log` on the command palette.
And if you have troubled something, please enable the `Verbose Log` on the `General Setting` pane.
@@ -73,16 +78,20 @@ However, the logs would not be kept so long and cleared when restarted. If you w
![ScreenShot](../images/write_logs_into_the_file.png)
> [!IMPORTANT]
>
> - Writing logs into the file will impact the performance.
> - Please make sure that you have erased all your confidential information before reporting issue.
### Why are the logs volatile and ephemeral?
To avoid unexpected exposure to our confidential things.
### Some network logs are not written into the file.
Especially the CORS error will be reported as a general error to the plug-in for security reasons. So we cannot detect and log it. We are only able to investigate them by [Checking the network log](#checking-the-network-log).
### If a file were deleted or trimmed, the capacity of the database should be reduced, right?
No, even though if files were deleted, chunks were not deleted.
Self-hosted LiveSync splits the files into multiple chunks and transfers only newly created. This behaviour enables us to less traffic. And, the chunks will be shared between the files to reduce the total usage of the database.
@@ -93,24 +102,42 @@ To shrink the database size, `Rebuild everything` only reliably and effectively.
### How can I use the DevTools?
#### Checking the network log
1. Open the network pane.
2. Find the requests marked in red.
![Errored](../images/devtools1.png)
![Errored](../images/devtools1.png)
3. Capture the `Headers`, `Payload`, and, `Response`. **Please be sure to keep important information confidential**. If the `Response` contains secrets, you can omitted that.
Note: Headers contains a some credentials. **The path of the request URL, Remote Address, authority, and authorization must be concealed.**
![Concealed sample](../images/devtools2.png)
Note: Headers contains a some credentials. **The path of the request URL, Remote Address, authority, and authorization must be concealed.**
![Concealed sample](../images/devtools2.png)
## Troubleshooting
<!-- Add here -->
### On the mobile device, cannot synchronise on the local network!
Obsidian mobile is not able to connect to the non-secure end-point, such as starting with `http://`. Make sure your URI of CouchDB. Also not able to use a self-signed certificate.
### I think that something bad happening on the vault...
Place `redflag.md` on top of the vault, and restart Obsidian. The most simple way is to create a new note and rename it to `redflag`. Of course, we can put it without Obsidian.
If there is `redflag.md`, Self-hosted LiveSync suspends all database and storage processes.
There are some options to use `redflag.md`.
| Filename | Human-Friendly Name | Description |
| ------------- | ------------------- | ------------------------------------------------------------------------------------ |
| `redflag.md` | - | Suspends all processes. |
| `redflag2.md` | `flag_rebuild.md` | Suspends all processes, and rebuild both local and remote databases by local files. |
| `redflag3.md` | `flag_fetch.md` | Suspends all processes, discard the local database, and fetch from the remote again. |
When fetching everything remotely or performing a rebuild, restarting Obsidian is performed once for safety reasons. At that time, Self-hosted LiveSync uses these files to determine whether the process should be carried out.
(The use of normal markdown files is a trick to externally force cancellation in the event of faults in the rebuild or fetch function itself, especially on mobile devices).
This mechanism is also used for set-up. And just for information, these files are also not subject to synchronisation.
However, occasionally the deletion of files may fail. This should generally work normally after restarting Obsidian. (As far as I can observe).
## Tips
### How to resolve `Tweaks Mismatched of Changed`
@@ -125,7 +152,7 @@ Following dialogue will be shown:
- If we want to propagate the setting of the device, we should choose `Update with mine`.
- On other devices, we should choose `Use configured` to accept and use the configured configuration.
- `Dismiss` can postpone a decision. However, we cannot synchronise until we have decided.
- `Dismiss` can postpone a decision. However, we cannot synchronise until we have decided.
Rest assured that in most cases we can choose `Use configured`. (Unless you are certain that you have not changed the configuration).
@@ -133,16 +160,16 @@ If we see it for the first time, it reflects the settings of the device that has
<!-- Add here -->
### Old tips
- Rarely, a file in the database could be corrupted. The plugin will not write to local storage when a file looks corrupted. If a local version of the file is on your device, the corruption could be fixed by editing the local file and synchronizing it. But if the file does not exist on any of your devices, then it can not be rescued. In this case, you can delete these items from the settings dialog.
- To stop the boot-up sequence (eg. for fixing problems on databases), you can put a `redflag.md` file (or directory) at the root of your vault.
Tip for iOS: a redflag directory can be created at the root of the vault using the File application.
- Also, with `redflag2.md` placed, we can automatically rebuild both the local and the remote databases during the boot-up sequence. With `redflag3.md`, we can discard only the local database and fetch from the remote again.
- Q: The database is growing, how can I shrink it down?
A: each of the docs is saved with their past 100 revisions for detecting and resolving conflicts. Picturing that one device has been offline for a while, and comes online again. The device has to compare its notes with the remotely saved ones. If there exists a historic revision in which the note used to be identical, it could be updated safely (like git fast-forward). Even if that is not in revision histories, we only have to check the differences after the revision that both devices commonly have. This is like git's conflict-resolving method. So, We have to make the database again like an enlarged git repo if you want to solve the root of the problem.
- And more technical Information is in the [Technical Information](tech_info.md)
- If you want to synchronize files without obsidian, you can use [filesystem-livesync](https://github.com/vrtmrz/filesystem-livesync).
- WebClipper is also available on Chrome Web Store:[obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
- Rarely, a file in the database could be corrupted. The plugin will not write to local storage when a file looks corrupted. If a local version of the file is on your device, the corruption could be fixed by editing the local file and synchronizing it. But if the file does not exist on any of your devices, then it can not be rescued. In this case, you can delete these items from the settings dialog.
- To stop the boot-up sequence (eg. for fixing problems on databases), you can put a `redflag.md` file (or directory) at the root of your vault.
Tip for iOS: a redflag directory can be created at the root of the vault using the File application.
- Also, with `redflag2.md` placed, we can automatically rebuild both the local and the remote databases during the boot-up sequence. With `redflag3.md`, we can discard only the local database and fetch from the remote again.
- Q: The database is growing, how can I shrink it down?
A: each of the docs is saved with their past 100 revisions for detecting and resolving conflicts. Picturing that one device has been offline for a while, and comes online again. The device has to compare its notes with the remotely saved ones. If there exists a historic revision in which the note used to be identical, it could be updated safely (like git fast-forward). Even if that is not in revision histories, we only have to check the differences after the revision that both devices commonly have. This is like git's conflict-resolving method. So, We have to make the database again like an enlarged git repo if you want to solve the root of the problem.
- And more technical Information is in the [Technical Information](tech_info.md)
- If you want to synchronize files without obsidian, you can use [filesystem-livesync](https://github.com/vrtmrz/filesystem-livesync).
- WebClipper is also available on Chrome Web Store:[obsidian-livesync-webclip](https://chrome.google.com/webstore/detail/obsidian-livesync-webclip/jfpaflmpckblieefkegjncjoceapakdf)
Repo is here: [obsidian-livesync-webclip](https://github.com/vrtmrz/obsidian-livesync-webclip). (Docs are a work in progress.)

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-livesync",
"name": "Self-hosted LiveSync",
"version": "0.24.14",
"version": "0.24.16",
"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",

22
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "obsidian-livesync",
"version": "0.24.14",
"version": "0.24.16",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "obsidian-livesync",
"version": "0.24.14",
"version": "0.24.16",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-s3": "^3.645.0",
@@ -18,7 +18,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.2",
"minimatch": "^10.0.1",
"octagonal-wheels": "^0.1.23",
"octagonal-wheels": "^0.1.24",
"svelte-check": "^4.1.4",
"trystero": "^0.20.1",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
@@ -8513,12 +8513,12 @@
}
},
"node_modules/octagonal-wheels": {
"version": "0.1.23",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.23.tgz",
"integrity": "sha512-Nq49bWDcIeSsGV0R7RCcERDSWOiExE9cBy6PKN9WStJw0A/GHrr8dhXxLbmwezdSmDcS4kdLxHRF3FdgnMmHXA==",
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.24.tgz",
"integrity": "sha512-ywzcq3FyW/xM37RhXkgwkERzgO6hG7uQHfpqHKcvPaT0H54e0/WoWEV65A2ttGp7Kxrq+UgXxVM1UT+KcSbPkA==",
"license": "MIT",
"dependencies": {
"idb": "^8.0.0"
"idb": "^8.0.2"
}
},
"node_modules/once": {
@@ -17552,11 +17552,11 @@
}
},
"octagonal-wheels": {
"version": "0.1.23",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.23.tgz",
"integrity": "sha512-Nq49bWDcIeSsGV0R7RCcERDSWOiExE9cBy6PKN9WStJw0A/GHrr8dhXxLbmwezdSmDcS4kdLxHRF3FdgnMmHXA==",
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.24.tgz",
"integrity": "sha512-ywzcq3FyW/xM37RhXkgwkERzgO6hG7uQHfpqHKcvPaT0H54e0/WoWEV65A2ttGp7Kxrq+UgXxVM1UT+KcSbPkA==",
"requires": {
"idb": "^8.0.0"
"idb": "^8.0.2"
}
},
"once": {

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-livesync",
"version": "0.24.14",
"version": "0.24.16",
"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",
@@ -77,7 +77,7 @@
"fflate": "^0.8.2",
"idb": "^8.0.2",
"minimatch": "^10.0.1",
"octagonal-wheels": "^0.1.23",
"octagonal-wheels": "^0.1.24",
"svelte-check": "^4.1.4",
"trystero": "^0.20.1",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"

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...9cf9bb6f1f

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,42 @@ 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.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
- Now, even without WeakRef, Polyfill is used and the whole thing works without error. However, if you can switch WebView Engine, it is recommended to switch to a WebView Engine that supports WeakRef.
## 0.24.14
### Fixed
@@ -105,46 +141,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)