Compare commits

...

7 Commits

Author SHA1 Message Date
vorotamoroz
9503474d06 bump 2025-06-15 18:49:31 +09:00
vorotamoroz
ddf7b243e4 ## 0.24.28
### Fixed

- Batch Update is no longer available in LiveSync mode to avoid unexpected behaviour. (#653)
- Now compatible with Cloudflare R2 again for bucket synchronisation.
- Prevention of broken behaviour due to database connection failures added (#649).
2025-06-15 18:49:16 +09:00
vorotamoroz
f37561c3c1 Update Library 2025-06-15 18:24:19 +09:00
vorotamoroz
f01429decc Merge pull request #633 from jmarmstrong1207/patch-1
Add simple docker compose to the setup guide
2025-06-10 11:27:22 +09:00
vorotamoroz
c0fcb66924 bump 2025-06-10 02:52:45 +01:00
vorotamoroz
5f76b9809b ## 0.24.27
### Improved

- We can use prefix for path for the Bucket synchronisation.
- The "Use Request API to avoid `inevitable` CORS problem" option is now promoted to the normal setting, not a niche patch.

### Fixed

- Now switching replicators applied immediately, without the need to restart Obsidian.

### Tidied up

- Some dependencies have been updated to the latest version.
2025-06-10 02:51:47 +01:00
James Armstrong
1f1a39e5a0 Add simple docker compose 2025-04-28 06:10:29 -07:00
10 changed files with 1767 additions and 2498 deletions

View File

@@ -49,6 +49,23 @@ Once CouchDB run, these directories will be owned by uid:`5984`. Please chown it
``` ```
$ docker run --name couchdb-for-ols -d --restart always -e COUCHDB_USER=${username} -e COUCHDB_PASSWORD=${password} -v ${PWD}/couchdb-data:/opt/couchdb/data -v ${PWD}/couchdb-etc:/opt/couchdb/etc/local.d -p 5984:5984 couchdb $ docker run --name couchdb-for-ols -d --restart always -e COUCHDB_USER=${username} -e COUCHDB_PASSWORD=${password} -v ${PWD}/couchdb-data:/opt/couchdb/data -v ${PWD}/couchdb-etc:/opt/couchdb/etc/local.d -p 5984:5984 couchdb
``` ```
If you prefer a compose file instead of docker run, here is the equivalent below:
```
services:
couchdb:
image: couchdb:latest
container_name: couchdb-for-ols
user: 1000:1000
environment:
- COUCHDB_USER=${username}
- COUCHDB_PASSWORD=${password}
volumes:
- ./couchdb-data:/opt/couchdb/data
- ./couchdb-etc:/opt/couchdb/etc/local.d
ports:
- 5984:5984
restart: unless-stopped
```
### B. Install CouchDB directly ### B. Install CouchDB directly
Please refer to the [official document](https://docs.couchdb.org/en/stable/install/index.html). However, we do not have to configure it fully. Just the administrator needs to be configured. Please refer to the [official document](https://docs.couchdb.org/en/stable/install/index.html). However, we do not have to configure it fully. Just the administrator needs to be configured.

View File

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

4056
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.24.26", "version": "0.24.28",
"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",
@@ -81,10 +81,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.25", "octagonal-wheels": "^0.1.30",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
"svelte-check": "^4.1.7", "svelte-check": "^4.1.7",
"trystero": "^0.21.3", "trystero": "^0.21.5",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
} }
} }

Submodule src/lib updated: d53cad1c68...3f3cf7d61d

View File

