Compare commits

...

14 Commits

Author SHA1 Message Date
vorotamoroz
4354cc3054 bump 2023-06-01 13:05:29 +09:00
vorotamoroz
0664427c63 Refined:
- Configuration dialogue refined.
2023-06-01 13:02:56 +09:00
vorotamoroz
49c4736d69 Improved:
- Confirmation for new adapters while rebuilding.
- Batched file is now shown in digits.

Fixed:
- Some framework have been upgraded.
2023-06-01 12:47:41 +09:00
vorotamoroz
f0ce8f0e05 Fixed:
- Import declarations
- Logging has been tweaked
Improved:
2023-06-01 12:36:10 +09:00
vorotamoroz
0a70afc5a3 Update issue templates 2023-05-24 11:51:53 +09:00
vorotamoroz
431239a736 Merge pull request #218 from garlic-hub/garlic-hub-patch-1
Update setup_own_server.md
2023-05-23 17:46:35 +09:00
vorotamoroz
1ceb671683 bump 2023-05-23 17:40:47 +09:00
vorotamoroz
ea40e5918c Fixed:
- Now hidden file synchronisation would not be hanged, even if so many files exist.

Improved:
- Customisation sync works more smoothly.
2023-05-23 17:39:02 +09:00
garlic-hub
64681729ff Update setup_own_server.md 2023-05-23 04:49:50 +00:00
vorotamoroz
830f2f25d1 update a dependency. 2023-05-17 16:27:35 +09:00
vorotamoroz
05f0abebf0 bump 2023-05-17 16:26:46 +09:00
vorotamoroz
842da980d7 Improved:
- Reduced remote database checking to improve speed and reduce bandwidth.

Fixed:
- Chunks which previously misinterpreted are now interpreted correctly.
- Deleted file detection on hidden file synchronising now works fine.
- Now the Customisation sync is surely quiet while it has been disabled.
2023-05-17 16:20:07 +09:00
vorotamoroz
d8ecbb593b bump 2023-05-09 18:03:57 +09:00
vorotamoroz
8d66c372e1 Improved:
- Now replication will be paced by collecting chunks.
2023-05-09 17:49:40 +09:00
20 changed files with 1315 additions and 1067 deletions

78
.github/ISSUE_TEMPLATE/issue-report.md vendored Normal file
View File

