mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-21 14:51:34 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca5a7ae18c | ||
|
|
a27652ac34 | ||
|
|
29b89efc47 | ||
|
|
ef3eef2d08 | ||
|
|
ffbbe32e36 | ||
|
|
0a5371cdee | ||
|
|
466bb142e2 | ||
|
|
d394a4ce7f | ||
|
|
71ce76e502 | ||
|
|
ae7a7dd456 | ||
|
|
4048186bb5 | ||
|
|
2b94fd9139 | ||
|
|
ec72ece86d | ||
|
|
e394a994c5 | ||
|
|
aa23b6a39a | ||
|
|
dfeac201a2 | ||
|
|
44b022f003 |
@@ -18,7 +18,7 @@ Additionally, it supports peer-to-peer synchronisation using WebRTC now (experim
|
|||||||
- Use open-source solutions for the server.
|
- Use open-source solutions for the server.
|
||||||
- Compatible solutions are supported.
|
- Compatible solutions are supported.
|
||||||
- Support end-to-end encryption.
|
- Support end-to-end encryption.
|
||||||
- Synchronise settings, snippets, themes, and plug-ins via [Customisation Sync (Beta)](#customization-sync) or [Hidden File Sync](#hiddenfilesync).
|
- Synchronise settings, snippets, themes, and plug-ins via [Customisation Sync (Beta)](docs/settings.md#6-customization-sync-advanced) or [Hidden File Sync](docs/settings.md#7-hidden-files-advanced).
|
||||||
- Enable WebRTC peer-to-peer synchronisation without requiring a `host` (Experimental).
|
- Enable WebRTC peer-to-peer synchronisation without requiring a `host` (Experimental).
|
||||||
- This feature is still in the experimental stage. Please exercise caution when using it.
|
- This feature is still in the experimental stage. Please exercise caution when using it.
|
||||||
- WebRTC is a peer-to-peer synchronisation method, so **at least one device must be online to synchronise**.
|
- WebRTC is a peer-to-peer synchronisation method, so **at least one device must be online to synchronise**.
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ If it results like the following:
|
|||||||
|
|
||||||
Your CouchDB has been initialised successfully. If you want this manually, please read the script.
|
Your CouchDB has been initialised successfully. If you want this manually, please read the script.
|
||||||
|
|
||||||
|
If you are using Docker Compose and the above command does not work or displays `ERROR: Hostname missing`, you can try running the following command, replacing the placeholders with your own values:
|
||||||
|
```
|
||||||
|
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> bash
|
||||||
|
```
|
||||||
|
|
||||||
## 3. Expose CouchDB to the Internet
|
## 3. Expose CouchDB to the Internet
|
||||||
|
|
||||||
- You can skip this instruction if you using only in intranet and only with desktop devices.
|
- You can skip this instruction if you using only in intranet and only with desktop devices.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.14",
|
"version": "0.25.20",
|
||||||
"minAppVersion": "0.9.12",
|
"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.",
|
"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",
|
"author": "vorotamoroz",
|
||||||
|
|||||||
68
package-lock.json
generated
68
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.14",
|
"version": "0.25.20",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.14",
|
"version": "0.25.20",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.808.0",
|
"@aws-sdk/client-s3": "^3.808.0",
|
||||||
@@ -20,10 +20,10 @@
|
|||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"octagonal-wheels": "^0.1.38",
|
"octagonal-wheels": "^0.1.40",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"svelte-check": "^4.1.7",
|
"svelte-check": "^4.1.7",
|
||||||
"trystero": "^0.21.7",
|
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -8555,9 +8555,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/octagonal-wheels": {
|
"node_modules/octagonal-wheels": {
|
||||||
"version": "0.1.38",
|
"version": "0.1.40",
|
||||||
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.38.tgz",
|
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.40.tgz",
|
||||||
"integrity": "sha512-GR7b6yi9JeZm4L+NzpgKmIZq6yRL0g9/OIVtgrmmRaPTbY/6Vr15rwihkoMcbj4cWeYE4GcewgeBIWVnqXVRJQ==",
|
"integrity": "sha512-qZkPnuVGCqpfLfu8xtZIxfQRVvmE5BmdzMF/rySriGi5JoctGhMNDjF0aLU/4GWUD5yW1X3io6VhJW4a7k1ieA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"idb": "^8.0.3"
|
"idb": "^8.0.3"
|
||||||
@@ -8752,9 +8752,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||||
"version": "11.1.0",
|
"version": "11.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz",
|
||||||
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
"integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -10365,20 +10365,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/trystero": {
|
"node_modules/trystero": {
|
||||||
"version": "0.21.7",
|
"version": "0.21.8",
|
||||||
"resolved": "https://registry.npmjs.org/trystero/-/trystero-0.21.7.tgz",
|
"resolved": "git+ssh://git@github.com/vrtmrz/trystero.git#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"integrity": "sha512-SdpxC9Q29bDBUdIWvkELHvOlmpelHAbjgclXR6cPeUWeLkbyY8ku2ZCJjozO4L2CLOMiclOoMKdmtpYLAwZvIw==",
|
"integrity": "sha512-LUlmTTSxgomyYvU1K05snl18+GLAxqO4W8P9lePLbRnZVJyGZUVXMM4/aW9384GtIf0VLJquloWqz9PriJZg2w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/secp256k1": "^1.7.1",
|
"@noble/secp256k1": "^3.0.0",
|
||||||
"@supabase/supabase-js": "^2.49.8",
|
"@supabase/supabase-js": "^2.49.8",
|
||||||
"@waku/discovery": "^0.0.8",
|
"@waku/discovery": "^0.0.8",
|
||||||
"@waku/sdk": "^0.0.31",
|
"@waku/sdk": "^0.0.31",
|
||||||
"firebase": "^12.0.0",
|
"firebase": "^12.2.1",
|
||||||
"libp2p": "^2.8.8",
|
"libp2p": "^2.8.8",
|
||||||
"mqtt": "^5.13.0"
|
"mqtt": "^5.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/trystero/node_modules/@noble/secp256k1": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
|
||||||
@@ -17023,9 +17032,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"octagonal-wheels": {
|
"octagonal-wheels": {
|
||||||
"version": "0.1.38",
|
"version": "0.1.40",
|
||||||
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.38.tgz",
|
"resolved": "https://registry.npmjs.org/octagonal-wheels/-/octagonal-wheels-0.1.40.tgz",
|
||||||
"integrity": "sha512-GR7b6yi9JeZm4L+NzpgKmIZq6yRL0g9/OIVtgrmmRaPTbY/6Vr15rwihkoMcbj4cWeYE4GcewgeBIWVnqXVRJQ==",
|
"integrity": "sha512-qZkPnuVGCqpfLfu8xtZIxfQRVvmE5BmdzMF/rySriGi5JoctGhMNDjF0aLU/4GWUD5yW1X3io6VhJW4a7k1ieA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"idb": "^8.0.3"
|
"idb": "^8.0.3"
|
||||||
}
|
}
|
||||||
@@ -17147,9 +17156,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": {
|
"lru-cache": {
|
||||||
"version": "11.1.0",
|
"version": "11.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz",
|
||||||
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
"integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18229,17 +18238,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"trystero": {
|
"trystero": {
|
||||||
"version": "0.21.7",
|
"version": "git+ssh://git@github.com/vrtmrz/trystero.git#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"resolved": "https://registry.npmjs.org/trystero/-/trystero-0.21.7.tgz",
|
"integrity": "sha512-LUlmTTSxgomyYvU1K05snl18+GLAxqO4W8P9lePLbRnZVJyGZUVXMM4/aW9384GtIf0VLJquloWqz9PriJZg2w==",
|
||||||
"integrity": "sha512-SdpxC9Q29bDBUdIWvkELHvOlmpelHAbjgclXR6cPeUWeLkbyY8ku2ZCJjozO4L2CLOMiclOoMKdmtpYLAwZvIw==",
|
"from": "trystero@github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@noble/secp256k1": "^1.7.1",
|
"@noble/secp256k1": "^3.0.0",
|
||||||
"@supabase/supabase-js": "^2.49.8",
|
"@supabase/supabase-js": "^2.49.8",
|
||||||
"@waku/discovery": "^0.0.8",
|
"@waku/discovery": "^0.0.8",
|
||||||
"@waku/sdk": "^0.0.31",
|
"@waku/sdk": "^0.0.31",
|
||||||
"firebase": "^12.0.0",
|
"firebase": "^12.2.1",
|
||||||
"libp2p": "^2.8.8",
|
"libp2p": "^2.8.8",
|
||||||
"mqtt": "^5.13.0"
|
"mqtt": "^5.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/secp256k1": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ts-api-utils": {
|
"ts-api-utils": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.14",
|
"version": "0.25.20",
|
||||||
"description": "Reflect your vault changes to some other devices immediately. Please make sure to disable other synchronize solutions to avoid content corruption or duplication.",
|
"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",
|
"main": "main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -92,10 +92,10 @@
|
|||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"octagonal-wheels": "^0.1.38",
|
"octagonal-wheels": "^0.1.40",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"svelte-check": "^4.1.7",
|
"svelte-check": "^4.1.7",
|
||||||
"trystero": "^0.21.7",
|
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PersistentMap } from "../lib/src/dataobject/PersistentMap.ts";
|
import { PersistentMap } from "octagonal-wheels/dataobject/PersistentMap";
|
||||||
|
|
||||||
export let sameChangePairs: PersistentMap<number[]>;
|
export let sameChangePairs: PersistentMap<number[]>;
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { promiseWithResolver, type PromiseWithResolvers } from "octagonal-wheels
|
|||||||
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts";
|
import { AuthorizationHeaderGenerator } from "../lib/src/replication/httplib.ts";
|
||||||
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
import type { KeyValueDatabase } from "../lib/src/interfaces/KeyValueDatabase.ts";
|
||||||
|
|
||||||
export { scheduleTask, cancelTask, cancelAllTasks } from "../lib/src/concurrency/task.ts";
|
export { scheduleTask, cancelTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||||
|
|
||||||
// For backward compatibility, using the path for determining id.
|
// For backward compatibility, using the path for determining id.
|
||||||
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
// Only CouchDB unacceptable ID (that starts with an underscore) has been prefixed with "/".
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
} from "../../lib/src/common/utils.ts";
|
} from "../../lib/src/common/utils.ts";
|
||||||
import { digestHash } from "../../lib/src/string_and_binary/hash.ts";
|
import { digestHash } from "../../lib/src/string_and_binary/hash.ts";
|
||||||
import { arrayBufferToBase64, decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts";
|
import { arrayBufferToBase64, decodeBinary, readString } from "../../lib/src/string_and_binary/convert.ts";
|
||||||
import { serialized, shareRunningResult } from "../../lib/src/concurrency/lock.ts";
|
import { serialized, shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import {
|
import {
|
||||||
@@ -62,7 +62,7 @@ import {
|
|||||||
scheduleTask,
|
scheduleTask,
|
||||||
} from "../../common/utils.ts";
|
} from "../../common/utils.ts";
|
||||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||||
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
|
import { pluginScanningCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
|
import { base64ToArrayBuffer, base64ToString } from "octagonal-wheels/binary/base64";
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ import {
|
|||||||
BASE_IS_NEW,
|
BASE_IS_NEW,
|
||||||
EVEN,
|
EVEN,
|
||||||
} from "../../common/utils.ts";
|
} from "../../common/utils.ts";
|
||||||
import { serialized, skipIfDuplicated } from "../../lib/src/concurrency/lock.ts";
|
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
import { JsonResolveModal } from "../HiddenFileCommon/JsonResolveModal.ts";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { addPrefix, stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import { QueueProcessor } from "../../lib/src/concurrency/processor.ts";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
|
import { hiddenFilesEventCount, hiddenFilesProcessingCount } from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
||||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
@@ -1765,10 +1765,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (ignoreFilter && ignoreFilter.some((e) => e.test(v))) {
|
||||||
ignoreFilter &&
|
|
||||||
ignoreFilter.some((e) => (e.pattern.startsWith("/") || e.pattern.startsWith("\\/")) && e.test(v))
|
|
||||||
) {
|
|
||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
|
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
|
||||||
|
|||||||
@@ -174,6 +174,13 @@ export class P2PReplicator
|
|||||||
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
if (this.settings.P2P_Enabled && this.settings.P2P_AutoStart) {
|
||||||
setTimeout(() => void this.open(), 100);
|
setTimeout(() => void this.open(), 100);
|
||||||
}
|
}
|
||||||
|
const rep = this._replicatorInstance;
|
||||||
|
rep?.allowReconnection();
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
$everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
|
const rep = this._replicatorInstance;
|
||||||
|
rep?.disconnectFromServer();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: f21001fcb2...21ca077163
@@ -31,7 +31,7 @@ import { type KeyValueDatabase } from "./lib/src/interfaces/KeyValueDatabase.ts"
|
|||||||
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "./features/LiveSyncCommands.ts";
|
||||||
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
import { HiddenFileSync } from "./features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
|
import { ConfigSync } from "./features/ConfigSync/CmdConfigSync.ts";
|
||||||
import { reactiveSource, type ReactiveValue } from "./lib/src/dataobject/reactive.js";
|
import { reactiveSource, type ReactiveValue } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
|
import { type LiveSyncJournalReplicatorEnv } from "./lib/src/replication/journal/LiveSyncJournalReplicator.js";
|
||||||
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
|
import { type LiveSyncCouchDBReplicatorEnv } from "./lib/src/replication/couchdb/LiveSyncReplicator.js";
|
||||||
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
|
import type { CheckPointInfo } from "./lib/src/replication/journal/JournalSyncTypes.js";
|
||||||
|
|||||||
@@ -15,10 +15,40 @@ import { TFileToUXFileInfoStub, TFolderToUXFileInfoStub } from "./storageLib/uti
|
|||||||
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
import { StorageEventManagerObsidian, type StorageEventManager } from "./storageLib/StorageEventManager";
|
||||||
import type { StorageAccess } from "../interfaces/StorageAccess";
|
import type { StorageAccess } from "../interfaces/StorageAccess";
|
||||||
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
import { createBlob, type CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||||
|
|
||||||
|
const fileLockPrefix = "file-lock:";
|
||||||
|
|
||||||
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
||||||
|
processingFiles: Set<FilePathWithPrefix> = new Set();
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T> {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return serialized(`${fileLockPrefix}${path}`, async () => {
|
||||||
|
try {
|
||||||
|
this.processingFiles.add(path);
|
||||||
|
return await proc();
|
||||||
|
} finally {
|
||||||
|
this.processingFiles.delete(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean {
|
||||||
|
const path = typeof file === "string" ? file : file.path;
|
||||||
|
return this.processingFiles.has(path);
|
||||||
|
}
|
||||||
vaultAccess!: SerializedFileAccess;
|
vaultAccess!: SerializedFileAccess;
|
||||||
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core);
|
vaultManager: StorageEventManager = new StorageEventManagerObsidian(this.plugin, this.core, this);
|
||||||
$everyOnload(): Promise<boolean> {
|
$everyOnload(): Promise<boolean> {
|
||||||
this.core.storageAccess = this;
|
this.core.storageAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -42,7 +72,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin);
|
this.vaultAccess = new SerializedFileAccess(this.app, this.plugin, this);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "../../../deps.ts";
|
||||||
import { serialized } from "../../../lib/src/concurrency/lock.ts";
|
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
import { isPlainText } from "../../../lib/src/string_and_binary/path.ts";
|
||||||
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
import type { FilePath, HasSettings, UXFileInfoStub } from "../../../lib/src/common/types.ts";
|
||||||
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
import { createBinaryBlob, isDocContentSame } from "../../../lib/src/common/utils.ts";
|
||||||
import type { InternalFileInfo } from "../../../common/types.ts";
|
import type { InternalFileInfo } from "../../../common/types.ts";
|
||||||
import { markChangesAreSame } from "../../../common/utils.ts";
|
import { markChangesAreSame } from "../../../common/utils.ts";
|
||||||
import { type UXFileInfo } from "../../../lib/src/common/types.ts";
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
|
|
||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
|
||||||
return `fl:${typeof file == "string" ? file : file.path}`;
|
|
||||||
}
|
|
||||||
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
return arr.buffer;
|
return arr.buffer;
|
||||||
@@ -21,60 +16,55 @@ function toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<Arr
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// function isFile(file: TFile | TFolder | string | UXFileInfo): boolean {
|
|
||||||
// file instanceof TFile;
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function processReadFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
async function processWriteFile<T>(file: TFile | TFolder | string | UXFileInfo, proc: () => Promise<T>) {
|
|
||||||
const ret = await serialized(getFileLockKey(file), () => proc());
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SerializedFileAccess {
|
export class SerializedFileAccess {
|
||||||
app: App;
|
app: App;
|
||||||
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
plugin: HasSettings<{ handleFilenameCaseSensitive: boolean }>;
|
||||||
constructor(app: App, plugin: (typeof this)["plugin"]) {
|
storageAccess: StorageAccess;
|
||||||
|
constructor(app: App, plugin: SerializedFileAccess["plugin"], storageAccess: StorageAccess) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryAdapterStat(file: TFile | string) {
|
async tryAdapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, async () => {
|
return await this.storageAccess.processReadFile(path as FilePath, async () => {
|
||||||
if (!(await this.app.vault.adapter.exists(path))) return null;
|
if (!(await this.app.vault.adapter.exists(path))) return null;
|
||||||
return this.app.vault.adapter.stat(path);
|
return this.app.vault.adapter.stat(path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async adapterStat(file: TFile | string) {
|
async adapterStat(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.stat(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.stat(path));
|
||||||
}
|
}
|
||||||
async adapterExists(file: TFile | string) {
|
async adapterExists(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.exists(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.exists(path));
|
||||||
}
|
}
|
||||||
async adapterRemove(file: TFile | string) {
|
async adapterRemove(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.remove(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.remove(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterRead(file: TFile | string) {
|
async adapterRead(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
}
|
}
|
||||||
async adapterReadBinary(file: TFile | string) {
|
async adapterReadBinary(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterReadAuto(file: TFile | string) {
|
async adapterReadAuto(file: TFile | string) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.adapter.read(path));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.adapter.readBinary(path));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.adapter.read(path));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.readBinary(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adapterWrite(
|
async adapterWrite(
|
||||||
@@ -84,35 +74,39 @@ export class SerializedFileAccess {
|
|||||||
) {
|
) {
|
||||||
const path = file instanceof TFile ? file.path : file;
|
const path = file instanceof TFile ? file.path : file;
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, () => this.app.vault.adapter.write(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.adapter.write(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, () =>
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultCacheRead(file: TFile) {
|
async vaultCacheRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.cachedRead(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.cachedRead(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultRead(file: TFile) {
|
async vaultRead(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.read(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.read(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadBinary(file: TFile) {
|
async vaultReadBinary(file: TFile) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(file.path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultReadAuto(file: TFile) {
|
async vaultReadAuto(file: TFile) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
if (isPlainText(path)) return await processReadFile(file, () => this.app.vault.read(file));
|
if (isPlainText(path)) {
|
||||||
return await processReadFile(file, () => this.app.vault.readBinary(file));
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.read(file));
|
||||||
|
}
|
||||||
|
return await this.storageAccess.processReadFile(path as FilePath, () => this.app.vault.readBinary(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: DataWriteOptions) {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.read(file);
|
const oldData = await this.app.vault.read(file);
|
||||||
if (data === oldData) {
|
if (data === oldData) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -122,7 +116,7 @@ export class SerializedFileAccess {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(file, async () => {
|
return await this.storageAccess.processWriteFile(file.path as FilePath, async () => {
|
||||||
const oldData = await this.app.vault.readBinary(file);
|
const oldData = await this.app.vault.readBinary(file);
|
||||||
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
if (await isDocContentSame(createBinaryBlob(oldData), createBinaryBlob(data))) {
|
||||||
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
if (options && options.mtime) markChangesAreSame(file.path, file.stat.mtime, options.mtime);
|
||||||
@@ -139,9 +133,13 @@ export class SerializedFileAccess {
|
|||||||
options?: DataWriteOptions
|
options?: DataWriteOptions
|
||||||
): Promise<TFile> {
|
): Promise<TFile> {
|
||||||
if (typeof data === "string") {
|
if (typeof data === "string") {
|
||||||
return await processWriteFile(path, () => this.app.vault.create(path, data, options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.create(path, data, options)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return await processWriteFile(path, () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
|
return await this.storageAccess.processWriteFile(path as FilePath, () =>
|
||||||
|
this.app.vault.createBinary(path, toArrayBuffer(data), options)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +152,14 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(file: TFile | TFolder, force = false) {
|
async delete(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.delete(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.delete(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async trash(file: TFile | TFolder, force = false) {
|
async trash(file: TFile | TFolder, force = false) {
|
||||||
return await processWriteFile(file, () => this.app.vault.trash(file, force));
|
return await this.storageAccess.processWriteFile(file.path as FilePath, () =>
|
||||||
|
this.app.vault.trash(file, force)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
isStorageInsensitive(): boolean {
|
isStorageInsensitive(): boolean {
|
||||||
|
|||||||
@@ -14,17 +14,18 @@ import {
|
|||||||
} from "../../../lib/src/common/types.ts";
|
} from "../../../lib/src/common/types.ts";
|
||||||
import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
|
import { delay, fireAndForget, getFileRegExp } from "../../../lib/src/common/utils.ts";
|
||||||
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
|
import { type FileEventItem, type FileEventType } from "../../../common/types.ts";
|
||||||
import { serialized, skipIfDuplicated } from "../../../lib/src/concurrency/lock.ts";
|
import { serialized, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import {
|
import {
|
||||||
finishAllWaitingForTimeout,
|
finishAllWaitingForTimeout,
|
||||||
finishWaitingForTimeout,
|
finishWaitingForTimeout,
|
||||||
isWaitingForTimeout,
|
isWaitingForTimeout,
|
||||||
waitForTimeout,
|
waitForTimeout,
|
||||||
} from "../../../lib/src/concurrency/task.ts";
|
} from "octagonal-wheels/concurrency/task";
|
||||||
import { Semaphore } from "../../../lib/src/concurrency/semaphore.ts";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import type { LiveSyncCore } from "../../../main.ts";
|
import type { LiveSyncCore } from "../../../main.ts";
|
||||||
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
import { InternalFileToUXFileInfoStub, TFileToUXFileInfoStub } from "./utilObsidian.ts";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
import type { StorageAccess } from "../../interfaces/StorageAccess.ts";
|
||||||
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
// import { InternalFileToUXFileInfo } from "../platforms/obsidian.ts";
|
||||||
|
|
||||||
export type FileEvent = {
|
export type FileEvent = {
|
||||||
@@ -46,6 +47,7 @@ export abstract class StorageEventManager {
|
|||||||
export class StorageEventManagerObsidian extends StorageEventManager {
|
export class StorageEventManagerObsidian extends StorageEventManager {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
core: LiveSyncCore;
|
core: LiveSyncCore;
|
||||||
|
storageAccess: StorageAccess;
|
||||||
|
|
||||||
get shouldBatchSave() {
|
get shouldBatchSave() {
|
||||||
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
return this.core.settings?.batchSave && this.core.settings?.liveSync != true;
|
||||||
@@ -56,8 +58,9 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
get batchSaveMaximumDelay(): number {
|
get batchSaveMaximumDelay(): number {
|
||||||
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
return this.core.settings?.batchSaveMaximumDelay ?? DEFAULT_SETTINGS.batchSaveMaximumDelay;
|
||||||
}
|
}
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore, storageAccess: StorageAccess) {
|
||||||
super();
|
super();
|
||||||
|
this.storageAccess = storageAccess;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
@@ -88,6 +91,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
const file = info?.file as TFile;
|
const file = info?.file as TFile;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`Editor change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.isWaiting(file.path as FilePath)) {
|
if (!this.isWaiting(file.path as FilePath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,22 +109,35 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
|
|
||||||
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
watchVaultCreate(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File create skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CREATE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
watchVaultChange(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File change skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "CHANGED", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
watchVaultDelete(file: TAbstractFile, ctx?: any) {
|
||||||
if (file instanceof TFolder) return;
|
if (file instanceof TFolder) return;
|
||||||
|
if (this.storageAccess.isFileProcessing(file.path as FilePath)) {
|
||||||
|
// Logger(`File delete skipped because the file is being processed: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fileInfo = TFileToUXFileInfoStub(file, true);
|
const fileInfo = TFileToUXFileInfoStub(file, true);
|
||||||
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
void this.appendQueue([{ type: "DELETE", file: fileInfo }], ctx);
|
||||||
}
|
}
|
||||||
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
watchVaultRename(file: TAbstractFile, oldFile: string, ctx?: any) {
|
||||||
|
// vault Rename will not be raised for self-events (Self-hosted LiveSync will not handle 'rename').
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
const fileInfo = TFileToUXFileInfoStub(file);
|
const fileInfo = TFileToUXFileInfoStub(file);
|
||||||
void this.appendQueue(
|
void this.appendQueue(
|
||||||
@@ -145,6 +165,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
// Watch raw events (Internal API)
|
// Watch raw events (Internal API)
|
||||||
watchVaultRawEvents(path: FilePath) {
|
watchVaultRawEvents(path: FilePath) {
|
||||||
|
if (this.storageAccess.isFileProcessing(path)) {
|
||||||
|
// Logger(`Raw file event skipped because the file is being processed: ${path}`, LOG_LEVEL_VERBOSE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Only for internal files.
|
// Only for internal files.
|
||||||
if (!this.plugin.settings) return;
|
if (!this.plugin.settings) return;
|
||||||
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
// if (this.plugin.settings.useIgnoreFiles && this.plugin.ignoreFiles.some(e => path.endsWith(e.trim()))) {
|
||||||
|
|||||||
@@ -209,8 +209,8 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
const messageUnrecoverable =
|
const messageUnrecoverable =
|
||||||
unrecoverable.length > 0
|
unrecoverable.length > 0
|
||||||
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
||||||
filesNotRecoverable: unrecoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
filesNotRecoverable: unrecoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
})
|
})
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const message = $msg("moduleMigration.fix0256.message", {
|
const message = $msg("moduleMigration.fix0256.message", {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { fireAndForget } from "../../../lib/src/common/utils.ts";
|
import { fireAndForget } from "../../../lib/src/common/utils.ts";
|
||||||
import { serialized } from "../../../lib/src/concurrency/lock.ts";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
|
|
||||||
let plugin: ObsidianLiveSyncPlugin;
|
let plugin: ObsidianLiveSyncPlugin;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Trench } from "../../../lib/src/memory/memutil.ts";
|
import { Trench } from "octagonal-wheels/memory/memutil";
|
||||||
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
type MeasureResult = [times: number, spent: number];
|
type MeasureResult = [times: number, spent: number];
|
||||||
type NamedMeasureResult = [name: string, result: MeasureResult];
|
type NamedMeasureResult = [name: string, result: MeasureResult];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
import { logMessages } from "../../../lib/src/mock_and_interop/stores";
|
||||||
import { reactive, type ReactiveInstance } from "../../../lib/src/dataobject/reactive";
|
import { reactive, type ReactiveInstance } from "octagonal-wheels/dataobject/reactive";
|
||||||
import { Logger } from "../../../lib/src/common/logger";
|
import { Logger } from "../../../lib/src/common/logger";
|
||||||
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
import { $msg as msg, currentLang as lang } from "../../../lib/src/common/i18n.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictRes
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
import { displayRev, getPath, getPathWithoutPrefix } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { serialized } from "../../lib/src/concurrency/lock.ts";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
|
|
||||||
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
$everyOnloadStart(): Promise<boolean> {
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
if (level == LOG_LEVEL_DEBUG && !showDebugLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (level < LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
if (level <= LOG_LEVEL_INFO && this.settings && this.settings.lessInformationInLog) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
if (this.settings && !this.settings.showVerboseLog && level == LOG_LEVEL_VERBOSE) {
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
jwtSub: settings.jwtSub,
|
jwtSub: settings.jwtSub,
|
||||||
useRequestAPI: settings.useRequestAPI,
|
useRequestAPI: settings.useRequestAPI,
|
||||||
bucketPrefix: settings.bucketPrefix,
|
bucketPrefix: settings.bucketPrefix,
|
||||||
|
forcePathStyle: settings.forcePathStyle,
|
||||||
};
|
};
|
||||||
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
|
||||||
JSON.stringify(connectionSetting),
|
JSON.stringify(connectionSetting),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { delay, isObjectDifferent, sizeToHumanReadable } from "../../../lib/src/
|
|||||||
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
import { versionNumberString2Number } from "../../../lib/src/string_and_binary/convert.ts";
|
||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts";
|
import { checkSyncInfo } from "@/lib/src/pouchdb/negotiation.ts";
|
||||||
import { testCrypt } from "../../../lib/src/encryption/e2ee_v2.ts";
|
import { testCrypt } from "octagonal-wheels/encryption/encryption";
|
||||||
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
import ObsidianLiveSyncPlugin from "../../../main.ts";
|
||||||
import { scheduleTask } from "../../../common/utils.ts";
|
import { scheduleTask } from "../../../common/utils.ts";
|
||||||
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
import { LiveSyncCouchDBReplicator } from "../../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
||||||
@@ -859,26 +859,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMinioJournalSyncClient() {
|
getMinioJournalSyncClient() {
|
||||||
const id = this.plugin.settings.accessKey;
|
return new JournalSyncMinio(this.plugin.settings, this.plugin.simpleStore, this.plugin);
|
||||||
const key = this.plugin.settings.secretKey;
|
|
||||||
const bucket = this.plugin.settings.bucket;
|
|
||||||
const prefix = this.plugin.settings.bucketPrefix;
|
|
||||||
const region = this.plugin.settings.region;
|
|
||||||
const endpoint = this.plugin.settings.endpoint;
|
|
||||||
const useCustomRequestHandler = this.plugin.settings.useCustomRequestHandler;
|
|
||||||
const customHeaders = this.plugin.settings.bucketCustomHeaders;
|
|
||||||
return new JournalSyncMinio(
|
|
||||||
id,
|
|
||||||
key,
|
|
||||||
endpoint,
|
|
||||||
bucket,
|
|
||||||
prefix,
|
|
||||||
this.plugin.simpleStore,
|
|
||||||
this.plugin,
|
|
||||||
useCustomRequestHandler,
|
|
||||||
region,
|
|
||||||
customHeaders
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
async resetRemoteBucket() {
|
async resetRemoteBucket() {
|
||||||
const minioJournal = this.getMinioJournalSyncClient();
|
const minioJournal = this.getMinioJournalSyncClient();
|
||||||
|
|||||||
@@ -101,6 +101,23 @@ export function paneRemoteConfig(
|
|||||||
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgIfConfigNotPersistent"), ["ob-btn-config-info"]);
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgConfigCheck"), ["ob-btn-config-head"]);
|
||||||
|
|
||||||
|
const serverBanner = r.headers["server"] ?? r.headers["Server"] ?? "unknown";
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.serverVersion", { info: serverBanner }));
|
||||||
|
const versionMatch = serverBanner.match(/CouchDB(\/([0-9.]+))?/);
|
||||||
|
const versionStr = versionMatch ? versionMatch[2] : "0.0.0";
|
||||||
|
const versionParts = `${versionStr}.0.0.0`.split(".");
|
||||||
|
// Compare version string with the target version.
|
||||||
|
// version must be a string like "3.2.1" or "3.10.2", and must be two or three parts.
|
||||||
|
function isGreaterThanOrEqual(version: string) {
|
||||||
|
const targetParts = version.split(".");
|
||||||
|
for (let i = 0; i < targetParts.length; i++) {
|
||||||
|
// compare as number if possible (so 3.10 > 3.2, 3.10.1b > 3.10.1a)
|
||||||
|
const result = versionParts[i].localeCompare(targetParts[i], undefined, { numeric: true });
|
||||||
|
if (result > 0) return true;
|
||||||
|
if (result < 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// Admin check
|
// Admin check
|
||||||
// for database creation and deletion
|
// for database creation and deletion
|
||||||
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
if (!(this.editingSettings.couchDB_USER in responseConfig.admins)) {
|
||||||
@@ -108,28 +125,31 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
addResult($msg("obsidianLiveSyncSettingTab.okAdminPrivileges"));
|
||||||
}
|
}
|
||||||
// HTTP user-authorization check
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
// HTTP user-authorization check
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.require_valid_user != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
isSuccessful = false;
|
||||||
addConfigFixButton(
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUser"));
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
addConfigFixButton(
|
||||||
"chttpd/require_valid_user",
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUser"),
|
||||||
"true"
|
"chttpd/require_valid_user",
|
||||||
);
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUser"));
|
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
||||||
}
|
isSuccessful = false;
|
||||||
if (responseConfig?.chttpd_auth?.require_valid_user != "true") {
|
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
||||||
isSuccessful = false;
|
addConfigFixButton(
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errRequireValidUserAuth"));
|
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
||||||
addConfigFixButton(
|
"chttpd_auth/require_valid_user",
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetRequireValidUserAuth"),
|
"true"
|
||||||
"chttpd_auth/require_valid_user",
|
);
|
||||||
"true"
|
} else {
|
||||||
);
|
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
||||||
} else {
|
}
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okRequireValidUserAuth"));
|
|
||||||
}
|
}
|
||||||
// HTTPD check
|
// HTTPD check
|
||||||
// Check Authentication header
|
// Check Authentication header
|
||||||
@@ -144,12 +164,26 @@ export function paneRemoteConfig(
|
|||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
addResult($msg("obsidianLiveSyncSettingTab.okWwwAuth"));
|
||||||
}
|
}
|
||||||
if (responseConfig?.httpd?.enable_cors != "true") {
|
if (isGreaterThanOrEqual("3.2.0")) {
|
||||||
isSuccessful = false;
|
if (responseConfig?.chttpd?.enable_cors != "true") {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
isSuccessful = false;
|
||||||
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCorsChttpd"));
|
||||||
|
addConfigFixButton(
|
||||||
|
$msg("obsidianLiveSyncSettingTab.msgEnableCorsChttpd"),
|
||||||
|
"chttpd/enable_cors",
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCorsChttpd"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
if (responseConfig?.httpd?.enable_cors != "true") {
|
||||||
|
isSuccessful = false;
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.errEnableCors"));
|
||||||
|
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgEnableCors"), "httpd/enable_cors", "true");
|
||||||
|
} else {
|
||||||
|
addResult($msg("obsidianLiveSyncSettingTab.okEnableCors"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If the server is not cloudant, configure request size
|
// If the server is not cloudant, configure request size
|
||||||
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
if (!isCloudantURI(this.editingSettings.couchDB_URI)) {
|
||||||
@@ -320,6 +354,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
syncWarnMinio.addClass("op-warn-info");
|
syncWarnMinio.addClass("op-warn-info");
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("endpoint", { holdValue: true });
|
new Setting(paneEl).autoWireText("endpoint", { holdValue: true });
|
||||||
|
new Setting(paneEl).autoWireToggle("forcePathStyle", { holdValue: true });
|
||||||
new Setting(paneEl).autoWireText("accessKey", { holdValue: true });
|
new Setting(paneEl).autoWireText("accessKey", { holdValue: true });
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("secretKey", {
|
new Setting(paneEl).autoWireText("secretKey", {
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import type {
|
|||||||
import type { CustomRegExp } from "../../lib/src/common/utils";
|
import type { CustomRegExp } from "../../lib/src/common/utils";
|
||||||
|
|
||||||
export interface StorageAccess {
|
export interface StorageAccess {
|
||||||
|
processWriteFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
processReadFile<T>(file: UXFileInfoStub | FilePathWithPrefix, proc: () => Promise<T>): Promise<T>;
|
||||||
|
isFileProcessing(file: UXFileInfoStub | FilePathWithPrefix): boolean;
|
||||||
|
|
||||||
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
deleteVaultItem(file: FilePathWithPrefix | UXFileInfoStub | UXFolderInfo): Promise<void>;
|
||||||
|
|
||||||
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
writeFileAuto(path: string, data: string | ArrayBuffer, opt?: UXDataWriteOptions): Promise<boolean>;
|
||||||
|
|||||||
172
updates.md
172
updates.md
@@ -1,3 +1,88 @@
|
|||||||
|
## 0.25
|
||||||
|
|
||||||
|
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|
||||||
|
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
||||||
|
|
||||||
|
I have now rewritten the E2EE code to be more robust and easier to understand. It is significantly more readable and should be easier to maintain in the future. The performance issue, previously considered a concern, has been addressed by introducing a master key and deriving keys using HKDF. This approach is both fast and robust, and it provides protection against rainbow table attacks. (In addition, this implementation has been [a dedicated package on the npm registry](https://github.com/vrtmrz/octagonal-wheels), and tested in 100% branch-coverage).
|
||||||
|
|
||||||
|
As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution.
|
||||||
|
|
||||||
|
## 0.25.20
|
||||||
|
|
||||||
|
26th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710).
|
||||||
|
- Just using the fetched chunk temporarily.
|
||||||
|
- Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712).
|
||||||
|
- It no longer detects files that the plug-in has modified.
|
||||||
|
- It may reduce unnecessary file comparisons and unexpected file states.
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now checking the remote database configuration respecting the CouchDB version (#714).
|
||||||
|
|
||||||
|
## 0.25.19
|
||||||
|
|
||||||
|
18th September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now encoding/decoding for chunk data and encryption/decryption are performed in native functions (if they were available).
|
||||||
|
- This uses Uint8Array.fromBase64 and Uint8Array.toBase64, which are natively available in iOS 18.2+ and Android with Chrome 140+.
|
||||||
|
- In Android, WebView is by default updated with Chrome, so it should be available in most cases.
|
||||||
|
- Note that this is not available in Desktop yet (due to being based on Electron). We are staying tuned for future updates.
|
||||||
|
- This realised by an external(?) package [octagonal-wheels](https://github.com/vrtmrz/octagonal-wheels). Therefore, this update only updates the dependency.
|
||||||
|
|
||||||
|
## 0.25.18
|
||||||
|
|
||||||
|
17th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Property encryption detection now works correctly (On Self-hosted LiveSync, it was not broken, but as a library, it was not working correctly).
|
||||||
|
- Initialising the chunk splitter is now surely performed.
|
||||||
|
- DirectFileManipulator now works fine (as a library)
|
||||||
|
- Old `DirectFileManipulatorV1` is now removed.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Removed some unnecessary intermediate files.
|
||||||
|
|
||||||
|
## 0.25.17
|
||||||
|
|
||||||
|
16th September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- No longer information-level logs have produced during toggling `Show only notifications` in the settings (#708).
|
||||||
|
- Ignoring filters for Hidden file sync now works correctly (#709).
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Removed some unnecessary intermediate files.
|
||||||
|
|
||||||
|
## 0.25.16
|
||||||
|
|
||||||
|
4th September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Improved connectivity for P2P connections
|
||||||
|
- The connection to the signalling server can now be disconnected while in the background or when explicitly disconnected.
|
||||||
|
- These features use a patch that has not been incorporated upstream.
|
||||||
|
- This patch is available at [vrtmrz/trystero](https://github.com/vrtmrz/trystero).
|
||||||
|
|
||||||
|
## 0.25.15
|
||||||
|
|
||||||
|
3rd September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now we can configure `forcePathStyle` for bucket synchronisation (#707).
|
||||||
|
|
||||||
## 0.25.14
|
## 0.25.14
|
||||||
|
|
||||||
2nd September, 2025
|
2nd September, 2025
|
||||||
@@ -9,92 +94,5 @@
|
|||||||
- Now informs us about conflicted files as non-recoverable, but noted so.
|
- Now informs us about conflicted files as non-recoverable, but noted so.
|
||||||
- No longer errors on not-found files.
|
- No longer errors on not-found files.
|
||||||
|
|
||||||
## 0.25.13
|
|
||||||
|
|
||||||
1st September, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Conflict resolving dialogue now properly displays the changeset name instead of A or B (#691).
|
|
||||||
|
|
||||||
## 0.25.12
|
|
||||||
|
|
||||||
29th August, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fixed an issue with automatic synchronisation starting (#702).
|
|
||||||
|
|
||||||
## 0.25.11
|
|
||||||
|
|
||||||
28th August, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Automatic translation detection on the first launch now works correctly (#630).
|
|
||||||
- No errors are shown during synchronisations in offline (if not explicitly requested) (#699).
|
|
||||||
- Missing some checking during automatic-synchronisation now works correctly.
|
|
||||||
|
|
||||||
## 0.25.10
|
|
||||||
|
|
||||||
26th August, 2025
|
|
||||||
|
|
||||||
### New experimental feature
|
|
||||||
|
|
||||||
- We can perform Garbage Collection (Beta2) without rebuilding the entire database, and also fetch the database.
|
|
||||||
- Note that this feature is very experimental and should be used with caution.
|
|
||||||
- This feature requires disabling `Fetch chunks on demand`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Resetting the bucket now properly clears all uploaded files.
|
|
||||||
|
|
||||||
### Refactored
|
|
||||||
|
|
||||||
- Some files have been moved to better reflect their purpose and improve maintainability.
|
|
||||||
- The extensive LiveSyncLocalDB has been split into separate files for each role.
|
|
||||||
|
|
||||||
## 0.25.9
|
|
||||||
|
|
||||||
20th August, 2025
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- CORS Checking messages now use replacements.
|
|
||||||
- Configuring CORS setting via the UI now respects the existing rules.
|
|
||||||
- Now startup-checking works correctly again, performs migration check serially and then it will also fix starting LiveSync or start-up sync. (#696)
|
|
||||||
- Statusline in editor now supported 'Bases'.
|
|
||||||
|
|
||||||
## 0.25.8
|
|
||||||
|
|
||||||
18th August, 2025
|
|
||||||
|
|
||||||
### New feature
|
|
||||||
|
|
||||||
- Insecure chunk detection has been implemented.
|
|
||||||
- A notification dialogue will be shown if any insecure chunks are detected; these may have been created by v0.25.6 due to its issue. If this dialogue appears, please ensure you rebuild the database after backing it up.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Unexpected `Failed to obtain PBKDF2 salt` or similar errors during bucket-synchronisation no longer occur.
|
|
||||||
- Unexpected long delays for chunk-missing documents when using bucket-synchronisation have been resolved.
|
|
||||||
- Fetched remote chunks are now properly stored in the local database if `Fetch chunks on demand` is enabled.
|
|
||||||
- The 'fetch' dialogue's message has been refined.
|
|
||||||
- No longer overwriting any corrupted documents to the storage on boot-sequence.
|
|
||||||
|
|
||||||
### Refactored
|
|
||||||
|
|
||||||
- Type errors have been corrected.
|
|
||||||
|
|
||||||
## 0.25.0
|
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
|
||||||
|
|
||||||
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
|
||||||
|
|
||||||
I have now rewritten the E2EE code to be more robust and easier to understand. It is significantly more readable and should be easier to maintain in the future. The performance issue, previously considered a concern, has been addressed by introducing a master key and deriving keys using HKDF. This approach is both fast and robust, and it provides protection against rainbow table attacks. (In addition, this implementation has been [a dedicated package on the npm registry](https://github.com/vrtmrz/octagonal-wheels), and tested in 100% branch-coverage).
|
|
||||||
|
|
||||||
As a result, this is the first time in a while that forward compatibility has been broken. We have also taken the opportunity to change all metadata to use encryption rather than obfuscation. Furthermore, the `Dynamic Iteration Count` setting is now redundant and has been moved to the `Patches` pane in the settings. Thanks to Rabin-Karp, the eden setting is also no longer necessary and has been relocated accordingly. Therefore, v0.25.0 represents a legitimate and correct evolution.
|
|
||||||
|
|
||||||
Older notes are in
|
Older notes are in
|
||||||
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
[updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md).
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
# 0.25
|
||||||
|
|
||||||
## 0.25.0
|
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
|
||||||
|
|
||||||
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
After reading Issue #668, I conducted another self-review of the E2EE-related code. In retrospect, it was clearly written by someone inexperienced, which is understandable, but it is still rather embarrassing. Three years is certainly enough time for growth.
|
||||||
|
|
||||||
@@ -11,6 +10,82 @@ As a result, this is the first time in a while that forward compatibility has be
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.25.13
|
||||||
|
|
||||||
|
1st September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Conflict resolving dialogue now properly displays the changeset name instead of A or B (#691).
|
||||||
|
|
||||||
|
## 0.25.12
|
||||||
|
|
||||||
|
29th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue with automatic synchronisation starting (#702).
|
||||||
|
|
||||||
|
## 0.25.11
|
||||||
|
|
||||||
|
28th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Automatic translation detection on the first launch now works correctly (#630).
|
||||||
|
- No errors are shown during synchronisations in offline (if not explicitly requested) (#699).
|
||||||
|
- Missing some checking during automatic-synchronisation now works correctly.
|
||||||
|
|
||||||
|
## 0.25.10
|
||||||
|
|
||||||
|
26th August, 2025
|
||||||
|
|
||||||
|
### New experimental feature
|
||||||
|
|
||||||
|
- We can perform Garbage Collection (Beta2) without rebuilding the entire database, and also fetch the database.
|
||||||
|
- Note that this feature is very experimental and should be used with caution.
|
||||||
|
- This feature requires disabling `Fetch chunks on demand`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Resetting the bucket now properly clears all uploaded files.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Some files have been moved to better reflect their purpose and improve maintainability.
|
||||||
|
- The extensive LiveSyncLocalDB has been split into separate files for each role.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Unexpected `Failed to obtain PBKDF2 salt` or similar errors during bucket-synchronisation no longer occur.
|
||||||
|
- Unexpected long delays for chunk-missing documents when using bucket-synchronisation have been resolved.
|
||||||
|
- Fetched remote chunks are now properly stored in the local database if `Fetch chunks on demand` is enabled.
|
||||||
|
- The 'fetch' dialogue's message has been refined.
|
||||||
|
- No longer overwriting any corrupted documents to the storage on boot-sequence.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Type errors have been corrected.
|
||||||
|
|
||||||
|
## 0.25.9
|
||||||
|
|
||||||
|
20th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- CORS Checking messages now use replacements.
|
||||||
|
- Configuring CORS setting via the UI now respects the existing rules.
|
||||||
|
- Now startup-checking works correctly again, performs migration check serially and then it will also fix starting LiveSync or start-up sync. (#696)
|
||||||
|
- Statusline in editor now supported 'Bases'.
|
||||||
|
|
||||||
|
## 0.25.8
|
||||||
|
|
||||||
|
18th August, 2025
|
||||||
|
|
||||||
|
### New feature
|
||||||
|
|
||||||
|
- Insecure chunk detection has been implemented.
|
||||||
|
- A notification dialogue will be shown if any insecure chunks are detected; these may have been created by v0.25.6 due to its issue. If this dialogue appears, please ensure you rebuild the database after backing it up.
|
||||||
|
|
||||||
## 0.25.7
|
## 0.25.7
|
||||||
|
|
||||||
@@ -49,7 +124,6 @@ In next version, insecure chunk detection will be implemented.
|
|||||||
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
||||||
- Removed unused code.
|
- Removed unused code.
|
||||||
|
|
||||||
|
|
||||||
## ~~0.25.5~~ 0.25.6
|
## ~~0.25.5~~ 0.25.6
|
||||||
|
|
||||||
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
||||||
@@ -150,7 +224,6 @@ The PBKDF2Salt will be referred to as the `Security Seed`, and it is used to der
|
|||||||
- The Set-up URI is now encrypted with a new encryption algorithm (mostly the same as `V2`).
|
- The Set-up URI is now encrypted with a new encryption algorithm (mostly the same as `V2`).
|
||||||
- The new Set-up URI is not compatible with version 0.24.x or earlier.
|
- The new Set-up URI is not compatible with version 0.24.x or earlier.
|
||||||
|
|
||||||
|
|
||||||
## 0.25.0
|
## 0.25.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -368,7 +441,6 @@ However, just to whisper, this is tremendously fast.
|
|||||||
- Dependent libraries have been updated to the latest version.
|
- Dependent libraries have been updated to the latest version.
|
||||||
- Some build processes have been separated to `pre` and `post` processes.
|
- Some build processes have been separated to `pre` and `post` processes.
|
||||||
|
|
||||||
|
|
||||||
## 0.24.25
|
## 0.24.25
|
||||||
|
|
||||||
22nd April, 2025
|
22nd April, 2025
|
||||||
@@ -446,8 +518,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- No longer conflicted files are handled in the boot-up process. No more
|
- No longer conflicted files are handled in the boot-up process. No more
|
||||||
unexpected overwriting.
|
unexpected overwriting.
|
||||||
- It ignores `Always overwrite with a newer file`, and always be prevented for
|
- It ignores `Always overwrite with a newer file`, and always be prevented for
|
||||||
the safety. Please pick it manually or open the file.
|
the safety. Please pick it manually or open the file.
|
||||||
- Some log messages on conflict resolution has been corrected.
|
- Some log messages on conflict resolution has been corrected.
|
||||||
- Automatic merge notifications, displayed on the grounds of `same`, have been
|
- Automatic merge notifications, displayed on the grounds of `same`, have been
|
||||||
degraded to logs.
|
degraded to logs.
|
||||||
@@ -456,8 +528,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- Now we can fetch the remote database with keeping local files completely
|
- Now we can fetch the remote database with keeping local files completely
|
||||||
intact.
|
intact.
|
||||||
- In new option, all files are stored into the local database before the
|
- In new option, all files are stored into the local database before the
|
||||||
fetching, and will be merged automatically or detected as conflicts.
|
fetching, and will be merged automatically or detected as conflicts.
|
||||||
- The dialogue presenting options when performing `Fetch` are now more
|
- The dialogue presenting options when performing `Fetch` are now more
|
||||||
informative.
|
informative.
|
||||||
|
|
||||||
@@ -479,8 +551,8 @@ However, just to whisper, this is tremendously fast.
|
|||||||
|
|
||||||
- **NOW INDEED AND ACTUALLY** `Compute revisions for chunks` are backed into
|
- **NOW INDEED AND ACTUALLY** `Compute revisions for chunks` are backed into
|
||||||
enabled again. it is necessary for garbage collection of chunks.
|
enabled again. it is necessary for garbage collection of chunks.
|
||||||
- As far as existing users are concerned, this will not automatically change,
|
- As far as existing users are concerned, this will not automatically change,
|
||||||
but the Doctor will inform us.
|
but the Doctor will inform us.
|
||||||
|
|
||||||
## 0.24.19
|
## 0.24.19
|
||||||
|
|
||||||
@@ -521,7 +593,6 @@ Confession. I got the default values wrong. So scary and sorry.
|
|||||||
|
|
||||||
## 0.24.16
|
## 0.24.16
|
||||||
|
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
|
|
||||||
#### Peer-to-Peer
|
#### Peer-to-Peer
|
||||||
@@ -602,7 +673,6 @@ Sorry for the lack of replies. The ones that were not good are popping up, so I
|
|||||||
- Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic.
|
- Displaying replication status of the peer-to-peer synchronisation is separated from the main-log-logic.
|
||||||
- Some file names have been changed to be more consistent.
|
- Some file names have been changed to be more consistent.
|
||||||
|
|
||||||
|
|
||||||
## 0.24.12
|
## 0.24.12
|
||||||
|
|
||||||
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
I created a SPA called [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) (well, right... I will think of a name again), which replaces the server when using Peer-to-Peer synchronisation. This is a pseudo-client that appears to other devices as if it were one of the clients. . As with the client, it receives and sends data without storing it as a file.
|
||||||
@@ -618,7 +688,6 @@ And, this is just a single web page, without any server-side code. It is a stati
|
|||||||
- And you can see the actual usage of this on [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) that a pseudo client for peer-to-peer synchronisation.
|
- And you can see the actual usage of this on [webpeer](https://github.com/vrtmrz/livesync-commonlib/tree/main/apps/webpeer) that a pseudo client for peer-to-peer synchronisation.
|
||||||
- Some UIs have been got isomorphic among Obsidian and web applications (for `webpeer`).
|
- Some UIs have been got isomorphic among Obsidian and web applications (for `webpeer`).
|
||||||
|
|
||||||
|
|
||||||
## 0.24.11
|
## 0.24.11
|
||||||
|
|
||||||
Peer-to-peer synchronisation has been implemented!
|
Peer-to-peer synchronisation has been implemented!
|
||||||
|
|||||||
Reference in New Issue
Block a user