@@ -27,20 +27,28 @@ import {
updatePreviousExecutionTime, updatePreviousExecutionTime,
} from "../../common/utils"; } from "../../common/utils";
import { isAnyNote } from "../../lib/src/common/utils"; import { isAnyNote } from "../../lib/src/common/utils";
import { EVENT_FILE_SAVED, eventHub } from "../../common/events"; import { EVENT_FILE_SAVED, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator"; import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
import { globalSlipBoard } from "../../lib/src/bureau/bureau"; import { globalSlipBoard } from "../../lib/src/bureau/bureau";
import { $msg } from "../../lib/src/common/i18n"; import { $msg } from "../../lib/src/common/i18n";
const KEY_REPLICATION_ON_EVENT = "replicationOnEvent"; const KEY_REPLICATION_ON_EVENT = "replicationOnEvent";
const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000; const REPLICATION_ON_EVENT_FORECASTED_TIME = 5000;
export class ModuleReplicator extends AbstractModule implements ICoreModule { export class ModuleReplicator extends AbstractModule implements ICoreModule {
_replicatorType?: string;
$everyOnloadAfterLoadSettings(): Promise<boolean> { $everyOnloadAfterLoadSettings(): Promise<boolean> {
eventHub.onEvent(EVENT_FILE_SAVED, () => { eventHub.onEvent(EVENT_FILE_SAVED, () => {
if (this.settings.syncOnSave && !this.core.$$isSuspended()) { if (this.settings.syncOnSave && !this.core.$$isSuspended()) {
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent()); scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent());
} }
}); });
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
if (this._replicatorType !== setting.remoteType) {
void this.setReplicator();
}
});
return Promise.resolve(true); return Promise.resolve(true);
} }
@@ -50,7 +58,12 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE); this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
return false; return false;
} }
if (this.core.replicator) {
await this.core.replicator.closeReplication();
this._log("Replicator closed for changing", LOG_LEVEL_VERBOSE);
}
this.core.replicator = replicator; this.core.replicator = replicator;
this._replicatorType = this.settings.remoteType;
await yieldMicrotask(); await yieldMicrotask();
return true; return true;
} }
@@ -231,34 +244,49 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
async loadQueuedFiles() { async loadQueuedFiles() {
if (this.settings.suspendParseReplicationResult) return; if (this.settings.suspendParseReplicationResult) return;
if (!this.settings.isConfigured) return; if (!this.settings.isConfigured) return;
const kvDBKey = "queued-files"; try {
// const ids = [...new Set(JSON.parse(localStorage.getItem(lsKey) || "[]"))] as string[]; const kvDBKey = "queued-files";
const ids = [...new Set((await this.core.kvDB.get<string[]>(kvDBKey)) ?? [])]; // const ids = [...new Set(JSON.parse(localStorage.getItem(lsKey) || "[]"))] as string[];
const batchSize = 100; const ids = [...new Set((await this.core.kvDB.get<string[]>(kvDBKey)) ?? [])];
const chunkedIds = arrayToChunkedArray(ids, batchSize); const batchSize = 100;
const chunkedIds = arrayToChunkedArray(ids, batchSize);
// suspendParseReplicationResult is true, so we have to resume it if it is suspended. // suspendParseReplicationResult is true, so we have to resume it if it is suspended.
if (this.replicationResultProcessor.isSuspended) { if (this.replicationResultProcessor.isSuspended) {
this.replicationResultProcessor.resume(); this.replicationResultProcessor.resume();
} }
for await (const idsBatch of chunkedIds) { for await (const idsBatch of chunkedIds) {
const ret = await this.localDatabase.allDocsRaw<EntryDoc>({ const ret = await this.localDatabase.allDocsRaw<EntryDoc>({
keys: idsBatch, keys: idsBatch,
include_docs: true, include_docs: true,
limit: 100, limit: 100,
}); });
const docs = ret.rows.filter((e) => e.doc).map((e) => e.doc) as PouchDB.Core.ExistingDocument<EntryDoc>[]; const docs = ret.rows
const errors = ret.rows.filter((e) => !e.doc && !e.value.deleted); .filter((e) => e.doc)
if (errors.length > 0) { .map((e) => e.doc) as PouchDB.Core.ExistingDocument<EntryDoc>[];
Logger("Some queued processes were not resurrected"); const errors = ret.rows.filter((e) => !e.doc && !e.value.deleted);
Logger(JSON.stringify(errors), LOG_LEVEL_VERBOSE); if (errors.length > 0) {
Logger("Some queued processes were not resurrected");
Logger(JSON.stringify(errors), LOG_LEVEL_VERBOSE);
}
this.replicationResultProcessor.enqueueAll(docs);
}
} catch (e) {
Logger(`Failed to load queued files.`, LOG_LEVEL_NOTICE);
Logger(e, LOG_LEVEL_VERBOSE);
} finally {
// Check again before awaiting,
if (this.replicationResultProcessor.isSuspended) {
this.replicationResultProcessor.resume();
} }
this.replicationResultProcessor.enqueueAll(docs);
} }
if (this.replicationResultProcessor.isSuspended) { // Wait for all queued files to be processed.
this.replicationResultProcessor.resume(); try {
await this.replicationResultProcessor.waitForAllProcessed();
} catch (e) {
Logger(`Failed to wait for all queued files to be processed.`, LOG_LEVEL_NOTICE);
Logger(e, LOG_LEVEL_VERBOSE);
} }
await this.replicationResultProcessor.waitForAllProcessed();
} }
replicationResultProcessor = new QueueProcessor( replicationResultProcessor = new QueueProcessor(

View File

@@ -138,6 +138,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
jwtKid: settings.jwtKid, jwtKid: settings.jwtKid,
jwtExpDuration: settings.jwtExpDuration, jwtExpDuration: settings.jwtExpDuration,
jwtSub: settings.jwtSub, jwtSub: settings.jwtSub,
useRequestAPI: settings.useRequestAPI,
bucketPrefix: settings.bucketPrefix,
}; };
settings.encryptedCouchDBConnection = await this.encryptConfigurationItem( settings.encryptedCouchDBConnection = await this.encryptConfigurationItem(
JSON.stringify(connectionSetting), JSON.stringify(connectionSetting),

View File

@@ -1475,6 +1475,10 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
new Setting(paneEl).autoWireText("region", { holdValue: true }); new Setting(paneEl).autoWireText("region", { holdValue: true });
new Setting(paneEl).autoWireText("bucket", { holdValue: true }); new Setting(paneEl).autoWireText("bucket", { holdValue: true });
new Setting(paneEl).autoWireText("bucketPrefix", {
holdValue: true,
placeHolder: "vaultname/",
});
new Setting(paneEl).autoWireToggle("useCustomRequestHandler", { holdValue: true }); new Setting(paneEl).autoWireToggle("useCustomRequestHandler", { holdValue: true });
new Setting(paneEl).autoWireTextArea("bucketCustomHeaders", { new Setting(paneEl).autoWireTextArea("bucketCustomHeaders", {
@@ -1503,6 +1507,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
"bucket", "bucket",
"useCustomRequestHandler", "useCustomRequestHandler",
"bucketCustomHeaders", "bucketCustomHeaders",
"bucketPrefix",
]) ])
.addOnUpdate(onlyOnMinIO); .addOnUpdate(onlyOnMinIO);
}); });
@@ -1662,6 +1667,10 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
onUpdate: enableOnlySyncDisabled, onUpdate: enableOnlySyncDisabled,
}); });
new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true }); new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true });
new Setting(paneEl).autoWireToggle("useRequestAPI", {
holdValue: true,
onUpdate: enableOnlySyncDisabled,
});
new Setting(paneEl) new Setting(paneEl)
.setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection")) .setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
.setClass("wizardHidden") .setClass("wizardHidden")
@@ -1706,6 +1715,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
"jwtKid", "jwtKid",
"useJWT", "useJWT",
"couchDB_CustomHeaders", "couchDB_CustomHeaders",
"useRequestAPI",
]) ])
.addOnUpdate(onlyOnCouchDB); .addOnUpdate(onlyOnCouchDB);
}); });
@@ -2080,7 +2090,12 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
.autoWireToggle("syncAfterMerge", { onUpdate: onlyOnNonLiveSync }); .autoWireToggle("syncAfterMerge", { onUpdate: onlyOnNonLiveSync });
}); });
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleUpdateThinning")).then((paneEl) => { void addPanel(
paneEl,
$msg("obsidianLiveSyncSettingTab.titleUpdateThinning"),
undefined,
visibleOnly(() => !this.isConfiguredAs("syncMode", "LIVESYNC"))
).then((paneEl) => {
paneEl.addClass("wizardHidden"); paneEl.addClass("wizardHidden");
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("batchSave"); new Setting(paneEl).setClass("wizardHidden").autoWireToggle("batchSave");
new Setting(paneEl).setClass("wizardHidden").autoWireNumeric("batchSaveMinimumDelay", { new Setting(paneEl).setClass("wizardHidden").autoWireNumeric("batchSaveMinimumDelay", {
@@ -3104,9 +3119,9 @@ ${stringifyYaml(pluginConfig)}`;
onUpdate: visibleOnly(() => this.isConfiguredAs("disableWorkerForGeneratingChunks", false)), onUpdate: visibleOnly(() => this.isConfiguredAs("disableWorkerForGeneratingChunks", false)),
}); });
}); });
void addPanel(paneEl, "Edge case addressing (Networking)").then((paneEl) => { // void addPanel(paneEl, "Edge case addressing (Networking)").then((paneEl) => {
new Setting(paneEl).autoWireToggle("useRequestAPI"); // new Setting(paneEl).autoWireToggle("useRequestAPI");
}); // });
void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => { void addPanel(paneEl, "Compatibility (Trouble addressed)").then((paneEl) => {
new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch"); new Setting(paneEl).autoWireToggle("disableCheckingConfigMismatch");
}); });
@@ -3547,6 +3562,7 @@ ${stringifyYaml(pluginConfig)}`;
const id = this.plugin.settings.accessKey; const id = this.plugin.settings.accessKey;
const key = this.plugin.settings.secretKey; const key = this.plugin.settings.secretKey;
const bucket = this.plugin.settings.bucket; const bucket = this.plugin.settings.bucket;
const prefix = this.plugin.settings.bucketPrefix;
const region = this.plugin.settings.region; const region = this.plugin.settings.region;
const endpoint = this.plugin.settings.endpoint; const endpoint = this.plugin.settings.endpoint;
const useCustomRequestHandler = this.plugin.settings.useCustomRequestHandler; const useCustomRequestHandler = this.plugin.settings.useCustomRequestHandler;
@@ -3556,6 +3572,7 @@ ${stringifyYaml(pluginConfig)}`;
key, key,
endpoint, endpoint,
bucket, bucket,
prefix,
this.plugin.simpleStore, this.plugin.simpleStore,
this.plugin, this.plugin,
useCustomRequestHandler, useCustomRequestHandler,

View File

@@ -385,6 +385,10 @@ export const SettingInformation: Partial<Record<keyof AllSettings, Configuration
name: "Show status icon instead of file warnings banner", name: "Show status icon instead of file warnings banner",
desc: "If enabled, the ⛔ icon will be shown inside the status instead of the file warnings banner. No details will be shown.", desc: "If enabled, the ⛔ icon will be shown inside the status instead of the file warnings banner. No details will be shown.",
}, },
bucketPrefix: {
name: "File prefix on the bucket",
desc: "Effectively a directory. Should end with `/`. e.g., `vault-name/`.",
},
}; };
function translateInfo(infoSrc: ConfigurationItem | undefined | false) { function translateInfo(infoSrc: ConfigurationItem | undefined | false) {
if (!infoSrc) return false; if (!infoSrc) return false;

View File

@@ -1,3 +1,28 @@
## 0.24.28
### Fixed
- Batch Update is no longer available in LiveSync mode to avoid unexpected behaviour. (#653)
- Now compatible with Cloudflare R2 again for bucket synchronisation.
- @edo-bari-ikutsu, thank you for [your contribution](https://github.com/vrtmrz/livesync-commonlib/pull/12)!
- Prevention of broken behaviour due to database connection failures added (#649).
## 0.24.27
### Improved
- We can use prefix for path for the Bucket synchronisation.
- For example, if you set the `vaultName/` as a prefix for the bucket in the root directory, all data will be transferred to the bucket under the `vaultName/` directory.
- The "Use Request API to avoid `inevitable` CORS problem" option is now promoted to the normal setting, not a niche patch.
### Fixed
- Now switching replicators applied immediately, without the need to restart Obsidian.
### Tidied up
- Some dependencies have been updated to the latest version.
## 0.24.26 ## 0.24.26
This update introduces an option to circumvent Cross-Origin Resource Sharing This update introduces an option to circumvent Cross-Origin Resource Sharing
@@ -30,18 +55,18 @@ However, just to whisper, this is tremendously fast.
- Automatic display-language changing according to the Obsidian language - Automatic display-language changing according to the Obsidian language
setting. setting.
- We will be asked on the migration or first startup. - We will be asked on the migration or first startup.
- **Note: Please revert to the default language if you report any issues.** - **Note: Please revert to the default language if you report any issues.**
- Not all messages are translated yet. We welcome your contribution! - Not all messages are translated yet. We welcome your contribution!
- Now we can limit files to be synchronised even in the hidden files. - Now we can limit files to be synchronised even in the hidden files.
- "Use Request API to avoid `inevitable` CORS problem" has been implemented. - "Use Request API to avoid `inevitable` CORS problem" has been implemented.
- Less secure, please use it only if you are sure that you are in the trusted - Less secure, please use it only if you are sure that you are in the trusted
environment and be able to ignore the CORS. No `Web viewer` or similar tools environment and be able to ignore the CORS. No `Web viewer` or similar tools
are recommended. (To avoid the origin forged attack). If you are able to are recommended. (To avoid the origin forged attack). If you are able to
configure the server setting, always that is recommended. configure the server setting, always that is recommended.
- `Show status icon instead of file warnings banner` has been implemented. - `Show status icon instead of file warnings banner` has been implemented.
- If enabled, the ⛔ icon will be shown inside the status instead of the file - If enabled, the ⛔ icon will be shown inside the status instead of the file
warnings banner. No details will be shown. warnings banner. No details will be shown.
### Improved ### Improved
@@ -55,7 +80,7 @@ However, just to whisper, this is tremendously fast.
- We can purge the remote bucket again if we using MinIO instead of AWS S3 or - We can purge the remote bucket again if we using MinIO instead of AWS S3 or
Cloudflare R2. Cloudflare R2.
- Purging the remote bucket is now more reliable. - Purging the remote bucket is now more reliable.
- 100 files are purged at a time. - 100 files are purged at a time.
- Some wrong messages have been fixed. - Some wrong messages have been fixed.
### Behaviour changed ### Behaviour changed
@@ -63,8 +88,8 @@ However, just to whisper, this is tremendously fast.
- Entering into the deeper directories to gather the hidden files is now limited - Entering into the deeper directories to gather the hidden files is now limited
by `/` or `\/` prefixed ignore filters. (It means that directories are scanned by `/` or `\/` prefixed ignore filters. (It means that directories are scanned
deeper than before). deeper than before).
- However, inside the these directories, the files are still limited by the - However, inside the these directories, the files are still limited by the
ignore filters. ignore filters.
### Etcetera ### Etcetera
@@ -104,25 +129,25 @@ However, just to whisper, this is tremendously fast.
- Bucket synchronisation has been enhanced for better performance and - Bucket synchronisation has been enhanced for better performance and
reliability. reliability.
- Now less duplicated chunks are sent to the server. Note: If you have - Now less duplicated chunks are sent to the server. Note: If you have
encountered about too less chunks, please let me know. However, you can send encountered about too less chunks, please let me know. However, you can send
it to the server by `Overwrite remote`. it to the server by `Overwrite remote`.
- Fetching conflicted files from the server is now more reliable. - Fetching conflicted files from the server is now more reliable.
- Dependent libraries have been updated to the latest version. - Dependent libraries have been updated to the latest version.
- Also, let me know if you have encountered any issues with this update. - Also, let me know if you have encountered any issues with this update.
Especially you are using a device that has been in use for a little Especially you are using a device that has been in use for a little
longer. longer.
## 0.24.23 ## 0.24.23
### New Feature ### New Feature
- Now, we can send custom headers to the server. - Now, we can send custom headers to the server.
- They can be sent to either CouchDB or Object Storage. - They can be sent to either CouchDB or Object Storage.
- Authentication with JWT in CouchDB is now supported. - Authentication with JWT in CouchDB is now supported.
- I will describe steps later, but please refer to the - I will describe steps later, but please refer to the
[CouchDB document](https://docs.couchdb.org/en/stable/config/auth.html#authentication-configuration). [CouchDB document](https://docs.couchdb.org/en/stable/config/auth.html#authentication-configuration).
- A JWT keypair for testing can be generated in the setting dialogue. - A JWT keypair for testing can be generated in the setting dialogue.
### Improved ### Improved