@@ -0,0 +1,78 @@
---
name: Issue report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Thank you for taking the time to report this issue!
To improve the process, I would like to ask you to let me know the information in advance.
All instructions and examples, and empty entries can be deleted.
Just for your information, a [filled example](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Issue+example) is also written.
## Abstract
The synchronisation hung up immediately after connecting.
## Expected behaviour
- Synchronisation ends with the message `Replication completed`
- Everything synchronised
## Actually happened
- Synchronisation has been cancelled with the message `TypeError ... ` (captured in the attached log, around LL.10-LL.12)
- No files synchronised
## Reproducing procedure
1. Configure LiveSync as in the attached material.
2. Click the replication button on the ribbon.
3. Synchronising has begun.
4. About two or three seconds later, we got the error `TypeError ... `.
5. Replication has been stopped. No files synchronised.
Note: If you do not catch the reproducing procedure, please let me know the frequency and signs.
## Report materials
If the information is not available, do not hesitate to report it as it is. You can also of course omit it if you think this is indeed unnecessary. If it is necessary, I will ask you.
### Report from the LiveSync
For more information, please refer to [Making the report](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Making+the+report).
<details>
<summary>Report from hatch</summary>
```
<!-- paste here -->
```
</details>
### Obsidian debug info
<details>
<summary>Debug info</summary>
```
<!-- paste here -->
```
</details>
### Plug-in log
We can see the log by tapping the Document box icon. If you noticed something suspicious, please let me know.
Note: **Please enable `Verbose Log`**. For detail, refer to [Logging](https://docs.vrtmrz.net/LiveSync/hintandtrivia/Logging), please.
<details>
<summary>Plug-in log</summary>
```
<!-- paste here -->
```
</details>
### Network log
Network logs displayed in DevTools will possibly help with connection-related issues. To capture that, please refer to [DevTools](https://docs.vrtmrz.net/LiveSync/hintandtrivia/DevTools).
### Screenshots
If applicable, please add screenshots to help explain your problem.
### Other information, insights and intuition.
Please provide any additional context or information about the problem.

View File

@@ -1,13 +1,12 @@
# Setup CouchDB to your server # Setup a CouchDB server
## Configure
## Install CouchDB and access from a PC or Mac The easiest way to set up a CouchDB instance is using the official [docker image](https://hub.docker.com/_/couchdb).
The easiest way to set up the CouchDB is using the [docker image]((https://hub.docker.com/_/couchdb)). Some initial configuration is required. Create a `local.ini` to use Self-hosted LiveSync as follows:
But some additional configurations are required in `local.ini` to use from Self-hosted LiveSync, like below: ```ini
```
[couchdb] [couchdb]
single_node=true single_node=true
max_document_size = 50000000 max_document_size = 50000000
@@ -32,28 +31,62 @@ methods = GET, PUT, POST, HEAD, DELETE
max_age = 3600 max_age = 3600
``` ```
Make `local.ini` and run with docker run like this, you can launch the CouchDB. ## Run
### Docker CLI
You can launch CouchDB using your `local.ini` like this:
``` ```
$ docker run --rm -it -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb $ docker run --rm -it -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
``` ```
*Remember to replace the path with the path to your local.ini* *Remember to replace the path with the path to your local.ini*
Note: At this time, the file owner of local.ini became 5984:5984. It's the limitation docker image. please change the owner before editing local.ini again.
If you could confirm that Self-hosted LiveSync can sync with the server, launch the docker image as a background as you like. Run in detached mode:
Example to run docker in detached mode:
``` ```
$ docker run -d --restart always -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb $ docker run -d --restart always -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password -v /path/to/local.ini:/opt/couchdb/etc/local.ini -p 5984:5984 couchdb
``` ```
*Remember to replace the path with the path to your local.ini* *Remember to replace the path with the path to your local.ini*
### Docker Compose
Create a directory, place your `local.ini` within it, and create a `docker-compose.yml` alongside it. The directory structure should look similar to this:
```
obsidian-livesync
├── docker-compose.yml
└── local.ini
```
A good place to start for `docker-compose.yml`:
```yaml
version: "2.1"
services:
couchdb:
image: couchdb
container_name: obsidian-livesync
user: 1000:1000
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=password
volumes:
- ./data:/opt/couchdb/data
- ./local.ini:/opt/couchdb/etc/local.ini
ports:
- 5984:5984
restart: unless-stopped
```
And finally launch the container
```
# -d will launch detached so the container runs in background
docker compose up -d
```
## Access from a mobile device ## Access from a mobile device
If you want to access Self-hosted LiveSync from mobile devices, you need a valid SSL certificate. If you want to access Self-hosted LiveSync from mobile devices, you need a valid SSL certificate.
### Testing from a mobile ### Testing from a mobile
In the testing phase, [localhost.run](http://localhost.run/) or something like services is very useful. In the testing phase, [localhost.run](https://localhost.run/) or something like services is very useful.
example on using localhost.run) example using localhost.run:
``` ```
$ ssh -R 80:localhost:5984 nokey@localhost.run $ ssh -R 80:localhost:5984 nokey@localhost.run
Warning: Permanently added the RSA host key for IP address '35.171.254.69' to the list of known hosts. Warning: Permanently added the RSA host key for IP address '35.171.254.69' to the list of known hosts.

View File

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

112
package-lock.json generated
View File

@@ -1,22 +1,22 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.2", "version": "0.19.6",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.2", "version": "0.19.6",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"esbuild": "0.15.15",
"esbuild-svelte": "^0.7.3",
"idb": "^7.1.1", "idb": "^7.1.1",
"xxhash-wasm": "^0.4.2" "xxhash-wasm": "^0.4.2"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/svelte": "^4.0.1",
"@types/diff-match-patch": "^1.0.32", "@types/diff-match-patch": "^1.0.32",
"@types/node": "^20.2.5",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"@types/pouchdb-browser": "^6.1.3", "@types/pouchdb-browser": "^6.1.3",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
@@ -39,11 +39,11 @@
"pouchdb-mapreduce": "^8.0.1", "pouchdb-mapreduce": "^8.0.1",
"pouchdb-replication": "^8.0.1", "pouchdb-replication": "^8.0.1",
"pouchdb-utils": "^8.0.1", "pouchdb-utils": "^8.0.1",
"svelte": "^3.55.1", "svelte": "^3.59.1",
"svelte-preprocess": "^5.0.1", "svelte-preprocess": "^5.0.3",
"transform-pouch": "^2.0.0", "transform-pouch": "^2.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^4.9.5" "typescript": "^5.0.4"
} }
}, },
"node_modules/@codemirror/state": { "node_modules/@codemirror/state": {
@@ -203,6 +203,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@tsconfig/svelte": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
"integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
"dev": true
},
"node_modules/@types/codemirror": { "node_modules/@types/codemirror": {
"version": "0.0.108", "version": "0.0.108",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz",
@@ -252,9 +258,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "14.17.21", "version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.21.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
"integrity": "sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA==", "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==",
"dev": true "dev": true
}, },
"node_modules/@types/pouchdb": { "node_modules/@types/pouchdb": {
@@ -443,15 +449,6 @@
"integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
"dev": true "dev": true
}, },
"node_modules/@types/sass": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.43.1.tgz",
"integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.3.13", "version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -3719,23 +3716,22 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "3.55.1", "version": "3.59.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz",
"integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/svelte-preprocess": { "node_modules/svelte-preprocess": {
"version": "5.0.1", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz",
"integrity": "sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==", "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@types/pug": "^2.0.6", "@types/pug": "^2.0.6",
"@types/sass": "^1.43.1",
"detect-indent": "^6.1.0", "detect-indent": "^6.1.0",
"magic-string": "^0.27.0", "magic-string": "^0.27.0",
"sorcery": "^0.11.0", "sorcery": "^0.11.0",
@@ -3754,8 +3750,8 @@
"sass": "^1.26.8", "sass": "^1.26.8",
"stylus": "^0.55.0", "stylus": "^0.55.0",
"sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
"svelte": "^3.23.0", "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0",
"typescript": "^3.9.5 || ^4.0.0" "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@babel/core": { "@babel/core": {
@@ -3916,16 +3912,16 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.9.5", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
}, },
"engines": { "engines": {
"node": ">=4.2.0" "node": ">=12.20"
} }
}, },
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
@@ -4087,9 +4083,9 @@
"dev": true "dev": true
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.1.3", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
@@ -4233,6 +4229,12 @@
"fastq": "^1.6.0" "fastq": "^1.6.0"
} }
}, },
"@tsconfig/svelte": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-4.0.1.tgz",
"integrity": "sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==",
"dev": true
},
"@types/codemirror": { "@types/codemirror": {
"version": "0.0.108", "version": "0.0.108",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz",
@@ -4282,9 +4284,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "14.17.21", "version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.21.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
"integrity": "sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA==", "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==",
"dev": true "dev": true
}, },
"@types/pouchdb": { "@types/pouchdb": {
@@ -4473,15 +4475,6 @@
"integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==", "integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
"dev": true "dev": true
}, },
"@types/sass": {
"version": "1.43.1",
"resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.43.1.tgz",
"integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/semver": { "@types/semver": {
"version": "7.3.13", "version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -6787,19 +6780,18 @@
"dev": true "dev": true
}, },
"svelte": { "svelte": {
"version": "3.55.1", "version": "3.59.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.1.tgz",
"integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", "integrity": "sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==",
"dev": true "dev": true
}, },
"svelte-preprocess": { "svelte-preprocess": {
"version": "5.0.1", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.4.tgz",
"integrity": "sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==", "integrity": "sha512-ABia2QegosxOGsVlsSBJvoWeXy1wUKSfF7SWJdTjLAbx/Y3SrVevvvbFNQqrSJw89+lNSsM58SipmZJ5SRi5iw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/pug": "^2.0.6", "@types/pug": "^2.0.6",
"@types/sass": "^1.43.1",
"detect-indent": "^6.1.0", "detect-indent": "^6.1.0",
"magic-string": "^0.27.0", "magic-string": "^0.27.0",
"sorcery": "^0.11.0", "sorcery": "^0.11.0",
@@ -6910,9 +6902,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.9.5", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"dev": true "dev": true
}, },
"unbox-primitive": { "unbox-primitive": {
@@ -7047,9 +7039,9 @@
"dev": true "dev": true
}, },
"yaml": { "yaml": {
"version": "2.1.3", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==", "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
"dev": true "dev": true
}, },
"yocto-queue": { "yocto-queue": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.19.2", "version": "0.19.6",
"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",
@@ -13,7 +13,9 @@
"author": "vorotamoroz", "author": "vorotamoroz",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@tsconfig/svelte": "^4.0.1",
"@types/diff-match-patch": "^1.0.32", "@types/diff-match-patch": "^1.0.32",
"@types/node": "^20.2.5",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"@types/pouchdb-browser": "^6.1.3", "@types/pouchdb-browser": "^6.1.3",
"@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
@@ -36,16 +38,14 @@
"pouchdb-mapreduce": "^8.0.1", "pouchdb-mapreduce": "^8.0.1",
"pouchdb-replication": "^8.0.1", "pouchdb-replication": "^8.0.1",
"pouchdb-utils": "^8.0.1", "pouchdb-utils": "^8.0.1",
"svelte": "^3.55.1", "svelte": "^3.59.1",
"svelte-preprocess": "^5.0.1", "svelte-preprocess": "^5.0.3",
"transform-pouch": "^2.0.0", "transform-pouch": "^2.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"typescript": "^4.9.5" "typescript": "^5.0.4"
}, },
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"esbuild": "0.15.15",
"esbuild-svelte": "^0.7.3",
"idb": "^7.1.1", "idb": "^7.1.1",
"xxhash-wasm": "^0.4.2" "xxhash-wasm": "^0.4.2"
} }

View File

@@ -1,13 +1,13 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Notice, PluginManifest, stringifyYaml, parseYaml } from "./deps"; import { Notice, type PluginManifest, parseYaml } from "./deps";
import { EntryDoc, LoadedEntry, LOG_LEVEL, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID } from "./lib/src/types"; import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry } from "./lib/src/types";
import { LOG_LEVEL } from "./lib/src/types";
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types"; import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types";
import { delay, getDocData } from "./lib/src/utils"; import { Parallels, delay, getDocData } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { WrappedNotice } from "./lib/src/wrapper"; import { WrappedNotice } from "./lib/src/wrapper";
import { base64ToArrayBuffer, arrayBufferToBase64, readString, writeString, uint8ArrayToHexString } from "./lib/src/strbin"; import { base64ToArrayBuffer, arrayBufferToBase64, readString, uint8ArrayToHexString } from "./lib/src/strbin";
import { runWithLock } from "./lib/src/lock"; import { runWithLock } from "./lib/src/lock";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { stripAllPrefixes } from "./lib/src/path"; import { stripAllPrefixes } from "./lib/src/path";
@@ -17,13 +17,29 @@ import { PluginDialogModal } from "./dialogs";
import { JsonResolveModal } from "./JsonResolveModal"; import { JsonResolveModal } from "./JsonResolveModal";
function serialize<T>(obj: T): string {
return JSON.stringify(obj, null, 1);
}
function deserialize<T>(str: string, def: T) {
try {
return JSON.parse(str) as T;
} catch (ex) {
try {
return parseYaml(str);
} catch (ex) {
return def;
}
}
}
export const pluginList = writable([] as PluginDataExDisplay[]); export const pluginList = writable([] as PluginDataExDisplay[]);
export const pluginIsEnumerating = writable(false); export const pluginIsEnumerating = writable(false);
const encoder = new TextEncoder();
const hashString = (async (key: string) => { const hashString = (async (key: string) => {
const buff = writeString(key); // const buff = writeString(key);
const buff = encoder.encode(key);
const digest = await crypto.subtle.digest('SHA-256', buff); const digest = await crypto.subtle.digest('SHA-256', buff);
return uint8ArrayToHexString(new Uint8Array(digest)); return uint8ArrayToHexString(new Uint8Array(digest));
}) })
@@ -145,7 +161,7 @@ export class ConfigSync extends LiveSyncCommands {
if (this.plugin.suspended) { if (this.plugin.suspended) {
return; return;
} }
if (this.settings.autoSweepPlugins) { if (this.settings.autoSweepPlugins && this.settings.usePluginSync) {
await this.scanAllConfigFiles(false); await this.scanAllConfigFiles(false);
} }
this.periodicPluginSweepProcessor.enable(this.settings.autoSweepPluginsPeriodic && !this.settings.watchInternalFileChanges ? (PERIODIC_PLUGIN_SWEEP * 1000) : 0); this.periodicPluginSweepProcessor.enable(this.settings.autoSweepPluginsPeriodic && !this.settings.watchInternalFileChanges ? (PERIODIC_PLUGIN_SWEEP * 1000) : 0);
@@ -171,7 +187,7 @@ export class ConfigSync extends LiveSyncCommands {
const entries = [] as PluginDataExDisplay[] const entries = [] as PluginDataExDisplay[]
const plugins = this.localDatabase.findEntries(ICXHeader + "", `${ICXHeader}\u{10ffff}`, { include_docs: true }); const plugins = this.localDatabase.findEntries(ICXHeader + "", `${ICXHeader}\u{10ffff}`, { include_docs: true });
const semaphore = Semaphore(4); const semaphore = Semaphore(4);
const processes = [] as Promise<void>[]; const para = Parallels();
let count = 0; let count = 0;
pluginIsEnumerating.set(true); pluginIsEnumerating.set(true);
let processed = false; let processed = false;
@@ -184,16 +200,18 @@ export class ConfigSync extends LiveSyncCommands {
processed = true; processed = true;
const oldEntry = (this.pluginList.find(e => e.documentPath == path)); const oldEntry = (this.pluginList.find(e => e.documentPath == path));
if (oldEntry && oldEntry.mtime == plugin.mtime) continue; if (oldEntry && oldEntry.mtime == plugin.mtime) continue;
processes.push((async (v) => { await para.wait(5);
para.add((async (v) => {
const release = await semaphore.acquire(1); const release = await semaphore.acquire(1);
try { try {
Logger(`Enumerating files... ${count++}`, logLevel, "get-plugins"); count++;
if (count % 10 == 0) Logger(`Enumerating files... ${count}`, logLevel, "get-plugins");
Logger(`plugin-${path}`, LOG_LEVEL.VERBOSE); Logger(`plugin-${path}`, LOG_LEVEL.VERBOSE);
const wx = await this.localDatabase.getDBEntry(path, null, false, false); const wx = await this.localDatabase.getDBEntry(path, null, false, false);
if (wx) { if (wx) {
const data = parseYaml(getDocData(wx.data)) as PluginDataEx; const data = deserialize(getDocData(wx.data), {}) as PluginDataEx;
const xFiles = [] as PluginDataExFile[]; const xFiles = [] as PluginDataExFile[];
for (const file of data.files) { for (const file of data.files) {
const work = { ...file }; const work = { ...file };
@@ -217,7 +235,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
)(plugin)); )(plugin));
} }
await Promise.all(processes); await para.all();
let newList = [...this.pluginList]; let newList = [...this.pluginList];
for (const item of entries) { for (const item of entries) {
newList = newList.filter(x => x.documentPath != item.documentPath); newList = newList.filter(x => x.documentPath != item.documentPath);
@@ -241,9 +259,9 @@ export class ConfigSync extends LiveSyncCommands {
const docB = await this.localDatabase.getDBEntry(dataB.documentPath); const docB = await this.localDatabase.getDBEntry(dataB.documentPath);
if (docA && docB) { if (docA && docB) {
const pluginDataA = parseYaml(getDocData(docA.data)) as PluginDataEx; const pluginDataA = deserialize(getDocData(docA.data), {}) as PluginDataEx;
pluginDataA.documentPath = dataA.documentPath; pluginDataA.documentPath = dataA.documentPath;
const pluginDataB = parseYaml(getDocData(docB.data)) as PluginDataEx; const pluginDataB = deserialize(getDocData(docB.data), {}) as PluginDataEx;
pluginDataB.documentPath = dataB.documentPath; pluginDataB.documentPath = dataB.documentPath;
// Use outer structure to wrap each data. // Use outer structure to wrap each data.
@@ -282,7 +300,7 @@ export class ConfigSync extends LiveSyncCommands {
if (dx == false) { if (dx == false) {
throw "Not found on database" throw "Not found on database"
} }
const loadedData = parseYaml(getDocData(dx.data)) as PluginDataEx; const loadedData = deserialize(getDocData(dx.data), {}) as PluginDataEx;
for (const f of loadedData.files) { for (const f of loadedData.files) {
Logger(`Applying ${f.filename} of ${data.displayName || data.name}..`); Logger(`Applying ${f.filename} of ${data.displayName || data.name}..`);
try { try {
@@ -354,7 +372,7 @@ export class ConfigSync extends LiveSyncCommands {
async parseReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) { async parseReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
if (docs._id.startsWith(ICXHeader)) { if (docs._id.startsWith(ICXHeader)) {
if (this.plugin.settings.usePluginSync) { if (this.plugin.settings.usePluginSync) {
await this.updatePluginList(false, docs.path ? docs.path : this.getPath(docs)); await this.updatePluginList(false, (docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath((docs as AnyEntry)));
} }
if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) { if (this.plugin.settings.usePluginSync && this.plugin.settings.notifyPluginOrSettingUpdated) {
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) { if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
@@ -497,7 +515,7 @@ export class ConfigSync extends LiveSyncCommands {
for (const target of fileTargets) { for (const target of fileTargets) {
const data = await this.makeEntryFromFile(target); const data = await this.makeEntryFromFile(target);
if (data == false) { if (data == false) {
Logger(`Config: skipped: ${target} `, LOG_LEVEL.VERBOSE); // Logger(`Config: skipped: ${target} `, LOG_LEVEL.VERBOSE);
continue; continue;
} }
if (data.version) { if (data.version) {
@@ -520,7 +538,7 @@ export class ConfigSync extends LiveSyncCommands {
return return
} }
const content = stringifyYaml(dt); const content = serialize(dt);
try { try {
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false); const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false);
let saveData: LoadedEntry; let saveData: LoadedEntry;
@@ -567,6 +585,7 @@ export class ConfigSync extends LiveSyncCommands {
} }
async watchVaultRawEventsAsync(path: FilePath) { async watchVaultRawEventsAsync(path: FilePath) {
if (!this.settings.usePluginSync) return false;
if (!this.isTargetPath(path)) return false; if (!this.isTargetPath(path)) return false;
const stat = await this.app.vault.adapter.stat(path); const stat = await this.app.vault.adapter.stat(path);
// Make sure that target is a file. // Make sure that target is a file.

View File

@@ -1,14 +1,13 @@
import { Notice, normalizePath, PluginManifest } from "./deps"; import { Notice, normalizePath, PluginManifest } from "./deps";
import { EntryDoc, LoadedEntry, LOG_LEVEL, InternalFileEntry, FilePathWithPrefix, FilePath } from "./lib/src/types"; import { EntryDoc, LoadedEntry, LOG_LEVEL, InternalFileEntry, FilePathWithPrefix, FilePath } from "./lib/src/types";
import { InternalFileInfo, ICHeader, ICHeaderEnd } from "./types"; import { InternalFileInfo, ICHeader, ICHeaderEnd } from "./types";
import { delay, isDocContentSame } from "./lib/src/utils"; import { Parallels, delay, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask, isInternalMetadata, PeriodicProcessor } from "./utils"; import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject, scheduleTask, isInternalMetadata, PeriodicProcessor } from "./utils";
import { WrappedNotice } from "./lib/src/wrapper"; import { WrappedNotice } from "./lib/src/wrapper";
import { base64ToArrayBuffer, arrayBufferToBase64 } from "./lib/src/strbin"; import { base64ToArrayBuffer, arrayBufferToBase64 } from "./lib/src/strbin";
import { runWithLock } from "./lib/src/lock"; import { runWithLock } from "./lib/src/lock";
import { Semaphore } from "./lib/src/semaphore";
import { JsonResolveModal } from "./JsonResolveModal"; import { JsonResolveModal } from "./JsonResolveModal";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { addPrefix, stripAllPrefixes } from "./lib/src/path"; import { addPrefix, stripAllPrefixes } from "./lib/src/path";
@@ -254,37 +253,35 @@ export class HiddenFileSync extends LiveSyncCommands {
c = pieces.shift(); c = pieces.shift();
} }
}; };
const p = [] as Promise<void>[];
const semaphore = Semaphore(10);
// Cache update time information for files which have already been processed (mainly for files that were skipped due to the same content) // Cache update time information for files which have already been processed (mainly for files that were skipped due to the same content)
let caches: { [key: string]: { storageMtime: number; docMtime: number; }; } = {}; let caches: { [key: string]: { storageMtime: number; docMtime: number; }; } = {};
caches = await this.kvDB.get<{ [key: string]: { storageMtime: number; docMtime: number; }; }>("diff-caches-internal") || {}; caches = await this.kvDB.get<{ [key: string]: { storageMtime: number; docMtime: number; }; }>("diff-caches-internal") || {};
const filesMap = files.reduce((acc, cur) => {
acc[cur.path] = cur;
return acc;
}, {} as { [key: string]: InternalFileInfo; });
const filesOnDBMap = filesOnDB.reduce((acc, cur) => {
acc[stripAllPrefixes(this.getPath(cur))] = cur;
return acc;
}, {} as { [key: string]: InternalFileEntry; });
const para = Parallels();
for (const filename of allFileNames) { for (const filename of allFileNames) {
if (!filename) continue;
processed++; processed++;
if (processed % 100 == 0) if (processed % 100 == 0) {
Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal"); Logger(`Hidden file: ${processed}/${fileCount}`, logLevel, "sync_internal");
}
if (!filename) continue;
if (ignorePatterns.some(e => filename.match(e))) if (ignorePatterns.some(e => filename.match(e)))
continue; continue;
const fileOnStorage = files.find(e => e.path == filename); const fileOnStorage = filename in filesMap ? filesMap[filename] : undefined;
const fileOnDatabase = filesOnDB.find(e => stripAllPrefixes(this.getPath(e)) == filename); const fileOnDatabase = filename in filesOnDBMap ? filesOnDBMap[filename] : undefined;
const addProc = async (p: () => Promise<void>): Promise<void> => {
const releaser = await semaphore.acquire(1);
try {
return p();
} catch (ex) {
Logger("Some process failed", logLevel);
Logger(ex);
} finally {
releaser();
}
};
const cache = filename in caches ? caches[filename] : { storageMtime: 0, docMtime: 0 }; const cache = filename in caches ? caches[filename] : { storageMtime: 0, docMtime: 0 };
p.push(addProc(async () => { await para.wait(5);
const xFileOnStorage = fileOnStorage; const proc = (async (xFileOnStorage: InternalFileInfo, xFileOnDatabase: InternalFileEntry) => {
const xFileOnDatabase = fileOnDatabase;
if (xFileOnStorage && xFileOnDatabase) { if (xFileOnStorage && xFileOnDatabase) {
// Both => Synchronize // Both => Synchronize
if ((direction != "pullForce" && direction != "pushForce") && xFileOnDatabase.mtime == cache.docMtime && xFileOnStorage.mtime == cache.storageMtime) { if ((direction != "pullForce" && direction != "pushForce") && xFileOnDatabase.mtime == cache.docMtime && xFileOnStorage.mtime == cache.storageMtime) {
@@ -326,9 +323,11 @@ export class HiddenFileSync extends LiveSyncCommands {
throw new Error("Invalid state on hidden file sync"); throw new Error("Invalid state on hidden file sync");
// Something corrupted? // Something corrupted?
} }
}));
});
para.add(proc(fileOnStorage, fileOnDatabase))
} }
await Promise.all(p); await para.all();
await this.kvDB.set("diff-caches-internal", caches); await this.kvDB.set("diff-caches-internal", caches);
// When files has been retrieved from the database. they must be reloaded. // When files has been retrieved from the database. they must be reloaded.
@@ -495,7 +494,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const mtime = new Date().getTime(); const mtime = new Date().getTime();
await runWithLock("file-" + prefixedFileName, false, async () => { await runWithLock("file-" + prefixedFileName, false, async () => {
try { try {
const old = await this.localDatabase.getDBEntry(prefixedFileName, null, false, false) as InternalFileEntry | false; const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, true) as InternalFileEntry | false;
let saveData: InternalFileEntry; let saveData: InternalFileEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
@@ -541,7 +540,7 @@ export class HiddenFileSync extends LiveSyncCommands {
try { try {
// Check conflicted status // Check conflicted status
//TODO option //TODO option
const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, false); const fileOnDB = await this.localDatabase.getDBEntry(prefixedFileName, { conflicts: true }, false, true);
if (fileOnDB === false) if (fileOnDB === false)
throw new Error(`File not found on database.:${filename}`); throw new Error(`File not found on database.:${filename}`);
// Prevent overwrite for Prevent overwriting while some conflicted revision exists. // Prevent overwrite for Prevent overwriting while some conflicted revision exists.

View File

@@ -1,6 +1,7 @@
import { normalizePath, PluginManifest } from "./deps"; import { normalizePath, type PluginManifest } from "./deps";
import { DocumentID, EntryDoc, FilePathWithPrefix, LoadedEntry, LOG_LEVEL } from "./lib/src/types"; import type { DocumentID, EntryDoc, FilePathWithPrefix, LoadedEntry } from "./lib/src/types";
import { PluginDataEntry, PERIODIC_PLUGIN_SWEEP, PluginList, DevicePluginList, PSCHeader, PSCHeaderEnd } from "./types"; import { LOG_LEVEL } from "./lib/src/types";
import { type PluginDataEntry, PERIODIC_PLUGIN_SWEEP, type PluginList, type DevicePluginList, PSCHeader, PSCHeaderEnd } from "./types";
import { getDocData, isDocContentSame } from "./lib/src/utils"; import { getDocData, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";

View File

@@ -1,4 +1,4 @@
import { EntryDoc, ObsidianLiveSyncSettings, LOG_LEVEL, DEFAULT_SETTINGS } from "./lib/src/types"; import { type EntryDoc, type ObsidianLiveSyncSettings, LOG_LEVEL, DEFAULT_SETTINGS } from "./lib/src/types";
import { configURIBase } from "./types"; import { configURIBase } from "./types";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
@@ -284,8 +284,22 @@ Of course, we are able to disable these features.`
this.plugin.settings.syncAfterMerge = false; this.plugin.settings.syncAfterMerge = false;
//this.suspendExtraSync(); //this.suspendExtraSync();
} }
async askUseNewAdapter() {
if (!this.plugin.settings.useIndexedDBAdapter) {
const message = `Now this plugin has been configured to use the old database adapter for keeping compatibility. Do you want to deactivate it?`;
const CHOICE_YES = "Yes, disable and use latest";
const CHOICE_NO = "No, keep compatibility";
const choices = [CHOICE_YES, CHOICE_NO];
const ret = await confirmWithMessage(this.plugin, "Database adapter", message, choices, CHOICE_YES, 10);
if (ret == CHOICE_YES) {
this.plugin.settings.useIndexedDBAdapter = false;
}
}
}
async fetchLocal() { async fetchLocal() {
this.suspendExtraSync(); this.suspendExtraSync();
this.askUseNewAdapter();
await this.plugin.realizeSettingSyncMode(); await this.plugin.realizeSettingSyncMode();
await this.plugin.resetLocalDatabase(); await this.plugin.resetLocalDatabase();
await delay(1000); await delay(1000);
@@ -313,6 +327,7 @@ Of course, we are able to disable these features.`
} }
async rebuildEverything() { async rebuildEverything() {
this.suspendExtraSync(); this.suspendExtraSync();
this.askUseNewAdapter();
await this.plugin.realizeSettingSyncMode(); await this.plugin.realizeSettingSyncMode();
await this.plugin.resetLocalDatabase(); await this.plugin.resetLocalDatabase();
await delay(1000); await delay(1000);

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { type Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import type { FilePath, LoadedEntry } from "./lib/src/types"; import type { FilePath, LoadedEntry } from "./lib/src/types";
import { base64ToString } from "./lib/src/strbin"; import { base64ToString } from "./lib/src/strbin";
import { getDocData } from "./lib/src/utils"; import { getDocData } from "./lib/src/utils";

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
import type { PluginDataExDisplay } from "./CmdConfigSync"; import type { PluginDataExDisplay } from "./CmdConfigSync";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { versionNumberString2Number } from "./lib/src/strbin"; import { versionNumberString2Number } from "./lib/src/strbin";
import { FilePath, LOG_LEVEL } from "./lib/src/types"; import { type FilePath, LOG_LEVEL } from "./lib/src/types";
import { getDocData } from "./lib/src/utils"; import { getDocData } from "./lib/src/utils";
import type ObsidianLiveSyncPlugin from "./main"; import type ObsidianLiveSyncPlugin from "./main";
import { askString, scheduleTask } from "./utils"; import { askString, scheduleTask } from "./utils";

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { PluginDataExDisplay, pluginIsEnumerating, pluginList } from "./CmdConfigSync"; import { type PluginDataExDisplay, pluginIsEnumerating, pluginList } from "./CmdConfigSync";
import PluginCombo from "./PluginCombo.svelte"; import PluginCombo from "./PluginCombo.svelte";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;

View File

@@ -112,7 +112,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
} }
if (this.plugin.settings.batchSave) { if (this.plugin.settings.batchSave && !this.plugin.settings.liveSync) {
// if the latest event is the same type, omit that // if the latest event is the same type, omit that
// a.md MODIFY <- this should be cancelled when a.md MODIFIED // a.md MODIFY <- this should be cancelled when a.md MODIFIED
// b.md MODIFY <- this should be cancelled when b.md MODIFIED // b.md MODIFY <- this should be cancelled when b.md MODIFIED

Submodule src/lib updated: fb3070851f...63fa0074fe

View File

@@ -1,10 +1,10 @@
const isDebug = false; const isDebug = false;
import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch"; import { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "diff-match-patch";
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, RequestUrlParam, RequestUrlResponse, requestUrl } from "./deps"; import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, type RequestUrlParam, type RequestUrlResponse, requestUrl } from "./deps";
import { EntryDoc, LoadedEntry, ObsidianLiveSyncSettings, diff_check_result, diff_result_leaf, EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, ConfigPassphraseStore, CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, DatabaseConnectingStatus, EntryHasPath, DocumentID, FilePathWithPrefix, FilePath, AnyEntry } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type diff_check_result, type diff_result_leaf, type EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, type ConfigPassphraseStore, type CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, type AnyEntry } from "./lib/src/types";
import { InternalFileInfo, queueItem, CacheData, FileEventItem, FileWatchEventQueueMax } from "./types"; import { type InternalFileInfo, type queueItem, type CacheData, type FileEventItem, FileWatchEventQueueMax } from "./types";
import { getDocData, isDocContentSame } from "./lib/src/utils"; import { getDocData, isDocContentSame, Parallels } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { LogDisplayModal } from "./LogDisplayModal"; import { LogDisplayModal } from "./LogDisplayModal";
@@ -22,9 +22,9 @@ import { addPrefix, isPlainText, shouldBeIgnored, stripAllPrefixes } from "./lib
import { runWithLock } from "./lib/src/lock"; import { runWithLock } from "./lib/src/lock";
import { Semaphore } from "./lib/src/semaphore"; import { Semaphore } from "./lib/src/semaphore";
import { StorageEventManager, StorageEventManagerObsidian } from "./StorageEventManager"; import { StorageEventManager, StorageEventManagerObsidian } from "./StorageEventManager";
import { LiveSyncLocalDB, LiveSyncLocalDBEnv } from "./lib/src/LiveSyncLocalDB"; import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/LiveSyncLocalDB";
import { LiveSyncDBReplicator, LiveSyncReplicatorEnv } from "./lib/src/LiveSyncReplicator"; import { LiveSyncDBReplicator, type LiveSyncReplicatorEnv } from "./lib/src/LiveSyncReplicator";
import { KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB"; import { type KeyValueDatabase, OpenKeyValueDatabase } from "./KeyValueDB";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { HiddenFileSync } from "./CmdHiddenFileSync"; import { HiddenFileSync } from "./CmdHiddenFileSync";
import { SetupLiveSync } from "./CmdSetupLiveSync"; import { SetupLiveSync } from "./CmdSetupLiveSync";
@@ -87,7 +87,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
processReplication = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => this.parseReplicationResult(e); processReplication = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => this.parseReplicationResult(e);
async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> { async connectRemoteCouchDB(uri: string, auth: { username: string; password: string }, disableRequestURI: boolean, passphrase: string | false, useDynamicIterationCount: boolean, performSetup: boolean, skipInfo: boolean): Promise<string | { db: PouchDB.Database<EntryDoc>; info: PouchDB.Core.DatabaseInfo }> {
if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid"; if (!isValidRemoteCouchDBURI(uri)) return "Remote URI is not valid";
if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters."; if (uri.toLowerCase() != uri) return "Remote URI and database name could not contain capital letters.";
if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces."; if (uri.indexOf(" ") !== -1) return "Remote URI and database name could not contain spaces.";
@@ -104,6 +104,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = { const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
adapter: "http", adapter: "http",
auth, auth,
skip_setup: !performSetup,
fetch: async (url: string | Request, opts: RequestInit) => { fetch: async (url: string | Request, opts: RequestInit) => {
let size = ""; let size = "";
const localURL = url.toString().substring(uri.length); const localURL = url.toString().substring(uri.length);
@@ -192,6 +193,9 @@ export default class ObsidianLiveSyncPlugin extends Plugin
if (passphrase !== "false" && typeof passphrase === "string") { if (passphrase !== "false" && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount); enableEncryption(db, passphrase, useDynamicIterationCount);
} }
if (skipInfo) {
return { db: db, info: { db_name: "", doc_count: 0, update_seq: "" } };
}
try { try {
const info = await db.info(); const info = await db.info();
return { db: db, info: info }; return { db: db, info: info };
@@ -878,7 +882,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
async procFileEvent(applyBatch?: boolean) { async procFileEvent(applyBatch?: boolean) {
if (!this.isReady) return; if (!this.isReady) return;
if (this.settings.batchSave) { if (this.settings.batchSave && !this.settings.liveSync) {
if (!applyBatch && this.vaultManager.getQueueLength() < FileWatchEventQueueMax) { if (!applyBatch && this.vaultManager.getQueueLength() < FileWatchEventQueueMax) {
// Defer till applying batch save or queue has been grown enough. // Defer till applying batch save or queue has been grown enough.
// or 30 seconds after. // or 30 seconds after.
@@ -964,7 +968,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
async applyBatchChange() { async applyBatchChange() {
if (this.settings.batchSave) { if (this.settings.batchSave && !this.settings.liveSync) {
return await this.procFileEvent(true); return await this.procFileEvent(true);
} }
} }
@@ -1364,8 +1368,8 @@ export default class ObsidianLiveSyncPlugin extends Plugin
// If `Read chunks online` is disabled, chunks should be transferred before here. // If `Read chunks online` is disabled, chunks should be transferred before here.
// However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them. // However, in some cases, chunks are after that. So, if missing chunks exist, we have to wait for them.
if ((!this.settings.readChunksOnline) && "children" in doc) { if ((!this.settings.readChunksOnline) && "children" in doc) {
const c = await this.localDatabase.collectChunksWithCache(doc.children) const c = await this.localDatabase.collectChunksWithCache(doc.children as DocumentID[]);
const missing = c.filter((e) => !e.chunk).map((e) => e.id); const missing = c.filter((e) => e.chunk === false).map((e) => e.id);
if (missing.length > 0) Logger(`${path} (${doc._id}, ${doc._rev}) Queued (waiting ${missing.length} items)`, LOG_LEVEL.VERBOSE); if (missing.length > 0) Logger(`${path} (${doc._id}, ${doc._rev}) Queued (waiting ${missing.length} items)`, LOG_LEVEL.VERBOSE);
newQueue.missingChildren = missing; newQueue.missingChildren = missing;
this.queuedFiles.push(newQueue); this.queuedFiles.push(newQueue);
@@ -1381,15 +1385,15 @@ export default class ObsidianLiveSyncPlugin extends Plugin
const docsSorted = docs.sort((a, b) => b.mtime - a.mtime); const docsSorted = docs.sort((a, b) => b.mtime - a.mtime);
L1: L1:
for (const change of docsSorted) { for (const change of docsSorted) {
if (isChunk(change._id)) {
await this.parseIncomingChunk(change);
continue;
}
for (const proc of this.addOns) { for (const proc of this.addOns) {
if (await proc.parseReplicationResultItem(change)) { if (await proc.parseReplicationResultItem(change)) {
continue L1; continue L1;
} }
} }
if (isChunk(change._id)) {
await this.parseIncomingChunk(change);
continue;
}
if (change._id == SYNCINFO_ID) { if (change._id == SYNCINFO_ID) {
continue; continue;
} }
@@ -1466,9 +1470,11 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
this.statusBar.title = e.syncStatus; this.statusBar.title = e.syncStatus;
let waiting = ""; let waiting = "";
if (this.settings.batchSave) { if (this.settings.batchSave && !this.settings.liveSync) {
waiting = " " + "🛫".repeat(this.vaultManager.getQueueLength()); const len = this.vaultManager.getQueueLength();
waiting = waiting.replace(/(🛫){10}/g, "🚀"); if (len != 0) {
waiting = ` 🛫${len}`;
}
} }
let queued = ""; let queued = "";
const queue = Object.entries(e.queuedItems).filter((e) => !e[1].warned); const queue = Object.entries(e.queuedItems).filter((e) => !e[1].warned);
@@ -1476,7 +1482,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
if (queuedCount) { if (queuedCount) {
const pieces = queue.map((e) => e[1].missingChildren).reduce((prev, cur) => prev + cur.length, 0); const pieces = queue.map((e) => e[1].missingChildren).reduce((prev, cur) => prev + cur.length, 0);
queued = ` 🧩 ${queuedCount} (${pieces})`; queued = ` 🧩${queuedCount} (${pieces})`;
} }
const processes = e.count; const processes = e.count;
const processesDisp = processes == 0 ? "" : `${processes}`; const processesDisp = processes == 0 ? "" : `${processes}`;
@@ -1488,19 +1494,20 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
return proc.substring(0, p); return proc.substring(0, p);
} }
const pendingTask = e.pending.length const pendingTask = e.pending.length
? "\nPending: " + ? e.pending.length < 10 ? ("\nPending: " +
Object.entries(e.pending.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number })) Object.entries(e.pending.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`) .map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ") .join(", ")
: ""; ) : `\n Pending: ${e.pending.length}` : "";
const runningTask = e.running.length const runningTask = e.running.length
? "\nRunning: " + ? e.running.length < 10 ? ("\nRunning: " +
Object.entries(e.running.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number })) Object.entries(e.running.reduce((p, c) => ({ ...p, [getProcKind(c)]: (p[getProcKind(c)] ?? 0) + 1 }), {} as { [key: string]: number }))
.map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`) .map((e) => `${e[0]}${e[1] == 1 ? "" : `(${e[1]})`}`)
.join(", ") .join(", ")
: ""; ) : `\n Running: ${e.running.length}` : "";
this.setStatusBarText(message + pendingTask + runningTask); this.setStatusBarText(message + pendingTask + runningTask);
}) })
} }
@@ -1638,13 +1645,15 @@ Or if you are sure know what had been happened, we can unlock the database from
const filesStorageName = filesStorage.map((e) => e.path); const filesStorageName = filesStorage.map((e) => e.path);
Logger("Collecting local files on the DB", LOG_LEVEL.VERBOSE); Logger("Collecting local files on the DB", LOG_LEVEL.VERBOSE);
const filesDatabase = [] as FilePathWithPrefix[] const filesDatabase = [] as FilePathWithPrefix[]
let count = 0;
for await (const doc of this.localDatabase.findAllNormalDocs()) { for await (const doc of this.localDatabase.findAllNormalDocs()) {
count++;
if (count % 25 == 0) Logger(`Collecting local files on the DB: ${count}`, showingNotice ? LOG_LEVEL.NOTICE : LOG_LEVEL.INFO, "syncAll");
const path = getPath(doc); const path = getPath(doc);
if (isValidPath(path) && this.isTargetFile(path)) { if (isValidPath(path) && this.isTargetFile(path)) {
filesDatabase.push(path); filesDatabase.push(path);
} }
} }
Logger("Opening the key-value database", LOG_LEVEL.VERBOSE); Logger("Opening the key-value database", LOG_LEVEL.VERBOSE);
const isInitialized = await (this.kvDB.get<boolean>("initialized")) || false; const isInitialized = await (this.kvDB.get<boolean>("initialized")) || false;
// Make chunk bigger if it is the initial scan. There must be non-active docs. // Make chunk bigger if it is the initial scan. There must be non-active docs.
@@ -1663,25 +1672,23 @@ Or if you are sure know what had been happened, we can unlock the database from
const runAll = async<T>(procedureName: string, objects: T[], callback: (arg: T) => Promise<void>) => { const runAll = async<T>(procedureName: string, objects: T[], callback: (arg: T) => Promise<void>) => {
Logger(procedureName); Logger(procedureName);
const semaphore = Semaphore(25);
if (!this.localDatabase.isReady) throw Error("Database is not ready!"); if (!this.localDatabase.isReady) throw Error("Database is not ready!");
const processes = objects.map(e => (async (v) => { const para = Parallels();
const releaser = await semaphore.acquire(1, procedureName); for (const v of objects) {
await para.wait(10);
para.add((async (v) => {
try {
await callback(v);
} catch (ex) {
Logger(`Error while ${procedureName}`, LOG_LEVEL.NOTICE);
Logger(ex);
}
})(v));
try {
await callback(v);
} catch (ex) {
Logger(`Error while ${procedureName}`, LOG_LEVEL.NOTICE);
Logger(ex);
} finally {
releaser();
}
} }
)(e)); await para.all();
await Promise.all(processes);
Logger(`${procedureName} done.`); Logger(`${procedureName} done.`);
}; }
await runAll("UPDATE DATABASE", onlyInStorage, async (e) => { await runAll("UPDATE DATABASE", onlyInStorage, async (e) => {
Logger(`UPDATE DATABASE ${e.path}`); Logger(`UPDATE DATABASE ${e.path}`);

View File

@@ -1,8 +1,8 @@
import { DataWriteOptions, normalizePath, TFile, Platform, TAbstractFile, App, Plugin_2, RequestUrlParam, requestUrl } from "./deps"; import { type DataWriteOptions, normalizePath, TFile, Platform, TAbstractFile, App, Plugin_2, type RequestUrlParam, requestUrl } from "./deps";
import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path"; import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { AnyEntry, DocumentID, EntryDoc, EntryHasPath, FilePath, FilePathWithPrefix, LOG_LEVEL, NewEntry } from "./lib/src/types"; import { type AnyEntry, type DocumentID, type EntryDoc, type EntryHasPath, type FilePath, type FilePathWithPrefix, LOG_LEVEL, type NewEntry } from "./lib/src/types";
import { CHeader, ICHeader, ICHeaderLength, PSCHeader } from "./types"; import { CHeader, ICHeader, ICHeaderLength, PSCHeader } from "./types";
import { InputStringDialog, PopoverSelectString } from "./dialogs"; import { InputStringDialog, PopoverSelectString } from "./dialogs";
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
@@ -535,7 +535,7 @@ export const localDatabaseCleanUp = async (plugin: ObsidianLiveSyncPlugin, force
await performRebuildDB(plugin, "localOnly"); await performRebuildDB(plugin, "localOnly");
return; return;
} else { } else {
Logger("This feature requires enabling `Use new adapter`. Please enable it", LOG_LEVEL.NOTICE, "clean-up-db"); Logger("This feature requires disabling `Use an old adapter for compatibility`.", LOG_LEVEL.NOTICE, "clean-up-db");
return; return;
} }
} }

View File

@@ -252,3 +252,7 @@ div.sls-setting-menu-btn {
.sls-item-dirty::before { .sls-item-dirty::before {
content: "✏"; content: "✏";
} }
.sls-setting-hidden {
display: none;
}

View File

@@ -1,4 +1,6 @@
{ {
"extends": "@tsconfig/svelte/tsconfig.json",
"inlineSourceMap": true,
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
@@ -6,6 +8,10 @@
"allowJs": true, "allowJs": true,
"noImplicitAny": true, "noImplicitAny": true,
"moduleResolution": "node", "moduleResolution": "node",
"types": [
"svelte",
"node"
],
// "importsNotUsedAsValues": "error", // "importsNotUsedAsValues": "error",
"importHelpers": false, "importHelpers": false,
"alwaysStrict": true, "alwaysStrict": true,

View File

@@ -24,5 +24,39 @@ I hope you will give it a try.
- Improved: - Improved:
- Showing status is now thinned for performance. - Showing status is now thinned for performance.
- Enhance caching while collecting chunks. - Enhance caching while collecting chunks.
- 0.19.3
- Improved:
- Now replication will be paced by collecting chunks. If synchronisation has been deadlocked, please enable `Do not pace synchronization` once.
- 0.19.4
- Improved:
- Reduced remote database checking to improve speed and reduce bandwidth.
- Fixed:
- Chunks which previously misinterpreted are now interpreted correctly.
- No more missing chunks which not be found forever, except if it has been actually missing.
- Deleted file detection on hidden file synchronising now works fine.
- Now the Customisation sync is surely quiet while it has been disabled.
- 0.19.5
- Fixed:
- Now hidden file synchronisation would not be hanged, even if so many files exist.
- Improved:
- Customisation sync works more smoothly.
- Note: Concurrent processing has been rollbacked into the original implementation. As a result, the total number of processes is no longer shown next to the hourglass icon. However, only the processes that are running concurrently are shown.
- 0.19.6
- Fixed:
- Logging has been tweaked.
- No more too many planes and rockets.
- The batch database update now surely only works in non-live mode.
- Internal things:
- Some frameworks has been upgraded.
- Import declaration has been fixed.
- Improved:
- The plug-in now asks to enable a new adaptor, when rebuilding, if it is not enabled yet.
- The setting dialogue refined.
- Configurations for compatibilities have been moved under the hatch.
- Made it clear that disabled is the default.
- Ambiguous names configuration have been renamed.
- Items that have no meaning in the settings are no longer displayed.
- Some items have been reordered for clarity.
- Each configuration has been grouped.
... To continue on to `updates_old.md`. ... To continue on to `updates_old.md`.