mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-19 05:51:18 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a93066870 | ||
|
|
3a73073505 | ||
|
|
ee0c0ee611 | ||
|
|
d7ea30e304 | ||
|
|
2b9ded60f7 | ||
|
|
40508822cf | ||
|
|
6f938d5f54 | ||
|
|
51dc44bfb0 | ||
|
|
7c4f2bf78a | ||
|
|
67c9b4cf06 | ||
|
|
4808876968 | ||
|
|
cccff21ecc | ||
|
|
d8415a97e5 | ||
|
|
85e9aa2978 | ||
|
|
b4eb0e4868 | ||
|
|
3ea348f468 | ||
|
|
81362816d6 | ||
|
|
d6efe4510f | ||
|
|
ca5a7ae18c | ||
|
|
a27652ac34 | ||
|
|
29b89efc47 | ||
|
|
ef3eef2d08 | ||
|
|
ffbbe32e36 | ||
|
|
0a5371cdee | ||
|
|
466bb142e2 | ||
|
|
d394a4ce7f | ||
|
|
71ce76e502 | ||
|
|
ae7a7dd456 | ||
|
|
4048186bb5 | ||
|
|
2b94fd9139 | ||
|
|
ec72ece86d | ||
|
|
e394a994c5 | ||
|
|
aa23b6a39a | ||
|
|
58e328a591 | ||
|
|
1730c39d70 | ||
|
|
dfeac201a2 | ||
|
|
b42152db5e | ||
|
|
171cfc0a38 | ||
|
|
d2787bdb6a | ||
|
|
44b022f003 | ||
|
|
58845276e7 | ||
|
|
a2cc093a9e | ||
|
|
fec203a751 | ||
|
|
1a06837769 | ||
|
|
18d1ce8ec8 | ||
|
|
2221d8c4e8 | ||
|
|
08548f8630 | ||
|
|
5d24c3b984 | ||
|
|
de8fd43c8b | ||
|
|
ed88761eaa | ||
|
|
4dcb37f5a2 | ||
|
|
db0562eda1 | ||
|
|
b610d5d959 | ||
|
|
5abba74f3b | ||
|
|
021c1fccfe | ||
|
|
341f0ab12d | ||
|
|
39340c1e1b | ||
|
|
55cdc58857 | ||
|
|
4f1a9dc4e8 | ||
|
|
013818b7d0 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,6 +9,7 @@ package-lock.json
|
|||||||
# build
|
# build
|
||||||
main.js
|
main.js
|
||||||
main_org.js
|
main_org.js
|
||||||
|
main_org_*.js
|
||||||
*.js.map
|
*.js.map
|
||||||
meta.json
|
meta.json
|
||||||
meta-*.json
|
meta-*.json
|
||||||
@@ -17,3 +18,6 @@ meta-*.json
|
|||||||
# obsidian
|
# obsidian
|
||||||
data.json
|
data.json
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
|||||||
@@ -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**.
|
||||||
|
|||||||
@@ -5,10 +5,15 @@
|
|||||||
- [Setup a CouchDB server](#setup-a-couchdb-server)
|
- [Setup a CouchDB server](#setup-a-couchdb-server)
|
||||||
- [Table of Contents](#table-of-contents)
|
- [Table of Contents](#table-of-contents)
|
||||||
- [1. Prepare CouchDB](#1-prepare-couchdb)
|
- [1. Prepare CouchDB](#1-prepare-couchdb)
|
||||||
- [A. Using Docker container](#a-using-docker-container)
|
- [A. Using Docker](#a-using-docker)
|
||||||
- [1. Prepare](#1-prepare)
|
- [1. Prepare](#1-prepare)
|
||||||
- [2. Run docker container](#2-run-docker-container)
|
- [2. Run docker container](#2-run-docker-container)
|
||||||
- [B. Install CouchDB directly](#b-install-couchdb-directly)
|
- [B. Using Docker Compose](#b-using-docker-compose)
|
||||||
|
- [1. Prepare](#1-prepare-1)
|
||||||
|
- [2. Creating Compose file](#2-create-a-docker-composeyml-file-with-the-following-added-to-it)
|
||||||
|
- [3. Boot check](#3-run-the-docker-compose-file-to-boot-check)
|
||||||
|
- [4. Starting Docker Compose in background](#4-run-the-docker-compose-file-in-the-background)
|
||||||
|
- [C. Install CouchDB directly](#c-install-couchdb-directly)
|
||||||
- [2. Run couchdb-init.sh for initialise](#2-run-couchdb-initsh-for-initialise)
|
- [2. Run couchdb-init.sh for initialise](#2-run-couchdb-initsh-for-initialise)
|
||||||
- [3. Expose CouchDB to the Internet](#3-expose-couchdb-to-the-internet)
|
- [3. Expose CouchDB to the Internet](#3-expose-couchdb-to-the-internet)
|
||||||
- [4. Client Setup](#4-client-setup)
|
- [4. Client Setup](#4-client-setup)
|
||||||
@@ -21,44 +26,56 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
## 1. Prepare CouchDB
|
## 1. Prepare CouchDB
|
||||||
### A. Using Docker container
|
### A. Using Docker
|
||||||
|
|
||||||
#### 1. Prepare
|
#### 1. Prepare
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
# Prepare environment variables.
|
# Adding environment variables.
|
||||||
export hostname=localhost:5984
|
export hostname=localhost:5984
|
||||||
export username=goojdasjdas #Please change as you like.
|
export username=goojdasjdas #Please change as you like.
|
||||||
export password=kpkdasdosakpdsa #Please change as you like
|
export password=kpkdasdosakpdsa #Please change as you like
|
||||||
|
|
||||||
# Prepare directories which save data and configurations.
|
# Creating the save data & configuration directories.
|
||||||
mkdir couchdb-data
|
mkdir couchdb-data
|
||||||
mkdir couchdb-etc
|
mkdir couchdb-etc
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Run docker container
|
#### 2. Run docker container
|
||||||
|
|
||||||
1. Boot Check.
|
1. Boot Check.
|
||||||
```
|
```
|
||||||
$ docker run --name couchdb-for-ols --rm -it -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 --rm -it -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 your container has been exited, please check the permission of couchdb-data, and couchdb-etc.
|
> [!WARNING]
|
||||||
Once CouchDB run, these directories will be owned by uid:`5984`. Please chown it for you again.
|
> If your container threw an error or exited unexpectedly, please check the permission of couchdb-data, and couchdb-etc.
|
||||||
|
> Once CouchDB starts, these directories will be owned by uid:`5984`. Please chown it for that uid again.
|
||||||
|
|
||||||
2. Enable it in the background
|
2. Enable it in the background
|
||||||
```
|
```
|
||||||
$ 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:
|
|
||||||
|
Congrats, move on to [step 2](#2-run-couchdb-initsh-for-initialise)
|
||||||
|
### B. Using Docker Compose
|
||||||
|
|
||||||
|
#### 1. Prepare
|
||||||
|
|
||||||
|
```
|
||||||
|
# Creating the save data & configuration directories.
|
||||||
|
mkdir couchdb-data
|
||||||
|
mkdir couchdb-etc
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Create a `docker-compose.yml` file with the following added to it
|
||||||
```
|
```
|
||||||
services:
|
services:
|
||||||
couchdb:
|
couchdb:
|
||||||
image: couchdb:latest
|
image: couchdb:latest
|
||||||
container_name: couchdb-for-ols
|
container_name: couchdb-for-ols
|
||||||
user: 1000:1000
|
user: 5984:5984
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_USER=${username}
|
- COUCHDB_USER=<INSERT USERNAME HERE> #Please change as you like.
|
||||||
- COUCHDB_PASSWORD=${password}
|
- COUCHDB_PASSWORD=<INSERT PASSWORD HERE> #Please change as you like.
|
||||||
volumes:
|
volumes:
|
||||||
- ./couchdb-data:/opt/couchdb/data
|
- ./couchdb-data:/opt/couchdb/data
|
||||||
- ./couchdb-etc:/opt/couchdb/etc/local.d
|
- ./couchdb-etc:/opt/couchdb/etc/local.d
|
||||||
@@ -66,7 +83,30 @@ services:
|
|||||||
- 5984:5984
|
- 5984:5984
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
### B. Install CouchDB directly
|
|
||||||
|
#### 3. Run the Docker Compose file to boot check
|
||||||
|
|
||||||
|
```
|
||||||
|
docker compose up
|
||||||
|
# Or if using the old version
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
> [!WARNING]
|
||||||
|
> If your container threw an error or exited unexpectedly, please check the permission of couchdb-data, and couchdb-etc.
|
||||||
|
> Once CouchDB starts, these directories will be owned by uid:`5984`. Please chown it for that uid again.
|
||||||
|
|
||||||
|
#### 4. Run the Docker Compose file in the background
|
||||||
|
If all went well and didn't throw any errors, `CTRL+C` out of it, and then run this command
|
||||||
|
```
|
||||||
|
docker compose up -d
|
||||||
|
# Or if using the old version
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Congrats, move on to [step 2](#2-run-couchdb-initsh-for-initialise)
|
||||||
|
|
||||||
|
|
||||||
|
### C. 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.
|
||||||
|
|
||||||
## 2. Run couchdb-init.sh for initialise
|
## 2. Run couchdb-init.sh for initialise
|
||||||
@@ -92,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.
|
||||||
|
|||||||
@@ -19,6 +19,18 @@ const manifestJson = JSON.parse(fs.readFileSync("./manifest.json") + "");
|
|||||||
const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
|
const packageJson = JSON.parse(fs.readFileSync("./package.json") + "");
|
||||||
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
const updateInfo = JSON.stringify(fs.readFileSync("./updates.md") + "");
|
||||||
|
|
||||||
|
const PATHS_TEST_INSTALL = process.env?.PATHS_TEST_INSTALL || "";
|
||||||
|
const PATH_TEST_INSTALL = PATHS_TEST_INSTALL.split(path.delimiter).map(p => p.trim()).filter(p => p.length);
|
||||||
|
if (!prod) {
|
||||||
|
if (PATH_TEST_INSTALL) {
|
||||||
|
console.log(`Built files will be copied to ${PATH_TEST_INSTALL}`);
|
||||||
|
} else {
|
||||||
|
console.log("Development build: You can install the plug-in to Obsidian for testing by exporting the PATHS_TEST_INSTALL environment variable with the paths to your vault plugins directories separated by your system path delimiter (':' on Unix, ';' on Windows).");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Production build");
|
||||||
|
}
|
||||||
|
|
||||||
const moduleAliasPlugin = {
|
const moduleAliasPlugin = {
|
||||||
name: "module-alias",
|
name: "module-alias",
|
||||||
setup(build) {
|
setup(build) {
|
||||||
@@ -95,6 +107,21 @@ const plugins = [
|
|||||||
} else {
|
} else {
|
||||||
fs.copyFileSync("./main_org.js", "./main.js");
|
fs.copyFileSync("./main_org.js", "./main.js");
|
||||||
}
|
}
|
||||||
|
if (PATH_TEST_INSTALL) {
|
||||||
|
for (const installPath of PATH_TEST_INSTALL) {
|
||||||
|
const realPath = path.resolve(installPath);
|
||||||
|
console.log(`Copying built files to ${realPath}`);
|
||||||
|
if (!fs.existsSync(realPath)) {
|
||||||
|
console.warn(`Test install path ${installPath} does not exist`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const manifestX = JSON.parse(fs.readFileSync("./manifest.json") + "");
|
||||||
|
manifestX.version = manifestJson.version + "." + Date.now();
|
||||||
|
fs.writeFileSync(path.join(installPath, "manifest.json"), JSON.stringify(manifestX, null, 2));
|
||||||
|
fs.copyFileSync("./main.js", path.join(installPath, "main.js"));
|
||||||
|
fs.copyFileSync("./styles.css", path.join(installPath, "styles.css"));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
1
example.env
Normal file
1
example.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PATHS_TEST_INSTALL=your-vault-plugin-path:and-another-path
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.2",
|
"version": "0.25.21.beta2",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-livesync",
|
"id": "obsidian-livesync",
|
||||||
"name": "Self-hosted LiveSync",
|
"name": "Self-hosted LiveSync",
|
||||||
"version": "0.25.6",
|
"version": "0.25.22",
|
||||||
"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",
|
||||||
|
|||||||
1510
package-lock.json
generated
1510
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-livesync",
|
"name": "obsidian-livesync",
|
||||||
"version": "0.25.6",
|
"version": "0.25.22",
|
||||||
"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,7 @@
|
|||||||
"postbakei18n": "prettier --config ./.prettierrc ./src/lib/src/common/messages/*.ts --write --log-level error",
|
"postbakei18n": "prettier --config ./.prettierrc ./src/lib/src/common/messages/*.ts --write --log-level error",
|
||||||
"posti18n:yaml2json": "npm run prettyjson",
|
"posti18n:yaml2json": "npm run prettyjson",
|
||||||
"predev": "npm run bakei18n",
|
"predev": "npm run bakei18n",
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node --env-file=.env esbuild.config.mjs",
|
||||||
"prebuild": "npm run bakei18n",
|
"prebuild": "npm run bakei18n",
|
||||||
"build": "node esbuild.config.mjs production",
|
"build": "node esbuild.config.mjs production",
|
||||||
"buildDev": "node esbuild.config.mjs dev",
|
"buildDev": "node esbuild.config.mjs dev",
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
"@typescript-eslint/parser": "8.25.0",
|
"@typescript-eslint/parser": "8.25.0",
|
||||||
"builtin-modules": "5.0.0",
|
"builtin-modules": "5.0.0",
|
||||||
"esbuild": "0.25.0",
|
"esbuild": "0.25.0",
|
||||||
|
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||||
"esbuild-svelte": "^0.9.0",
|
"esbuild-svelte": "^0.9.0",
|
||||||
"eslint": "^9.21.0",
|
"eslint": "^9.21.0",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"pouchdb-utils": "^9.0.0",
|
"pouchdb-utils": "^9.0.0",
|
||||||
"prettier": "3.5.2",
|
"prettier": "3.5.2",
|
||||||
"svelte": "5.28.6",
|
"svelte": "5.28.6",
|
||||||
|
"svelte-check": "^4.1.7",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"terser": "^5.39.0",
|
"terser": "^5.39.0",
|
||||||
"transform-pouch": "^2.0.0",
|
"transform-pouch": "^2.0.0",
|
||||||
@@ -88,14 +90,12 @@
|
|||||||
"@smithy/protocol-http": "^5.1.0",
|
"@smithy/protocol-http": "^5.1.0",
|
||||||
"@smithy/querystring-builder": "^4.0.2",
|
"@smithy/querystring-builder": "^4.0.2",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.2",
|
||||||
"octagonal-wheels": "^0.1.37",
|
"octagonal-wheels": "^0.1.41",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"svelte-check": "^4.1.7",
|
"trystero": "github:vrtmrz/trystero#9e892a93ec14eeb57ce806d272fbb7c3935256d8",
|
||||||
"trystero": "^0.21.5",
|
|
||||||
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueDatab
|
|||||||
}
|
}
|
||||||
const storeKey = dbKey;
|
const storeKey = dbKey;
|
||||||
const dbPromise = openDB(dbKey, 1, {
|
const dbPromise = openDB(dbKey, 1, {
|
||||||
upgrade(db) {
|
upgrade(db, _oldVersion, _newVersion, _transaction, _event) {
|
||||||
db.createObjectStore(storeKey);
|
return db.createObjectStore(storeKey);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const db = await dbPromise;
|
const db = await dbPromise;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const EVENT_REQUEST_OPEN_P2P = "request-open-p2p";
|
|||||||
export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p";
|
export const EVENT_REQUEST_CLOSE_P2P = "request-close-p2p";
|
||||||
|
|
||||||
export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor";
|
export const EVENT_REQUEST_RUN_DOCTOR = "request-run-doctor";
|
||||||
|
export const EVENT_REQUEST_RUN_FIX_INCOMPLETE = "request-run-fix-incomplete";
|
||||||
|
|
||||||
// export const EVENT_FILE_CHANGED = "file-changed";
|
// export const EVENT_FILE_CHANGED = "file-changed";
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ declare global {
|
|||||||
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
|
[EVENT_REQUEST_COPY_SETUP_URI]: undefined;
|
||||||
[EVENT_REQUEST_SHOW_SETUP_QR]: undefined;
|
[EVENT_REQUEST_SHOW_SETUP_QR]: undefined;
|
||||||
[EVENT_REQUEST_RUN_DOCTOR]: string;
|
[EVENT_REQUEST_RUN_DOCTOR]: string;
|
||||||
|
[EVENT_REQUEST_RUN_FIX_INCOMPLETE]: undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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[]>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import { type PluginManifest, TFile } from "../deps.ts";
|
import { type PluginManifest, TFile } from "../deps.ts";
|
||||||
import {
|
import { type DatabaseEntry, type EntryBody, type FilePath } from "../lib/src/common/types.ts";
|
||||||
type DatabaseEntry,
|
export type { CacheData, FileEventItem } from "../lib/src/common/types.ts";
|
||||||
type EntryBody,
|
|
||||||
type FilePath,
|
|
||||||
type UXFileInfoStub,
|
|
||||||
type UXInternalFileInfoStub,
|
|
||||||
} from "../lib/src/common/types.ts";
|
|
||||||
|
|
||||||
export interface PluginDataEntry extends DatabaseEntry {
|
export interface PluginDataEntry extends DatabaseEntry {
|
||||||
deviceVaultName: string;
|
deviceVaultName: string;
|
||||||
@@ -54,23 +49,6 @@ export type queueItem = {
|
|||||||
warned?: boolean;
|
warned?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CacheData = string | ArrayBuffer;
|
|
||||||
export type FileEventType = "CREATE" | "DELETE" | "CHANGED" | "INTERNAL";
|
|
||||||
export type FileEventArgs = {
|
|
||||||
file: UXFileInfoStub | UXInternalFileInfoStub;
|
|
||||||
cache?: CacheData;
|
|
||||||
oldPath?: string;
|
|
||||||
ctx?: any;
|
|
||||||
};
|
|
||||||
export type FileEventItem = {
|
|
||||||
type: FileEventType;
|
|
||||||
args: FileEventArgs;
|
|
||||||
key: string;
|
|
||||||
skipBatchWait?: boolean;
|
|
||||||
cancelled?: boolean;
|
|
||||||
batched?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hidden items (Now means `chunk`)
|
// Hidden items (Now means `chunk`)
|
||||||
export const CHeader = "h:";
|
export const CHeader = "h:";
|
||||||
|
|
||||||
|
|||||||
@@ -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 "/".
|
||||||
@@ -189,7 +189,7 @@ export class PeriodicProcessor {
|
|||||||
() =>
|
() =>
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.process();
|
await this.process();
|
||||||
if (this._plugin.$$isUnloaded()) {
|
if (this._plugin.services?.appLifecycle?.hasUnloaded()) {
|
||||||
this.disable();
|
this.disable();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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,20 +62,29 @@ 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";
|
||||||
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
import { ConflictResolveModal } from "../../modules/features/InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule.ts";
|
|
||||||
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_PLUGIN_SYNC_DIALOG, eventHub } from "../../common/events.ts";
|
||||||
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
import { PluginDialogModal } from "./PluginDialogModal.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
const d = "\u200b";
|
const d = "\u200b";
|
||||||
const d2 = "\n";
|
const d2 = "\n";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface OPTIONAL_SYNC_FEATURES {
|
||||||
|
DISABLE: "DISABLE";
|
||||||
|
CUSTOMIZE: "CUSTOMIZE";
|
||||||
|
DISABLE_CUSTOM: "DISABLE_CUSTOM";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function serialize(data: PluginDataEx): string {
|
function serialize(data: PluginDataEx): string {
|
||||||
// For higher performance, create custom plug-in data strings.
|
// For higher performance, create custom plug-in data strings.
|
||||||
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
|
// Self-hosted LiveSync uses `\n` to split chunks. Therefore, grouping together those with similar entropy would work nicely.
|
||||||
@@ -384,7 +393,7 @@ export type PluginDataEx = {
|
|||||||
mtime: number;
|
mtime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
export class ConfigSync extends LiveSyncCommands {
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
pluginScanningCount.onChanged((e) => {
|
pluginScanningCount.onChanged((e) => {
|
||||||
@@ -402,7 +411,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
get useSyncPluginEtc() {
|
get useSyncPluginEtc() {
|
||||||
return this.plugin.settings.usePluginEtc;
|
return this.plugin.settings.usePluginEtc;
|
||||||
}
|
}
|
||||||
_isThisModuleEnabled() {
|
isThisModuleEnabled() {
|
||||||
return this.plugin.settings.usePluginSync;
|
return this.plugin.settings.usePluginSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +420,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
|
|
||||||
pluginList: IPluginDataExDisplay[] = [];
|
pluginList: IPluginDataExDisplay[] = [];
|
||||||
showPluginSyncModal() {
|
showPluginSyncModal() {
|
||||||
if (!this._isThisModuleEnabled()) {
|
if (!this.isThisModuleEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.pluginDialog) {
|
if (this.pluginDialog) {
|
||||||
@@ -482,8 +491,8 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
// Idea non-filter option?
|
// Idea non-filter option?
|
||||||
return this.getFileCategory(filePath) != "";
|
return this.getFileCategory(filePath) != "";
|
||||||
}
|
}
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
private async _everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
if (!this._isThisModuleEnabled()) return true;
|
if (!this.isThisModuleEnabled()) return true;
|
||||||
try {
|
try {
|
||||||
this._log("Scanning customizations...");
|
this._log("Scanning customizations...");
|
||||||
await this.scanAllConfigFiles(showNotice);
|
await this.scanAllConfigFiles(showNotice);
|
||||||
@@ -494,16 +503,16 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showNotice: boolean) {
|
async _everyBeforeReplicate(showNotice: boolean) {
|
||||||
if (!this._isThisModuleEnabled()) return true;
|
if (!this.isThisModuleEnabled()) return true;
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
await this.scanAllConfigFiles(showNotice);
|
await this.scanAllConfigFiles(showNotice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyOnResumeProcess(): Promise<boolean> {
|
async _everyOnResumeProcess(): Promise<boolean> {
|
||||||
if (!this._isThisModuleEnabled()) return true;
|
if (!this.isThisModuleEnabled()) return true;
|
||||||
if (this._isMainSuspended()) {
|
if (this._isMainSuspended()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -517,9 +526,9 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
|
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
|
||||||
q?.toggleClass("sls-hidden", !this._isThisModuleEnabled());
|
q?.toggleClass("sls-hidden", !this.isThisModuleEnabled());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async reloadPluginList(showMessage: boolean) {
|
async reloadPluginList(showMessage: boolean) {
|
||||||
@@ -633,7 +642,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
).startPipeline();
|
).startPipeline();
|
||||||
|
|
||||||
filenameToUnifiedKey(path: string, termOverRide?: string) {
|
filenameToUnifiedKey(path: string, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
const category = this.getFileCategory(path);
|
const category = this.getFileCategory(path);
|
||||||
const name =
|
const name =
|
||||||
category == "CONFIG" || category == "SNIPPET"
|
category == "CONFIG" || category == "SNIPPET"
|
||||||
@@ -645,7 +654,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filenameWithUnifiedKey(path: string, termOverRide?: string) {
|
filenameWithUnifiedKey(path: string, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
const category = this.getFileCategory(path);
|
const category = this.getFileCategory(path);
|
||||||
const name =
|
const name =
|
||||||
category == "CONFIG" || category == "SNIPPET" ? path.split("/").slice(-1)[0] : path.split("/").slice(-2)[0];
|
category == "CONFIG" || category == "SNIPPET" ? path.split("/").slice(-1)[0] : path.split("/").slice(-2)[0];
|
||||||
@@ -654,7 +663,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unifiedKeyPrefixOfTerminal(termOverRide?: string) {
|
unifiedKeyPrefixOfTerminal(termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
return `${ICXHeader}${term}/` as FilePathWithPrefix;
|
return `${ICXHeader}${term}/` as FilePathWithPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,7 +840,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
const v2Path = (prefixPath + relativeFilename) as FilePathWithPrefix;
|
const v2Path = (prefixPath + relativeFilename) as FilePathWithPrefix;
|
||||||
// console.warn(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`);
|
// console.warn(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`);
|
||||||
this._log(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`, LOG_LEVEL_VERBOSE);
|
this._log(`Migrating ${v1Path} / ${relativeFilename} to ${v2Path}`, LOG_LEVEL_VERBOSE);
|
||||||
const newId = await this.plugin.$$path2id(v2Path);
|
const newId = await this.services.path.path2id(v2Path);
|
||||||
// const buf =
|
// const buf =
|
||||||
|
|
||||||
const data = createBlob([DUMMY_HEAD, DUMMY_END, ...getDocDataAsArray(f.data)]);
|
const data = createBlob([DUMMY_HEAD, DUMMY_END, ...getDocDataAsArray(f.data)]);
|
||||||
@@ -861,7 +870,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
|
async updatePluginList(showMessage: boolean, updatedDocumentPath?: FilePathWithPrefix): Promise<void> {
|
||||||
if (!this._isThisModuleEnabled()) {
|
if (!this.isThisModuleEnabled()) {
|
||||||
this.pluginScanProcessor.clearQueue();
|
this.pluginScanProcessor.clearQueue();
|
||||||
this.pluginList = [];
|
this.pluginList = [];
|
||||||
pluginList.set(this.pluginList);
|
pluginList.set(this.pluginList);
|
||||||
@@ -999,7 +1008,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await this.plugin.storageAccess.ensureDir(path);
|
await this.plugin.storageAccess.ensureDir(path);
|
||||||
// If the content has applied, modified time will be updated to the current time.
|
// If the content has applied, modified time will be updated to the current time.
|
||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content);
|
||||||
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
|
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
|
||||||
} else {
|
} else {
|
||||||
const files = data.files;
|
const files = data.files;
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
@@ -1042,7 +1051,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
await this.plugin.storageAccess.writeHiddenFileAuto(path, content, stat);
|
||||||
}
|
}
|
||||||
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
this._log(`Applied ${f.filename} of ${data.displayName || data.name}..`);
|
||||||
await this.storeCustomisationFileV2(path, this.plugin.$$getDeviceAndVaultName());
|
await this.storeCustomisationFileV2(path, this.services.setting.getDeviceAndVaultName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -1114,7 +1123,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (data.category == "CONFIG") {
|
} else if (data.category == "CONFIG") {
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@@ -1157,15 +1166,15 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
|
async _anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>) {
|
||||||
if (!docs._id.startsWith(ICXHeader)) return undefined;
|
if (!docs._id.startsWith(ICXHeader)) return false;
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
await this.updatePluginList(
|
await this.updatePluginList(
|
||||||
false,
|
false,
|
||||||
(docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry)
|
(docs as AnyEntry).path ? (docs as AnyEntry).path : this.getPath(docs as AnyEntry)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this._isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
|
if (this.isThisModuleEnabled() && this.plugin.settings.notifyPluginOrSettingUpdated) {
|
||||||
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
if (!this.pluginDialog || (this.pluginDialog && !this.pluginDialog.isOpened())) {
|
||||||
const fragment = createFragment((doc) => {
|
const fragment = createFragment((doc) => {
|
||||||
doc.createEl("span", undefined, (a) => {
|
doc.createEl("span", undefined, (a) => {
|
||||||
@@ -1205,11 +1214,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyRealizeSettingSyncMode(): Promise<boolean> {
|
async _everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
this.periodicPluginSweepProcessor?.disable();
|
this.periodicPluginSweepProcessor?.disable();
|
||||||
if (!this._isMainReady) return true;
|
if (!this._isMainReady) return true;
|
||||||
if (!this._isMainSuspended()) return true;
|
if (!this._isMainSuspended()) return true;
|
||||||
if (!this._isThisModuleEnabled()) return true;
|
if (!this.isThisModuleEnabled()) return true;
|
||||||
if (this.settings.autoSweepPlugins) {
|
if (this.settings.autoSweepPlugins) {
|
||||||
await this.scanAllConfigFiles(false);
|
await this.scanAllConfigFiles(false);
|
||||||
}
|
}
|
||||||
@@ -1345,7 +1354,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
|
async storeCustomizationFiles(path: FilePath, termOverRide?: string) {
|
||||||
const term = termOverRide || this.plugin.$$getDeviceAndVaultName();
|
const term = termOverRide || this.services.setting.getDeviceAndVaultName();
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -1488,14 +1497,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
|
||||||
return await this.watchVaultRawEventsAsync(path);
|
return await this.watchVaultRawEventsAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchVaultRawEventsAsync(path: FilePath) {
|
async watchVaultRawEventsAsync(path: FilePath) {
|
||||||
if (!this._isMainReady) return false;
|
if (!this._isMainReady) return false;
|
||||||
if (this._isMainSuspended()) return false;
|
if (this._isMainSuspended()) return false;
|
||||||
if (!this._isThisModuleEnabled()) return false;
|
if (!this.isThisModuleEnabled()) return false;
|
||||||
// if (!this.isTargetPath(path)) return false;
|
// if (!this.isTargetPath(path)) return false;
|
||||||
const stat = await this.plugin.storageAccess.statHidden(path);
|
const stat = await this.plugin.storageAccess.statHidden(path);
|
||||||
// Make sure that target is a file.
|
// Make sure that target is a file.
|
||||||
@@ -1535,7 +1544,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
await shareRunningResult("scanAllConfigFiles", async () => {
|
await shareRunningResult("scanAllConfigFiles", async () => {
|
||||||
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
const logLevel = showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
this._log("Scanning customizing files.", logLevel, "scan-all-config");
|
this._log("Scanning customizing files.", logLevel, "scan-all-config");
|
||||||
const term = this.plugin.$$getDeviceAndVaultName();
|
const term = this.services.setting.getDeviceAndVaultName();
|
||||||
if (term == "") {
|
if (term == "") {
|
||||||
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
this._log("We have to configure the device name", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -1673,11 +1682,14 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return filenames as FilePath[];
|
return filenames as FilePath[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
private async _allAskUsingOptionalSyncFeature(opt: {
|
||||||
await this._askHiddenFileConfiguration(opt);
|
enableFetch?: boolean;
|
||||||
|
enableOverwrite?: boolean;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
await this.__askHiddenFileConfiguration(opt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private async __askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
||||||
const message = `Would you like to enable **Customization sync**?
|
const message = `Would you like to enable **Customization sync**?
|
||||||
|
|
||||||
> [!DETAILS]-
|
> [!DETAILS]-
|
||||||
@@ -1707,7 +1719,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
||||||
if (isPluginMetadata(path)) {
|
if (isPluginMetadata(path)) {
|
||||||
return Promise.resolve("newer");
|
return Promise.resolve("newer");
|
||||||
}
|
}
|
||||||
@@ -1717,7 +1729,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
private _allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
if (this.plugin.settings.usePluginSync || this.plugin.settings.autoSweepPlugins) {
|
||||||
this._log(
|
this._log(
|
||||||
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
"Customisation sync have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
||||||
@@ -1729,10 +1741,11 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyConfigureOptionalSyncFeature(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
|
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
await this.configureHiddenFileSync(mode);
|
await this.configureHiddenFileSync(mode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
async configureHiddenFileSync(mode: "CUSTOMIZE" | "DISABLE" | "DISABLE_CUSTOM") {
|
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
if (mode == "DISABLE") {
|
if (mode == "DISABLE") {
|
||||||
this.plugin.settings.usePluginSync = false;
|
this.plugin.settings.usePluginSync = false;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -1740,7 +1753,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mode == "CUSTOMIZE") {
|
if (mode == "CUSTOMIZE") {
|
||||||
if (!this.plugin.$$getDeviceAndVaultName()) {
|
if (!this.services.setting.getDeviceAndVaultName()) {
|
||||||
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
|
let name = await this.plugin.confirm.askString("Device name", "Please set this device name", `desktop`);
|
||||||
if (!name) {
|
if (!name) {
|
||||||
if (Platform.isAndroidApp) {
|
if (Platform.isAndroidApp) {
|
||||||
@@ -1764,7 +1777,7 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
name = name + Math.random().toString(36).slice(-4);
|
name = name + Math.random().toString(36).slice(-4);
|
||||||
}
|
}
|
||||||
this.plugin.$$setDeviceAndVaultName(name);
|
this.services.setting.setDeviceAndVaultName(name);
|
||||||
}
|
}
|
||||||
this.plugin.settings.usePluginSync = true;
|
this.plugin.settings.usePluginSync = true;
|
||||||
this.plugin.settings.useAdvancedMode = true;
|
this.plugin.settings.useAdvancedMode = true;
|
||||||
@@ -1789,4 +1802,17 @@ export class ConfigSync extends LiveSyncCommands implements IObsidianModule {
|
|||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
|
||||||
|
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
|
||||||
|
services.replication.handleProcessVirtualDocuments(this._anyModuleParsedReplicationResultItem.bind(this));
|
||||||
|
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
|
||||||
|
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||||
|
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
export let plugin: ObsidianLiveSyncPlugin;
|
export let plugin: ObsidianLiveSyncPlugin;
|
||||||
|
|
||||||
$: hideNotApplicable = false;
|
$: hideNotApplicable = false;
|
||||||
$: thisTerm = plugin.$$getDeviceAndVaultName();
|
$: thisTerm = plugin.services.setting.getDeviceAndVaultName();
|
||||||
|
|
||||||
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
|
const addOn = plugin.getAddOn(ConfigSync.name) as ConfigSync;
|
||||||
if (!addOn) {
|
if (!addOn) {
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
await requestUpdate();
|
await requestUpdate();
|
||||||
}
|
}
|
||||||
async function replicate() {
|
async function replicate() {
|
||||||
await plugin.$$replicate(true);
|
await plugin.services.replication.replicate(true);
|
||||||
}
|
}
|
||||||
function selectAllNewest(selectMode: boolean) {
|
function selectAllNewest(selectMode: boolean) {
|
||||||
selectNewestPulse++;
|
selectNewestPulse++;
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
plugin.settings.pluginSyncExtendedSetting[key].files = files;
|
plugin.settings.pluginSyncExtendedSetting[key].files = files;
|
||||||
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
|
plugin.settings.pluginSyncExtendedSetting[key].mode = mode;
|
||||||
}
|
}
|
||||||
plugin.$$saveSettingData();
|
plugin.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
function getIcon(mode: SYNC_MODE) {
|
function getIcon(mode: SYNC_MODE) {
|
||||||
if (mode in ICONS) {
|
if (mode in ICONS) {
|
||||||
|
|||||||
@@ -45,18 +45,26 @@ 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 { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
type SyncDirection = "push" | "pull" | "safe" | "pullForce" | "pushForce";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface OPTIONAL_SYNC_FEATURES {
|
||||||
|
FETCH: "FETCH";
|
||||||
|
OVERWRITE: "OVERWRITE";
|
||||||
|
MERGE: "MERGE";
|
||||||
|
DISABLE: "DISABLE";
|
||||||
|
DISABLE_HIDDEN: "DISABLE_HIDDEN";
|
||||||
|
}
|
||||||
|
}
|
||||||
function getComparingMTime(
|
function getComparingMTime(
|
||||||
doc: (MetaEntry | LoadedEntry | false) | UXFileInfo | UXStat | null | undefined,
|
doc: (MetaEntry | LoadedEntry | false) | UXFileInfo | UXStat | null | undefined,
|
||||||
includeDeleted = false
|
includeDeleted = false
|
||||||
@@ -72,21 +80,21 @@ function getComparingMTime(
|
|||||||
return doc.mtime ?? 0;
|
return doc.mtime ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule {
|
export class HiddenFileSync extends LiveSyncCommands {
|
||||||
_isThisModuleEnabled() {
|
isThisModuleEnabled() {
|
||||||
return this.plugin.settings.syncInternalFiles;
|
return this.plugin.settings.syncInternalFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
|
periodicInternalFileScanProcessor: PeriodicProcessor = new PeriodicProcessor(
|
||||||
this.plugin,
|
this.plugin,
|
||||||
async () => this._isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
|
async () => this.isThisModuleEnabled() && this._isDatabaseReady() && (await this.scanAllStorageChanges(false))
|
||||||
);
|
);
|
||||||
|
|
||||||
get kvDB() {
|
get kvDB() {
|
||||||
return this.plugin.kvDB;
|
return this.plugin.kvDB;
|
||||||
}
|
}
|
||||||
getConflictedDoc(path: FilePathWithPrefix, rev: string) {
|
getConflictedDoc(path: FilePathWithPrefix, rev: string) {
|
||||||
return this.plugin.localDatabase.getConflictedDoc(path, rev);
|
return this.plugin.managers.conflictManager.getConflictedDoc(path, rev);
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
@@ -132,14 +140,18 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
this.updateSettingCache();
|
this.updateSettingCache();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
// We cannot initialise autosaveCache because kvDB is not ready yet
|
||||||
|
// async _everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
|
// this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
||||||
|
// this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
||||||
|
// this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
private async _everyOnDatabaseInitialized(showNotice: boolean) {
|
||||||
this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
this._fileInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed");
|
||||||
this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
this._databaseInfoLastProcessed = await autosaveCache(this.kvDB, "hidden-file-lastProcessed-database");
|
||||||
this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
this._fileInfoLastKnown = await autosaveCache(this.kvDB, "hidden-file-lastKnown");
|
||||||
return true;
|
if (this.isThisModuleEnabled()) {
|
||||||
}
|
|
||||||
async $everyOnDatabaseInitialized(showNotice: boolean) {
|
|
||||||
if (this._isThisModuleEnabled()) {
|
|
||||||
if (this._fileInfoLastProcessed.size == 0 && this._fileInfoLastProcessed.size == 0) {
|
if (this._fileInfoLastProcessed.size == 0 && this._fileInfoLastProcessed.size == 0) {
|
||||||
this._log(`No cache found. Performing startup scan.`, LOG_LEVEL_VERBOSE);
|
this._log(`No cache found. Performing startup scan.`, LOG_LEVEL_VERBOSE);
|
||||||
await this.performStartupScan(true);
|
await this.performStartupScan(true);
|
||||||
@@ -149,9 +161,9 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showNotice: boolean) {
|
async _everyBeforeReplicate(showNotice: boolean) {
|
||||||
if (
|
if (
|
||||||
this._isThisModuleEnabled() &&
|
this.isThisModuleEnabled() &&
|
||||||
this._isDatabaseReady() &&
|
this._isDatabaseReady() &&
|
||||||
this.settings.syncInternalFilesBeforeReplication &&
|
this.settings.syncInternalFilesBeforeReplication &&
|
||||||
!this.settings.watchInternalFileChanges
|
!this.settings.watchInternalFileChanges
|
||||||
@@ -161,7 +173,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
this.updateSettingCache();
|
this.updateSettingCache();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -188,7 +200,7 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
isReady() {
|
isReady() {
|
||||||
if (!this._isMainReady) return false;
|
if (!this._isMainReady) return false;
|
||||||
if (this._isMainSuspended()) return false;
|
if (this._isMainSuspended()) return false;
|
||||||
if (!this._isThisModuleEnabled()) return false;
|
if (!this.isThisModuleEnabled()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
shouldSkipFile = [] as FilePathWithPrefixLC[];
|
shouldSkipFile = [] as FilePathWithPrefixLC[];
|
||||||
@@ -197,26 +209,26 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
await this.applyOfflineChanges(showNotice);
|
await this.applyOfflineChanges(showNotice);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnResumeProcess(): Promise<boolean> {
|
async _everyOnResumeProcess(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this._isMainSuspended()) return true;
|
if (this._isMainSuspended()) return true;
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
await this.performStartupScan(false);
|
await this.performStartupScan(false);
|
||||||
}
|
}
|
||||||
this.periodicInternalFileScanProcessor.enable(
|
this.periodicInternalFileScanProcessor.enable(
|
||||||
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
||||||
? this.settings.syncInternalFilesInterval * 1000
|
? this.settings.syncInternalFilesInterval * 1000
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
_everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
this.periodicInternalFileScanProcessor?.disable();
|
this.periodicInternalFileScanProcessor?.disable();
|
||||||
if (this._isMainSuspended()) return Promise.resolve(true);
|
if (this._isMainSuspended()) return Promise.resolve(true);
|
||||||
if (!this.plugin.$$isReady()) return Promise.resolve(true);
|
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
|
||||||
this.periodicInternalFileScanProcessor.enable(
|
this.periodicInternalFileScanProcessor.enable(
|
||||||
this._isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
this.isThisModuleEnabled() && this.settings.syncInternalFilesInterval
|
||||||
? this.settings.syncInternalFilesInterval * 1000
|
? this.settings.syncInternalFilesInterval * 1000
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
@@ -227,13 +239,14 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
async _anyProcessOptionalFileEvent(path: FilePath): Promise<boolean> {
|
||||||
if (this.isReady()) {
|
if (this.isReady()) {
|
||||||
return await this.trackStorageFileModification(path);
|
return (await this.trackStorageFileModification(path)) || false;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
_anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | "newer"> {
|
||||||
if (isInternalMetadata(path)) {
|
if (isInternalMetadata(path)) {
|
||||||
this.queueConflictCheck(path);
|
this.queueConflictCheck(path);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -241,12 +254,12 @@ export class HiddenFileSync extends LiveSyncCommands implements IObsidianModule
|
|||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
async _anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean> {
|
||||||
if (isInternalMetadata(doc._id)) {
|
if (isInternalMetadata(doc._id)) {
|
||||||
if (this._isThisModuleEnabled()) {
|
if (this.isThisModuleEnabled()) {
|
||||||
//system file
|
//system file
|
||||||
const filename = getPath(doc);
|
const filename = getPath(doc);
|
||||||
if (await this.plugin.$$isTargetFile(filename)) {
|
if (await this.services.vault.isTargetFile(filename)) {
|
||||||
// this.procInternalFile(filename);
|
// this.procInternalFile(filename);
|
||||||
await this.processReplicationResult(doc);
|
await this.processReplicationResult(doc);
|
||||||
return true;
|
return true;
|
||||||
@@ -699,7 +712,7 @@ Offline Changed files: ${processFiles.length}`;
|
|||||||
revFrom._revs_info
|
revFrom._revs_info
|
||||||
?.filter((e) => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo)
|
?.filter((e) => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo)
|
||||||
.first()?.rev ?? "";
|
.first()?.rev ?? "";
|
||||||
const result = await this.plugin.localDatabase.mergeObject(
|
const result = await this.plugin.managers.conflictManager.mergeObject(
|
||||||
doc.path,
|
doc.path,
|
||||||
commonBase,
|
commonBase,
|
||||||
doc._rev,
|
doc._rev,
|
||||||
@@ -1091,14 +1104,14 @@ Offline Changed files: ${files.length}`;
|
|||||||
|
|
||||||
// If something changes left, notify for reloading Obsidian.
|
// If something changes left, notify for reloading Obsidian.
|
||||||
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
|
if (updatedFolders.indexOf(this.plugin.app.vault.configDir) >= 0) {
|
||||||
if (!this.plugin.$$isReloadingScheduled()) {
|
if (!this.services.appLifecycle.isReloadingScheduled()) {
|
||||||
this.plugin.confirm.askInPopup(
|
this.plugin.confirm.askInPopup(
|
||||||
`updated-any-hidden`,
|
`updated-any-hidden`,
|
||||||
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
|
`Some setting files have been modified\nPress {HERE} to schedule a reload of Obsidian, or press elsewhere to dismiss this message.`,
|
||||||
(anchor) => {
|
(anchor) => {
|
||||||
anchor.text = "HERE";
|
anchor.text = "HERE";
|
||||||
anchor.addEventListener("click", () => {
|
anchor.addEventListener("click", () => {
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1318,7 +1331,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
|
async storeInternalFileToDatabase(file: InternalFileInfo | UXFileInfo, forceWrite = false) {
|
||||||
const storeFilePath = stripAllPrefixes(file.path as FilePath);
|
const storeFilePath = stripAllPrefixes(file.path as FilePath);
|
||||||
const storageFilePath = file.path;
|
const storageFilePath = file.path;
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
@@ -1372,7 +1385,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
const displayFileName = filenameSrc;
|
const displayFileName = filenameSrc;
|
||||||
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storeFilePath, ICHeader);
|
||||||
const mtime = new Date().getTime();
|
const mtime = new Date().getTime();
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
@@ -1432,7 +1445,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
includeDeletion = true
|
includeDeletion = true
|
||||||
) {
|
) {
|
||||||
const prefixedFileName = addPrefix(storageFilePath, ICHeader);
|
const prefixedFileName = addPrefix(storageFilePath, ICHeader);
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(storageFilePath)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(storageFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return await serialized("file-" + prefixedFileName, async () => {
|
return await serialized("file-" + prefixedFileName, async () => {
|
||||||
@@ -1479,7 +1492,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
const deleted = metaOnDB.deleted || metaOnDB._deleted || false;
|
const deleted = metaOnDB.deleted || metaOnDB._deleted || false;
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
const result = await this._deleteFile(storageFilePath);
|
const result = await this.__deleteFile(storageFilePath);
|
||||||
if (result == "OK") {
|
if (result == "OK") {
|
||||||
this.updateLastProcessedDeletion(storageFilePath, metaOnDB);
|
this.updateLastProcessedDeletion(storageFilePath, metaOnDB);
|
||||||
return true;
|
return true;
|
||||||
@@ -1493,7 +1506,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
if (fileOnDB === false) {
|
if (fileOnDB === false) {
|
||||||
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
throw new Error(`Failed to read file from database:${storageFilePath}`);
|
||||||
}
|
}
|
||||||
const resultStat = await this._writeFile(storageFilePath, fileOnDB, force);
|
const resultStat = await this.__writeFile(storageFilePath, fileOnDB, force);
|
||||||
if (resultStat) {
|
if (resultStat) {
|
||||||
this.updateLastProcessed(storageFilePath, metaOnDB, resultStat);
|
this.updateLastProcessed(storageFilePath, metaOnDB, resultStat);
|
||||||
this.queueNotification(storageFilePath);
|
this.queueNotification(storageFilePath);
|
||||||
@@ -1526,7 +1539,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
|
async __writeFile(storageFilePath: FilePath, fileOnDB: LoadedEntry, force: boolean): Promise<false | UXStat> {
|
||||||
try {
|
try {
|
||||||
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
|
const statBefore = await this.plugin.storageAccess.statHidden(storageFilePath);
|
||||||
const isExist = statBefore != null;
|
const isExist = statBefore != null;
|
||||||
@@ -1565,7 +1578,7 @@ Offline Changed files: ${files.length}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
|
async __deleteFile(storageFilePath: FilePath): Promise<false | "OK" | "ALREADY"> {
|
||||||
const result = await this.__removeFile(storageFilePath);
|
const result = await this.__removeFile(storageFilePath);
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
this._log(`STORAGE <x- DB: ${storageFilePath}: deleting (hidden) Failed`);
|
this._log(`STORAGE <x- DB: ${storageFilePath}: deleting (hidden) Failed`);
|
||||||
@@ -1582,11 +1595,11 @@ Offline Changed files: ${files.length}`;
|
|||||||
|
|
||||||
// <-- Database To Storage Functions
|
// <-- Database To Storage Functions
|
||||||
|
|
||||||
async $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private async _allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
||||||
await this._askHiddenFileConfiguration(opt);
|
await this.__askHiddenFileConfiguration(opt);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
private async __askHiddenFileConfiguration(opt: { enableFetch?: boolean; enableOverwrite?: boolean }) {
|
||||||
const messageFetch = `${opt.enableFetch ? `> - Fetch: Use the files stored from other devices. Choose this option if you have already configured hidden file synchronization on those devices and wish to accept their files.\n` : ""}`;
|
const messageFetch = `${opt.enableFetch ? `> - Fetch: Use the files stored from other devices. Choose this option if you have already configured hidden file synchronization on those devices and wish to accept their files.\n` : ""}`;
|
||||||
const messageOverwrite = `${opt.enableOverwrite ? `> - Overwrite: Use the files from this device. Select this option if you want to overwrite the files stored on other devices.\n` : ""}`;
|
const messageOverwrite = `${opt.enableOverwrite ? `> - Overwrite: Use the files from this device. Select this option if you want to overwrite the files stored on other devices.\n` : ""}`;
|
||||||
const messageMerge = `> - Merge: Merge the files from this device with those on other devices. Choose this option if you wish to combine files from multiple sources.
|
const messageMerge = `> - Merge: Merge the files from this device with those on other devices. Choose this option if you wish to combine files from multiple sources.
|
||||||
@@ -1632,7 +1645,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
private _allSuspendExtraSync(): Promise<boolean> {
|
||||||
if (this.plugin.settings.syncInternalFiles) {
|
if (this.plugin.settings.syncInternalFiles) {
|
||||||
this._log(
|
this._log(
|
||||||
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
"Hidden file synchronization have been temporarily disabled. Please enable them after the fetching, if you need them.",
|
||||||
@@ -1644,11 +1657,12 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --> Configuration handling
|
// --> Configuration handling
|
||||||
async $anyConfigureOptionalSyncFeature(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
|
private async _anyConfigureOptionalSyncFeature(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
await this.configureHiddenFileSync(mode);
|
await this.configureHiddenFileSync(mode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async configureHiddenFileSync(mode: "FETCH" | "OVERWRITE" | "MERGE" | "DISABLE" | "DISABLE_HIDDEN") {
|
async configureHiddenFileSync(mode: keyof OPTIONAL_SYNC_FEATURES) {
|
||||||
if (
|
if (
|
||||||
mode != "FETCH" &&
|
mode != "FETCH" &&
|
||||||
mode != "OVERWRITE" &&
|
mode != "OVERWRITE" &&
|
||||||
@@ -1718,7 +1732,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
const result: InternalFileInfo[] = [];
|
const result: InternalFileInfo[] = [];
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
const w = await f;
|
const w = await f;
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(w.path)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(w.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const mtime = w.stat?.mtime ?? 0;
|
const mtime = w.stat?.mtime ?? 0;
|
||||||
@@ -1756,7 +1770,7 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
|
if (ignoreFilter && ignoreFilter.some((ee) => ee.test(file))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
L1: for (const v of w.folders) {
|
L1: for (const v of w.folders) {
|
||||||
@@ -1765,13 +1779,10 @@ ${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.services.vault.isIgnoredByIgnoreFile(v)) {
|
||||||
continue L1;
|
continue L1;
|
||||||
}
|
}
|
||||||
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
|
files = files.concat(await this.getFiles(v, ignoreList, filter, ignoreFilter));
|
||||||
@@ -1780,4 +1791,20 @@ ${messageFetch}${messageOverwrite}${messageMerge}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// <-- Local Storage SubFunctions
|
// <-- Local Storage SubFunctions
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// No longer needed on initialisation
|
||||||
|
// services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.fileProcessing.handleOptionalFileEvent(this._anyProcessOptionalFileEvent.bind(this));
|
||||||
|
services.conflict.handleGetOptionalConflictCheckMethod(this._anyGetOptionalConflictCheckMethod.bind(this));
|
||||||
|
services.replication.handleProcessOptionalSynchroniseResult(this._anyProcessOptionalSyncFiles.bind(this));
|
||||||
|
services.setting.handleOnRealiseSetting(this._everyRealizeSettingSyncMode.bind(this));
|
||||||
|
services.appLifecycle.handleOnResuming(this._everyOnResumeProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.databaseEvents.handleDatabaseInitialised(this._everyOnDatabaseInitialized.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
services.setting.handleSuggestOptionalFeatures(this._allAskUsingOptionalSyncFeature.bind(this));
|
||||||
|
services.setting.handleEnableOptionalFeature(this._anyConfigureOptionalSyncFeature.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,14 @@ import {
|
|||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
type AnyEntry,
|
type AnyEntry,
|
||||||
type DocumentID,
|
type DocumentID,
|
||||||
type EntryHasPath,
|
|
||||||
type FilePath,
|
type FilePath,
|
||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type LOG_LEVEL,
|
type LOG_LEVEL,
|
||||||
} from "../lib/src/common/types.ts";
|
} from "../lib/src/common/types.ts";
|
||||||
import type ObsidianLiveSyncPlugin from "../main.ts";
|
import type ObsidianLiveSyncPlugin from "../main.ts";
|
||||||
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
import { MARK_DONE } from "../modules/features/ModuleLog.ts";
|
||||||
|
import type { LiveSyncCore } from "../main.ts";
|
||||||
|
import { __$checkInstanceBinding } from "../lib/src/dev/checks.ts";
|
||||||
|
|
||||||
let noticeIndex = 0;
|
let noticeIndex = 0;
|
||||||
export abstract class LiveSyncCommands {
|
export abstract class LiveSyncCommands {
|
||||||
@@ -25,12 +26,15 @@ export abstract class LiveSyncCommands {
|
|||||||
get localDatabase() {
|
get localDatabase() {
|
||||||
return this.plugin.localDatabase;
|
return this.plugin.localDatabase;
|
||||||
}
|
}
|
||||||
|
get services() {
|
||||||
id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
return this.plugin.services;
|
||||||
return this.plugin.$$id2path(id, entry, stripPrefix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
||||||
|
// return this.plugin.$$id2path(id, entry, stripPrefix);
|
||||||
|
// }
|
||||||
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
async path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
return await this.plugin.$$path2id(filename, prefix);
|
return await this.services.path.path2id(filename, prefix);
|
||||||
}
|
}
|
||||||
getPath(entry: AnyEntry): FilePathWithPrefix {
|
getPath(entry: AnyEntry): FilePathWithPrefix {
|
||||||
return getPath(entry);
|
return getPath(entry);
|
||||||
@@ -38,18 +42,20 @@ export abstract class LiveSyncCommands {
|
|||||||
|
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.onBindFunction(plugin, plugin.services);
|
||||||
|
__$checkInstanceBinding(this);
|
||||||
}
|
}
|
||||||
abstract onunload(): void;
|
abstract onunload(): void;
|
||||||
abstract onload(): void | Promise<void>;
|
abstract onload(): void | Promise<void>;
|
||||||
|
|
||||||
_isMainReady() {
|
_isMainReady() {
|
||||||
return this.plugin.$$isReady();
|
return this.plugin.services.appLifecycle.isReady();
|
||||||
}
|
}
|
||||||
_isMainSuspended() {
|
_isMainSuspended() {
|
||||||
return this.plugin.$$isSuspended();
|
return this.services.appLifecycle.isSuspended();
|
||||||
}
|
}
|
||||||
_isDatabaseReady() {
|
_isDatabaseReady() {
|
||||||
return this.plugin.$$isDatabaseReady();
|
return this.services.database.isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
@@ -89,4 +95,8 @@ export abstract class LiveSyncCommands {
|
|||||||
_debug = (msg: any, key?: string) => {
|
_debug = (msg: any, key?: string) => {
|
||||||
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
this._log(msg, LOG_LEVEL_VERBOSE, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// Override if needed.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
||||||
import { LOG_LEVEL_NOTICE, type MetaEntry } from "../../lib/src/common/types";
|
import {
|
||||||
|
EntryTypes,
|
||||||
|
LOG_LEVEL_INFO,
|
||||||
|
LOG_LEVEL_NOTICE,
|
||||||
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type DocumentID,
|
||||||
|
type EntryDoc,
|
||||||
|
type EntryLeaf,
|
||||||
|
type MetaEntry,
|
||||||
|
} from "../../lib/src/common/types";
|
||||||
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
import { getNoFromRev } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands";
|
import { LiveSyncCommands } from "../LiveSyncCommands";
|
||||||
|
import { serialized } from "octagonal-wheels/concurrency/lock_v2";
|
||||||
|
import { arrayToChunkedArray } from "octagonal-wheels/collection";
|
||||||
|
const DB_KEY_SEQ = "gc-seq";
|
||||||
|
const DB_KEY_CHUNK_SET = "chunk-set";
|
||||||
|
const DB_KEY_DOC_USAGE_MAP = "doc-usage-map";
|
||||||
|
type ChunkID = DocumentID;
|
||||||
|
type NoteDocumentID = DocumentID;
|
||||||
|
type Rev = string;
|
||||||
|
|
||||||
export class LocalDatabaseMaintenance extends LiveSyncCommands implements IObsidianModule {
|
type ChunkUsageMap = Map<NoteDocumentID, Map<Rev, Set<ChunkID>>>;
|
||||||
$everyOnload(): Promise<boolean> {
|
export class LocalDatabaseMaintenance extends LiveSyncCommands {
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
onunload(): void {
|
onunload(): void {
|
||||||
// NO OP.
|
// NO OP.
|
||||||
}
|
}
|
||||||
@@ -262,4 +276,213 @@ Note: **Make sure to synchronise all devices before deletion.**
|
|||||||
this.clearHash();
|
this.clearHash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async scanUnusedChunks() {
|
||||||
|
const kvDB = this.plugin.kvDB;
|
||||||
|
const chunkSet = (await kvDB.get<Set<DocumentID>>(DB_KEY_CHUNK_SET)) || new Set();
|
||||||
|
const chunkUsageMap = (await kvDB.get<ChunkUsageMap>(DB_KEY_DOC_USAGE_MAP)) || new Map();
|
||||||
|
const KEEP_MAX_REVS = 10;
|
||||||
|
const unusedSet = new Set<DocumentID>([...chunkSet]);
|
||||||
|
for (const [, revIdMap] of chunkUsageMap) {
|
||||||
|
const sortedRevId = [...revIdMap.entries()].sort((a, b) => getNoFromRev(b[0]) - getNoFromRev(a[0]));
|
||||||
|
if (sortedRevId.length > KEEP_MAX_REVS) {
|
||||||
|
// If we have more revisions than we want to keep, we need to delete the extras
|
||||||
|
}
|
||||||
|
const keepRevID = sortedRevId.slice(0, KEEP_MAX_REVS);
|
||||||
|
keepRevID.forEach((e) => e[1].forEach((ee) => unusedSet.delete(ee)));
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
chunkSet,
|
||||||
|
chunkUsageMap,
|
||||||
|
unusedSet,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Track changes in the database and update the chunk usage map for garbage collection.
|
||||||
|
* Note that this only able to perform without Fetch chunks on demand.
|
||||||
|
*/
|
||||||
|
async trackChanges(fromStart: boolean = false, showNotice: boolean = false) {
|
||||||
|
if (!this.isAvailable()) return;
|
||||||
|
const logLevel = showNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
|
const kvDB = this.plugin.kvDB;
|
||||||
|
|
||||||
|
const previousSeq = fromStart ? "" : await kvDB.get<string>(DB_KEY_SEQ);
|
||||||
|
const chunkSet = (await kvDB.get<Set<DocumentID>>(DB_KEY_CHUNK_SET)) || new Set();
|
||||||
|
|
||||||
|
const chunkUsageMap = (await kvDB.get<ChunkUsageMap>(DB_KEY_DOC_USAGE_MAP)) || new Map();
|
||||||
|
|
||||||
|
const db = this.localDatabase.localDatabase;
|
||||||
|
const verbose = (msg: string) => this._verbose(msg);
|
||||||
|
|
||||||
|
const processDoc = async (doc: EntryDoc, isDeleted: boolean) => {
|
||||||
|
if (!("children" in doc)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = doc._id;
|
||||||
|
const rev = doc._rev!;
|
||||||
|
const deleted = doc._deleted || isDeleted;
|
||||||
|
const softDeleted = doc.deleted;
|
||||||
|
const children = (doc.children || []) as DocumentID[];
|
||||||
|
if (!chunkUsageMap.has(id)) {
|
||||||
|
chunkUsageMap.set(id, new Map<Rev, Set<ChunkID>>());
|
||||||
|
}
|
||||||
|
for (const chunkId of children) {
|
||||||
|
if (deleted) {
|
||||||
|
chunkUsageMap.get(id)!.delete(rev);
|
||||||
|
// chunkSet.add(chunkId as DocumentID);
|
||||||
|
} else {
|
||||||
|
if (softDeleted) {
|
||||||
|
//TODO: Soft delete
|
||||||
|
chunkUsageMap.get(id)!.set(rev, (chunkUsageMap.get(id)!.get(rev) || new Set()).add(chunkId));
|
||||||
|
} else {
|
||||||
|
chunkUsageMap.get(id)!.set(rev, (chunkUsageMap.get(id)!.get(rev) || new Set()).add(chunkId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verbose(
|
||||||
|
`Tracking chunk: ${id}/${rev} (${doc?.path}), deleted: ${deleted ? "yes" : "no"} Soft-Deleted:${softDeleted ? "yes" : "no"}`
|
||||||
|
);
|
||||||
|
return await Promise.resolve();
|
||||||
|
};
|
||||||
|
// let saveQueue = 0;
|
||||||
|
const saveState = async (seq: string | number) => {
|
||||||
|
await kvDB.set(DB_KEY_SEQ, seq);
|
||||||
|
await kvDB.set(DB_KEY_CHUNK_SET, chunkSet);
|
||||||
|
await kvDB.set(DB_KEY_DOC_USAGE_MAP, chunkUsageMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
const processDocRevisions = async (doc: EntryDoc) => {
|
||||||
|
try {
|
||||||
|
const oldRevisions = await db.get(doc._id, { revs: true, revs_info: true, conflicts: true });
|
||||||
|
const allRevs = oldRevisions._revs_info?.length || 0;
|
||||||
|
const info = (oldRevisions._revs_info || [])
|
||||||
|
.filter((e) => e.status == "available" && e.rev != doc._rev)
|
||||||
|
.filter((info) => !chunkUsageMap.get(doc._id)?.has(info.rev));
|
||||||
|
const infoLength = info.length;
|
||||||
|
this._log(`Found ${allRevs} old revisions for ${doc._id} . ${infoLength} items to check `);
|
||||||
|
if (info.length > 0) {
|
||||||
|
const oldDocs = await Promise.all(
|
||||||
|
info
|
||||||
|
.filter((revInfo) => revInfo.status == "available")
|
||||||
|
.map((revInfo) => db.get(doc._id, { rev: revInfo.rev }))
|
||||||
|
).then((docs) => docs.filter((doc) => doc));
|
||||||
|
for (const oldDoc of oldDocs) {
|
||||||
|
await processDoc(oldDoc as EntryDoc, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
if ((ex as any)?.status == 404) {
|
||||||
|
this._log(`No revisions found for ${doc._id}`, LOG_LEVEL_VERBOSE);
|
||||||
|
} else {
|
||||||
|
this._log(`Error finding revisions for ${doc._id}`);
|
||||||
|
this._verbose(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const processChange = async (doc: EntryDoc, isDeleted: boolean, seq: string | number) => {
|
||||||
|
if (doc.type === EntryTypes.CHUNK) {
|
||||||
|
if (isDeleted) return;
|
||||||
|
chunkSet.add(doc._id);
|
||||||
|
} else if ("children" in doc) {
|
||||||
|
await processDoc(doc, isDeleted);
|
||||||
|
await serialized("x-process-doc", async () => await processDocRevisions(doc));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Track changes
|
||||||
|
let i = 0;
|
||||||
|
await db
|
||||||
|
.changes({
|
||||||
|
since: previousSeq || "",
|
||||||
|
live: false,
|
||||||
|
conflicts: true,
|
||||||
|
include_docs: true,
|
||||||
|
style: "all_docs",
|
||||||
|
return_docs: false,
|
||||||
|
})
|
||||||
|
.on("change", async (change) => {
|
||||||
|
// handle change
|
||||||
|
await processChange(change.doc!, change.deleted ?? false, change.seq);
|
||||||
|
if (i++ % 100 == 0) {
|
||||||
|
await saveState(change.seq);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("complete", async (info) => {
|
||||||
|
await saveState(info.last_seq);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track all changed docs and new-leafs;
|
||||||
|
|
||||||
|
const result = await this.scanUnusedChunks();
|
||||||
|
|
||||||
|
const message = `Total chunks: ${result.chunkSet.size}\nUnused chunks: ${result.unusedSet.size}`;
|
||||||
|
this._log(message, logLevel);
|
||||||
|
}
|
||||||
|
async performGC(showingNotice = false) {
|
||||||
|
if (!this.isAvailable()) return;
|
||||||
|
await this.trackChanges(false, showingNotice);
|
||||||
|
const title = "Are all devices synchronised?";
|
||||||
|
const confirmMessage = `This function deletes unused chunks from the device. If there are differences between devices, some chunks may be missing when resolving conflicts.
|
||||||
|
Be sure to synchronise before executing.
|
||||||
|
|
||||||
|
However, if you have deleted them, you may be able to recover them by performing Hatch -> Recreate missing chunks for all files.
|
||||||
|
|
||||||
|
Are you ready to delete unused chunks?`;
|
||||||
|
|
||||||
|
const logLevel = showingNotice ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO;
|
||||||
|
|
||||||
|
const BUTTON_OK = `Yes, delete chunks`;
|
||||||
|
const BUTTON_CANCEL = "Cancel";
|
||||||
|
|
||||||
|
const result = await this.plugin.confirm.askSelectStringDialogue(
|
||||||
|
confirmMessage,
|
||||||
|
[BUTTON_OK, BUTTON_CANCEL] as const,
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
defaultAction: BUTTON_CANCEL,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (result !== BUTTON_OK) {
|
||||||
|
this._log("User cancelled chunk deletion", logLevel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { unusedSet, chunkSet } = await this.scanUnusedChunks();
|
||||||
|
const deleteChunks = await this.database.allDocs({
|
||||||
|
keys: [...unusedSet],
|
||||||
|
include_docs: true,
|
||||||
|
});
|
||||||
|
for (const chunk of deleteChunks.rows) {
|
||||||
|
if ((chunk as any)?.value?.deleted) {
|
||||||
|
chunkSet.delete(chunk.key as DocumentID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const deleteDocs = deleteChunks.rows
|
||||||
|
.filter((e) => "doc" in e)
|
||||||
|
.map((e) => ({
|
||||||
|
...(e as any).doc!,
|
||||||
|
_deleted: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._log(`Deleting chunks: ${deleteDocs.length}`, logLevel);
|
||||||
|
const deleteChunkBatch = arrayToChunkedArray(deleteDocs, 100);
|
||||||
|
let successCount = 0;
|
||||||
|
let errored = 0;
|
||||||
|
for (const batch of deleteChunkBatch) {
|
||||||
|
const results = await this.database.bulkDocs(batch as EntryLeaf[]);
|
||||||
|
for (const result of results) {
|
||||||
|
if ("ok" in result) {
|
||||||
|
chunkSet.delete(result.id as DocumentID);
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
this._log(`Failed to delete doc: ${result.id}`, LOG_LEVEL_VERBOSE);
|
||||||
|
errored++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._log(`Deleting chunks: ${successCount} `, logLevel, "gc-preforming");
|
||||||
|
}
|
||||||
|
const message = `Garbage Collection completed.
|
||||||
|
Success: ${successCount}, Errored: ${errored}`;
|
||||||
|
this._log(message, logLevel);
|
||||||
|
const kvDB = this.plugin.kvDB;
|
||||||
|
await kvDB.set(DB_KEY_CHUNK_SET, chunkSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import type { IObsidianModule } from "../../modules/AbstractObsidianModule";
|
|
||||||
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
import { P2PReplicatorPaneView, VIEW_TYPE_P2P } from "./P2PReplicator/P2PReplicatorPaneView.ts";
|
||||||
import {
|
import {
|
||||||
AutoAccepting,
|
AutoAccepting,
|
||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
|
P2P_DEFAULT_SETTINGS,
|
||||||
REMOTE_P2P,
|
REMOTE_P2P,
|
||||||
type EntryDoc,
|
type EntryDoc,
|
||||||
type P2PSyncSetting,
|
type P2PSyncSetting,
|
||||||
type RemoteDBSettings,
|
type RemoteDBSettings,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
import { LiveSyncCommands } from "../LiveSyncCommands.ts";
|
||||||
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
import {
|
||||||
|
LiveSyncTrysteroReplicator,
|
||||||
|
setReplicatorFunc,
|
||||||
|
} from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator.ts";
|
||||||
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_P2P, eventHub } from "../../common/events.ts";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator.ts";
|
||||||
import { Logger } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||||
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
import type { CommandShim } from "../../lib/src/replication/trystero/P2PReplicatorPaneCommon.ts";
|
||||||
import {
|
import {
|
||||||
P2PReplicatorMixIn,
|
addP2PEventHandlers,
|
||||||
|
closeP2PReplicator,
|
||||||
|
openP2PReplicator,
|
||||||
|
P2PLogCollector,
|
||||||
removeP2PReplicatorInstance,
|
removeP2PReplicatorInstance,
|
||||||
type P2PReplicatorBase,
|
type P2PReplicatorBase,
|
||||||
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
} from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
@@ -24,8 +30,10 @@ import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
|||||||
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
|
||||||
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
|
import { getPlatformName } from "../../lib/src/PlatformAPIs/obsidian/Environment.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { TrysteroReplicator } from "../../lib/src/replication/trystero/TrysteroReplicator.ts";
|
||||||
|
|
||||||
class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicatorBase {
|
export class P2PReplicator extends LiveSyncCommands implements P2PReplicatorBase, CommandShim {
|
||||||
storeP2PStatusLine = reactiveSource("");
|
storeP2PStatusLine = reactiveSource("");
|
||||||
|
|
||||||
getSettings(): P2PSyncSetting {
|
getSettings(): P2PSyncSetting {
|
||||||
@@ -49,47 +57,127 @@ class P2PReplicatorCommandBase extends LiveSyncCommands implements P2PReplicator
|
|||||||
|
|
||||||
constructor(plugin: ObsidianLiveSyncPlugin) {
|
constructor(plugin: ObsidianLiveSyncPlugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
|
setReplicatorFunc(() => this._replicatorInstance);
|
||||||
|
addP2PEventHandlers(this);
|
||||||
|
this.afterConstructor();
|
||||||
|
// onBindFunction is called in super class
|
||||||
|
// this.onBindFunction(plugin, plugin.services);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
|
async handleReplicatedDocuments(docs: EntryDoc[]): Promise<void> {
|
||||||
// console.log("Processing Replicated Docs", docs);
|
// console.log("Processing Replicated Docs", docs);
|
||||||
return await this.plugin.$$parseReplicationResult(docs as PouchDB.Core.ExistingDocument<EntryDoc>[]);
|
return await this.services.replication.parseSynchroniseResult(
|
||||||
}
|
docs as PouchDB.Core.ExistingDocument<EntryDoc>[]
|
||||||
onunload(): void {
|
);
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
onload(): void | Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
||||||
this._simpleStore = this.plugin.$$getSimpleStore("p2p-sync");
|
|
||||||
return Promise.resolve(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class P2PReplicator
|
|
||||||
extends P2PReplicatorMixIn(P2PReplicatorCommandBase)
|
|
||||||
implements IObsidianModule, CommandShim
|
|
||||||
{
|
|
||||||
storeP2PStatusLine = reactiveSource("");
|
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.plugin));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return undefined!;
|
||||||
}
|
}
|
||||||
override getPlatform(): string {
|
_replicatorInstance?: TrysteroReplicator;
|
||||||
|
p2pLogCollector = new P2PLogCollector();
|
||||||
|
|
||||||
|
afterConstructor() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open() {
|
||||||
|
await openP2PReplicator(this);
|
||||||
|
}
|
||||||
|
async close() {
|
||||||
|
await closeP2PReplicator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(key: string) {
|
||||||
|
const vaultName = this.services.vault.getVaultName();
|
||||||
|
const dbKey = `${vaultName}-${key}`;
|
||||||
|
return localStorage.getItem(dbKey);
|
||||||
|
}
|
||||||
|
setConfig(key: string, value: string) {
|
||||||
|
const vaultName = this.services.vault.getVaultName();
|
||||||
|
const dbKey = `${vaultName}-${key}`;
|
||||||
|
localStorage.setItem(dbKey, value);
|
||||||
|
}
|
||||||
|
enableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.enableBroadcastChanges();
|
||||||
|
}
|
||||||
|
disableBroadcastCastings() {
|
||||||
|
return this?._replicatorInstance?.disableBroadcastChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._simpleStore = this.services.database.openSimpleStore("p2p-sync");
|
||||||
|
return Promise.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialiseP2PReplicator(): Promise<TrysteroReplicator> {
|
||||||
|
await this.init();
|
||||||
|
try {
|
||||||
|
if (this._replicatorInstance) {
|
||||||
|
await this._replicatorInstance.close();
|
||||||
|
this._replicatorInstance = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.settings.P2P_AppID) {
|
||||||
|
this.settings.P2P_AppID = P2P_DEFAULT_SETTINGS.P2P_AppID;
|
||||||
|
}
|
||||||
|
const getInitialDeviceName = () => this.getConfig("p2p_device_name") || this.services.vault.getVaultName();
|
||||||
|
|
||||||
|
const getSettings = () => this.settings;
|
||||||
|
const store = () => this.simpleStore();
|
||||||
|
const getDB = () => this.getDB();
|
||||||
|
|
||||||
|
const getConfirm = () => this.confirm;
|
||||||
|
const getPlatform = () => this.getPlatform();
|
||||||
|
const env = {
|
||||||
|
get db() {
|
||||||
|
return getDB();
|
||||||
|
},
|
||||||
|
get confirm() {
|
||||||
|
return getConfirm();
|
||||||
|
},
|
||||||
|
get deviceName() {
|
||||||
|
return getInitialDeviceName();
|
||||||
|
},
|
||||||
|
get platform() {
|
||||||
|
return getPlatform();
|
||||||
|
},
|
||||||
|
get settings() {
|
||||||
|
return getSettings();
|
||||||
|
},
|
||||||
|
processReplicatedDocs: async (docs: EntryDoc[]): Promise<void> => {
|
||||||
|
await this.handleReplicatedDocuments(docs);
|
||||||
|
// No op. This is a client and does not need to process the docs
|
||||||
|
},
|
||||||
|
get simpleStore() {
|
||||||
|
return store();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this._replicatorInstance = new TrysteroReplicator(env);
|
||||||
|
return this._replicatorInstance;
|
||||||
|
} catch (e) {
|
||||||
|
this._log(
|
||||||
|
e instanceof Error ? e.message : "Something occurred on Initialising P2P Replicator",
|
||||||
|
LOG_LEVEL_INFO
|
||||||
|
);
|
||||||
|
this._log(e, LOG_LEVEL_VERBOSE);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getPlatform(): string {
|
||||||
return getPlatformName();
|
return getPlatformName();
|
||||||
}
|
}
|
||||||
|
|
||||||
override onunload(): void {
|
onunload(): void {
|
||||||
removeP2PReplicatorInstance();
|
removeP2PReplicatorInstance();
|
||||||
void this.close();
|
void this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
override onload(): void | Promise<void> {
|
onload(): void | Promise<void> {
|
||||||
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
eventHub.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
|
||||||
void this.openPane();
|
void this.openPane();
|
||||||
});
|
});
|
||||||
@@ -97,12 +185,12 @@ export class P2PReplicator
|
|||||||
this.storeP2PStatusLine.value = line.value;
|
this.storeP2PStatusLine.value = line.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $everyOnInitializeDatabase(): Promise<boolean> {
|
async _everyOnInitializeDatabase(): Promise<boolean> {
|
||||||
await this.initialiseP2PReplicator();
|
await this.initialiseP2PReplicator();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allSuspendExtraSync() {
|
private async _allSuspendExtraSync() {
|
||||||
this.plugin.settings.P2P_Enabled = false;
|
this.plugin.settings.P2P_Enabled = false;
|
||||||
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
this.plugin.settings.P2P_AutoAccepting = AutoAccepting.NONE;
|
||||||
this.plugin.settings.P2P_AutoBroadcast = false;
|
this.plugin.settings.P2P_AutoBroadcast = false;
|
||||||
@@ -112,15 +200,15 @@ export class P2PReplicator
|
|||||||
return await Promise.resolve(true);
|
return await Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnLoadStart() {
|
// async $everyOnLoadStart() {
|
||||||
return await Promise.resolve();
|
// return await Promise.resolve();
|
||||||
}
|
// }
|
||||||
|
|
||||||
async openPane() {
|
async openPane() {
|
||||||
await this.plugin.$$showView(VIEW_TYPE_P2P);
|
await this.services.API.showWindow(VIEW_TYPE_P2P);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnloadStart(): Promise<boolean> {
|
async _everyOnloadStart(): Promise<boolean> {
|
||||||
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
this.plugin.registerView(VIEW_TYPE_P2P, (leaf) => new P2PReplicatorPaneView(leaf, this.plugin));
|
||||||
this.plugin.addCommand({
|
this.plugin.addCommand({
|
||||||
id: "open-p2p-replicator",
|
id: "open-p2p-replicator",
|
||||||
@@ -170,10 +258,26 @@ export class P2PReplicator
|
|||||||
|
|
||||||
return await Promise.resolve(true);
|
return await Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
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);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
_everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
|
const rep = this._replicatorInstance;
|
||||||
|
rep?.disconnectFromServer();
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
override onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
services.setting.handleSuspendExtraSync(this._allSuspendExtraSync.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
const initialSettings = { ...plugin.settings };
|
const initialSettings = { ...plugin.settings };
|
||||||
|
|
||||||
let settings = $state<P2PSyncSetting>(initialSettings);
|
let settings = $state<P2PSyncSetting>(initialSettings);
|
||||||
// const vaultName = plugin.$$getVaultName();
|
// const vaultName = service.vault.getVaultName();
|
||||||
// const dbKey = `${vaultName}-p2p-device-name`;
|
// const dbKey = `${vaultName}-p2p-device-name`;
|
||||||
|
|
||||||
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.$$getVaultName();
|
const initialDeviceName = cmdSync.getConfig("p2p_device_name") ?? plugin.services.vault.getVaultName();
|
||||||
let deviceName = $state<string>(initialDeviceName);
|
let deviceName = $state<string>(initialDeviceName);
|
||||||
|
|
||||||
let eP2PEnabled = $state<boolean>(initialSettings.P2P_Enabled);
|
let eP2PEnabled = $state<boolean>(initialSettings.P2P_Enabled);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ And you can also drop the local database to rebuild from the remote device.`,
|
|||||||
if (yn === DROP) {
|
if (yn === DROP) {
|
||||||
await this.plugin.rebuilder.scheduleFetch();
|
await this.plugin.rebuilder.scheduleFetch();
|
||||||
} else {
|
} else {
|
||||||
await this.plugin.$$scheduleAppReload();
|
this.plugin.services.appLifecycle.scheduleRestart();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
Logger(`Cancelled\nRemote config for ${peer.name} is not applied`, LOG_LEVEL_NOTICE);
|
||||||
|
|||||||
2
src/lib
2
src/lib
Submodule src/lib updated: 7a0d8e449a...422194fb1b
795
src/main.ts
795
src/main.ts
@@ -1,26 +1,10 @@
|
|||||||
import { Plugin } from "./deps";
|
import { Plugin } from "./deps";
|
||||||
import {
|
import {
|
||||||
type EntryDoc,
|
type EntryDoc,
|
||||||
type LoadedEntry,
|
|
||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
type LOG_LEVEL,
|
|
||||||
type diff_result,
|
|
||||||
type DatabaseConnectingStatus,
|
type DatabaseConnectingStatus,
|
||||||
type EntryHasPath,
|
|
||||||
type DocumentID,
|
|
||||||
type FilePathWithPrefix,
|
|
||||||
type FilePath,
|
|
||||||
LOG_LEVEL_INFO,
|
|
||||||
type HasSettings,
|
type HasSettings,
|
||||||
type MetaEntry,
|
|
||||||
type UXFileInfoStub,
|
|
||||||
type MISSING_OR_ERROR,
|
|
||||||
type AUTO_MERGED,
|
|
||||||
type RemoteDBSettings,
|
|
||||||
type TweakValues,
|
|
||||||
type CouchDBCredentials,
|
|
||||||
} from "./lib/src/common/types.ts";
|
} from "./lib/src/common/types.ts";
|
||||||
import { type FileEventItem } from "./common/types.ts";
|
|
||||||
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
import { type SimpleStore } from "./lib/src/common/utils.ts";
|
||||||
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import { LiveSyncLocalDB, type LiveSyncLocalDBEnv } from "./lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import {
|
import {
|
||||||
@@ -31,11 +15,10 @@ 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";
|
||||||
import { ObsHttpHandler } from "./modules/essentialObsidian/APILib/ObsHttpHandler.js";
|
|
||||||
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
|
import type { IObsidianModule } from "./modules/AbstractObsidianModule.ts";
|
||||||
|
|
||||||
import { ModuleDev } from "./modules/extras/ModuleDev.ts";
|
import { ModuleDev } from "./modules/extras/ModuleDev.ts";
|
||||||
@@ -59,8 +42,7 @@ import { ModuleDatabaseFileAccess } from "./modules/core/ModuleDatabaseFileAcces
|
|||||||
import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts";
|
import { ModuleFileHandler } from "./modules/core/ModuleFileHandler.ts";
|
||||||
import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts";
|
import { ModuleObsidianAPI } from "./modules/essentialObsidian/ModuleObsidianAPI.ts";
|
||||||
import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts";
|
import { ModuleObsidianEvents } from "./modules/essentialObsidian/ModuleObsidianEvents.ts";
|
||||||
import { injectModules, type AbstractModule } from "./modules/AbstractModule.ts";
|
import { type AbstractModule } from "./modules/AbstractModule.ts";
|
||||||
import type { ICoreModule } from "./modules/ModuleTypes.ts";
|
|
||||||
import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts";
|
import { ModuleObsidianSettingDialogue } from "./modules/features/ModuleObsidianSettingTab.ts";
|
||||||
import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts";
|
import { ModuleObsidianDocumentHistory } from "./modules/features/ModuleObsidianDocumentHistory.ts";
|
||||||
import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts";
|
import { ModuleObsidianGlobalHistory } from "./modules/features/ModuleGlobalHistory.ts";
|
||||||
@@ -84,13 +66,17 @@ import { ModuleLiveSyncMain } from "./modules/main/ModuleLiveSyncMain.ts";
|
|||||||
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
import { ModuleExtraSyncObsidian } from "./modules/extraFeaturesObsidian/ModuleExtraSyncObsidian.ts";
|
||||||
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
import { LocalDatabaseMaintenance } from "./features/LocalDatabaseMainte/CmdLocalDatabaseMainte.ts";
|
||||||
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
|
import { P2PReplicator } from "./features/P2PSync/CmdP2PReplicator.ts";
|
||||||
|
import type { LiveSyncManagers } from "./lib/src/managers/LiveSyncManagers.ts";
|
||||||
|
import { ObsidianServiceHub } from "./modules/services/ObsidianServices.ts";
|
||||||
|
import type { InjectableServiceHub } from "./lib/src/services/InjectableServices.ts";
|
||||||
|
|
||||||
|
// function throwShouldBeOverridden(): never {
|
||||||
|
// throw new Error("This function should be overridden by the module.");
|
||||||
|
// }
|
||||||
|
// const InterceptiveAll = Promise.resolve(true);
|
||||||
|
// const InterceptiveEvery = Promise.resolve(true);
|
||||||
|
// const InterceptiveAny = Promise.resolve(undefined);
|
||||||
|
|
||||||
function throwShouldBeOverridden(): never {
|
|
||||||
throw new Error("This function should be overridden by the module.");
|
|
||||||
}
|
|
||||||
const InterceptiveAll = Promise.resolve(true);
|
|
||||||
const InterceptiveEvery = Promise.resolve(true);
|
|
||||||
const InterceptiveAny = Promise.resolve(undefined);
|
|
||||||
/**
|
/**
|
||||||
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
||||||
* Please refer to the module's source code to understand the function.
|
* Please refer to the module's source code to understand the function.
|
||||||
@@ -100,6 +86,13 @@ const InterceptiveAny = Promise.resolve(undefined);
|
|||||||
* $any : Process all modules until the first success.
|
* $any : Process all modules until the first success.
|
||||||
* $ : Other interceptive points. You should manually assign the module
|
* $ : Other interceptive points. You should manually assign the module
|
||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
|
*
|
||||||
|
* No longer used! See AppLifecycleService in Services.ts.
|
||||||
|
* For a while, just commented out some previously used code. (sorry, some are deleted...)
|
||||||
|
* 'Convention over configuration' was a lie for me. At least, very lack of refactor-ability.
|
||||||
|
*
|
||||||
|
* Still some modules are separated, and connected by `ThroughHole` class.
|
||||||
|
* However, it is not a good design. I am going to manage the modules in a more explicit way.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class ObsidianLiveSyncPlugin
|
export default class ObsidianLiveSyncPlugin
|
||||||
@@ -111,6 +104,18 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
LiveSyncCouchDBReplicatorEnv,
|
LiveSyncCouchDBReplicatorEnv,
|
||||||
HasSettings<ObsidianLiveSyncSettings>
|
HasSettings<ObsidianLiveSyncSettings>
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The service hub for managing all services.
|
||||||
|
*/
|
||||||
|
_services: InjectableServiceHub = new ObsidianServiceHub();
|
||||||
|
get services() {
|
||||||
|
return this._services;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Bind functions to the service hub (for migration purpose).
|
||||||
|
*/
|
||||||
|
// bindFunctions = (this.serviceHub as ObsidianServiceHub).bindFunctions.bind(this.serviceHub);
|
||||||
|
|
||||||
// --> Module System
|
// --> Module System
|
||||||
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
getAddOn<T extends LiveSyncCommands>(cls: string) {
|
||||||
for (const addon of this.addOns) {
|
for (const addon of this.addOns) {
|
||||||
@@ -172,45 +177,14 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
new ModuleReplicateTest(this, this),
|
new ModuleReplicateTest(this, this),
|
||||||
new ModuleIntegratedTest(this, this),
|
new ModuleIntegratedTest(this, this),
|
||||||
] as (IObsidianModule | AbstractModule)[];
|
] as (IObsidianModule | AbstractModule)[];
|
||||||
injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
// injected = injectModules(this, [...this.modules, ...this.addOns] as ICoreModule[]);
|
||||||
// <-- Module System
|
// <-- Module System
|
||||||
|
|
||||||
$$isSuspended(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$setSuspended(value: boolean): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isDatabaseReady(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getDeviceAndVaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$setDeviceAndVaultName(name: string): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$isReady(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$markIsReady(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$resetIsReady(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Following are plugged by the modules.
|
// Following are plugged by the modules.
|
||||||
|
|
||||||
settings!: ObsidianLiveSyncSettings;
|
settings!: ObsidianLiveSyncSettings;
|
||||||
localDatabase!: LiveSyncLocalDB;
|
localDatabase!: LiveSyncLocalDB;
|
||||||
|
managers!: LiveSyncManagers;
|
||||||
simpleStore!: SimpleStore<CheckPointInfo>;
|
simpleStore!: SimpleStore<CheckPointInfo>;
|
||||||
replicator!: LiveSyncAbstractReplicator;
|
replicator!: LiveSyncAbstractReplicator;
|
||||||
confirm!: Confirm;
|
confirm!: Confirm;
|
||||||
@@ -227,30 +201,6 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
return this.settings;
|
return this.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$customFetchHandler(): ObsHttpHandler {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getLastPostFailedBySize(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isStorageInsensitive(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$shouldCheckCaseInsensitive(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isUnloaded(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
requestCount = reactiveSource(0);
|
requestCount = reactiveSource(0);
|
||||||
responseCount = reactiveSource(0);
|
responseCount = reactiveSource(0);
|
||||||
totalQueued = reactiveSource(0);
|
totalQueued = reactiveSource(0);
|
||||||
@@ -275,101 +225,6 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
syncStatus: "CLOSED" as DatabaseConnectingStatus,
|
syncStatus: "CLOSED" as DatabaseConnectingStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
$$isReloadingScheduled(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$getReplicator(): LiveSyncAbstractReplicator {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$connectRemoteCouchDB(
|
|
||||||
uri: string,
|
|
||||||
auth: CouchDBCredentials,
|
|
||||||
disableRequestURI: boolean,
|
|
||||||
passphrase: string | false,
|
|
||||||
useDynamicIterationCount: boolean,
|
|
||||||
performSetup: boolean,
|
|
||||||
skipInfo: boolean,
|
|
||||||
compression: boolean,
|
|
||||||
customHeaders: Record<string, string>,
|
|
||||||
useRequestAPI: boolean,
|
|
||||||
getPBKDF2Salt: () => Promise<Uint8Array>
|
|
||||||
): Promise<
|
|
||||||
| string
|
|
||||||
| {
|
|
||||||
db: PouchDB.Database<EntryDoc>;
|
|
||||||
info: PouchDB.Core.DatabaseInfo;
|
|
||||||
}
|
|
||||||
> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$isMobile(): boolean {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
$$vaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --> Path
|
|
||||||
|
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <-- Path
|
|
||||||
|
|
||||||
// --> Path conversion
|
|
||||||
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
// <!-- Path conversion
|
|
||||||
|
|
||||||
// --> Database
|
|
||||||
$$createPouchDBInstance<T extends object>(
|
|
||||||
name?: string,
|
|
||||||
options?: PouchDB.Configuration.DatabaseConfiguration
|
|
||||||
): PouchDB.Database<T> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$allOnDBUnload(db: LiveSyncLocalDB): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$allOnDBClose(db: LiveSyncLocalDB): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// <!-- Database
|
|
||||||
|
|
||||||
$anyNewReplicator(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
|
||||||
return InterceptiveEvery;
|
|
||||||
}
|
|
||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
|
||||||
return InterceptiveEvery;
|
|
||||||
}
|
|
||||||
|
|
||||||
// end interfaces
|
|
||||||
|
|
||||||
$$getVaultName(): string {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$$getSimpleStore<T>(kind: string): SimpleStore<T> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
// trench!: Trench;
|
|
||||||
|
|
||||||
// --> Events
|
// --> Events
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -410,326 +265,404 @@ export default class ObsidianLiveSyncPlugin
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
// $everyOnLayoutReady(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onLayoutReady
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
$everyOnFirstInitialize(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnFirstInitialize(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onFirstInitialize
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
// Some Module should call this function to start the plugin.
|
// Some Module should call this function to start the plugin.
|
||||||
$$onLiveSyncReady(): Promise<false | undefined> {
|
// $$onLiveSyncReady(): Promise<false | undefined> {
|
||||||
throwShouldBeOverridden();
|
// //TODO: AppLifecycleService.onLiveSyncReady
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$wireUpEvents(): void {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$wireUpEvents(): void {
|
||||||
}
|
// //TODO: AppLifecycleService.wireUpEvents
|
||||||
$$onLiveSyncLoad(): Promise<void> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$onLiveSyncLoad(): Promise<void> {
|
||||||
|
// //TODO: AppLifecycleService.onLoad
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$onLiveSyncUnload(): Promise<void> {
|
// $$onLiveSyncUnload(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// //TODO: AppLifecycleService.onAppUnload
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$allScanStat(): Promise<boolean> {
|
// $allScanStat(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// //TODO: AppLifecycleService.scanStartupIssues
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnloadStart(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onInitialise
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
// $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onApplyStartupLoaded
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
// $everyOnload(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// //TODO: AppLifecycleService.onLoaded
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
// $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// //TODO: FileProcessingService.processFileEvent
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> {
|
// $allStartOnUnload(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// //TODO: AppLifecycleService.onBeforeUnload
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$allOnUnload(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveAll;
|
// $allOnUnload(): Promise<boolean> {
|
||||||
}
|
// //TODO: AppLifecycleService.onUnload
|
||||||
|
// return InterceptiveAll;
|
||||||
|
// }
|
||||||
|
|
||||||
$$openDatabase(): Promise<boolean> {
|
// $$openDatabase(): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseService.openDatabase
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$realizeSettingSyncMode(): Promise<void> {
|
// $$realizeSettingSyncMode(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.realiseSetting
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$performRestart() {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$performRestart() {
|
||||||
}
|
// // AppLifecycleService.performRestart
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$clearUsedPassphrase(): void {
|
// $$clearUsedPassphrase(): void {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.clearUsedPassphrase
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
// $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.decryptSettings
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
}
|
// // SettingService.adjustSettings
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$loadSettings(): Promise<void> {
|
// $$loadSettings(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.loadSettings
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$saveDeviceAndVaultName(): void {
|
// $$saveDeviceAndVaultName(): void {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.saveDeviceAndVaultName
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$saveSettingData(): Promise<void> {
|
// $$saveSettingData(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // SettingService.saveSettingData
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
// $anyProcessOptionalFileEvent(path: FilePath): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // FileProcessingService.processOptionalFileEvent
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyCommitPendingFileEvent(): Promise<boolean> {
|
// $everyCommitPendingFileEvent(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// // FileProcessingService.commitPendingFileEvent
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
// ->
|
// ->
|
||||||
$anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
|
// $anyGetOptionalConflictCheckMethod(path: FilePathWithPrefix): Promise<boolean | undefined | "newer"> {
|
||||||
return InterceptiveAny;
|
// return InterceptiveAny;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
// $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.queueCheckForConflictIfOpen
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
// $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.queueCheckForConflict
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$waitForAllConflictProcessed(): Promise<boolean> {
|
// $$waitForAllConflictProcessed(): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // ConflictEventManager.ensureAllConflictProcessed
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
//<-- Conflict Check
|
//<-- Conflict Check
|
||||||
|
|
||||||
$anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
// $anyProcessOptionalSyncFiles(doc: LoadedEntry): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processOptionalSyncFile
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
$anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
|
// $anyProcessReplicatedDoc(doc: MetaEntry): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processReplicatedDocument
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
//---> Sync
|
//---> Sync
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
// $$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.parseSynchroniseResult
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
|
// $anyModuleParsedReplicationResultItem(docs: PouchDB.Core.ExistingDocument<EntryDoc>): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ReplicationService.processVirtualDocument
|
||||||
}
|
// return InterceptiveAny;
|
||||||
$everyBeforeRealizeSetting(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyBeforeRealizeSetting(): Promise<boolean> {
|
||||||
}
|
// // SettingEventManager.beforeRealiseSetting
|
||||||
$everyAfterRealizeSetting(): Promise<boolean> {
|
// return InterceptiveEvery;
|
||||||
return InterceptiveEvery;
|
// }
|
||||||
}
|
// $everyAfterRealizeSetting(): Promise<boolean> {
|
||||||
$everyRealizeSettingSyncMode(): Promise<boolean> {
|
// // SettingEventManager.onSettingRealised
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
|
// $everyRealizeSettingSyncMode(): Promise<boolean> {
|
||||||
|
// // SettingEventManager.onRealiseSetting
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
// $everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// // AppLifecycleService.onSuspending
|
||||||
}
|
// return InterceptiveEvery;
|
||||||
$everyOnResumeProcess(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyOnResumeProcess(): Promise<boolean> {
|
||||||
}
|
// // AppLifecycleService.onResuming
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
// return InterceptiveEvery;
|
||||||
return InterceptiveEvery;
|
// }
|
||||||
}
|
// $everyAfterResumeProcess(): Promise<boolean> {
|
||||||
|
// // AppLifecycleService.onResumed
|
||||||
|
// return InterceptiveEvery;
|
||||||
|
// }
|
||||||
|
|
||||||
$$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
// $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
||||||
throwShouldBeOverridden();
|
// //TODO:TweakValueService.fetchRemotePreferred
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$checkAndAskResolvingMismatchedTweaks(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]> {
|
||||||
}
|
// //TODO:TweakValueService.checkAndAskResolvingMismatched
|
||||||
$$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$askResolvingMismatchedTweaks(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
||||||
|
// //TODO:TweakValueService.askResolvingMismatched
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$checkAndAskUseRemoteConfiguration(
|
// $$checkAndAskUseRemoteConfiguration(
|
||||||
settings: RemoteDBSettings
|
// settings: RemoteDBSettings
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
throwShouldBeOverridden();
|
// // TweakValueService.checkAndAskUseRemoteConfiguration
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$askUseRemoteConfiguration(
|
// $$askUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings,
|
// trialSetting: RemoteDBSettings,
|
||||||
preferred: TweakValues
|
// preferred: TweakValues
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
// ): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
throwShouldBeOverridden();
|
// // TweakValueService.askUseRemoteConfiguration
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
// }
|
||||||
return InterceptiveEvery;
|
// $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
}
|
// // ReplicationService.beforeReplicate
|
||||||
$$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
// return InterceptiveEvery;
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
|
||||||
$$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
|
|
||||||
throwShouldBeOverridden();
|
|
||||||
}
|
|
||||||
|
|
||||||
$everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
|
// $$canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.isReplicationReady
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$initializeDatabase(
|
// $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
showingNotice: boolean = false,
|
// // ReplicationService.replicate
|
||||||
reopenDatabase = true,
|
// throwShouldBeOverridden();
|
||||||
ignoreSuspending: boolean = false
|
// }
|
||||||
): Promise<boolean> {
|
// $$replicateByEvent(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
throwShouldBeOverridden();
|
// // ReplicationService.replicateByEvent
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
// $everyOnDatabaseInitialized(showingNotice: boolean): Promise<boolean> {
|
||||||
return InterceptiveAny;
|
// // DatabaseEventService.onDatabaseInitialised
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$replicateAllToServer(
|
// $$initializeDatabase(
|
||||||
showingNotice: boolean = false,
|
// showingNotice: boolean = false,
|
||||||
sendChunksInBulkDisabled: boolean = false
|
// reopenDatabase = true,
|
||||||
): Promise<boolean> {
|
// ignoreSuspending: boolean = false
|
||||||
throwShouldBeOverridden();
|
// ): Promise<boolean> {
|
||||||
}
|
// // DatabaseEventService.initializeDatabase
|
||||||
$$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
|
||||||
|
// $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
|
// // ReplicationService.checkConnectionFailure
|
||||||
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $$replicateAllToServer(
|
||||||
|
// showingNotice: boolean = false,
|
||||||
|
// sendChunksInBulkDisabled: boolean = false
|
||||||
|
// ): Promise<boolean> {
|
||||||
|
// // RemoteService.replicateAllToRemote
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
// $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
||||||
|
// // RemoteService.replicateAllFromRemote
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// Remote Governing
|
// Remote Governing
|
||||||
$$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
// $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markLocked;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$markRemoteUnlocked(): Promise<void> {
|
// $$markRemoteUnlocked(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markUnlocked;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$markRemoteResolved(): Promise<void> {
|
// $$markRemoteResolved(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.markResolved;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// <-- Remote Governing
|
// <-- Remote Governing
|
||||||
|
|
||||||
$$isFileSizeExceeded(size: number): boolean {
|
// $$isFileSizeExceeded(size: number): boolean {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isFileSizeTooLarge
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
|
// $$performFullScan(showingNotice?: boolean, ignoreSuspending?: boolean): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.scanVault
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$anyResolveConflictByUI(
|
// $anyResolveConflictByUI(
|
||||||
filename: FilePathWithPrefix,
|
// filename: FilePathWithPrefix,
|
||||||
conflictCheckResult: diff_result
|
// conflictCheckResult: diff_result
|
||||||
): Promise<boolean | undefined> {
|
// ): Promise<boolean | undefined> {
|
||||||
return InterceptiveAny;
|
// // ConflictService.resolveConflictByUserInteraction
|
||||||
}
|
// return InterceptiveAny;
|
||||||
$$resolveConflictByDeletingRev(
|
// }
|
||||||
path: FilePathWithPrefix,
|
// $$resolveConflictByDeletingRev(
|
||||||
deleteRevision: string,
|
// path: FilePathWithPrefix,
|
||||||
subTitle = ""
|
// deleteRevision: string,
|
||||||
): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
// subTitle = ""
|
||||||
throwShouldBeOverridden();
|
// ): Promise<typeof MISSING_OR_ERROR | typeof AUTO_MERGED> {
|
||||||
}
|
// // ConflictService.resolveByDeletingRevision
|
||||||
$$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
// throwShouldBeOverridden();
|
||||||
throwShouldBeOverridden();
|
// }
|
||||||
}
|
// $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
$anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
// // ConflictService.resolveConflict
|
||||||
throwShouldBeOverridden();
|
// throwShouldBeOverridden();
|
||||||
}
|
// }
|
||||||
|
// $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
||||||
|
// // ConflictService.resolveByNewest
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$resetLocalDatabase(): Promise<void> {
|
// $$resetLocalDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // DatabaseService.resetDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$tryResetRemoteDatabase(): Promise<void> {
|
// $$tryResetRemoteDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.tryResetDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$tryCreateRemoteDatabase(): Promise<void> {
|
// $$tryCreateRemoteDatabase(): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // RemoteService.tryCreateDatabase;
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
// $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isIgnoredByIgnoreFiles
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
|
// $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // VaultService.isTargetFile
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$askReload(message?: string) {
|
// $$askReload(message?: string) {
|
||||||
throwShouldBeOverridden();
|
// // AppLifecycleService.askRestart
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$$scheduleAppReload() {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $$scheduleAppReload() {
|
||||||
}
|
// // AppLifecycleService.scheduleRestart
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
//--- Setup
|
//--- Setup
|
||||||
$allSuspendAllSync(): Promise<boolean> {
|
// $allSuspendAllSync(): Promise<boolean> {
|
||||||
return InterceptiveAll;
|
// // SettingEventManager.suspendAllSync
|
||||||
}
|
// return InterceptiveAll;
|
||||||
$allSuspendExtraSync(): Promise<boolean> {
|
// }
|
||||||
return InterceptiveAll;
|
// $allSuspendExtraSync(): Promise<boolean> {
|
||||||
}
|
// // SettingEventManager.suspendExtraSync
|
||||||
|
// return InterceptiveAll;
|
||||||
|
// }
|
||||||
|
|
||||||
$allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
// $allAskUsingOptionalSyncFeature(opt: { enableFetch?: boolean; enableOverwrite?: boolean }): Promise<boolean> {
|
||||||
throwShouldBeOverridden();
|
// // SettingEventManager.suggestOptionalFeatures
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
$anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
|
// }
|
||||||
throwShouldBeOverridden();
|
// $anyConfigureOptionalSyncFeature(mode: string): Promise<void> {
|
||||||
}
|
// // SettingEventManager.enableOptionalFeature
|
||||||
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
$$showView(viewType: string): Promise<void> {
|
// $$showView(viewType: string): Promise<void> {
|
||||||
throwShouldBeOverridden();
|
// // UIManager.showWindow //
|
||||||
}
|
// throwShouldBeOverridden();
|
||||||
|
// }
|
||||||
|
|
||||||
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
// For Development: Ensure reliability MORE AND MORE. May the this plug-in helps all of us.
|
||||||
$everyModuleTest(): Promise<boolean> {
|
// $everyModuleTest(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
$everyModuleTestMultiDevice(): Promise<boolean> {
|
// $everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
return InterceptiveEvery;
|
// return InterceptiveEvery;
|
||||||
}
|
// }
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
// $$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
||||||
throwShouldBeOverridden();
|
// throwShouldBeOverridden();
|
||||||
}
|
// }
|
||||||
|
|
||||||
_isThisModuleEnabled(): boolean {
|
// _isThisModuleEnabled(): boolean {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> {
|
// $anyGetAppId(): Promise<string | undefined> {
|
||||||
return InterceptiveAny;
|
// // APIService.getAppId
|
||||||
}
|
// return InterceptiveAny;
|
||||||
|
// }
|
||||||
|
|
||||||
// Plug-in's overrideable functions
|
// Plug-in's overrideable functions
|
||||||
onload() {
|
onload() {
|
||||||
void this.$$onLiveSyncLoad();
|
void this.services.appLifecycle.onLoad();
|
||||||
}
|
}
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
return void this.$$onLiveSyncUnload();
|
return void this.services.appLifecycle.onAppUnload();
|
||||||
}
|
}
|
||||||
// <-- Plug-in's overrideable functions
|
// <-- Plug-in's overrideable functions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,35 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
||||||
import type { LOG_LEVEL } from "../lib/src/common/types";
|
import type { LOG_LEVEL } from "../lib/src/common/types";
|
||||||
import type { LiveSyncCore } from "../main";
|
import type { LiveSyncCore } from "../main";
|
||||||
import { unique } from "octagonal-wheels/collection";
|
import { __$checkInstanceBinding } from "../lib/src/dev/checks";
|
||||||
import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
// import { unique } from "octagonal-wheels/collection";
|
||||||
import type {
|
// import type { IObsidianModule } from "./AbstractObsidianModule.ts";
|
||||||
ICoreModuleBase,
|
// import type {
|
||||||
AllInjectableProps,
|
// ICoreModuleBase,
|
||||||
AllExecuteProps,
|
// AllInjectableProps,
|
||||||
EveryExecuteProps,
|
// AllExecuteProps,
|
||||||
AnyExecuteProps,
|
// EveryExecuteProps,
|
||||||
ICoreModule,
|
// AnyExecuteProps,
|
||||||
} from "./ModuleTypes";
|
// ICoreModule,
|
||||||
|
// } from "./ModuleTypes";
|
||||||
|
|
||||||
function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
// function isOverridableKey(key: string): key is keyof ICoreModuleBase {
|
||||||
return key.startsWith("$");
|
// return key.startsWith("$");
|
||||||
}
|
// }
|
||||||
|
|
||||||
function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
// function isInjectableKey(key: string): key is keyof AllInjectableProps {
|
||||||
return key.startsWith("$$");
|
// return key.startsWith("$$");
|
||||||
}
|
// }
|
||||||
|
|
||||||
function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
// function isAllExecuteKey(key: string): key is keyof AllExecuteProps {
|
||||||
return key.startsWith("$all");
|
// return key.startsWith("$all");
|
||||||
}
|
// }
|
||||||
function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
|
// function isEveryExecuteKey(key: string): key is keyof EveryExecuteProps {
|
||||||
return key.startsWith("$every");
|
// return key.startsWith("$every");
|
||||||
}
|
// }
|
||||||
function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
// function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
||||||
return key.startsWith("$any");
|
// return key.startsWith("$any");
|
||||||
}
|
// }
|
||||||
/**
|
/**
|
||||||
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
* All $prefixed functions are hooked by the modules. Be careful to call them directly.
|
||||||
* Please refer to the module's source code to understand the function.
|
* Please refer to the module's source code to understand the function.
|
||||||
@@ -39,100 +40,100 @@ function isAnyExecuteKey(key: string): key is keyof AnyExecuteProps {
|
|||||||
* $ : Other interceptive points. You should manually assign the module
|
* $ : Other interceptive points. You should manually assign the module
|
||||||
* All of above performed on injectModules function.
|
* All of above performed on injectModules function.
|
||||||
*/
|
*/
|
||||||
export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
// export function injectModules<T extends ICoreModule>(target: T, modules: ICoreModule[]) {
|
||||||
const allKeys = unique([
|
// const allKeys = unique([
|
||||||
...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
// ...Object.keys(Object.getOwnPropertyDescriptors(target)),
|
||||||
...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
// ...Object.keys(Object.getOwnPropertyDescriptors(Object.getPrototypeOf(target))),
|
||||||
]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
// ]).filter((e) => e.startsWith("$")) as (keyof ICoreModule)[];
|
||||||
const moduleMap = new Map<string, IObsidianModule[]>();
|
// const moduleMap = new Map<string, IObsidianModule[]>();
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
for (const key of allKeys) {
|
// for (const key of allKeys) {
|
||||||
if (isOverridableKey(key)) {
|
// if (isOverridableKey(key)) {
|
||||||
if (key in module) {
|
// if (key in module) {
|
||||||
const list = moduleMap.get(key) || [];
|
// const list = moduleMap.get(key) || [];
|
||||||
if (typeof module[key] === "function") {
|
// if (typeof module[key] === "function") {
|
||||||
module[key] = module[key].bind(module) as any;
|
// module[key] = module[key].bind(module) as any;
|
||||||
}
|
// }
|
||||||
list.push(module);
|
// list.push(module);
|
||||||
moduleMap.set(key, list);
|
// moduleMap.set(key, list);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
// Logger(`Injecting modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
||||||
for (const key of allKeys) {
|
// for (const key of allKeys) {
|
||||||
const modules = moduleMap.get(key) || [];
|
// const modules = moduleMap.get(key) || [];
|
||||||
if (isInjectableKey(key)) {
|
// if (isInjectableKey(key)) {
|
||||||
if (modules.length == 0) {
|
// if (modules.length == 0) {
|
||||||
throw new Error(`No module injected for ${key}. This is a fatal error.`);
|
// throw new Error(`No module injected for ${key}. This is a fatal error.`);
|
||||||
}
|
// }
|
||||||
target[key] = modules[0][key]! as any;
|
// target[key] = modules[0][key]! as any;
|
||||||
Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${modules[0].constructor.name}]: Injected ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
} else if (isAllExecuteKey(key)) {
|
// } else if (isAllExecuteKey(key)) {
|
||||||
const modules = moduleMap.get(key) || [];
|
// const modules = moduleMap.get(key) || [];
|
||||||
target[key] = async (...args: any) => {
|
// target[key] = async (...args: any) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
await module[key]!(...args);
|
// await module[key]!(...args);
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: All handler for ${key} failed`, LOG_LEVEL_VERBOSE);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (All) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else if (isEveryExecuteKey(key)) {
|
// } else if (isEveryExecuteKey(key)) {
|
||||||
target[key] = async (...args: any) => {
|
// target[key] = async (...args: any) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore:2556
|
// //@ts-ignore:2556
|
||||||
const ret = await module[key]!(...args);
|
// const ret = await module[key]!(...args);
|
||||||
if (ret !== undefined && !ret) {
|
// if (ret !== undefined && !ret) {
|
||||||
// Failed then return that falsy value.
|
// // Failed then return that falsy value.
|
||||||
return ret;
|
// return ret;
|
||||||
}
|
// }
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
|
// Logger(`[${module.constructor.name}]: Every handler for ${key} failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (Every) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else if (isAnyExecuteKey(key)) {
|
// } else if (isAnyExecuteKey(key)) {
|
||||||
//@ts-ignore
|
// //@ts-ignore
|
||||||
target[key] = async (...args: any[]) => {
|
// target[key] = async (...args: any[]) => {
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
try {
|
// try {
|
||||||
//@ts-ignore:2556
|
// //@ts-ignore:2556
|
||||||
const ret = await module[key](...args);
|
// const ret = await module[key](...args);
|
||||||
// If truly value returned, then return that value.
|
// // If truly value returned, then return that value.
|
||||||
if (ret) {
|
// if (ret) {
|
||||||
return ret;
|
// return ret;
|
||||||
}
|
// }
|
||||||
} catch (ex) {
|
// } catch (ex) {
|
||||||
Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
|
// Logger(`[${module.constructor.name}]: Any handler for ${key} failed`);
|
||||||
Logger(ex, LOG_LEVEL_VERBOSE);
|
// Logger(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return false;
|
// return false;
|
||||||
};
|
// };
|
||||||
for (const module of modules) {
|
// for (const module of modules) {
|
||||||
Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`[${module.constructor.name}]: Injected (Any) ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
|
// Logger(`No injected handler for ${key} `, LOG_LEVEL_VERBOSE);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
// Logger(`Injected modules for ${target.constructor.name}`, LOG_LEVEL_VERBOSE);
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export abstract class AbstractModule {
|
export abstract class AbstractModule {
|
||||||
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
_log = (msg: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string) => {
|
||||||
@@ -153,14 +154,18 @@ export abstract class AbstractModule {
|
|||||||
this.core.settings = value;
|
this.core.settings = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
// Override if needed.
|
||||||
|
}
|
||||||
constructor(public core: LiveSyncCore) {
|
constructor(public core: LiveSyncCore) {
|
||||||
|
this.onBindFunction(core, core.services);
|
||||||
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
|
Logger(`[${this.constructor.name}] Loaded`, LOG_LEVEL_VERBOSE);
|
||||||
|
__$checkInstanceBinding(this);
|
||||||
}
|
}
|
||||||
saveSettings = this.core.saveSettings.bind(this.core);
|
saveSettings = this.core.saveSettings.bind(this.core);
|
||||||
|
|
||||||
// abstract $everyTest(): Promise<boolean>;
|
|
||||||
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
|
addTestResult(key: string, value: boolean, summary?: string, message?: string) {
|
||||||
this.core.$$addTestResult(`${this.constructor.name}`, key, value, summary, message);
|
this.services.test.addTestResult(`${this.constructor.name}`, key, value, summary, message);
|
||||||
}
|
}
|
||||||
testDone(result: boolean = true) {
|
testDone(result: boolean = true) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
@@ -185,4 +190,8 @@ export abstract class AbstractModule {
|
|||||||
}
|
}
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get services() {
|
||||||
|
return this.core._services;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,18 +37,18 @@ export abstract class AbstractObsidianModule extends AbstractModule {
|
|||||||
|
|
||||||
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
saveSettings = this.plugin.saveSettings.bind(this.plugin);
|
||||||
|
|
||||||
_isMainReady() {
|
isMainReady() {
|
||||||
return this.core.$$isReady();
|
return this.services.appLifecycle.isReady();
|
||||||
}
|
}
|
||||||
_isMainSuspended() {
|
isMainSuspended() {
|
||||||
return this.core.$$isSuspended();
|
return this.services.appLifecycle.isSuspended();
|
||||||
}
|
}
|
||||||
_isDatabaseReady() {
|
isDatabaseReady() {
|
||||||
return this.core.$$isDatabaseReady();
|
return this.services.database.isDatabaseReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
//should be overridden
|
//should be overridden
|
||||||
_isThisModuleEnabled() {
|
isThisModuleEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import type {
|
|||||||
DocumentID,
|
DocumentID,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess";
|
||||||
import { type IObsidianModule } from "../AbstractObsidianModule.ts";
|
|
||||||
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
import { isPlainText, shouldBeIgnored, stripAllPrefixes } from "../../lib/src/string_and_binary/path";
|
||||||
import {
|
import {
|
||||||
createBlob,
|
createBlob,
|
||||||
@@ -30,14 +29,15 @@ import {
|
|||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { ICHeader } from "../../common/types.ts";
|
import { ICHeader } from "../../common/types.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidianModule, DatabaseFileAccess {
|
export class ModuleDatabaseFileAccess extends AbstractModule implements DatabaseFileAccess {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.databaseFileAccess = this;
|
this.core.databaseFileAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyModuleTest(): Promise<boolean> {
|
private async _everyModuleTest(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
|
const testString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam nec purus nec nunc";
|
||||||
// Before test, we need to delete completely.
|
// Before test, we need to delete completely.
|
||||||
@@ -75,7 +75,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
|
|
||||||
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
async checkIsTargetFile(file: UXFileInfoStub | FilePathWithPrefix): Promise<boolean> {
|
||||||
const path = getStoragePathFromUXFileInfo(file);
|
const path = getStoragePathFromUXFileInfo(file);
|
||||||
if (!(await this.core.$$isTargetFile(path))) {
|
if (!(await this.services.vault.isTargetFile(path))) {
|
||||||
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
this._log(`File is not target`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -102,11 +102,11 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
async createChunks(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
||||||
return await this._store(file, force, skipCheck, true);
|
return await this.__store(file, force, skipCheck, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
async store(file: UXFileInfo, force: boolean = false, skipCheck?: boolean): Promise<boolean> {
|
||||||
return await this._store(file, force, skipCheck, false);
|
return await this.__store(file, force, skipCheck, false);
|
||||||
}
|
}
|
||||||
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
|
async storeContent(path: FilePathWithPrefix, content: string): Promise<boolean> {
|
||||||
const blob = createTextBlob(content);
|
const blob = createTextBlob(content);
|
||||||
@@ -124,10 +124,10 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
body: blob,
|
body: blob,
|
||||||
isInternal,
|
isInternal,
|
||||||
};
|
};
|
||||||
return await this._store(dummyUXFileInfo, true, false, false);
|
return await this.__store(dummyUXFileInfo, true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _store(
|
private async __store(
|
||||||
file: UXFileInfo,
|
file: UXFileInfo,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
skipCheck?: boolean,
|
skipCheck?: boolean,
|
||||||
@@ -177,7 +177,7 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const idMain = await this.core.$$path2id(fullPath);
|
const idMain = await this.services.path.path2id(fullPath);
|
||||||
|
|
||||||
const id = (idPrefix + idMain) as DocumentID;
|
const id = (idPrefix + idMain) as DocumentID;
|
||||||
const d: SavingEntry = {
|
const d: SavingEntry = {
|
||||||
@@ -345,4 +345,8 @@ export class ModuleDatabaseFileAccess extends AbstractModule implements IObsidia
|
|||||||
eventHub.emitEvent(EVENT_FILE_SAVED);
|
eventHub.emitEvent(EVENT_FILE_SAVED);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.test.handleTest(this._everyModuleTest.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import {
|
|||||||
getStoragePathFromUXFileInfo,
|
getStoragePathFromUXFileInfo,
|
||||||
markChangesAreSame,
|
markChangesAreSame,
|
||||||
} from "../../common/utils";
|
} from "../../common/utils";
|
||||||
import { getDocDataAsArray, isDocContentSame, readContent } from "../../lib/src/common/utils";
|
import { getDocDataAsArray, isDocContentSame, readAsBlob, readContent } from "../../lib/src/common/utils";
|
||||||
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
import { shouldBeIgnored } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
export class ModuleFileHandler extends AbstractModule {
|
||||||
get db() {
|
get db() {
|
||||||
return this.core.databaseFileAccess;
|
return this.core.databaseFileAccess;
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return this.core.storageAccess;
|
return this.core.storageAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.core.fileHandler = this;
|
this.core.fileHandler = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
|
info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix,
|
||||||
force: boolean = false,
|
force: boolean = false,
|
||||||
onlyChunks: boolean = false
|
onlyChunks: boolean = false
|
||||||
): Promise<boolean | undefined> {
|
): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -94,10 +94,14 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
let readFile: UXFileInfo | undefined = undefined;
|
let readFile: UXFileInfo | undefined = undefined;
|
||||||
if (!shouldApplied) {
|
if (!shouldApplied) {
|
||||||
readFile = await this.readFileFromStub(file);
|
readFile = await this.readFileFromStub(file);
|
||||||
|
if (!readFile) {
|
||||||
|
this._log(`File ${file.path} is not exist on the storage`, LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
|
if (await isDocContentSame(getDocDataAsArray(entry.data), readFile.body)) {
|
||||||
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
// Timestamp is different but the content is same. therefore, two timestamps should be handled as same.
|
||||||
// So, mark the changes are same.
|
// So, mark the changes are same.
|
||||||
markChangesAreSame(file, file.stat.mtime, entry.mtime);
|
markChangesAreSame(readFile, readFile.stat.mtime, entry.mtime);
|
||||||
} else {
|
} else {
|
||||||
shouldApplied = true;
|
shouldApplied = true;
|
||||||
}
|
}
|
||||||
@@ -125,7 +129,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean | undefined> {
|
async deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean> {
|
||||||
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
const file = typeof info === "string" ? this.storage.getFileStub(info) : info;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${info} is not exist on the storage`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -222,7 +226,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
// NO OP
|
// NO OP
|
||||||
} else {
|
} else {
|
||||||
// If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved).
|
// If not, then it should be checked. and will be processed later (i.e., after the conflict is resolved).
|
||||||
await this.core.$$queueConflictCheckIfOpen(path);
|
await this.services.conflict.queueCheckForIfOpen(path);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,6 +260,17 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
this._log(`File ${path} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${path} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we want to process size mismatched files -- in case of having files created by some integrations, enable the toggle.
|
||||||
|
if (!this.settings.processSizeMismatchedFiles) {
|
||||||
|
// Check the file is not corrupted
|
||||||
|
// (Zero is a special case, may be created by some APIs and it might be acceptable).
|
||||||
|
if (docRead.size != 0 && docRead.size !== readAsBlob(docRead).size) {
|
||||||
|
this._log(`File ${path} seems to be corrupted! Writing prevented.`, LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const docData = readContent(docRead);
|
const docData = readContent(docRead);
|
||||||
|
|
||||||
if (existOnStorage && !force) {
|
if (existOnStorage && !force) {
|
||||||
@@ -302,11 +317,11 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean | undefined> {
|
private async _anyHandlerProcessesFileEvent(item: FileEventItem): Promise<boolean> {
|
||||||
const eventItem = item.args;
|
const eventItem = item.args;
|
||||||
const type = item.type;
|
const type = item.type;
|
||||||
const path = eventItem.file.path;
|
const path = eventItem.file.path;
|
||||||
if (!(await this.core.$$isTargetFile(path))) {
|
if (!(await this.services.vault.isTargetFile(path))) {
|
||||||
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -332,9 +347,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean | undefined> {
|
async _anyProcessReplicatedDoc(entry: MetaEntry): Promise<boolean> {
|
||||||
return await serialized(entry.path, async () => {
|
return await serialized(entry.path, async () => {
|
||||||
if (!(await this.core.$$isTargetFile(entry.path))) {
|
if (!(await this.services.vault.isTargetFile(entry.path))) {
|
||||||
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
this._log(`File ${entry.path} is not the target file`, LOG_LEVEL_VERBOSE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -351,7 +366,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this._log(
|
this._log(
|
||||||
`Processing ${path} (${entry._id.substring(0, 8)}: ${entry._rev?.substring(0, 5)}) :Started...`,
|
`Processing ${path} (${entry._id.substring(0, 8)} :${entry._rev?.substring(0, 5)}) : Started...`,
|
||||||
LOG_LEVEL_VERBOSE
|
LOG_LEVEL_VERBOSE
|
||||||
);
|
);
|
||||||
// Before writing (or skipped ), merging dialogue should be cancelled.
|
// Before writing (or skipped ), merging dialogue should be cancelled.
|
||||||
@@ -380,7 +395,7 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
};
|
};
|
||||||
const total = filesStorageSrc.length;
|
const total = filesStorageSrc.length;
|
||||||
const procAllChunks = filesStorageSrc.map(async (file) => {
|
const procAllChunks = filesStorageSrc.map(async (file) => {
|
||||||
if (!(await this.core.$$isTargetFile(file))) {
|
if (!(await this.services.vault.isTargetFile(file))) {
|
||||||
incProcessed();
|
incProcessed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -405,4 +420,9 @@ export class ModuleFileHandler extends AbstractModule implements ICoreModule {
|
|||||||
"chunkCreation"
|
"chunkCreation"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.fileProcessing.handleProcessFileEvent(this._anyHandlerProcessesFileEvent.bind(this));
|
||||||
|
services.replication.handleProcessSynchroniseResult(this._anyProcessReplicatedDoc.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,45 @@ import { $msg } from "../../lib/src/common/i18n";
|
|||||||
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import { initializeStores } from "../../common/stores.ts";
|
import { initializeStores } from "../../common/stores.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import { LiveSyncManagers } from "../../lib/src/managers/LiveSyncManagers.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleLocalDatabaseObsidian extends AbstractModule implements ICoreModule {
|
export class ModuleLocalDatabaseObsidian extends AbstractModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async $$openDatabase(): Promise<boolean> {
|
private async _openDatabase(): Promise<boolean> {
|
||||||
if (this.localDatabase != null) {
|
if (this.localDatabase != null) {
|
||||||
await this.localDatabase.close();
|
await this.localDatabase.close();
|
||||||
}
|
}
|
||||||
const vaultName = this.core.$$getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
this._log($msg("moduleLocalDatabase.logWaitingForReady"));
|
this._log($msg("moduleLocalDatabase.logWaitingForReady"));
|
||||||
|
const getDB = () => this.core.localDatabase.localDatabase;
|
||||||
|
const getSettings = () => this.core.settings;
|
||||||
|
this.core.managers = new LiveSyncManagers({
|
||||||
|
get database() {
|
||||||
|
return getDB();
|
||||||
|
},
|
||||||
|
getActiveReplicator: () => this.core.replicator,
|
||||||
|
id2path: this.services.path.id2path,
|
||||||
|
// path2id: this.core.$$path2id.bind(this.core),
|
||||||
|
path2id: this.services.path.path2id,
|
||||||
|
get settings() {
|
||||||
|
return getSettings();
|
||||||
|
},
|
||||||
|
});
|
||||||
this.core.localDatabase = new LiveSyncLocalDB(vaultName, this.core);
|
this.core.localDatabase = new LiveSyncLocalDB(vaultName, this.core);
|
||||||
|
|
||||||
initializeStores(vaultName);
|
initializeStores(vaultName);
|
||||||
return await this.localDatabase.initializeDatabase();
|
return await this.localDatabase.initializeDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isDatabaseReady(): boolean {
|
_isDatabaseReady(): boolean {
|
||||||
return this.localDatabase != null && this.localDatabase.isReady;
|
return this.localDatabase != null && this.localDatabase.isReady;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.database.handleIsDatabaseReady(this._isDatabaseReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.database.handleOpenDatabase(this._openDatabase.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
import { PeriodicProcessor } from "../../common/utils";
|
import { PeriodicProcessor } from "../../common/utils";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
|
|
||||||
export class ModulePeriodicProcess extends AbstractModule implements ICoreModule {
|
export class ModulePeriodicProcess extends AbstractModule {
|
||||||
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.core.$$replicate());
|
periodicSyncProcessor = new PeriodicProcessor(this.core, async () => await this.services.replication.replicate());
|
||||||
|
|
||||||
_disablePeriodic() {
|
disablePeriodic() {
|
||||||
this.periodicSyncProcessor?.disable();
|
this.periodicSyncProcessor?.disable();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
_resumePeriodic() {
|
resumePeriodic() {
|
||||||
this.periodicSyncProcessor.enable(
|
this.periodicSyncProcessor.enable(
|
||||||
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
|
this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0
|
||||||
);
|
);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnUnload() {
|
private _allOnUnload() {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyBeforeRealizeSetting(): Promise<boolean> {
|
private _everyBeforeRealizeSetting(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
private _everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
return this._disablePeriodic();
|
return this.disablePeriodic();
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
private _everyAfterResumeProcess(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this.resumePeriodic();
|
||||||
}
|
}
|
||||||
$everyAfterRealizeSetting(): Promise<boolean> {
|
private _everyAfterRealizeSetting(): Promise<boolean> {
|
||||||
return this._resumePeriodic();
|
return this.resumePeriodic();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnUnload(this._allOnUnload.bind(this));
|
||||||
|
services.setting.handleBeforeRealiseSetting(this._everyBeforeRealizeSetting.bind(this));
|
||||||
|
services.setting.handleSettingRealised(this._everyAfterRealizeSetting.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
export class ModulePouchDB extends AbstractModule {
|
||||||
$$createPouchDBInstance<T extends object>(
|
_createPouchDBInstance<T extends object>(
|
||||||
name?: string,
|
name?: string,
|
||||||
options?: PouchDB.Configuration.DatabaseConfiguration
|
options?: PouchDB.Configuration.DatabaseConfiguration
|
||||||
): PouchDB.Database<T> {
|
): PouchDB.Database<T> {
|
||||||
@@ -16,4 +16,7 @@ export class ModulePouchDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return new PouchDB(name, optionPass);
|
return new PouchDB(name, optionPass);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.database.handleCreatePouchDBInstance(this._createPouchDBInstance.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import {
|
|||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
import type { Rebuilder } from "../interfaces/DatabaseRebuilder.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
import type { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator.ts";
|
||||||
import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts";
|
import { fetchAllUsedChunks } from "@/lib/src/pouchdb/chunks.ts";
|
||||||
import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts";
|
import { EVENT_DATABASE_REBUILT, eventHub } from "src/common/events.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebuilder {
|
export class ModuleRebuilder extends AbstractModule implements Rebuilder {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.rebuilder = this;
|
this.core.rebuilder = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -43,47 +43,47 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
|
{ title: "Enable extra features", defaultOption: "No", timeout: 15 }
|
||||||
)) == "yes"
|
)) == "yes"
|
||||||
) {
|
) {
|
||||||
await this.core.$allAskUsingOptionalSyncFeature(opt);
|
await this.services.setting.suggestOptionalFeatures(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async rebuildRemote() {
|
async rebuildRemote() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
|
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.services.remote.tryResetDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
await this.askUsingOptionalFeature({ enableOverwrite: true });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.services.remote.replicateAllToRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true, true);
|
await this.services.remote.replicateAllToRemote(true, true);
|
||||||
}
|
}
|
||||||
$rebuildRemote(): Promise<void> {
|
$rebuildRemote(): Promise<void> {
|
||||||
return this.rebuildRemote();
|
return this.rebuildRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
async rebuildEverything() {
|
async rebuildEverything() {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$initializeDatabase(true, true, true);
|
await this.services.databaseEvents.initialiseDatabase(true, true, true);
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await this.core.$$tryResetRemoteDatabase();
|
await this.services.remote.tryResetDatabase();
|
||||||
await this.core.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
// We do not have any other devices' data, so we do not need to ask for overwriting.
|
// We do not have any other devices' data, so we do not need to ask for overwriting.
|
||||||
await this.askUsingOptionalFeature({ enableOverwrite: false });
|
await this.askUsingOptionalFeature({ enableOverwrite: false });
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true);
|
await this.services.remote.replicateAllToRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllToServer(true, true);
|
await this.services.remote.replicateAllToRemote(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rebuildEverything(): Promise<void> {
|
$rebuildEverything(): Promise<void> {
|
||||||
@@ -101,7 +101,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE);
|
this._log("Could not create red_flag_rebuild.md", LOG_LEVEL_NOTICE);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
async scheduleFetch(): Promise<void> {
|
async scheduleFetch(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -110,20 +110,20 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE);
|
this._log("Could not create red_flag_fetch.md", LOG_LEVEL_NOTICE);
|
||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$tryResetRemoteDatabase(): Promise<void> {
|
private async _tryResetRemoteDatabase(): Promise<void> {
|
||||||
await this.core.replicator.tryResetRemoteDatabase(this.settings);
|
await this.core.replicator.tryResetRemoteDatabase(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$tryCreateRemoteDatabase(): Promise<void> {
|
private async _tryCreateRemoteDatabase(): Promise<void> {
|
||||||
await this.core.replicator.tryCreateRemoteDatabase(this.settings);
|
await this.core.replicator.tryCreateRemoteDatabase(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$resetLocalDatabase(): Promise<void> {
|
private async _resetLocalDatabase(): Promise<boolean> {
|
||||||
this.core.storageAccess.clearTouched();
|
this.core.storageAccess.clearTouched();
|
||||||
await this.localDatabase.resetDatabase();
|
return await this.localDatabase.resetDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
async suspendAllSync() {
|
async suspendAllSync() {
|
||||||
@@ -134,7 +134,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this.core.settings.syncOnStart = false;
|
this.core.settings.syncOnStart = false;
|
||||||
this.core.settings.syncOnFileOpen = false;
|
this.core.settings.syncOnFileOpen = false;
|
||||||
this.core.settings.syncAfterMerge = false;
|
this.core.settings.syncAfterMerge = false;
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
}
|
}
|
||||||
async suspendReflectingDatabase() {
|
async suspendReflectingDatabase() {
|
||||||
if (this.core.settings.doNotSuspendOnFetching) return;
|
if (this.core.settings.doNotSuspendOnFetching) return;
|
||||||
@@ -153,8 +153,8 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE);
|
this._log(`Database and storage reflection has been resumed!`, LOG_LEVEL_NOTICE);
|
||||||
this.core.settings.suspendParseReplicationResult = false;
|
this.core.settings.suspendParseReplicationResult = false;
|
||||||
this.core.settings.suspendFileWatching = false;
|
this.core.settings.suspendFileWatching = false;
|
||||||
await this.core.$$performFullScan(true);
|
await this.services.vault.scanVault(true);
|
||||||
await this.core.$everyBeforeReplicate(false); //TODO: Check actual need of this.
|
await this.services.replication.onBeforeReplicate(false); //TODO: Check actual need of this.
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
}
|
}
|
||||||
async askUseNewAdapter() {
|
async askUseNewAdapter() {
|
||||||
@@ -177,28 +177,28 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
|
async fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean) {
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
await this.askUseNewAdapter();
|
await this.askUseNewAdapter();
|
||||||
this.core.settings.isConfigured = true;
|
this.core.settings.isConfigured = true;
|
||||||
await this.suspendReflectingDatabase();
|
await this.suspendReflectingDatabase();
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.resetLocalDatabase();
|
await this.resetLocalDatabase();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$openDatabase();
|
await this.services.database.openDatabase();
|
||||||
// this.core.isReady = true;
|
// this.core.isReady = true;
|
||||||
this.core.$$markIsReady();
|
this.services.appLifecycle.markIsReady();
|
||||||
if (makeLocalChunkBeforeSync) {
|
if (makeLocalChunkBeforeSync) {
|
||||||
await this.core.fileHandler.createAllChunks(true);
|
await this.core.fileHandler.createAllChunks(true);
|
||||||
} else if (!preventMakeLocalFilesBeforeSync) {
|
} else if (!preventMakeLocalFilesBeforeSync) {
|
||||||
await this.core.$$initializeDatabase(true, true, true);
|
await this.services.databaseEvents.initialiseDatabase(true, true, true);
|
||||||
} else {
|
} else {
|
||||||
// Do not create local file entries before sync (Means use remote information)
|
// Do not create local file entries before sync (Means use remote information)
|
||||||
}
|
}
|
||||||
await this.core.$$markRemoteResolved();
|
await this.services.remote.markResolved();
|
||||||
await delay(500);
|
await delay(500);
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.services.remote.replicateAllFromRemote(true);
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
await this.core.$$replicateAllFromServer(true);
|
await this.services.remote.replicateAllFromRemote(true);
|
||||||
await this.resumeReflectingDatabase();
|
await this.resumeReflectingDatabase();
|
||||||
await this.askUsingOptionalFeature({ enableFetch: true });
|
await this.askUsingOptionalFeature({ enableFetch: true });
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
return await this.fetchLocal(true);
|
return await this.fetchLocal(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allSuspendAllSync(): Promise<boolean> {
|
private async _allSuspendAllSync(): Promise<boolean> {
|
||||||
await this.suspendAllSync();
|
await this.suspendAllSync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -214,11 +214,11 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
async resetLocalDatabase() {
|
async resetLocalDatabase() {
|
||||||
if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") {
|
if (this.core.settings.isConfigured && this.core.settings.additionalSuffixOfDatabaseName == "") {
|
||||||
// Discard the non-suffixed database
|
// Discard the non-suffixed database
|
||||||
await this.core.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
}
|
}
|
||||||
const suffix = (await this.core.$anyGetAppId()) || "";
|
const suffix = this.services.API.getAppID() || "";
|
||||||
this.core.settings.additionalSuffixOfDatabaseName = suffix;
|
this.core.settings.additionalSuffixOfDatabaseName = suffix;
|
||||||
await this.core.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
eventHub.emitEvent(EVENT_DATABASE_REBUILT);
|
eventHub.emitEvent(EVENT_DATABASE_REBUILT);
|
||||||
}
|
}
|
||||||
async fetchRemoteChunks() {
|
async fetchRemoteChunks() {
|
||||||
@@ -228,10 +228,10 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
this.core.settings.remoteType == REMOTE_COUCHDB
|
this.core.settings.remoteType == REMOTE_COUCHDB
|
||||||
) {
|
) {
|
||||||
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
this._log(`Fetching chunks`, LOG_LEVEL_NOTICE);
|
||||||
const replicator = this.core.$$getReplicator() as LiveSyncCouchDBReplicator;
|
const replicator = this.services.replicator.getActiveReplicator() as LiveSyncCouchDBReplicator;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
this.settings,
|
this.settings,
|
||||||
this.core.$$isMobile(),
|
this.services.API.isMobile(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
@@ -254,8 +254,15 @@ export class ModuleRebuilder extends AbstractModule implements ICoreModule, Rebu
|
|||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
"resolveAllConflictedFilesByNewerOnes"
|
"resolveAllConflictedFilesByNewerOnes"
|
||||||
);
|
);
|
||||||
await this.core.$anyResolveConflictByNewest(file);
|
await this.services.conflict.resolveByNewest(file);
|
||||||
}
|
}
|
||||||
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
this._log(`Done!`, LOG_LEVEL_NOTICE, "resolveAllConflictedFilesByNewerOnes");
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.database.handleResetDatabase(this._resetLocalDatabase.bind(this));
|
||||||
|
services.remote.handleTryResetDatabase(this._tryResetRemoteDatabase.bind(this));
|
||||||
|
services.remote.handleTryCreateDatabase(this._tryCreateRemoteDatabase.bind(this));
|
||||||
|
services.setting.handleSuspendAllSync(this._allSuspendAllSync.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
|
import { fireAndForget, yieldMicrotask } from "octagonal-wheels/promises";
|
||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
import { isLockAcquired, shareRunningResult, skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
||||||
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
|
import { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks";
|
||||||
@@ -17,6 +16,7 @@ import {
|
|||||||
type EntryLeaf,
|
type EntryLeaf,
|
||||||
type LoadedEntry,
|
type LoadedEntry,
|
||||||
type MetaEntry,
|
type MetaEntry,
|
||||||
|
type RemoteType,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import {
|
import {
|
||||||
@@ -33,16 +33,18 @@ import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveS
|
|||||||
|
|
||||||
import { $msg } from "../../lib/src/common/i18n";
|
import { $msg } from "../../lib/src/common/i18n";
|
||||||
import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler";
|
import { clearHandlers } from "../../lib/src/replication/SyncParamsHandler";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
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 {
|
||||||
_replicatorType?: string;
|
_replicatorType?: RemoteType;
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
|
||||||
|
private _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.services.appLifecycle.isSuspended()) {
|
||||||
scheduleTask("perform-replicate-after-save", 250, () => this.core.$$replicateByEvent());
|
scheduleTask("perform-replicate-after-save", 250, () => this.services.replication.replicateByEvent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (setting) => {
|
||||||
@@ -55,7 +57,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setReplicator() {
|
async setReplicator() {
|
||||||
const replicator = await this.core.$anyNewReplicator();
|
const replicator = await this.services.replicator.getNewReplicator();
|
||||||
if (!replicator) {
|
if (!replicator) {
|
||||||
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -72,25 +74,33 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$getReplicator(): LiveSyncAbstractReplicator {
|
_getReplicator(): LiveSyncAbstractReplicator {
|
||||||
return this.core.replicator;
|
return this.core.replicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.setReplicator();
|
return this.setReplicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.setReplicator();
|
return this.setReplicator();
|
||||||
}
|
}
|
||||||
async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise<boolean> {
|
async ensureReplicatorPBKDF2Salt(showMessage: boolean = false): Promise<boolean> {
|
||||||
// Checking salt
|
// Checking salt
|
||||||
const replicator = this.core.$$getReplicator();
|
const replicator = this.services.replicator.getActiveReplicator();
|
||||||
|
if (!replicator) {
|
||||||
|
this._log($msg("Replicator.Message.InitialiseFatalError"), LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
|
return await replicator.ensurePBKDF2Salt(this.settings, showMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
// Checking salt
|
// Checking salt
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it).
|
// Showing message is false: that because be shown here. (And it is a fatal error, no way to hide it).
|
||||||
if (!(await this.ensureReplicatorPBKDF2Salt(false))) {
|
if (!(await this.ensureReplicatorPBKDF2Salt(false))) {
|
||||||
Logger("Failed to initialise the encryption key, preventing replication.", LOG_LEVEL_NOTICE);
|
Logger("Failed to initialise the encryption key, preventing replication.", LOG_LEVEL_NOTICE);
|
||||||
@@ -100,7 +110,7 @@ export class ModuleReplicator extends AbstractModule implements ICoreModule {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicate(showMessage: boolean = false): Promise<boolean | void> {
|
private async _replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
try {
|
try {
|
||||||
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
|
updatePreviousExecutionTime(KEY_REPLICATION_ON_EVENT, REPLICATION_ON_EVENT_FORECASTED_TIME);
|
||||||
return await this.$$_replicate(showMessage);
|
return await this.$$_replicate(showMessage);
|
||||||
@@ -137,11 +147,11 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
await this.core.rebuilder.$performRebuildDB("localOnly");
|
await this.core.rebuilder.$performRebuildDB("localOnly");
|
||||||
}
|
}
|
||||||
if (ret == CHOICE_CLEAN) {
|
if (ret == CHOICE_CLEAN) {
|
||||||
const replicator = this.core.$$getReplicator();
|
const replicator = this.services.replicator.getActiveReplicator();
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
||||||
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
const remoteDB = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
this.settings,
|
this.settings,
|
||||||
this.core.$$isMobile(),
|
this.services.API.isMobile(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
if (typeof remoteDB == "string") {
|
if (typeof remoteDB == "string") {
|
||||||
@@ -156,7 +166,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
await balanceChunkPurgedDBs(this.localDatabase.localDatabase, remoteDB.db);
|
||||||
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
await purgeUnreferencedChunks(this.localDatabase.localDatabase, false);
|
||||||
this.localDatabase.clearCaches();
|
this.localDatabase.clearCaches();
|
||||||
await this.core.$$getReplicator().markRemoteResolved(this.settings);
|
await this.services.replicator.getActiveReplicator()?.markRemoteResolved(this.settings);
|
||||||
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
Logger("The local database has been cleaned up.", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
} else {
|
} else {
|
||||||
Logger(
|
Logger(
|
||||||
@@ -167,31 +177,48 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
|
||||||
//--?
|
async _canReplicate(showMessage: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) {
|
||||||
|
Logger(`Not ready`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isLockAcquired("cleanup")) {
|
if (isLockAcquired("cleanup")) {
|
||||||
Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.Cleaned"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.versionUpFlash != "") {
|
if (this.settings.versionUpFlash != "") {
|
||||||
Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.VersionUpFlash"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyCommitPendingFileEvent())) {
|
|
||||||
|
if (!(await this.services.fileProcessing.commitPendingFileEvents())) {
|
||||||
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.Pending"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyBeforeReplicate(showMessage))) {
|
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
this._log("Network is offline", showMessage ? LOG_LEVEL_NOTICE : LOG_LEVEL_INFO);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(await this.services.replication.onBeforeReplicate(showMessage))) {
|
||||||
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async $$_replicate(showMessage: boolean = false): Promise<boolean | void> {
|
||||||
|
const checkBeforeReplicate = await this.services.replication.isReplicationReady(showMessage);
|
||||||
|
if (!checkBeforeReplicate) return false;
|
||||||
|
|
||||||
//<-- Here could be an module.
|
//<-- Here could be an module.
|
||||||
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
const ret = await this.core.replicator.openReplication(this.settings, false, showMessage, false);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) {
|
if (this.core.replicator.tweakSettingsMismatched && this.core.replicator.preferredTweakValue) {
|
||||||
await this.core.$$askResolvingMismatchedTweaks(this.core.replicator.preferredTweakValue);
|
await this.services.tweakValue.askResolvingMismatched(this.core.replicator.preferredTweakValue);
|
||||||
} else {
|
} else {
|
||||||
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
if (this.core.replicator?.remoteLockedAndDeviceNotAccepted) {
|
||||||
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
if (this.core.replicator.remoteCleaned && this.settings.useIndexedDBAdapter) {
|
||||||
@@ -213,7 +240,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
if (ret == CHOICE_FETCH) {
|
if (ret == CHOICE_FETCH) {
|
||||||
this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE);
|
this._log($msg("Replicator.Dialogue.Locked.Message.Fetch"), LOG_LEVEL_NOTICE);
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return;
|
||||||
} else if (ret == CHOICE_UNLOCK) {
|
} else if (ret == CHOICE_UNLOCK) {
|
||||||
await this.core.replicator.markRemoteResolved(this.settings);
|
await this.core.replicator.markRemoteResolved(this.settings);
|
||||||
@@ -227,16 +254,16 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateByEvent(): Promise<boolean | void> {
|
private async _replicateByEvent(): Promise<boolean | void> {
|
||||||
const least = this.settings.syncMinimumInterval;
|
const least = this.settings.syncMinimumInterval;
|
||||||
if (least > 0) {
|
if (least > 0) {
|
||||||
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
|
return rateLimitedSharedExecution(KEY_REPLICATION_ON_EVENT, least, async () => {
|
||||||
return await this.$$replicate();
|
return await this.services.replication.replicate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return await shareRunningResult(`replication`, () => this.core.$$replicate());
|
return await shareRunningResult(`replication`, () => this.services.replication.replicate());
|
||||||
}
|
}
|
||||||
$$parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
_parseReplicationResult(docs: Array<PouchDB.Core.ExistingDocument<EntryDoc>>): void {
|
||||||
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
if (this.settings.suspendParseReplicationResult && !this.replicationResultProcessor.isSuspended) {
|
||||||
this.replicationResultProcessor.suspend();
|
this.replicationResultProcessor.suspend();
|
||||||
}
|
}
|
||||||
@@ -313,7 +340,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
this.localDatabase.onNewLeaf(change as EntryLeaf);
|
this.localDatabase.onNewLeaf(change as EntryLeaf);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await this.core.$anyModuleParsedReplicationResultItem(change)) return;
|
if (await this.services.replication.processVirtualDocument(change)) return;
|
||||||
// any addon needs this item?
|
// any addon needs this item?
|
||||||
// for (const proc of this.core.addOns) {
|
// for (const proc of this.core.addOns) {
|
||||||
// if (await proc.parseReplicationResultItem(change)) {
|
// if (await proc.parseReplicationResultItem(change)) {
|
||||||
@@ -338,7 +365,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
if (isAnyNote(change)) {
|
if (isAnyNote(change)) {
|
||||||
const docPath = getPath(change);
|
const docPath = getPath(change);
|
||||||
if (!(await this.core.$$isTargetFile(docPath))) {
|
if (!(await this.services.vault.isTargetFile(docPath))) {
|
||||||
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
Logger(`Skipped: ${docPath}`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -346,7 +373,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
|
Logger(`Processing scheduled: ${docPath}`, LOG_LEVEL_INFO);
|
||||||
}
|
}
|
||||||
const size = change.size;
|
const size = change.size;
|
||||||
if (this.core.$$isFileSizeExceeded(size)) {
|
if (this.services.vault.isFileSizeTooLarge(size)) {
|
||||||
Logger(
|
Logger(
|
||||||
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
|
`Processing ${docPath} has been skipped due to file size exceeding the limit`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
@@ -390,7 +417,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.core.$anyProcessOptionalSyncFiles(dbDoc)) {
|
if (await this.services.replication.processOptionalSynchroniseResult(dbDoc)) {
|
||||||
// Already processed
|
// Already processed
|
||||||
} else if (isValidPath(getPath(doc))) {
|
} else if (isValidPath(getPath(doc))) {
|
||||||
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
this.storageApplyingProcessor.enqueue(doc as MetaEntry);
|
||||||
@@ -417,7 +444,7 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
storageApplyingProcessor = new QueueProcessor(
|
storageApplyingProcessor = new QueueProcessor(
|
||||||
async (docs: MetaEntry[]) => {
|
async (docs: MetaEntry[]) => {
|
||||||
const entry = docs[0];
|
const entry = docs[0];
|
||||||
await this.core.$anyProcessReplicatedDoc(entry);
|
await this.services.replication.processSynchroniseResult(entry);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -435,17 +462,17 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
})
|
})
|
||||||
.startPipeline();
|
.startPipeline();
|
||||||
|
|
||||||
$everyBeforeSuspendProcess(): Promise<boolean> {
|
_everyBeforeSuspendProcess(): Promise<boolean> {
|
||||||
this.core.replicator.closeReplication();
|
this.core.replicator?.closeReplication();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$replicateAllToServer(
|
private async _replicateAllToServer(
|
||||||
showingNotice: boolean = false,
|
showingNotice: boolean = false,
|
||||||
sendChunksInBulkDisabled: boolean = false
|
sendChunksInBulkDisabled: boolean = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return false;
|
if (!this.services.appLifecycle.isReady()) return false;
|
||||||
if (!(await this.core.$everyBeforeReplicate(showingNotice))) {
|
if (!(await this.services.replication.onBeforeReplicate(showingNotice))) {
|
||||||
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
Logger($msg("Replicator.Message.SomeModuleFailed"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -463,16 +490,31 @@ Even if you choose to clean up, you will see this option again if you exit Obsid
|
|||||||
}
|
}
|
||||||
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
const ret = await this.core.replicator.replicateAllToServer(this.settings, showingNotice);
|
||||||
if (ret) return true;
|
if (ret) return true;
|
||||||
const checkResult = await this.core.$anyAfterConnectCheckFailed();
|
const checkResult = await this.services.replication.checkConnectionFailure();
|
||||||
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllToServer(showingNotice);
|
if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllToRemote(showingNotice);
|
||||||
return !checkResult;
|
return !checkResult;
|
||||||
}
|
}
|
||||||
async $$replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
async _replicateAllFromServer(showingNotice: boolean = false): Promise<boolean> {
|
||||||
if (!this.core.$$isReady()) return false;
|
if (!this.services.appLifecycle.isReady()) return false;
|
||||||
const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice);
|
const ret = await this.core.replicator.replicateAllFromServer(this.settings, showingNotice);
|
||||||
if (ret) return true;
|
if (ret) return true;
|
||||||
const checkResult = await this.core.$anyAfterConnectCheckFailed();
|
const checkResult = await this.services.replication.checkConnectionFailure();
|
||||||
if (checkResult == "CHECKAGAIN") return await this.core.$$replicateAllFromServer(showingNotice);
|
if (checkResult == "CHECKAGAIN") return await this.services.remote.replicateAllFromRemote(showingNotice);
|
||||||
return !checkResult;
|
return !checkResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetActiveReplicator(this._getReplicator.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.replication.handleParseSynchroniseResult(this._parseReplicationResult.bind(this));
|
||||||
|
services.appLifecycle.handleOnSuspending(this._everyBeforeSuspendProcess.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.replication.handleIsReplicationReady(this._canReplicate.bind(this));
|
||||||
|
services.replication.handleReplicate(this._replicate.bind(this));
|
||||||
|
services.replication.handleReplicateByEvent(this._replicateByEvent.bind(this));
|
||||||
|
services.remote.handleReplicateAllToRemote(this._replicateAllToServer.bind(this));
|
||||||
|
services.remote.handleReplicateAllFromRemote(this._replicateAllFromServer.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,30 +3,40 @@ import { REMOTE_MINIO, REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/c
|
|||||||
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
import { LiveSyncCouchDBReplicator } from "../../lib/src/replication/couchdb/LiveSyncReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModuleReplicatorCouchDB extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorCouchDB extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
// If new remote types were added, add them here. Do not use `REMOTE_COUCHDB` directly for the safety valve.
|
||||||
if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_MINIO || settings.remoteType == REMOTE_P2P) {
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
return Promise.resolve(new LiveSyncCouchDBReplicator(this.core));
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
|
if (this.services.appLifecycle.isSuspended()) return Promise.resolve(true);
|
||||||
|
if (!this.services.appLifecycle.isReady()) return Promise.resolve(true);
|
||||||
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
if (this.settings.remoteType != REMOTE_MINIO && this.settings.remoteType != REMOTE_P2P) {
|
||||||
// If LiveSync enabled, open replication
|
const LiveSyncEnabled = this.settings.liveSync;
|
||||||
if (this.settings.liveSync) {
|
const continuous = LiveSyncEnabled;
|
||||||
fireAndForget(() => this.core.replicator.openReplication(this.settings, true, false, false));
|
const eventualOnStart = !LiveSyncEnabled && this.settings.syncOnStart;
|
||||||
}
|
// If enabled LiveSync or on start, open replication
|
||||||
// If sync on start enabled, open replication
|
if (LiveSyncEnabled || eventualOnStart) {
|
||||||
if (!this.settings.liveSync && this.settings.syncOnStart) {
|
// And note that we do not open the conflict detection dialogue directly during this process.
|
||||||
// Possibly ok as if only share the result
|
// This should be raised explicitly if needed.
|
||||||
fireAndForget(() => this.core.replicator.openReplication(this.settings, false, false, false));
|
fireAndForget(async () => {
|
||||||
|
const canReplicate = await this.services.replication.isReplicationReady(false);
|
||||||
|
if (!canReplicate) return;
|
||||||
|
void this.core.replicator.openReplication(this.settings, continuous, false, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
|
import { REMOTE_MINIO, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||||
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
|
import { LiveSyncJournalReplicator } from "../../lib/src/replication/journal/LiveSyncJournalReplicator";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
|
|
||||||
export class ModuleReplicatorMinIO extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorMinIO extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_MINIO) {
|
if (settings.remoteType == REMOTE_MINIO) {
|
||||||
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
return Promise.resolve(new LiveSyncJournalReplicator(this.core));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
|
import { REMOTE_P2P, type RemoteDBSettings } from "../../lib/src/common/types";
|
||||||
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
import type { LiveSyncAbstractReplicator } from "../../lib/src/replication/LiveSyncAbstractReplicator";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
import { LiveSyncTrysteroReplicator } from "../../lib/src/replication/trystero/LiveSyncTrysteroReplicator";
|
||||||
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
|
||||||
export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
|
export class ModuleReplicatorP2P extends AbstractModule {
|
||||||
$anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator> {
|
_anyNewReplicator(settingOverride: Partial<RemoteDBSettings> = {}): Promise<LiveSyncAbstractReplicator | false> {
|
||||||
const settings = { ...this.settings, ...settingOverride };
|
const settings = { ...this.settings, ...settingOverride };
|
||||||
if (settings.remoteType == REMOTE_P2P) {
|
if (settings.remoteType == REMOTE_P2P) {
|
||||||
return Promise.resolve(new LiveSyncTrysteroReplicator(this.core));
|
return Promise.resolve(new LiveSyncTrysteroReplicator(this.core));
|
||||||
}
|
}
|
||||||
return undefined!;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
$everyAfterResumeProcess(): Promise<boolean> {
|
_everyAfterResumeProcess(): Promise<boolean> {
|
||||||
if (this.settings.remoteType == REMOTE_P2P) {
|
if (this.settings.remoteType == REMOTE_P2P) {
|
||||||
// // If LiveSync enabled, open replication
|
// // If LiveSync enabled, open replication
|
||||||
// if (this.settings.liveSync) {
|
// if (this.settings.liveSync) {
|
||||||
@@ -27,4 +27,8 @@ export class ModuleReplicatorP2P extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.replicator.handleGetNewReplicator(this._anyNewReplicator.bind(this));
|
||||||
|
services.appLifecycle.handleOnResumed(this._everyAfterResumeProcess.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ import {
|
|||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
|
import { addPrefix, isAcceptedAll } from "../../lib/src/string_and_binary/path";
|
||||||
import { AbstractModule } from "../AbstractModule";
|
import { AbstractModule } from "../AbstractModule";
|
||||||
import type { ICoreModule } from "../ModuleTypes";
|
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
import { isDirty } from "../../lib/src/common/utils";
|
import { isDirty } from "../../lib/src/common/utils";
|
||||||
export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
import type { LiveSyncCore } from "../../main";
|
||||||
|
export class ModuleTargetFilter extends AbstractModule {
|
||||||
reloadIgnoreFiles() {
|
reloadIgnoreFiles() {
|
||||||
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
|
this.ignoreFiles = this.settings.ignoreFiles.split(",").map((e) => e.trim());
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (evt: ObsidianLiveSyncSettings) => {
|
||||||
this.reloadIgnoreFiles();
|
this.reloadIgnoreFiles();
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
_id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
|
||||||
const tempId = id2path(id, entry);
|
const tempId = id2path(id, entry);
|
||||||
if (stripPrefix && isInternalMetadata(tempId)) {
|
if (stripPrefix && isInternalMetadata(tempId)) {
|
||||||
const out = stripInternalMetadataPrefix(tempId);
|
const out = stripInternalMetadataPrefix(tempId);
|
||||||
@@ -43,7 +43,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return tempId;
|
return tempId;
|
||||||
}
|
}
|
||||||
async $$path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
async _path2id(filename: FilePathWithPrefix | FilePath, prefix?: string): Promise<DocumentID> {
|
||||||
const destPath = addPrefix(filename, prefix ?? "");
|
const destPath = addPrefix(filename, prefix ?? "");
|
||||||
return await path2id(
|
return await path2id(
|
||||||
destPath,
|
destPath,
|
||||||
@@ -52,7 +52,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isFileSizeExceeded(size: number) {
|
private _isFileSizeExceeded(size: number) {
|
||||||
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
if (this.settings.syncMaxSizeInMB > 0 && size > 0) {
|
||||||
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
if (this.settings.syncMaxSizeInMB * 1024 * 1024 < size) {
|
||||||
return true;
|
return true;
|
||||||
@@ -61,7 +61,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markFileListPossiblyChanged(): void {
|
_markFileListPossiblyChanged(): void {
|
||||||
this.totalFileEventCount++;
|
this.totalFileEventCount++;
|
||||||
}
|
}
|
||||||
totalFileEventCount = 0;
|
totalFileEventCount = 0;
|
||||||
@@ -72,7 +72,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
private async _isTargetFile(file: string | UXFileInfoStub, keepFileCheckList = false) {
|
||||||
const fileCount = useMemo<Record<string, number>>(
|
const fileCount = useMemo<Record<string, number>>(
|
||||||
{
|
{
|
||||||
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
key: "fileCount", // forceUpdate: !keepFileCheckList,
|
||||||
@@ -109,7 +109,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
|
|
||||||
const filepath = getStoragePathFromUXFileInfo(file);
|
const filepath = getStoragePathFromUXFileInfo(file);
|
||||||
const lc = filepath.toLowerCase();
|
const lc = filepath.toLowerCase();
|
||||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
if (this.services.setting.shouldCheckCaseInsensitively()) {
|
||||||
if (lc in fileCount && fileCount[lc] > 1) {
|
if (lc in fileCount && fileCount[lc] > 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
// We must reload ignore files due to the its change.
|
// We must reload ignore files due to the its change.
|
||||||
await this.readIgnoreFile(filepath);
|
await this.readIgnoreFile(filepath);
|
||||||
}
|
}
|
||||||
if (await this.core.$$isIgnoredByIgnoreFiles(file)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
return await this.readIgnoreFile(path);
|
return await this.readIgnoreFile(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $$isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
private async _isIgnoredByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
|
||||||
if (!this.settings.useIgnoreFiles) {
|
if (!this.settings.useIgnoreFiles) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -164,4 +164,14 @@ export class ModuleTargetFilter extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.vault.handleMarkFileListPossiblyChanged(this._markFileListPossiblyChanged.bind(this));
|
||||||
|
services.path.handleId2Path(this._id2path.bind(this));
|
||||||
|
services.path.handlePath2Id(this._path2id.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.vault.handleIsFileSizeTooLarge(this._isFileSizeExceeded.bind(this));
|
||||||
|
services.vault.handleIsIgnoredByIgnoreFile(this._isIgnoredByIgnoreFiles.bind(this));
|
||||||
|
services.vault.handleIsTargetFile(this._isTargetFile.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
import { sizeToHumanReadable } from "octagonal-wheels/number";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule {
|
export class ModuleCheckRemoteSize extends AbstractModule {
|
||||||
async $allScanStat(): Promise<boolean> {
|
async _allScanStat(): Promise<boolean> {
|
||||||
|
if (this.core.managers.networkManager.isOnline === false) {
|
||||||
|
this._log("Network is offline, skipping remote size check.", LOG_LEVEL_INFO);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
this._log($msg("moduleCheckRemoteSize.logCheckingStorageSizes"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleCheckRemoteSize.logCheckingStorageSizes"), LOG_LEVEL_VERBOSE);
|
||||||
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
if (this.settings.notifyThresholdOfRemoteStorageSize < 0) {
|
||||||
const message = $msg("moduleCheckRemoteSize.msgSetDBCapacity");
|
const message = $msg("moduleCheckRemoteSize.msgSetDBCapacity");
|
||||||
@@ -105,4 +109,7 @@ export class ModuleCheckRemoteSize extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,35 +2,36 @@ import { AbstractModule } from "../AbstractModule.ts";
|
|||||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
|
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
import { sendValue } from "octagonal-wheels/messagepassing/signal";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleConflictChecker extends AbstractModule implements ICoreModule {
|
export class ModuleConflictChecker extends AbstractModule {
|
||||||
async $$queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
async _queueConflictCheckIfOpen(file: FilePathWithPrefix): Promise<void> {
|
||||||
const path = file;
|
const path = file;
|
||||||
if (this.settings.checkConflictOnlyOnOpen) {
|
if (this.settings.checkConflictOnlyOnOpen) {
|
||||||
const af = this.core.$$getActiveFilePath();
|
const af = this.services.vault.getActiveFilePath();
|
||||||
if (af && af != path) {
|
if (af && af != path) {
|
||||||
this._log(`${file} is conflicted, merging process has been postponed.`, LOG_LEVEL_NOTICE);
|
this._log(`${file} is conflicted, merging process has been postponed.`, LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.core.$$queueConflictCheck(path);
|
await this.services.conflict.queueCheckFor(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
async _queueConflictCheck(file: FilePathWithPrefix): Promise<void> {
|
||||||
const optionalConflictResult = await this.core.$anyGetOptionalConflictCheckMethod(file);
|
const optionalConflictResult = await this.services.conflict.getOptionalConflictCheckMethod(file);
|
||||||
if (optionalConflictResult == true) {
|
if (optionalConflictResult == true) {
|
||||||
// The conflict has been resolved by another process.
|
// The conflict has been resolved by another process.
|
||||||
return;
|
return;
|
||||||
} else if (optionalConflictResult === "newer") {
|
} else if (optionalConflictResult === "newer") {
|
||||||
// The conflict should be resolved by the newer entry.
|
// The conflict should be resolved by the newer entry.
|
||||||
await this.core.$anyResolveConflictByNewest(file);
|
await this.services.conflict.resolveByNewest(file);
|
||||||
} else {
|
} else {
|
||||||
this.conflictCheckQueue.enqueue(file);
|
this.conflictCheckQueue.enqueue(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$$waitForAllConflictProcessed(): Promise<boolean> {
|
_waitForAllConflictProcessed(): Promise<boolean> {
|
||||||
return this.conflictResolveQueue.waitForAllProcessed();
|
return this.conflictResolveQueue.waitForAllProcessed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
|
|||||||
conflictResolveQueue = new QueueProcessor(
|
conflictResolveQueue = new QueueProcessor(
|
||||||
async (filenames: FilePathWithPrefix[]) => {
|
async (filenames: FilePathWithPrefix[]) => {
|
||||||
const filename = filenames[0];
|
const filename = filenames[0];
|
||||||
return await this.core.$$resolveConflict(filename);
|
return await this.services.conflict.resolve(filename);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suspended: false,
|
suspended: false,
|
||||||
@@ -73,4 +74,9 @@ export class ModuleConflictChecker extends AbstractModule implements ICoreModule
|
|||||||
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
|
totalRemainingReactiveSource: this.core.conflictProcessQueueCount,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.conflict.handleQueueCheckForIfOpen(this._queueConflictCheckIfOpen.bind(this));
|
||||||
|
services.conflict.handleQueueCheckFor(this._queueConflictCheck.bind(this));
|
||||||
|
services.conflict.handleEnsureAllProcessed(this._waitForAllConflictProcessed.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ import {
|
|||||||
} from "../../common/utils";
|
} from "../../common/utils";
|
||||||
import diff_match_patch from "diff-match-patch";
|
import diff_match_patch from "diff-match-patch";
|
||||||
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
import { stripAllPrefixes, isPlainText } from "../../lib/src/string_and_binary/path";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
@@ -29,8 +30,8 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleConflictResolver extends AbstractModule implements ICoreModule {
|
export class ModuleConflictResolver extends AbstractModule {
|
||||||
async $$resolveConflictByDeletingRev(
|
private async _resolveConflictByDeletingRev(
|
||||||
path: FilePathWithPrefix,
|
path: FilePathWithPrefix,
|
||||||
deleteRevision: string,
|
deleteRevision: string,
|
||||||
subTitle = ""
|
subTitle = ""
|
||||||
@@ -82,7 +83,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
return MISSING_OR_ERROR;
|
return MISSING_OR_ERROR;
|
||||||
}
|
}
|
||||||
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, ret.conflictedRev, "Sensible");
|
return await this.services.conflict.resolveByDeletingRevision(path, ret.conflictedRev, "Sensible");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rightRev, leftLeaf, rightLeaf } = ret;
|
const { rightRev, leftLeaf, rightLeaf } = ret;
|
||||||
@@ -95,7 +96,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
if (rightLeaf == false) {
|
if (rightLeaf == false) {
|
||||||
// Conflicted item could not load, delete this.
|
// Conflicted item could not load, delete this.
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, rightRev, "MISSING OLD REV");
|
return await this.services.conflict.resolveByDeletingRevision(path, rightRev, "MISSING OLD REV");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSame = leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted;
|
const isSame = leftLeaf.data == rightLeaf.data && leftLeaf.deleted == rightLeaf.deleted;
|
||||||
@@ -115,7 +116,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
]
|
]
|
||||||
.filter((e) => e.trim())
|
.filter((e) => e.trim())
|
||||||
.join(",");
|
.join(",");
|
||||||
return await this.core.$$resolveConflictByDeletingRev(path, loser.rev, subTitle);
|
return await this.services.conflict.resolveByDeletingRevision(path, loser.rev, subTitle);
|
||||||
}
|
}
|
||||||
// make diff.
|
// make diff.
|
||||||
const dmp = new diff_match_patch();
|
const dmp = new diff_match_patch();
|
||||||
@@ -129,7 +130,7 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
private async _resolveConflict(filename: FilePathWithPrefix): Promise<void> {
|
||||||
// const filename = filenames[0];
|
// const filename = filenames[0];
|
||||||
return await serialized(`conflict-resolve:${filename}`, async () => {
|
return await serialized(`conflict-resolve:${filename}`, async () => {
|
||||||
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
const conflictCheckResult = await this.checkConflictAndPerformAutoMerge(filename);
|
||||||
@@ -144,16 +145,16 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
if (conflictCheckResult === AUTO_MERGED) {
|
if (conflictCheckResult === AUTO_MERGED) {
|
||||||
//auto resolved, but need check again;
|
//auto resolved, but need check again;
|
||||||
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
|
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
|
||||||
//Wait for the running replication, if not running replication, run it once.
|
//Wait for the running replication, if not running replication, run it once.
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
this._log("[conflict] Automatically merged, but we have to check it again");
|
this._log("[conflict] Automatically merged, but we have to check it again");
|
||||||
await this.core.$$queueConflictCheck(filename);
|
await this.services.conflict.queueCheckFor(filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings.showMergeDialogOnlyOnActive) {
|
if (this.settings.showMergeDialogOnlyOnActive) {
|
||||||
const af = this.core.$$getActiveFilePath();
|
const af = this.services.vault.getActiveFilePath();
|
||||||
if (af && af != filename) {
|
if (af && af != filename) {
|
||||||
this._log(
|
this._log(
|
||||||
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
`[conflict] ${filename} is conflicted. Merging process has been postponed to the file have got opened.`,
|
||||||
@@ -164,11 +165,11 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
}
|
}
|
||||||
this._log("[conflict] Manual merge required!");
|
this._log("[conflict] Manual merge required!");
|
||||||
eventHub.emitEvent("conflict-cancelled", filename);
|
eventHub.emitEvent("conflict-cancelled", filename);
|
||||||
await this.core.$anyResolveConflictByUI(filename, conflictCheckResult);
|
await this.services.conflict.resolveByUserInteraction(filename, conflictCheckResult);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
private async _anyResolveConflictByNewest(filename: FilePathWithPrefix): Promise<boolean> {
|
||||||
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
|
const currentRev = await this.core.databaseFileAccess.fetchEntryMeta(filename, undefined, true);
|
||||||
if (currentRev == false) {
|
if (currentRev == false) {
|
||||||
this._log(`Could not get current revision of ${filename}`);
|
this._log(`Could not get current revision of ${filename}`);
|
||||||
@@ -206,8 +207,14 @@ export class ModuleConflictResolver extends AbstractModule implements ICoreModul
|
|||||||
this._log(
|
this._log(
|
||||||
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
|
`conflict: Deleting the older revision ${mTimeAndRev[i][1]} (${new Date(mTimeAndRev[i][0]).toLocaleString()}) of ${filename}`
|
||||||
);
|
);
|
||||||
await this.core.$$resolveConflictByDeletingRev(filename, mTimeAndRev[i][1], "NEWEST");
|
await this.services.conflict.resolveByDeletingRevision(filename, mTimeAndRev[i][1], "NEWEST");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.conflict.handleResolveByDeletingRevision(this._resolveConflictByDeletingRev.bind(this));
|
||||||
|
services.conflict.handleResolve(this._resolveConflict.bind(this));
|
||||||
|
services.conflict.handleResolveByNewest(this._anyResolveConflictByNewest.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import {
|
|||||||
type ObsidianLiveSyncSettings,
|
type ObsidianLiveSyncSettings,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
export class ModuleRedFlag extends AbstractModule {
|
||||||
async isFlagFileExist(path: string) {
|
async isFlagFileExist(path: string) {
|
||||||
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
const redflag = await this.core.storageAccess.isExists(normalizePath(path));
|
||||||
if (redflag) {
|
if (redflag) {
|
||||||
@@ -48,7 +48,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
await this.deleteFlagFile(FLAGMD_REDFLAG3);
|
await this.deleteFlagFile(FLAGMD_REDFLAG3);
|
||||||
await this.deleteFlagFile(FLAGMD_REDFLAG3_HR);
|
await this.deleteFlagFile(FLAGMD_REDFLAG3_HR);
|
||||||
}
|
}
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const isRedFlagRaised = await this.isRedFlagRaised();
|
const isRedFlagRaised = await this.isRedFlagRaised();
|
||||||
const isRedFlag2Raised = await this.isRedFlag2Raised();
|
const isRedFlag2Raised = await this.isRedFlag2Raised();
|
||||||
@@ -63,7 +63,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
)) !== "yes"
|
)) !== "yes"
|
||||||
) {
|
) {
|
||||||
await this.deleteRedFlag2();
|
await this.deleteRedFlag2();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,13 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
})) !== "yes"
|
})) !== "yes"
|
||||||
) {
|
) {
|
||||||
await this.deleteRedFlag3();
|
await this.deleteRedFlag3();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.settings.batchSave = false;
|
this.settings.batchSave = false;
|
||||||
await this.core.$allSuspendAllSync();
|
await this.services.setting.suspendAllSync();
|
||||||
await this.core.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.settings.suspendFileWatching = true;
|
this.settings.suspendFileWatching = true;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
if (isRedFlag2Raised) {
|
if (isRedFlag2Raised) {
|
||||||
@@ -99,7 +99,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
) {
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (isRedFlag3Raised) {
|
} else if (isRedFlag3Raised) {
|
||||||
@@ -148,7 +148,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
if (fetchRemote === optionFetchRemoteConf) {
|
if (fetchRemote === optionFetchRemoteConf) {
|
||||||
this._log("Fetching remote configuration", LOG_LEVEL_NOTICE);
|
this._log("Fetching remote configuration", LOG_LEVEL_NOTICE);
|
||||||
const newSettings = JSON.parse(JSON.stringify(this.core.settings)) as ObsidianLiveSyncSettings;
|
const newSettings = JSON.parse(JSON.stringify(this.core.settings)) as ObsidianLiveSyncSettings;
|
||||||
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
|
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
|
||||||
if (remoteConfig) {
|
if (remoteConfig) {
|
||||||
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
||||||
const mergedSettings = {
|
const mergedSettings = {
|
||||||
@@ -174,7 +174,7 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
) {
|
) {
|
||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -198,4 +198,8 @@ export class ModuleRedFlag extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
|
|
||||||
export class ModuleRemoteGovernor extends AbstractModule implements ICoreModule {
|
export class ModuleRemoteGovernor extends AbstractModule {
|
||||||
async $$markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
private async _markRemoteLocked(lockByClean: boolean = false): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean);
|
return await this.core.replicator.markRemoteLocked(this.settings, true, lockByClean);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$markRemoteUnlocked(): Promise<void> {
|
private async _markRemoteUnlocked(): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteLocked(this.settings, false, false);
|
return await this.core.replicator.markRemoteLocked(this.settings, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$markRemoteResolved(): Promise<void> {
|
private async _markRemoteResolved(): Promise<void> {
|
||||||
return await this.core.replicator.markRemoteResolved(this.settings);
|
return await this.core.replicator.markRemoteResolved(this.settings);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.remote.handleMarkLocked(this._markRemoteLocked.bind(this));
|
||||||
|
services.remote.handleMarkUnlocked(this._markRemoteUnlocked.bind(this));
|
||||||
|
services.remote.handleMarkResolved(this._markRemoteResolved.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,22 @@ import {
|
|||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
import { escapeMarkdownValue } from "../../lib/src/common/utils.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleResolvingMismatchedTweaks extends AbstractModule implements ICoreModule {
|
export class ModuleResolvingMismatchedTweaks extends AbstractModule {
|
||||||
async $anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
async _anyAfterConnectCheckFailed(): Promise<boolean | "CHECKAGAIN" | undefined> {
|
||||||
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
if (!this.core.replicator.tweakSettingsMismatched && !this.core.replicator.preferredTweakValue) return false;
|
||||||
const preferred = this.core.replicator.preferredTweakValue;
|
const preferred = this.core.replicator.preferredTweakValue;
|
||||||
if (!preferred) return false;
|
if (!preferred) return false;
|
||||||
const ret = await this.core.$$askResolvingMismatchedTweaks(preferred);
|
const ret = await this.services.tweakValue.askResolvingMismatched(preferred);
|
||||||
if (ret == "OK") return false;
|
if (ret == "OK") return false;
|
||||||
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
|
if (ret == "CHECKAGAIN") return "CHECKAGAIN";
|
||||||
if (ret == "IGNORE") return true;
|
if (ret == "IGNORE") return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$checkAndAskResolvingMismatchedTweaks(
|
async _checkAndAskResolvingMismatchedTweaks(
|
||||||
preferred: Partial<TweakValues>
|
preferred: Partial<TweakValues>
|
||||||
): Promise<[TweakValues | boolean, boolean]> {
|
): Promise<[TweakValues | boolean, boolean]> {
|
||||||
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
const mine = extractObject(TweakValuesShouldMatchedTemplate, this.settings);
|
||||||
@@ -127,7 +128,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return CHOICES[retKey];
|
return CHOICES[retKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
async _askResolvingMismatchedTweaks(): Promise<"OK" | "CHECKAGAIN" | "IGNORE"> {
|
||||||
if (!this.core.replicator.tweakSettingsMismatched) {
|
if (!this.core.replicator.tweakSettingsMismatched) {
|
||||||
return "OK";
|
return "OK";
|
||||||
}
|
}
|
||||||
@@ -137,7 +138,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
}
|
}
|
||||||
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
const preferred = extractObject(TweakValuesShouldMatchedTemplate, tweaks);
|
||||||
|
|
||||||
const [conf, rebuildRequired] = await this.core.$$checkAndAskResolvingMismatchedTweaks(preferred);
|
const [conf, rebuildRequired] = await this.services.tweakValue.checkAndAskResolvingMismatched(preferred);
|
||||||
if (!conf) return "IGNORE";
|
if (!conf) return "IGNORE";
|
||||||
|
|
||||||
if (conf === true) {
|
if (conf === true) {
|
||||||
@@ -154,7 +155,7 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
if (conf) {
|
if (conf) {
|
||||||
this.settings = { ...this.settings, ...conf };
|
this.settings = { ...this.settings, ...conf };
|
||||||
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
await this.core.replicator.setPreferredRemoteTweakSettings(this.settings);
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
if (rebuildRequired) {
|
if (rebuildRequired) {
|
||||||
await this.core.rebuilder.$fetchLocal();
|
await this.core.rebuilder.$fetchLocal();
|
||||||
}
|
}
|
||||||
@@ -164,8 +165,12 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return "IGNORE";
|
return "IGNORE";
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
async _fetchRemotePreferredTweakValues(trialSetting: RemoteDBSettings): Promise<TweakValues | false> {
|
||||||
const replicator = await this.core.$anyNewReplicator();
|
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
||||||
|
if (!replicator) {
|
||||||
|
this._log("The remote type is not supported for fetching preferred tweak values.", LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (await replicator.tryConnectRemote(trialSetting)) {
|
if (await replicator.tryConnectRemote(trialSetting)) {
|
||||||
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
const preferred = await replicator.getRemotePreferredTweakValues(trialSetting);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
@@ -178,17 +183,17 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$checkAndAskUseRemoteConfiguration(
|
async _checkAndAskUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings
|
trialSetting: RemoteDBSettings
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
const preferred = await this.core.$$fetchRemotePreferredTweakValues(trialSetting);
|
const preferred = await this.services.tweakValue.fetchRemotePreferred(trialSetting);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
return await this.$$askUseRemoteConfiguration(trialSetting, preferred);
|
return await this.services.tweakValue.askUseRemoteConfiguration(trialSetting, preferred);
|
||||||
}
|
}
|
||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$askUseRemoteConfiguration(
|
async _askUseRemoteConfiguration(
|
||||||
trialSetting: RemoteDBSettings,
|
trialSetting: RemoteDBSettings,
|
||||||
preferred: TweakValues
|
preferred: TweakValues
|
||||||
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
): Promise<{ result: false | TweakValues; requireFetch: boolean }> {
|
||||||
@@ -278,4 +283,13 @@ export class ModuleResolvingMismatchedTweaks extends AbstractModule implements I
|
|||||||
}
|
}
|
||||||
return { result: false, requireFetch: false };
|
return { result: false, requireFetch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.tweakValue.handleFetchRemotePreferred(this._fetchRemotePreferredTweakValues.bind(this));
|
||||||
|
services.tweakValue.handleCheckAndAskResolvingMismatched(this._checkAndAskResolvingMismatchedTweaks.bind(this));
|
||||||
|
services.tweakValue.handleAskResolvingMismatched(this._askResolvingMismatchedTweaks.bind(this));
|
||||||
|
services.tweakValue.handleCheckAndAskUseRemoteConfiguration(this._checkAndAskUseRemoteConfiguration.bind(this));
|
||||||
|
services.tweakValue.handleAskUseRemoteConfiguration(this._askUseRemoteConfiguration.bind(this));
|
||||||
|
services.replication.handleCheckConnectionFailure(this._anyAfterConnectCheckFailed.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TFile, TFolder, type ListedFiles } from "obsidian";
|
import { TFile, TFolder, type ListedFiles } from "obsidian";
|
||||||
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
import { SerializedFileAccess } from "./storageLib/SerializedFileAccess";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import type {
|
import type {
|
||||||
FilePath,
|
FilePath,
|
||||||
@@ -15,43 +15,72 @@ 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";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import type ObsidianLiveSyncPlugin from "../../main.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
|
||||||
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements IObsidianModule, StorageAccess {
|
const fileLockPrefix = "file-lock:";
|
||||||
|
|
||||||
|
export class ModuleFileAccessObsidian extends AbstractObsidianModule implements 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> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.storageAccess = this;
|
this.core.storageAccess = this;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnFirstInitialize(): Promise<boolean> {
|
_everyOnFirstInitialize(): Promise<boolean> {
|
||||||
this.vaultManager.beginWatch();
|
this.vaultManager.beginWatch();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnUnload(): Promise<boolean> {
|
|
||||||
// this.vaultManager.
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// $$flushFileEventQueue(): void {
|
// $$flushFileEventQueue(): void {
|
||||||
// this.vaultManager.flushQueue();
|
// this.vaultManager.flushQueue();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$everyCommitPendingFileEvent(): Promise<boolean> {
|
_everyCommitPendingFileEvent(): Promise<boolean> {
|
||||||
this.vaultManager.flushQueue();
|
this.vaultManager.flushQueue();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isStorageInsensitive(): boolean {
|
_isStorageInsensitive(): boolean {
|
||||||
return this.vaultAccess.isStorageInsensitive();
|
return this.vaultAccess.isStorageInsensitive();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$shouldCheckCaseInsensitive(): boolean {
|
_shouldCheckCaseInsensitive(): boolean {
|
||||||
if (this.$$isStorageInsensitive()) return false;
|
if (this.services.vault.isStorageInsensitive()) return false;
|
||||||
return !this.settings.handleFilenameCaseSensitive;
|
return !this.settings.handleFilenameCaseSensitive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +222,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false> {
|
async readStubContent(stub: UXFileInfoStub): Promise<UXFileInfo | false> {
|
||||||
const file = this.vaultAccess.getAbstractFileByPath(stub.path);
|
const file = this.vaultAccess.getAbstractFileByPath(stub.path);
|
||||||
if (!(file instanceof TFile)) {
|
if (!(file instanceof TFile)) {
|
||||||
@@ -202,6 +232,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
const data = await this.vaultAccess.vaultReadAuto(file);
|
const data = await this.vaultAccess.vaultReadAuto(file);
|
||||||
return {
|
return {
|
||||||
...stub,
|
...stub,
|
||||||
|
...TFileToUXFileInfoStub(file),
|
||||||
body: createBlob(data),
|
body: createBlob(data),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -245,7 +276,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
|
if (excludeFilter && excludeFilter.some((ee) => ee.test(file))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(file)) continue;
|
if (await this.services.vault.isIgnoredByIgnoreFile(file)) continue;
|
||||||
files.push(file);
|
files.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +289,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
|
if (excludeFilter && excludeFilter.some((e) => e.test(v))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (await this.plugin.$$isIgnoredByIgnoreFiles(v)) {
|
if (await this.services.vault.isIgnoredByIgnoreFile(v)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// OK, deep dive!
|
// OK, deep dive!
|
||||||
@@ -314,9 +345,9 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async _deleteVaultItem(file: TFile | TFolder) {
|
async __deleteVaultItem(file: TFile | TFolder) {
|
||||||
if (file instanceof TFile) {
|
if (file instanceof TFile) {
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) return;
|
if (!(await this.services.vault.isTargetFile(file.path))) return;
|
||||||
}
|
}
|
||||||
const dir = file.parent;
|
const dir = file.parent;
|
||||||
if (this.settings.trashInsteadDelete) {
|
if (this.settings.trashInsteadDelete) {
|
||||||
@@ -332,7 +363,7 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
this._log(
|
this._log(
|
||||||
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
|
`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`
|
||||||
);
|
);
|
||||||
await this._deleteVaultItem(dir);
|
await this.__deleteVaultItem(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +374,19 @@ export class ModuleFileAccessObsidian extends AbstractObsidianModule implements
|
|||||||
const file = this.vaultAccess.getAbstractFileByPath(path);
|
const file = this.vaultAccess.getAbstractFileByPath(path);
|
||||||
if (file === null) return;
|
if (file === null) return;
|
||||||
if (file instanceof TFile || file instanceof TFolder) {
|
if (file instanceof TFile || file instanceof TFolder) {
|
||||||
return await this._deleteVaultItem(file);
|
return await this.__deleteVaultItem(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(plugin: ObsidianLiveSyncPlugin, core: LiveSyncCore) {
|
||||||
|
super(plugin, core);
|
||||||
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.vault.handleIsStorageInsensitive(this._isStorageInsensitive.bind(this));
|
||||||
|
services.setting.handleShouldCheckCaseInsensitively(this._shouldCheckCaseInsensitive.bind(this));
|
||||||
|
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.fileProcessing.handleCommitPendingFileEvents(this._everyCommitPendingFileEvent.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// ModuleInputUIObsidian.ts
|
// ModuleInputUIObsidian.ts
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
import { disposeMemoObject, memoIfNotExist, memoObject, retrieveMemoObject } from "../../common/utils.ts";
|
||||||
import {
|
import {
|
||||||
@@ -13,12 +13,13 @@ import { Notice } from "../../deps.ts";
|
|||||||
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
import type { Confirm } from "../../lib/src/interfaces/Confirm.ts";
|
||||||
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
|
import { setConfirmInstance } from "../../lib/src/PlatformAPIs/obsidian/Confirm.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
// This module cannot be a common module because it depends on Obsidian's API.
|
// This module cannot be a common module because it depends on Obsidian's API.
|
||||||
// However, we have to make compatible one for other platform.
|
// However, we have to make compatible one for other platform.
|
||||||
|
|
||||||
export class ModuleInputUIObsidian extends AbstractObsidianModule implements IObsidianModule, Confirm {
|
export class ModuleInputUIObsidian extends AbstractObsidianModule implements Confirm {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.core.confirm = this;
|
this.core.confirm = this;
|
||||||
setConfirmInstance(this);
|
setConfirmInstance(this);
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
@@ -110,4 +111,8 @@ export class ModuleInputUIObsidian extends AbstractObsidianModule implements IOb
|
|||||||
): Promise<(typeof buttons)[number] | false> {
|
): Promise<(typeof buttons)[number] | false> {
|
||||||
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
return confirmWithMessage(this.plugin, title, contentMd, buttons, defaultAction, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
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 toArrayBuffer(arr: Uint8Array<ArrayBuffer> | ArrayBuffer | DataView<ArrayBuffer>): ArrayBuffer {
|
||||||
function getFileLockKey(file: TFile | TFolder | string | UXFileInfo) {
|
|
||||||
return `fl:${typeof file == "string" ? file : file.path}`;
|
|
||||||
}
|
|
||||||
function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike {
|
|
||||||
if (arr instanceof Uint8Array) {
|
if (arr instanceof Uint8Array) {
|
||||||
return arr.buffer;
|
return arr.buffer;
|
||||||
}
|
}
|
||||||
@@ -21,94 +16,97 @@ function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLik
|
|||||||
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(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
|
async adapterWrite(
|
||||||
|
file: TFile | string,
|
||||||
|
data: string | ArrayBuffer | Uint8Array<ArrayBuffer>,
|
||||||
|
options?: DataWriteOptions
|
||||||
|
) {
|
||||||
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, 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);
|
||||||
@@ -118,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);
|
||||||
@@ -131,13 +129,17 @@ export class SerializedFileAccess {
|
|||||||
}
|
}
|
||||||
async vaultCreate(
|
async vaultCreate(
|
||||||
path: string,
|
path: string,
|
||||||
data: string | ArrayBuffer | Uint8Array,
|
data: string | ArrayBuffer | Uint8Array<ArrayBuffer>,
|
||||||
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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,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 {
|
||||||
|
|||||||
@@ -7,24 +7,26 @@ import {
|
|||||||
LOG_LEVEL_INFO,
|
LOG_LEVEL_INFO,
|
||||||
LOG_LEVEL_NOTICE,
|
LOG_LEVEL_NOTICE,
|
||||||
LOG_LEVEL_VERBOSE,
|
LOG_LEVEL_VERBOSE,
|
||||||
|
type FileEventType,
|
||||||
type FilePath,
|
type FilePath,
|
||||||
type FilePathWithPrefix,
|
type FilePathWithPrefix,
|
||||||
type UXFileInfoStub,
|
type UXFileInfoStub,
|
||||||
type UXInternalFileInfoStub,
|
type UXInternalFileInfoStub,
|
||||||
} 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 } 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 +48,10 @@ 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 services() {
|
||||||
|
return this.core.services;
|
||||||
|
}
|
||||||
|
|
||||||
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 +62,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 +95,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 +113,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,13 +169,17 @@ 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()))) {
|
||||||
if (this.plugin.settings.useIgnoreFiles) {
|
if (this.plugin.settings.useIgnoreFiles) {
|
||||||
// If it is one of ignore files, refresh the cached one.
|
// If it is one of ignore files, refresh the cached one.
|
||||||
// (Calling$$isTargetFile will refresh the cache)
|
// (Calling$$isTargetFile will refresh the cache)
|
||||||
void this.plugin.$$isTargetFile(path).then(() => this._watchVaultRawEvents(path));
|
void this.services.vault.isTargetFile(path).then(() => this._watchVaultRawEvents(path));
|
||||||
} else {
|
} else {
|
||||||
this._watchVaultRawEvents(path);
|
this._watchVaultRawEvents(path);
|
||||||
}
|
}
|
||||||
@@ -185,7 +213,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
async appendQueue(params: FileEvent[], ctx?: any) {
|
async appendQueue(params: FileEvent[], ctx?: any) {
|
||||||
if (!this.core.settings.isConfigured) return;
|
if (!this.core.settings.isConfigured) return;
|
||||||
if (this.core.settings.suspendFileWatching) return;
|
if (this.core.settings.suspendFileWatching) return;
|
||||||
this.core.$$markFileListPossiblyChanged();
|
this.core.services.vault.markFileListPossiblyChanged();
|
||||||
// Flag up to be reload
|
// Flag up to be reload
|
||||||
const processFiles = new Set<FilePath>();
|
const processFiles = new Set<FilePath>();
|
||||||
for (const param of params) {
|
for (const param of params) {
|
||||||
@@ -198,7 +226,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
const oldPath = param.oldPath;
|
const oldPath = param.oldPath;
|
||||||
if (type !== "INTERNAL") {
|
if (type !== "INTERNAL") {
|
||||||
const size = (file as UXFileInfoStub).stat.size;
|
const size = (file as UXFileInfoStub).stat.size;
|
||||||
if (this.core.$$isFileSizeExceeded(size) && (type == "CREATE" || type == "CHANGED")) {
|
if (this.services.vault.isFileSizeTooLarge(size) && (type == "CREATE" || type == "CHANGED")) {
|
||||||
Logger(
|
Logger(
|
||||||
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
|
`The storage file has been changed but exceeds the maximum size. Skipping: ${param.file.path}`,
|
||||||
LOG_LEVEL_NOTICE
|
LOG_LEVEL_NOTICE
|
||||||
@@ -207,7 +235,10 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (file instanceof TFolder) continue;
|
if (file instanceof TFolder) continue;
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) continue;
|
// TODO: Confirm why only the TFolder skipping
|
||||||
|
// Possibly following line is needed...
|
||||||
|
// if (file?.isFolder) continue;
|
||||||
|
if (!(await this.services.vault.isTargetFile(file.path))) continue;
|
||||||
|
|
||||||
// Stop cache using to prevent the corruption;
|
// Stop cache using to prevent the corruption;
|
||||||
// let cache: null | string | ArrayBuffer;
|
// let cache: null | string | ArrayBuffer;
|
||||||
@@ -264,7 +295,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
concurrentProcessing = Semaphore(5);
|
concurrentProcessing = Semaphore(5);
|
||||||
waitedSince = new Map<FilePath | FilePathWithPrefix, number>();
|
waitedSince = new Map<FilePath | FilePathWithPrefix, number>();
|
||||||
async startStandingBy(filename: FilePath) {
|
async startStandingBy(filename: FilePath) {
|
||||||
// If waited, cancel previous waiting.
|
// If waited, no need to start again (looping inside the function)
|
||||||
await skipIfDuplicated(`storage-event-manager-${filename}`, async () => {
|
await skipIfDuplicated(`storage-event-manager-${filename}`, async () => {
|
||||||
Logger(`Processing ${filename}: Starting`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Starting`, LOG_LEVEL_DEBUG);
|
||||||
const release = await this.concurrentProcessing.acquire();
|
const release = await this.concurrentProcessing.acquire();
|
||||||
@@ -284,6 +315,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
const type = target.type;
|
const type = target.type;
|
||||||
|
// If already cancelled by other operation, skip this.
|
||||||
if (target.cancelled) {
|
if (target.cancelled) {
|
||||||
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
|
Logger(`Processing ${filename}: Cancelled (scheduled): ${operationType}`, LOG_LEVEL_DEBUG);
|
||||||
this.cancelStandingBy(target);
|
this.cancelStandingBy(target);
|
||||||
@@ -384,12 +416,12 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
const lockKey = `handleFile:${file.path}`;
|
const lockKey = `handleFile:${file.path}`;
|
||||||
return await serialized(lockKey, async () => {
|
return await serialized(lockKey, async () => {
|
||||||
if (queue.type == "INTERNAL" || file.isInternal) {
|
if (queue.type == "INTERNAL" || file.isInternal) {
|
||||||
await this.core.$anyProcessOptionalFileEvent(file.path as unknown as FilePath);
|
await this.core.services.fileProcessing.processOptionalFileEvent(file.path as unknown as FilePath);
|
||||||
} else {
|
} else {
|
||||||
const key = `file-last-proc-${queue.type}-${file.path}`;
|
const key = `file-last-proc-${queue.type}-${file.path}`;
|
||||||
const last = Number((await this.core.kvDB.get(key)) || 0);
|
const last = Number((await this.core.kvDB.get(key)) || 0);
|
||||||
if (queue.type == "DELETE") {
|
if (queue.type == "DELETE") {
|
||||||
await this.core.$anyHandlerProcessesFileEvent(queue);
|
await this.core.services.fileProcessing.processFileEvent(queue);
|
||||||
} else {
|
} else {
|
||||||
if (file.stat.mtime == last) {
|
if (file.stat.mtime == last) {
|
||||||
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
Logger(`File has been already scanned on ${queue.type}, skip: ${file.path}`, LOG_LEVEL_VERBOSE);
|
||||||
@@ -397,7 +429,7 @@ export class StorageEventManagerObsidian extends StorageEventManager {
|
|||||||
// this.cancelRelativeEvent(queue);
|
// this.cancelRelativeEvent(queue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$anyHandlerProcessesFileEvent(queue))) {
|
if (!(await this.core.services.fileProcessing.processFileEvent(queue))) {
|
||||||
Logger(
|
Logger(
|
||||||
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
|
`STORAGE -> DB: Handler failed, cancel the relative operations: ${file.path}`,
|
||||||
LOG_LEVEL_INFO
|
LOG_LEVEL_INFO
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import {
|
|||||||
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
import { isAnyNote } from "../../lib/src/common/utils.ts";
|
||||||
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
import { stripAllPrefixes } from "../../lib/src/string_and_binary/path.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { withConcurrency } from "octagonal-wheels/iterable/map";
|
import { withConcurrency } from "octagonal-wheels/iterable/map";
|
||||||
export class ModuleInitializerFile extends AbstractModule implements ICoreModule {
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
async $$performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<void> {
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
export class ModuleInitializerFile extends AbstractModule {
|
||||||
|
private async _performFullScan(showingNotice?: boolean, ignoreSuspending: boolean = false): Promise<boolean> {
|
||||||
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
this._log("Opening the key-value database", LOG_LEVEL_VERBOSE);
|
||||||
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
|
const isInitialized = (await this.core.kvDB.get<boolean>("initialized")) || false;
|
||||||
// synchronize all files between database and storage.
|
// synchronize all files between database and storage.
|
||||||
@@ -32,7 +33,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
"syncAll"
|
"syncAll"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ignoreSuspending && this.settings.suspendFileWatching) {
|
if (!ignoreSuspending && this.settings.suspendFileWatching) {
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
@@ -42,7 +43,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
"syncAll"
|
"syncAll"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
@@ -59,7 +60,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
const _filesStorage = [] as typeof filesStorageSrc;
|
const _filesStorage = [] as typeof filesStorageSrc;
|
||||||
|
|
||||||
for (const f of filesStorageSrc) {
|
for (const f of filesStorageSrc) {
|
||||||
if (await this.core.$$isTargetFile(f.path, f != filesStorageSrc[0])) {
|
if (await this.services.vault.isTargetFile(f.path, f != filesStorageSrc[0])) {
|
||||||
_filesStorage.push(f);
|
_filesStorage.push(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
);
|
);
|
||||||
const path = getPath(doc);
|
const path = getPath(doc);
|
||||||
|
|
||||||
if (isValidPath(path) && (await this.core.$$isTargetFile(path, true))) {
|
if (isValidPath(path) && (await this.services.vault.isTargetFile(path, true))) {
|
||||||
if (!isMetaEntry(doc)) {
|
if (!isMetaEntry(doc)) {
|
||||||
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
this._log(`Invalid entry: ${path}`, LOG_LEVEL_INFO);
|
||||||
continue;
|
continue;
|
||||||
@@ -133,7 +134,6 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the database: ${databaseFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files in the storage: ${storageFileNames.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
this._log(`Total files: ${allFiles.length}`, LOG_LEVEL_VERBOSE, "syncAll");
|
||||||
|
|
||||||
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
const filesExistOnlyInStorage = allFiles.filter((e) => !databaseFileNameCI2CS[e]);
|
||||||
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
const filesExistOnlyInDatabase = allFiles.filter((e) => !storageFileNameCI2CS[e]);
|
||||||
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
const filesExistBoth = allFiles.filter((e) => databaseFileNameCI2CS[e] && storageFileNameCI2CS[e]);
|
||||||
@@ -192,7 +192,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
runAll("UPDATE DATABASE", filesExistOnlyInStorage, async (e) => {
|
||||||
// Exists in storage but not in database.
|
// Exists in storage but not in database.
|
||||||
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
const file = storageFileNameMap[storageFileNameCI2CS[e]];
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
|
||||||
const path = file.path;
|
const path = file.path;
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
// fireAndForget(() => this.checkAndApplySettingFromMarkdown(path, true));
|
||||||
@@ -208,7 +208,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
// Exists in database but not in storage.
|
// Exists in database but not in storage.
|
||||||
const path = getPath(w) ?? e;
|
const path = getPath(w) ?? e;
|
||||||
if (w && !(w.deleted || w._deleted)) {
|
if (w && !(w.deleted || w._deleted)) {
|
||||||
if (!this.core.$$isFileSizeExceeded(w.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(w.size)) {
|
||||||
// Prevent applying the conflicted state to the storage.
|
// Prevent applying the conflicted state to the storage.
|
||||||
if (w._conflicts?.length ?? 0 > 0) {
|
if (w._conflicts?.length ?? 0 > 0) {
|
||||||
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped (x)`, LOG_LEVEL_INFO);
|
this._log(`UPDATE STORAGE: ${path} has conflicts. skipped (x)`, LOG_LEVEL_INFO);
|
||||||
@@ -250,7 +250,10 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
this._log(`SYNC DATABASE AND STORAGE: ${file.path} has conflicts. skipped`, LOG_LEVEL_INFO);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size) && !this.core.$$isFileSizeExceeded(doc.size)) {
|
if (
|
||||||
|
!this.services.vault.isFileSizeTooLarge(file.stat.size) &&
|
||||||
|
!this.services.vault.isFileSizeTooLarge(doc.size)
|
||||||
|
) {
|
||||||
await this.syncFileBetweenDBandStorage(file, doc);
|
await this.syncFileBetweenDBandStorage(file, doc);
|
||||||
} else {
|
} else {
|
||||||
this._log(
|
this._log(
|
||||||
@@ -271,6 +274,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
if (showingNotice) {
|
if (showingNotice) {
|
||||||
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
this._log("Initialize done!", LOG_LEVEL_NOTICE, "syncAll");
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
async syncFileBetweenDBandStorage(file: UXFileInfoStub, doc: MetaEntry) {
|
||||||
@@ -289,7 +293,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
const compareResult = compareFileFreshness(file, doc);
|
const compareResult = compareFileFreshness(file, doc);
|
||||||
switch (compareResult) {
|
switch (compareResult) {
|
||||||
case BASE_IS_NEW:
|
case BASE_IS_NEW:
|
||||||
if (!this.core.$$isFileSizeExceeded(file.stat.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(file.stat.size)) {
|
||||||
this._log("STORAGE -> DB :" + file.path);
|
this._log("STORAGE -> DB :" + file.path);
|
||||||
await this.core.fileHandler.storeFileToDB(file);
|
await this.core.fileHandler.storeFileToDB(file);
|
||||||
} else {
|
} else {
|
||||||
@@ -300,7 +304,7 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TARGET_IS_NEW:
|
case TARGET_IS_NEW:
|
||||||
if (!this.core.$$isFileSizeExceeded(doc.size)) {
|
if (!this.services.vault.isFileSizeTooLarge(doc.size)) {
|
||||||
this._log("STORAGE <- DB :" + file.path);
|
this._log("STORAGE <- DB :" + file.path);
|
||||||
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
if (await this.core.fileHandler.dbToStorage(doc, stripAllPrefixes(file.path), true)) {
|
||||||
eventHub.emitEvent("event-file-changed", {
|
eventHub.emitEvent("event-file-changed", {
|
||||||
@@ -365,27 +369,31 @@ export class ModuleInitializerFile extends AbstractModule implements ICoreModule
|
|||||||
this._log(`Checking expired file history done`);
|
this._log(`Checking expired file history done`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$initializeDatabase(
|
private async _initializeDatabase(
|
||||||
showingNotice: boolean = false,
|
showingNotice: boolean = false,
|
||||||
reopenDatabase = true,
|
reopenDatabase = true,
|
||||||
ignoreSuspending: boolean = false
|
ignoreSuspending: boolean = false
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
this.core.$$resetIsReady();
|
this.services.appLifecycle.resetIsReady();
|
||||||
if (!reopenDatabase || (await this.core.$$openDatabase())) {
|
if (!reopenDatabase || (await this.services.database.openDatabase())) {
|
||||||
if (this.localDatabase.isReady) {
|
if (this.localDatabase.isReady) {
|
||||||
await this.core.$$performFullScan(showingNotice, ignoreSuspending);
|
await this.services.vault.scanVault(showingNotice, ignoreSuspending);
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyOnDatabaseInitialized(showingNotice))) {
|
if (!(await this.services.databaseEvents.onDatabaseInitialised(showingNotice))) {
|
||||||
this._log(`Initializing database has been failed on some module`, LOG_LEVEL_NOTICE);
|
this._log(`Initializing database has been failed on some module!`, LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.core.$$markIsReady();
|
this.services.appLifecycle.markIsReady();
|
||||||
// run queued event once.
|
// run queued event once.
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.core.$$resetIsReady();
|
this.services.appLifecycle.resetIsReady();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
services.databaseEvents.handleInitialiseDatabase(this._initializeDatabase.bind(this));
|
||||||
|
services.vault.handleScanVault(this._performFullScan.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { OpenKeyValueDatabase } from "../../common/KeyValueDB.ts";
|
|||||||
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
import type { LiveSyncLocalDB } from "../../lib/src/pouchdb/LiveSyncLocalDB.ts";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
export class ModuleKeyValueDB extends AbstractModule {
|
||||||
tryCloseKvDB() {
|
tryCloseKvDB() {
|
||||||
try {
|
try {
|
||||||
this.core.kvDB?.close();
|
this.core.kvDB?.close();
|
||||||
@@ -22,7 +22,7 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
this.tryCloseKvDB();
|
this.tryCloseKvDB();
|
||||||
await delay(10);
|
await delay(10);
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
|
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
await delay(100);
|
await delay(100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -33,21 +33,23 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$allOnDBUnload(db: LiveSyncLocalDB): void {
|
_onDBUnload(db: LiveSyncLocalDB) {
|
||||||
if (this.core.kvDB) this.core.kvDB.close();
|
if (this.core.kvDB) this.core.kvDB.close();
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$allOnDBClose(db: LiveSyncLocalDB): void {
|
_onDBClose(db: LiveSyncLocalDB) {
|
||||||
if (this.core.kvDB) this.core.kvDB.close();
|
if (this.core.kvDB) this.core.kvDB.close();
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private async _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!(await this.openKeyValueDB())) {
|
if (!(await this.openKeyValueDB())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.core.simpleStore = this.core.$$getSimpleStore<any>("os");
|
this.core.simpleStore = this.services.database.openSimpleStore<any>("os");
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$$getSimpleStore<T>(kind: string) {
|
_getSimpleStore<T>(kind: string) {
|
||||||
const prefix = `${kind}-`;
|
const prefix = `${kind}-`;
|
||||||
return {
|
return {
|
||||||
get: async (key: string): Promise<T> => {
|
get: async (key: string): Promise<T> => {
|
||||||
@@ -75,18 +77,18 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
_everyOnInitializeDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
return this.openKeyValueDB();
|
return this.openKeyValueDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
async _everyOnResetDatabase(db: LiveSyncLocalDB): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const kvDBKey = "queued-files";
|
const kvDBKey = "queued-files";
|
||||||
await this.core.kvDB.del(kvDBKey);
|
await this.core.kvDB.del(kvDBKey);
|
||||||
// localStorage.removeItem(lsKey);
|
// localStorage.removeItem(lsKey);
|
||||||
await this.core.kvDB.destroy();
|
await this.core.kvDB.destroy();
|
||||||
await yieldMicrotask();
|
await yieldMicrotask();
|
||||||
this.core.kvDB = await OpenKeyValueDatabase(this.core.$$getVaultName() + "-livesync-kv");
|
this.core.kvDB = await OpenKeyValueDatabase(this.services.vault.getVaultName() + "-livesync-kv");
|
||||||
await delay(100);
|
await delay(100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.core.kvDB = undefined!;
|
this.core.kvDB = undefined!;
|
||||||
@@ -96,4 +98,12 @@ export class ModuleKeyValueDB extends AbstractModule implements ICoreModule {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.databaseEvents.handleOnUnloadDatabase(this._onDBUnload.bind(this));
|
||||||
|
services.databaseEvents.handleOnCloseDatabase(this._onDBClose.bind(this));
|
||||||
|
services.databaseEvents.handleOnDatabaseInitialisation(this._everyOnInitializeDatabase.bind(this));
|
||||||
|
services.databaseEvents.handleOnResetDatabase(this._everyOnResetDatabase.bind(this));
|
||||||
|
services.database.handleOpenSimpleStore(this._getSimpleStore.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
import { LOG_LEVEL_NOTICE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, Logger } from "../../lib/src/common/logger.ts";
|
||||||
import {
|
import {
|
||||||
EVENT_REQUEST_OPEN_P2P,
|
EVENT_REQUEST_OPEN_P2P,
|
||||||
EVENT_REQUEST_OPEN_SETTING_WIZARD,
|
EVENT_REQUEST_OPEN_SETTING_WIZARD,
|
||||||
EVENT_REQUEST_OPEN_SETTINGS,
|
EVENT_REQUEST_OPEN_SETTINGS,
|
||||||
EVENT_REQUEST_OPEN_SETUP_URI,
|
EVENT_REQUEST_OPEN_SETUP_URI,
|
||||||
EVENT_REQUEST_RUN_DOCTOR,
|
EVENT_REQUEST_RUN_DOCTOR,
|
||||||
|
EVENT_REQUEST_RUN_FIX_INCOMPLETE,
|
||||||
eventHub,
|
eventHub,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts";
|
import { performDoctorConsultation, RebuildOptions } from "../../lib/src/common/configForDoc.ts";
|
||||||
|
import { getPath, isValidPath } from "../../common/utils.ts";
|
||||||
|
import { isMetaEntry } from "../../lib/src/common/types.ts";
|
||||||
|
import { isDeletedEntry, isDocContentSame, isLoadedEntry, readAsBlob } from "../../lib/src/common/utils.ts";
|
||||||
|
import { countCompromisedChunks } from "../../lib/src/pouchdb/negotiation.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleMigration extends AbstractModule implements ICoreModule {
|
type ErrorInfo = {
|
||||||
|
path: string;
|
||||||
|
recordedSize: number;
|
||||||
|
actualSize: number;
|
||||||
|
storageSize: number;
|
||||||
|
contentMatched: boolean;
|
||||||
|
isConflicted?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ModuleMigration extends AbstractModule {
|
||||||
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
async migrateUsingDoctor(skipRebuild: boolean = false, activateReason = "updated", forceRescan = false) {
|
||||||
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
const { shouldRebuild, shouldRebuildLocal, isModified, settings } = await performDoctorConsultation(
|
||||||
this.core,
|
this.core,
|
||||||
@@ -31,12 +45,15 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
if (!skipRebuild) {
|
if (!skipRebuild) {
|
||||||
if (shouldRebuild) {
|
if (shouldRebuild) {
|
||||||
await this.core.rebuilder.scheduleRebuild();
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
|
return false;
|
||||||
} else if (shouldRebuildLocal) {
|
} else if (shouldRebuildLocal) {
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
await this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async migrateDisableBulkSend() {
|
async migrateDisableBulkSend() {
|
||||||
@@ -96,13 +113,215 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyOnFirstInitialize(): Promise<boolean> {
|
async hasIncompleteDocs(force: boolean = false): Promise<boolean> {
|
||||||
|
const incompleteDocsChecked = (await this.core.kvDB.get<boolean>("checkIncompleteDocs")) || false;
|
||||||
|
if (incompleteDocsChecked && !force) {
|
||||||
|
this._log("Incomplete docs check already done, skipping.", LOG_LEVEL_VERBOSE);
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log("Checking for incomplete documents...", LOG_LEVEL_NOTICE, "check-incomplete");
|
||||||
|
|
||||||
|
const errorFiles = [] as ErrorInfo[];
|
||||||
|
for await (const metaDoc of this.localDatabase.findAllNormalDocs({ conflicts: true })) {
|
||||||
|
const path = getPath(metaDoc);
|
||||||
|
|
||||||
|
if (!isValidPath(path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(await this.services.vault.isTargetFile(path, true))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isMetaEntry(metaDoc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await this.localDatabase.getDBEntryFromMeta(metaDoc);
|
||||||
|
if (!doc || !isLoadedEntry(doc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isDeletedEntry(doc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const isConflicted = metaDoc?._conflicts && metaDoc._conflicts.length > 0;
|
||||||
|
|
||||||
|
let storageFileContent;
|
||||||
|
try {
|
||||||
|
storageFileContent = await this.core.storageAccess.readHiddenFileBinary(path);
|
||||||
|
} catch (e) {
|
||||||
|
Logger(`Failed to read file ${path}: Possibly unprocessed or missing`);
|
||||||
|
Logger(e, LOG_LEVEL_VERBOSE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// const storageFileBlob = createBlob(storageFileContent);
|
||||||
|
const sizeOnStorage = storageFileContent.byteLength;
|
||||||
|
const recordedSize = doc.size;
|
||||||
|
const docBlob = readAsBlob(doc);
|
||||||
|
const actualSize = docBlob.size;
|
||||||
|
if (
|
||||||
|
recordedSize !== actualSize ||
|
||||||
|
sizeOnStorage !== actualSize ||
|
||||||
|
sizeOnStorage !== recordedSize ||
|
||||||
|
isConflicted
|
||||||
|
) {
|
||||||
|
const contentMatched = await isDocContentSame(doc.data, storageFileContent);
|
||||||
|
errorFiles.push({
|
||||||
|
path,
|
||||||
|
recordedSize,
|
||||||
|
actualSize,
|
||||||
|
storageSize: sizeOnStorage,
|
||||||
|
contentMatched,
|
||||||
|
isConflicted,
|
||||||
|
});
|
||||||
|
Logger(
|
||||||
|
`Size mismatch for ${path}: ${recordedSize} (DB Recorded) , ${actualSize} (DB Stored) , ${sizeOnStorage} (Storage Stored), ${contentMatched ? "Content Matched" : "Content Mismatched"} ${isConflicted ? "Conflicted" : "Not Conflicted"}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorFiles.length == 0) {
|
||||||
|
Logger("No size mismatches found", LOG_LEVEL_NOTICE);
|
||||||
|
await this.core.kvDB.set("checkIncompleteDocs", true);
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
Logger(`Found ${errorFiles.length} size mismatches`, LOG_LEVEL_NOTICE);
|
||||||
|
// We have to repair them following rules and situations:
|
||||||
|
// A. DB Recorded != DB Stored
|
||||||
|
// A.1. DB Recorded == Storage Stored
|
||||||
|
// Possibly recoverable from storage. Just overwrite the DB content with storage content.
|
||||||
|
// A.2. Neither
|
||||||
|
// Probably it cannot be resolved on this device. Even if the storage content is larger than DB Recorded, it possibly corrupted.
|
||||||
|
// We do not fix it automatically. Leave it as is. Possibly other device can do this.
|
||||||
|
// B. DB Recorded == DB Stored , < Storage Stored
|
||||||
|
// Very fragile, if DB Recorded size is less than Storage Stored size, we possibly repair the content (The issue was `unexpectedly shortened file`).
|
||||||
|
// We do not fix it automatically, but it will be automatically overwritten in other process.
|
||||||
|
// C. DB Recorded == DB Stored , > Storage Stored
|
||||||
|
// Probably restored by the user by resolving A or B on other device, We should overwrite the storage
|
||||||
|
// Also do not fix it automatically. It should be overwritten by replication.
|
||||||
|
const recoverable = errorFiles.filter((e) => {
|
||||||
|
return e.recordedSize === e.storageSize && !e.isConflicted;
|
||||||
|
});
|
||||||
|
const unrecoverable = errorFiles.filter((e) => {
|
||||||
|
return e.recordedSize !== e.storageSize || e.isConflicted;
|
||||||
|
});
|
||||||
|
const fileInfo = (e: (typeof errorFiles)[0]) => {
|
||||||
|
return `${e.path} (M: ${e.recordedSize}, A: ${e.actualSize}, S: ${e.storageSize}) ${e.isConflicted ? "(Conflicted)" : ""}`;
|
||||||
|
};
|
||||||
|
const messageUnrecoverable =
|
||||||
|
unrecoverable.length > 0
|
||||||
|
? $msg("moduleMigration.fix0256.messageUnrecoverable", {
|
||||||
|
filesNotRecoverable: unrecoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
|
})
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const message = $msg("moduleMigration.fix0256.message", {
|
||||||
|
files: recoverable.map((e) => `- ${fileInfo(e)}`).join("\n"),
|
||||||
|
messageUnrecoverable,
|
||||||
|
});
|
||||||
|
const CHECK_IT_LATER = $msg("moduleMigration.fix0256.buttons.checkItLater");
|
||||||
|
const FIX = $msg("moduleMigration.fix0256.buttons.fix");
|
||||||
|
const DISMISS = $msg("moduleMigration.fix0256.buttons.DismissForever");
|
||||||
|
const ret = await this.core.confirm.askSelectStringDialogue(message, [CHECK_IT_LATER, FIX, DISMISS], {
|
||||||
|
title: $msg("moduleMigration.fix0256.title"),
|
||||||
|
defaultAction: CHECK_IT_LATER,
|
||||||
|
});
|
||||||
|
if (ret == FIX) {
|
||||||
|
for (const file of recoverable) {
|
||||||
|
// Overwrite the database with the files on the storage
|
||||||
|
const stubFile = this.core.storageAccess.getFileStub(file.path);
|
||||||
|
if (stubFile == null) {
|
||||||
|
Logger(`Could not find stub file for ${file.path}`, LOG_LEVEL_NOTICE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stubFile.stat.mtime = Date.now();
|
||||||
|
const result = await this.core.fileHandler.storeFileToDB(stubFile, true, false);
|
||||||
|
if (result) {
|
||||||
|
Logger(`Successfully restored ${file.path} from storage`);
|
||||||
|
} else {
|
||||||
|
Logger(`Failed to restore ${file.path} from storage`, LOG_LEVEL_NOTICE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (ret === DISMISS) {
|
||||||
|
// User chose to dismiss the issue
|
||||||
|
await this.core.kvDB.set("checkIncompleteDocs", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasCompromisedChunks(): Promise<boolean> {
|
||||||
|
Logger(`Checking for compromised chunks...`, LOG_LEVEL_VERBOSE);
|
||||||
|
if (!this.settings.encrypt) {
|
||||||
|
// If not encrypted, we do not need to check for compromised chunks.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check local database for compromised chunks
|
||||||
|
const localCompromised = await countCompromisedChunks(this.localDatabase.localDatabase);
|
||||||
|
const remote = this.services.replicator.getActiveReplicator();
|
||||||
|
const remoteCompromised = this.core.managers.networkManager.isOnline
|
||||||
|
? await remote?.countCompromisedChunks()
|
||||||
|
: 0;
|
||||||
|
if (localCompromised === false) {
|
||||||
|
Logger(`Failed to count compromised chunks in local database`, LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (remoteCompromised === false) {
|
||||||
|
Logger(`Failed to count compromised chunks in remote database`, LOG_LEVEL_NOTICE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (remoteCompromised === 0 && localCompromised === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Logger(
|
||||||
|
`Found compromised chunks : ${localCompromised} in local, ${remoteCompromised} in remote`,
|
||||||
|
LOG_LEVEL_NOTICE
|
||||||
|
);
|
||||||
|
const title = $msg("moduleMigration.insecureChunkExist.title");
|
||||||
|
const msg = $msg("moduleMigration.insecureChunkExist.message");
|
||||||
|
const REBUILD = $msg("moduleMigration.insecureChunkExist.buttons.rebuild");
|
||||||
|
const FETCH = $msg("moduleMigration.insecureChunkExist.buttons.fetch");
|
||||||
|
const DISMISS = $msg("moduleMigration.insecureChunkExist.buttons.later");
|
||||||
|
const buttons = [REBUILD, FETCH, DISMISS];
|
||||||
|
if (remoteCompromised != 0) {
|
||||||
|
buttons.splice(buttons.indexOf(FETCH), 1);
|
||||||
|
}
|
||||||
|
const result = await this.core.confirm.askSelectStringDialogue(msg, buttons, {
|
||||||
|
title,
|
||||||
|
defaultAction: DISMISS,
|
||||||
|
timeout: 0,
|
||||||
|
});
|
||||||
|
if (result === REBUILD) {
|
||||||
|
// Rebuild the database
|
||||||
|
await this.core.rebuilder.scheduleRebuild();
|
||||||
|
this.services.appLifecycle.performRestart();
|
||||||
|
return false;
|
||||||
|
} else if (result === FETCH) {
|
||||||
|
// Fetch the latest data from remote
|
||||||
|
await this.core.rebuilder.scheduleFetch();
|
||||||
|
this.services.appLifecycle.performRestart();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// User chose to dismiss the issue
|
||||||
|
this._log($msg("moduleMigration.insecureChunkExist.laterMessage"), LOG_LEVEL_NOTICE);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _everyOnFirstInitialize(): Promise<boolean> {
|
||||||
if (!this.localDatabase.isReady) {
|
if (!this.localDatabase.isReady) {
|
||||||
this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleMigration.logLocalDatabaseNotReady"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.settings.isConfigured) {
|
if (this.settings.isConfigured) {
|
||||||
await this.migrateUsingDoctor(false);
|
if (!(await this.hasCompromisedChunks())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(await this.hasIncompleteDocs())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(await this.migrateUsingDoctor(false))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// await this.migrationCheck();
|
// await this.migrationCheck();
|
||||||
await this.migrateDisableBulkSend();
|
await this.migrateDisableBulkSend();
|
||||||
}
|
}
|
||||||
@@ -112,14 +331,24 @@ export class ModuleMigration extends AbstractModule implements ICoreModule {
|
|||||||
this._log($msg("moduleMigration.logSetupCancelled"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleMigration.logSetupCancelled"), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await this.migrateUsingDoctor(true);
|
if (!(await this.migrateUsingDoctor(true))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
_everyOnLayoutReady(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
|
eventHub.onEvent(EVENT_REQUEST_RUN_DOCTOR, async (reason) => {
|
||||||
await this.migrateUsingDoctor(false, reason, true);
|
await this.migrateUsingDoctor(false, reason, true);
|
||||||
});
|
});
|
||||||
|
eventHub.onEvent(EVENT_REQUEST_RUN_FIX_INCOMPLETE, async () => {
|
||||||
|
await this.hasIncompleteDocs(true);
|
||||||
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleFirstInitialise(this._everyOnFirstInitialize.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_DEBUG, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
|
import { Notice, requestUrl, type RequestUrlParam, type RequestUrlResponse } from "../../deps.ts";
|
||||||
import { type CouchDBCredentials, type EntryDoc, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import { type CouchDBCredentials, type EntryDoc, type FilePath } from "../../lib/src/common/types.ts";
|
||||||
import { getPathFromTFile } from "../../common/utils.ts";
|
import { getPathFromTFile } from "../../common/utils.ts";
|
||||||
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
import { isCloudantURI, isValidRemoteCouchDBURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
|
import { replicationFilter } from "@/lib/src/pouchdb/compress.ts";
|
||||||
@@ -11,6 +11,7 @@ import { setNoticeClass } from "../../lib/src/mock_and_interop/wrapper.ts";
|
|||||||
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
|
import { ObsHttpHandler } from "./APILib/ObsHttpHandler.ts";
|
||||||
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
|
import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser.ts";
|
||||||
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
|
import { AuthorizationHeaderGenerator } from "../../lib/src/replication/httplib.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
setNoticeClass(Notice);
|
setNoticeClass(Notice);
|
||||||
|
|
||||||
@@ -19,21 +20,21 @@ async function fetchByAPI(request: RequestUrlParam, errorAsResult = false): Prom
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianAPI extends AbstractObsidianModule {
|
||||||
_customHandler!: ObsHttpHandler;
|
_customHandler!: ObsHttpHandler;
|
||||||
|
|
||||||
_authHeader = new AuthorizationHeaderGenerator();
|
_authHeader = new AuthorizationHeaderGenerator();
|
||||||
|
|
||||||
last_successful_post = false;
|
last_successful_post = false;
|
||||||
$$customFetchHandler(): ObsHttpHandler {
|
_customFetchHandler(): ObsHttpHandler {
|
||||||
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
|
if (!this._customHandler) this._customHandler = new ObsHttpHandler(undefined, undefined);
|
||||||
return this._customHandler;
|
return this._customHandler;
|
||||||
}
|
}
|
||||||
$$getLastPostFailedBySize(): boolean {
|
_getLastPostFailedBySize(): boolean {
|
||||||
return !this.last_successful_post;
|
return !this.last_successful_post;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
|
async __fetchByAPI(url: string, authHeader: string, opts?: RequestInit): Promise<Response> {
|
||||||
const body = opts?.body as string;
|
const body = opts?.body as string;
|
||||||
|
|
||||||
const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
|
const transformedHeaders = { ...(opts?.headers as Record<string, string>) };
|
||||||
@@ -68,7 +69,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
const body = opts?.body as string;
|
const body = opts?.body as string;
|
||||||
const size = body ? ` (${body.length})` : "";
|
const size = body ? ` (${body.length})` : "";
|
||||||
try {
|
try {
|
||||||
const r = await this._fetchByAPI(url, authHeader, opts);
|
const r = await this.__fetchByAPI(url, authHeader, opts);
|
||||||
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
||||||
if (method == "POST" || method == "PUT") {
|
if (method == "POST" || method == "PUT") {
|
||||||
this.last_successful_post = r.status - (r.status % 100) == 200;
|
this.last_successful_post = r.status - (r.status % 100) == 200;
|
||||||
@@ -90,7 +91,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$connectRemoteCouchDB(
|
async _connectRemoteCouchDB(
|
||||||
uri: string,
|
uri: string,
|
||||||
auth: CouchDBCredentials,
|
auth: CouchDBCredentials,
|
||||||
disableRequestURI: boolean,
|
disableRequestURI: boolean,
|
||||||
@@ -106,6 +107,9 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
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.";
|
||||||
|
if (!this.core.managers.networkManager.isOnline) {
|
||||||
|
return "Network is offline";
|
||||||
|
}
|
||||||
// let authHeader = await this._authHeader.getAuthorizationHeader(auth);
|
// let authHeader = await this._authHeader.getAuthorizationHeader(auth);
|
||||||
|
|
||||||
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
const conf: PouchDB.HttpAdapter.HttpAdapterConfiguration = {
|
||||||
@@ -145,7 +149,7 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
try {
|
try {
|
||||||
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
this.plugin.requestCount.value = this.plugin.requestCount.value + 1;
|
||||||
const response: Response = await (useRequestAPI
|
const response: Response = await (useRequestAPI
|
||||||
? this._fetchByAPI(url.toString(), authHeader, { ...opts, headers })
|
? this.__fetchByAPI(url.toString(), authHeader, { ...opts, headers })
|
||||||
: fetch(url, { ...opts, headers }));
|
: fetch(url, { ...opts, headers }));
|
||||||
if (method == "POST" || method == "PUT") {
|
if (method == "POST" || method == "PUT") {
|
||||||
this.last_successful_post = response.ok;
|
this.last_successful_post = response.ok;
|
||||||
@@ -249,21 +253,21 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isMobile(): boolean {
|
_isMobile(): boolean {
|
||||||
//@ts-ignore : internal API
|
//@ts-ignore : internal API
|
||||||
return this.app.isMobile;
|
return this.app.isMobile;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$vaultName(): string {
|
_vaultName(): string {
|
||||||
return this.app.vault.getName();
|
return this.app.vault.getName();
|
||||||
}
|
}
|
||||||
$$getVaultName(): string {
|
_getVaultName(): string {
|
||||||
return (
|
return (
|
||||||
this.core.$$vaultName() +
|
this.services.vault.vaultName() +
|
||||||
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
|
(this.settings?.additionalSuffixOfDatabaseName ? "-" + this.settings.additionalSuffixOfDatabaseName : "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$$getActiveFilePath(): FilePathWithPrefix | undefined {
|
_getActiveFilePath(): FilePath | undefined {
|
||||||
const file = this.app.workspace.getActiveFile();
|
const file = this.app.workspace.getActiveFile();
|
||||||
if (file) {
|
if (file) {
|
||||||
return getPathFromTFile(file);
|
return getPathFromTFile(file);
|
||||||
@@ -271,7 +275,18 @@ export class ModuleObsidianAPI extends AbstractObsidianModule implements IObsidi
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
$anyGetAppId(): Promise<string | undefined> {
|
_anyGetAppId(): string {
|
||||||
return Promise.resolve(`${"appId" in this.app ? this.app.appId : ""}`);
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services) {
|
||||||
|
services.API.handleGetCustomFetchHandler(this._customFetchHandler.bind(this));
|
||||||
|
services.API.handleIsLastPostFailedDueToPayloadSize(this._getLastPostFailedBySize.bind(this));
|
||||||
|
services.remote.handleConnect(this._connectRemoteCouchDB.bind(this));
|
||||||
|
services.API.handleIsMobile(this._isMobile.bind(this));
|
||||||
|
services.vault.handleGetVaultName(this._getVaultName.bind(this));
|
||||||
|
services.vault.handleVaultName(this._vaultName.bind(this));
|
||||||
|
services.vault.handleGetActiveFilePath(this._getActiveFilePath.bind(this));
|
||||||
|
services.API.handleGetAppID(this._anyGetAppId.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
|
import { EVENT_FILE_RENAMED, EVENT_LEAF_ACTIVE_CHANGED, eventHub } from "../../common/events.js";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
@@ -12,9 +12,10 @@ import {
|
|||||||
hiddenFilesEventCount,
|
hiddenFilesEventCount,
|
||||||
hiddenFilesProcessingCount,
|
hiddenFilesProcessingCount,
|
||||||
} from "../../lib/src/mock_and_interop/stores.ts";
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleObsidianEvents extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianEvents extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
// this.registerEvent(this.app.workspace.on("editor-change", ));
|
||||||
this.plugin.registerEvent(
|
this.plugin.registerEvent(
|
||||||
this.app.vault.on("rename", (file, oldPath) => {
|
this.app.vault.on("rename", (file, oldPath) => {
|
||||||
@@ -30,11 +31,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$performRestart(): void {
|
private _performRestart(): void {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
_performAppReload() {
|
__performAppReload() {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
this.app.commands.executeCommandById("app:reload");
|
this.app.commands.executeCommandById("app:reload");
|
||||||
}
|
}
|
||||||
@@ -49,14 +50,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
this.initialCallback = save;
|
this.initialCallback = save;
|
||||||
saveCommandDefinition.callback = () => {
|
saveCommandDefinition.callback = () => {
|
||||||
scheduleTask("syncOnEditorSave", 250, () => {
|
scheduleTask("syncOnEditorSave", 250, () => {
|
||||||
if (this.core.$$isUnloaded()) {
|
if (this.services.appLifecycle.hasUnloaded()) {
|
||||||
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
|
this._log("Unload and remove the handler.", LOG_LEVEL_VERBOSE);
|
||||||
saveCommandDefinition.callback = this.initialCallback;
|
saveCommandDefinition.callback = this.initialCallback;
|
||||||
this.initialCallback = undefined;
|
this.initialCallback = undefined;
|
||||||
} else {
|
} else {
|
||||||
if (this.settings.syncOnEditorSave) {
|
if (this.settings.syncOnEditorSave) {
|
||||||
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
|
this._log("Sync on Editor Save.", LOG_LEVEL_VERBOSE);
|
||||||
fireAndForget(() => this.core.$$replicateByEvent());
|
fireAndForget(() => this.services.replication.replicateByEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -106,14 +107,14 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
// TODO:FIXME AT V0.17.31, this logic has been disabled.
|
||||||
if (navigator.onLine && this.localDatabase.needScanning) {
|
if (navigator.onLine && this.localDatabase.needScanning) {
|
||||||
this.localDatabase.needScanning = false;
|
this.localDatabase.needScanning = false;
|
||||||
await this.core.$$performFullScan();
|
await this.services.vault.scanVault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async watchWindowVisibilityAsync() {
|
async watchWindowVisibilityAsync() {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
|
|
||||||
if (this.isLastHidden && !this.hasFocus) {
|
if (this.isLastHidden && !this.hasFocus) {
|
||||||
// NO OP while non-focused after made hidden;
|
// NO OP while non-focused after made hidden;
|
||||||
@@ -126,22 +127,22 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
this.isLastHidden = isHidden;
|
this.isLastHidden = isHidden;
|
||||||
|
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
await this.core.$everyBeforeSuspendProcess();
|
await this.services.appLifecycle.onSuspending();
|
||||||
} else {
|
} else {
|
||||||
// suspend all temporary.
|
// suspend all temporary.
|
||||||
if (this.core.$$isSuspended()) return;
|
if (this.services.appLifecycle.isSuspended()) return;
|
||||||
if (!this.hasFocus) return;
|
if (!this.hasFocus) return;
|
||||||
await this.core.$everyOnResumeProcess();
|
await this.services.appLifecycle.onResuming();
|
||||||
await this.core.$everyAfterResumeProcess();
|
await this.services.appLifecycle.onResumed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watchWorkspaceOpen(file: TFile | null) {
|
watchWorkspaceOpen(file: TFile | null) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
|
scheduleTask("watch-workspace-open", 500, () => fireAndForget(() => this.watchWorkspaceOpenAsync(file)));
|
||||||
}
|
}
|
||||||
@@ -149,25 +150,25 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
async watchWorkspaceOpenAsync(file: TFile) {
|
async watchWorkspaceOpenAsync(file: TFile) {
|
||||||
if (this.settings.suspendFileWatching) return;
|
if (this.settings.suspendFileWatching) return;
|
||||||
if (!this.settings.isConfigured) return;
|
if (!this.settings.isConfigured) return;
|
||||||
if (!this.core.$$isReady()) return;
|
if (!this.services.appLifecycle.isReady()) return;
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.settings.syncOnFileOpen && !this.core.$$isSuspended()) {
|
if (this.settings.syncOnFileOpen && !this.services.appLifecycle.isSuspended()) {
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
await this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
|
await this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnLayoutReady(): Promise<boolean> {
|
_everyOnLayoutReady(): Promise<boolean> {
|
||||||
this.swapSaveCommand();
|
this.swapSaveCommand();
|
||||||
this.registerWatchEvents();
|
this.registerWatchEvents();
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$askReload(message?: string) {
|
private _askReload(message?: string) {
|
||||||
if (this.core.$$isReloadingScheduled()) {
|
if (this.services.appLifecycle.isReloadingScheduled()) {
|
||||||
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
this._log(`Reloading is already scheduled`, LOG_LEVEL_VERBOSE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -181,13 +182,13 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
{ defaultAction: RETRY_LATER }
|
{ defaultAction: RETRY_LATER }
|
||||||
);
|
);
|
||||||
if (ret == RESTART_NOW) {
|
if (ret == RESTART_NOW) {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
} else if (ret == RESTART_AFTER_STABLE) {
|
} else if (ret == RESTART_AFTER_STABLE) {
|
||||||
this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$$scheduleAppReload() {
|
private _scheduleAppReload() {
|
||||||
if (!this.core._totalProcessingCount) {
|
if (!this.core._totalProcessingCount) {
|
||||||
const __tick = reactiveSource(0);
|
const __tick = reactiveSource(0);
|
||||||
this.core._totalProcessingCount = reactive(() => {
|
this.core._totalProcessingCount = reactive(() => {
|
||||||
@@ -224,7 +225,7 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
this.core._totalProcessingCount.onChanged((e) => {
|
this.core._totalProcessingCount.onChanged((e) => {
|
||||||
if (e.value == 0) {
|
if (e.value == 0) {
|
||||||
if (stableCheck-- <= 0) {
|
if (stableCheck-- <= 0) {
|
||||||
this._performAppReload();
|
this.__performAppReload();
|
||||||
}
|
}
|
||||||
this._log(
|
this._log(
|
||||||
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
|
`Obsidian will be restarted soon! (Within ${stableCheck} seconds)`,
|
||||||
@@ -237,4 +238,11 @@ export class ModuleObsidianEvents extends AbstractObsidianModule implements IObs
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handlePerformRestart(this._performRestart.bind(this));
|
||||||
|
services.appLifecycle.handleAskRestart(this._askReload.bind(this));
|
||||||
|
services.appLifecycle.handleScheduleRestart(this._scheduleAppReload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
|
import { addIcon, type Editor, type MarkdownFileInfo, type MarkdownView } from "../../deps.ts";
|
||||||
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import { LOG_LEVEL_NOTICE, type FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianMenu extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
// UI
|
// UI
|
||||||
addIcon(
|
addIcon(
|
||||||
"replicate",
|
"replicate",
|
||||||
@@ -18,21 +19,21 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.addRibbonIcon("replicate", $msg("moduleObsidianMenu.replicate"), async () => {
|
this.addRibbonIcon("replicate", $msg("moduleObsidianMenu.replicate"), async () => {
|
||||||
await this.core.$$replicate(true);
|
await this.services.replication.replicate(true);
|
||||||
}).addClass("livesync-ribbon-replicate");
|
}).addClass("livesync-ribbon-replicate");
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-replicate",
|
id: "livesync-replicate",
|
||||||
name: "Replicate now",
|
name: "Replicate now",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-dump",
|
id: "livesync-dump",
|
||||||
name: "Dump information of this doc ",
|
name: "Dump information of this doc ",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const file = this.core.$$getActiveFilePath();
|
const file = this.services.vault.getActiveFilePath();
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
|
fireAndForget(() => this.localDatabase.getDBEntry(file, {}, true, false));
|
||||||
},
|
},
|
||||||
@@ -43,7 +44,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
|
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
|
||||||
const file = view.file;
|
const file = view.file;
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
void this.core.$$queueConflictCheckIfOpen(file.path as FilePathWithPrefix);
|
void this.services.conflict.queueCheckForIfOpen(file.path as FilePathWithPrefix);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,23 +59,23 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
this.settings.liveSync = true;
|
this.settings.liveSync = true;
|
||||||
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
|
this._log("LiveSync Enabled.", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-suspendall",
|
id: "livesync-suspendall",
|
||||||
name: "Toggle All Sync.",
|
name: "Toggle All Sync.",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
if (this.core.$$isSuspended()) {
|
if (this.services.appLifecycle.isSuspended()) {
|
||||||
this.core.$$setSuspended(false);
|
this.services.appLifecycle.setSuspended(false);
|
||||||
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
|
this._log("Self-hosted LiveSync resumed", LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
this.core.$$setSuspended(true);
|
this.services.appLifecycle.setSuspended(true);
|
||||||
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
|
this._log("Self-hosted LiveSync suspended", LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.core.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
id: "livesync-scan-files",
|
id: "livesync-scan-files",
|
||||||
name: "Scan storage and database again",
|
name: "Scan storage and database again",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$$performFullScan(true);
|
await this.services.vault.scanVault(true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
id: "livesync-runbatch",
|
id: "livesync-runbatch",
|
||||||
name: "Run pended batch processes",
|
name: "Run pended batch processes",
|
||||||
callback: async () => {
|
callback: async () => {
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,12 +105,15 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnload(): Promise<boolean> {
|
private __onWorkspaceReady() {
|
||||||
this.app.workspace.onLayoutReady(this.core.$$onLiveSyncReady.bind(this.core));
|
void this.services.appLifecycle.onReady();
|
||||||
|
}
|
||||||
|
private _everyOnload(): Promise<boolean> {
|
||||||
|
this.app.workspace.onLayoutReady(this.__onWorkspaceReady.bind(this));
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$showView(viewType: string) {
|
private async _showView(viewType: string) {
|
||||||
const leaves = this.app.workspace.getLeavesOfType(viewType);
|
const leaves = this.app.workspace.getLeavesOfType(viewType);
|
||||||
if (leaves.length == 0) {
|
if (leaves.length == 0) {
|
||||||
await this.app.workspace.getLeaf(true).setViewState({
|
await this.app.workspace.getLeaf(true).setViewState({
|
||||||
@@ -126,4 +130,9 @@ export class ModuleObsidianMenu extends AbstractObsidianModule implements IObsid
|
|||||||
await this.app.workspace.revealLeaf(leaves[0]);
|
await this.app.workspace.revealLeaf(leaves[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.API.handleShowWindow(this._showView.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
|
|
||||||
export class ModuleExtraSyncObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleExtraSyncObsidian extends AbstractObsidianModule {
|
||||||
deviceAndVaultName: string = "";
|
deviceAndVaultName: string = "";
|
||||||
|
|
||||||
$$getDeviceAndVaultName(): string {
|
_getDeviceAndVaultName(): string {
|
||||||
return this.deviceAndVaultName;
|
return this.deviceAndVaultName;
|
||||||
}
|
}
|
||||||
$$setDeviceAndVaultName(name: string): void {
|
_setDeviceAndVaultName(name: string): void {
|
||||||
this.deviceAndVaultName = name;
|
this.deviceAndVaultName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.setting.handleGetDeviceAndVaultName(this._getDeviceAndVaultName.bind(this));
|
||||||
|
services.setting.handleSetDeviceAndVaultName(this._setDeviceAndVaultName.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
import { delay, fireAndForget } from "octagonal-wheels/promises";
|
||||||
import { __onMissingTranslation } from "../../lib/src/common/i18n";
|
import { __onMissingTranslation } from "../../lib/src/common/i18n";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { eventHub } from "../../common/events";
|
import { eventHub } from "../../common/events";
|
||||||
import { enableTestFunction } from "./devUtil/testUtils.ts";
|
import { enableTestFunction } from "./devUtil/testUtils.ts";
|
||||||
import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
import { TestPaneView, VIEW_TYPE_TEST } from "./devUtil/TestPaneView.ts";
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
import type { FilePathWithPrefix } from "../../lib/src/common/types.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleDev extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleDev extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
__onMissingTranslation(() => {});
|
__onMissingTranslation(() => {});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
this.onMissingTranslation = this.onMissingTranslation.bind(this);
|
||||||
__onMissingTranslation((key) => {
|
__onMissingTranslation((key) => {
|
||||||
@@ -92,12 +93,12 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
id: "view-test",
|
id: "view-test",
|
||||||
name: "Open Test dialogue",
|
name: "Open Test dialogue",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_TEST);
|
void this.services.API.showWindow(VIEW_TYPE_TEST);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
// if (await this.core.storageAccess.isExistsIncludeHidden("_SHOWDIALOGAUTO.md")) {
|
||||||
// void this.core.$$showView(VIEW_TYPE_TEST);
|
// void this.core.$$showView(VIEW_TYPE_TEST);
|
||||||
@@ -121,7 +122,7 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (w) {
|
if (w) {
|
||||||
const id = await this.core.$$path2id(filename as FilePathWithPrefix);
|
const id = await this.services.path.path2id(filename as FilePathWithPrefix);
|
||||||
const f = await this.core.localDatabase.getRaw(id);
|
const f = await this.core.localDatabase.getRaw(id);
|
||||||
console.log(f);
|
console.log(f);
|
||||||
console.log(f._rev);
|
console.log(f._rev);
|
||||||
@@ -139,14 +140,14 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
testResults = writable<[boolean, string, string][]>([]);
|
testResults = writable<[boolean, string, string][]>([]);
|
||||||
// testResults: string[] = [];
|
// testResults: string[] = [];
|
||||||
|
|
||||||
$$addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
private _addTestResult(name: string, key: string, result: boolean, summary?: string, message?: string): void {
|
||||||
const logLine = `${name}: ${key} ${summary ?? ""}`;
|
const logLine = `${name}: ${key} ${summary ?? ""}`;
|
||||||
this.testResults.update((results) => {
|
this.testResults.update((results) => {
|
||||||
results.push([result, logLine, message ?? ""]);
|
results.push([result, logLine, message ?? ""]);
|
||||||
return results;
|
return results;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$everyModuleTest(): Promise<boolean> {
|
private _everyModuleTest(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// this.core.$$addTestResult("DevModule", "Test", true);
|
// this.core.$$addTestResult("DevModule", "Test", true);
|
||||||
// return Promise.resolve(true);
|
// return Promise.resolve(true);
|
||||||
@@ -155,4 +156,11 @@ export class ModuleDev extends AbstractObsidianModule implements IObsidianModule
|
|||||||
// this.addTestResult("Test of test3", true);
|
// this.addTestResult("Test of test3", true);
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.test.handleTest(this._everyModuleTest.bind(this));
|
||||||
|
services.test.handleAddTestResult(this._addTestResult.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
import { delay } from "octagonal-wheels/promises";
|
||||||
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
|
import { LOG_LEVEL_NOTICE, REMOTE_MINIO, type FilePathWithPrefix } from "src/lib/src/common/types";
|
||||||
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule";
|
||||||
|
|
||||||
export class ModuleIntegratedTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleIntegratedTest extends AbstractObsidianModule {
|
||||||
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
async waitFor(proc: () => Promise<boolean>, timeout = 10000): Promise<boolean> {
|
||||||
await delay(100);
|
await delay(100);
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
@@ -45,7 +45,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
async __orDie(key: string, proc: () => Promise<boolean>): Promise<true> | never {
|
||||||
if (!(await this._test(key, proc))) {
|
if (!(await this._test(key, proc))) {
|
||||||
throw new Error(`${key}`);
|
throw new Error(`${key}`);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
tryReplicate() {
|
tryReplicate() {
|
||||||
if (!this.settings.liveSync) {
|
if (!this.settings.liveSync) {
|
||||||
return shareRunningResult("replicate-test", async () => {
|
return shareRunningResult("replicate-test", async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,13 +64,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
}
|
}
|
||||||
return await this.core.storageAccess.readHiddenFileText(file);
|
return await this.core.storageAccess.readHiddenFileText(file);
|
||||||
}
|
}
|
||||||
async _proceed(no: number, title: string): Promise<boolean> {
|
async __proceed(no: number, title: string): Promise<boolean> {
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
||||||
const stepContent = `Step ${no}`;
|
const stepContent = `Step ${no}`;
|
||||||
await this.core.$anyResolveConflictByNewest(stepFile);
|
await this.services.conflict.resolveByNewest(stepFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepFile, stepContent);
|
||||||
await this._orDie(`Wait for acknowledge ${no}`, async () => {
|
await this.__orDie(`Wait for acknowledge ${no}`, async () => {
|
||||||
if (
|
if (
|
||||||
!(await this.waitWithReplicating(async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
return await this.storageContentIsEqual(stepAckFile, stepContent);
|
||||||
@@ -81,13 +81,13 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
async _join(no: number, title: string): Promise<boolean> {
|
async __join(no: number, title: string): Promise<boolean> {
|
||||||
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
const stepFile = "_STEP.md" as FilePathWithPrefix;
|
||||||
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
const stepAckFile = "_STEP_ACK.md" as FilePathWithPrefix;
|
||||||
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
|
// const otherStepFile = `_STEP_${isLeader ? "R" : "L"}.md` as FilePathWithPrefix;
|
||||||
const stepContent = `Step ${no}`;
|
const stepContent = `Step ${no}`;
|
||||||
|
|
||||||
await this._orDie(`Wait for step ${no} (${title})`, async () => {
|
await this.__orDie(`Wait for step ${no} (${title})`, async () => {
|
||||||
if (
|
if (
|
||||||
!(await this.waitWithReplicating(async () => {
|
!(await this.waitWithReplicating(async () => {
|
||||||
return await this.storageContentIsEqual(stepFile, stepContent);
|
return await this.storageContentIsEqual(stepFile, stepContent);
|
||||||
@@ -96,7 +96,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
await this.core.$anyResolveConflictByNewest(stepAckFile);
|
await this.services.conflict.resolveByNewest(stepAckFile);
|
||||||
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
await this.core.storageAccess.writeFileAuto(stepAckFile, stepContent);
|
||||||
await this.tryReplicate();
|
await this.tryReplicate();
|
||||||
return true;
|
return true;
|
||||||
@@ -116,16 +116,16 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
check: () => Promise<boolean>;
|
check: () => Promise<boolean>;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
if (isGameChanger) {
|
if (isGameChanger) {
|
||||||
await this._proceed(step, title);
|
await this.__proceed(step, title);
|
||||||
try {
|
try {
|
||||||
await proc();
|
await proc();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._log(`Error: ${e}`);
|
this._log(`Error: ${e}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await this._orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
return await this.__orDie(`Step ${step} - ${title}`, async () => await this.waitWithReplicating(check));
|
||||||
} else {
|
} else {
|
||||||
return await this._join(step, title);
|
return await this.__join(step, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// // see scenario.md
|
// // see scenario.md
|
||||||
@@ -151,7 +151,7 @@ export class ModuleIntegratedTest extends AbstractObsidianModule implements IObs
|
|||||||
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
`Test as ${isLeader ? "Leader" : "Receiver"} command file ${testCommandFile}`
|
||||||
);
|
);
|
||||||
if (isLeader) {
|
if (isLeader) {
|
||||||
await this._proceed(0, "start");
|
await this.__proceed(0, "start");
|
||||||
}
|
}
|
||||||
await this.tryReplicate();
|
await this.tryReplicate();
|
||||||
|
|
||||||
@@ -424,9 +424,9 @@ Line4:D`;
|
|||||||
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
await this._test("basic", async () => await this.nonLiveTestRunner(isLeader, (t, l) => this.testBasic(t, l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async $everyModuleTestMultiDevice(): Promise<boolean> {
|
async _everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
const isLeader = this.core.$$vaultName().indexOf("recv") === -1;
|
const isLeader = this.core.services.vault.vaultName().indexOf("recv") === -1;
|
||||||
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
|
this.addTestResult("-------", true, `Test as ${isLeader ? "Leader" : "Receiver"}`);
|
||||||
try {
|
try {
|
||||||
this._log(`Starting Test`);
|
this._log(`Starting Test`);
|
||||||
@@ -440,4 +440,7 @@ Line4:D`;
|
|||||||
|
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { delay } from "octagonal-wheels/promises";
|
import { delay } from "octagonal-wheels/promises";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
import { eventHub } from "../../common/events";
|
import { eventHub } from "../../common/events";
|
||||||
import { getWebCrypto } from "../../lib/src/mods.ts";
|
import { getWebCrypto } from "../../lib/src/mods.ts";
|
||||||
@@ -8,6 +8,7 @@ import { parseYaml, requestUrl, stringifyYaml } from "obsidian";
|
|||||||
import type { FilePath } from "../../lib/src/common/types.ts";
|
import type { FilePath } from "../../lib/src/common/types.ts";
|
||||||
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
import { scheduleTask } from "octagonal-wheels/concurrency/task";
|
||||||
import { getFileRegExp } from "../../lib/src/common/utils.ts";
|
import { getFileRegExp } from "../../lib/src/common/utils.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface LSEvents {
|
interface LSEvents {
|
||||||
@@ -15,12 +16,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ModuleReplicateTest extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleReplicateTest extends AbstractObsidianModule {
|
||||||
testRootPath = "_test/";
|
testRootPath = "_test/";
|
||||||
testInfoPath = "_testinfo/";
|
testInfoPath = "_testinfo/";
|
||||||
|
|
||||||
get isLeader() {
|
get isLeader() {
|
||||||
return this.core.$$getVaultName().indexOf("dev") >= 0 && this.core.$$vaultName().indexOf("recv") < 0;
|
return (
|
||||||
|
this.services.vault.getVaultName().indexOf("dev") >= 0 &&
|
||||||
|
this.services.vault.vaultName().indexOf("recv") < 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get nameByKind() {
|
get nameByKind() {
|
||||||
@@ -52,24 +56,24 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
async dumpList() {
|
async dumpList() {
|
||||||
if (this.settings.syncInternalFiles) {
|
if (this.settings.syncInternalFiles) {
|
||||||
this._log("Write file list (Include Hidden)");
|
this._log("Write file list (Include Hidden)");
|
||||||
await this._dumpFileListIncludeHidden("files.md");
|
await this.__dumpFileListIncludeHidden("files.md");
|
||||||
} else {
|
} else {
|
||||||
this._log("Write file list");
|
this._log("Write file list");
|
||||||
await this._dumpFileList("files.md");
|
await this.__dumpFileList("files.md");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async $everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
async _everyBeforeReplicate(showMessage: boolean): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
await this.dumpList();
|
await this.dumpList();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "dump-file-structure-normal",
|
id: "dump-file-structure-normal",
|
||||||
name: `Dump Structure (Normal)`,
|
name: `Dump Structure (Normal)`,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this._dumpFileList("files.md").finally(() => {
|
void this.__dumpFileList("files.md").finally(() => {
|
||||||
void this.refreshSyncStatus();
|
void this.refreshSyncStatus();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -79,7 +83,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
name: "Dump Structure (Include Hidden)",
|
name: "Dump Structure (Include Hidden)",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const d = "files.md";
|
const d = "files.md";
|
||||||
void this._dumpFileListIncludeHidden(d);
|
void this.__dumpFileListIncludeHidden(d);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
@@ -160,7 +164,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dumpFileList(outFile?: string) {
|
async __dumpFileList(outFile?: string) {
|
||||||
if (!this.core || !this.core.storageAccess) {
|
if (!this.core || !this.core.storageAccess) {
|
||||||
this._log("No storage access", LOG_LEVEL_INFO);
|
this._log("No storage access", LOG_LEVEL_INFO);
|
||||||
return;
|
return;
|
||||||
@@ -169,7 +173,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
const webcrypto = await getWebCrypto();
|
const webcrypto = await getWebCrypto();
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (!(await this.core.$$isTargetFile(file.path))) {
|
if (!(await this.services.vault.isTargetFile(file.path))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (file.path.startsWith(this.testInfoPath)) continue;
|
if (file.path.startsWith(this.testInfoPath)) continue;
|
||||||
@@ -200,7 +204,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
|
this._log(`Dumped ${out.length} files`, LOG_LEVEL_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dumpFileListIncludeHidden(outFile?: string) {
|
async __dumpFileListIncludeHidden(outFile?: string) {
|
||||||
const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
|
const ignorePatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesIgnorePatterns");
|
||||||
const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
|
const targetPatterns = getFileRegExp(this.plugin.settings, "syncInternalFilesTargetPatterns");
|
||||||
const out = [] as any[];
|
const out = [] as any[];
|
||||||
@@ -316,7 +320,7 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually1() {
|
async testConflictedManually1() {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve!
|
const commonFile = `Resolve!
|
||||||
*****, the amazing chocolatier!!`;
|
*****, the amazing chocolatier!!`;
|
||||||
@@ -325,8 +329,8 @@ export class ModuleReplicateTest extends AbstractObsidianModule implements IObsi
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "wonka.md", commonFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 1?", {
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
@@ -356,12 +360,12 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
return (
|
return (
|
||||||
(await this.__assertStorageContent(
|
(await this.__assertStorageContent(
|
||||||
(this.testRootPath + "wonka.md") as FilePath,
|
(this.testRootPath + "wonka.md") as FilePath,
|
||||||
@@ -379,7 +383,7 @@ Charlie Bucket, Charlie Bucket, the amazing chocolatier!!`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
async testConflictedManually2() {
|
async testConflictedManually2() {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
const commonFile = `Resolve To concatenate
|
const commonFile = `Resolve To concatenate
|
||||||
ABCDEFG`;
|
ABCDEFG`;
|
||||||
@@ -388,8 +392,8 @@ ABCDEFG`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "concat.md", commonFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
(await this.core.confirm.askYesNoDialog("Ready to begin the test conflict Manually 2?", {
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
@@ -420,12 +424,12 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(await this.waitFor(async () => {
|
!(await this.waitFor(async () => {
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
return (
|
return (
|
||||||
(await this.__assertStorageContent(
|
(await this.__assertStorageContent(
|
||||||
(this.testRootPath + "concat.md") as FilePath,
|
(this.testRootPath + "concat.md") as FilePath,
|
||||||
@@ -457,8 +461,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", baseDoc);
|
||||||
}
|
}
|
||||||
await delay(100);
|
await delay(100);
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
(await this.core.confirm.askYesNoDialog("Ready to test conflict?", {
|
||||||
@@ -487,8 +491,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
|
await this.core.storageAccess.writeHiddenFileAuto(this.testRootPath + "task.md", mod2Doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (
|
if (
|
||||||
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
(await this.core.confirm.askYesNoDialog("Ready to check result?", { timeout: 30, defaultOption: "Yes" })) ==
|
||||||
@@ -496,8 +500,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
const mergedDoc = `Tasks!
|
const mergedDoc = `Tasks!
|
||||||
- [ ] Task 1
|
- [ ] Task 1
|
||||||
- [v] Task 2
|
- [v] Task 2
|
||||||
@@ -511,7 +515,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
|
this._log("Before testing conflicted files, resolve all once", LOG_LEVEL_NOTICE);
|
||||||
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
||||||
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
await this.core.rebuilder.resolveAllConflictedFilesByNewerOnes();
|
||||||
await this.core.$$replicate();
|
await this.services.replication.replicate();
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
if (!(await this.testConflictAutomatic())) {
|
if (!(await this.testConflictAutomatic())) {
|
||||||
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
this._log("Conflict resolution (Auto) failed", LOG_LEVEL_NOTICE);
|
||||||
@@ -569,11 +573,16 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ`;
|
|||||||
// return results;
|
// return results;
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
async $everyModuleTestMultiDevice(): Promise<boolean> {
|
private async _everyModuleTestMultiDevice(): Promise<boolean> {
|
||||||
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
if (!this.settings.enableDebugTools) return Promise.resolve(true);
|
||||||
// this.core.$$addTestResult("DevModule", "Test", true);
|
// this.core.$$addTestResult("DevModule", "Test", true);
|
||||||
// return Promise.resolve(true);
|
// return Promise.resolve(true);
|
||||||
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
await this._test("Conflict resolution", async () => await this.checkConflictResolution());
|
||||||
return this.testDone();
|
return this.testDone();
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.replication.handleBeforeReplicate(this._everyBeforeReplicate.bind(this));
|
||||||
|
services.test.handleTestMultiDevice(this._everyModuleTestMultiDevice.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,14 +57,14 @@
|
|||||||
function moduleMultiDeviceTest() {
|
function moduleMultiDeviceTest() {
|
||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
plugin.$everyModuleTestMultiDevice().finally(() => {
|
plugin.services.test.testMultiDevice().finally(() => {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function moduleSingleDeviceTest() {
|
function moduleSingleDeviceTest() {
|
||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
plugin.$everyModuleTest().finally(() => {
|
plugin.services.test.test().finally(() => {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
if (moduleTesting) return;
|
if (moduleTesting) return;
|
||||||
moduleTesting = true;
|
moduleTesting = true;
|
||||||
try {
|
try {
|
||||||
await plugin.$everyModuleTest();
|
await plugin.services.test.test();
|
||||||
await plugin.$everyModuleTestMultiDevice();
|
await plugin.services.test.testMultiDevice();
|
||||||
} finally {
|
} finally {
|
||||||
moduleTesting = false;
|
moduleTesting = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ function readDocument(w: LoadedEntry) {
|
|||||||
}
|
}
|
||||||
export class DocumentHistoryModal extends Modal {
|
export class DocumentHistoryModal extends Modal {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
|
get services() {
|
||||||
|
return this.plugin.services;
|
||||||
|
}
|
||||||
range!: HTMLInputElement;
|
range!: HTMLInputElement;
|
||||||
contentView!: HTMLDivElement;
|
contentView!: HTMLDivElement;
|
||||||
info!: HTMLDivElement;
|
info!: HTMLDivElement;
|
||||||
@@ -74,7 +77,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.initialRev = revision;
|
this.initialRev = revision;
|
||||||
if (!file && id) {
|
if (!file && id) {
|
||||||
this.file = this.plugin.$$id2path(id);
|
this.file = this.services.path.id2path(id);
|
||||||
}
|
}
|
||||||
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
|
if (localStorage.getItem("ols-history-highlightdiff") == "1") {
|
||||||
this.showDiff = true;
|
this.showDiff = true;
|
||||||
@@ -83,7 +86,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
|
|
||||||
async loadFile(initialRev?: string) {
|
async loadFile(initialRev?: string) {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
this.id = await this.plugin.$$path2id(this.file);
|
this.id = await this.services.path.path2id(this.file);
|
||||||
}
|
}
|
||||||
const db = this.plugin.localDatabase;
|
const db = this.plugin.localDatabase;
|
||||||
try {
|
try {
|
||||||
@@ -126,7 +129,7 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
}
|
}
|
||||||
this.BlobURLs.delete(key);
|
this.BlobURLs.delete(key);
|
||||||
}
|
}
|
||||||
generateBlobURL(key: string, data: Uint8Array) {
|
generateBlobURL(key: string, data: Uint8Array<ArrayBuffer>) {
|
||||||
this.revokeURL(key);
|
this.revokeURL(key);
|
||||||
const v = URL.createObjectURL(new Blob([data], { endings: "transparent", type: "application/octet-stream" }));
|
const v = URL.createObjectURL(new Blob([data], { endings: "transparent", type: "application/octet-stream" }));
|
||||||
this.BlobURLs.set(key, v);
|
this.BlobURLs.set(key, v);
|
||||||
@@ -175,7 +178,10 @@ export class DocumentHistoryModal extends Modal {
|
|||||||
result = result.replace(/\n/g, "<br>");
|
result = result.replace(/\n/g, "<br>");
|
||||||
} else if (isImage(this.file)) {
|
} else if (isImage(this.file)) {
|
||||||
const src = this.generateBlobURL("base", w1data);
|
const src = this.generateBlobURL("base", w1data);
|
||||||
const overlay = this.generateBlobURL("overlay", readDocument(w2) as Uint8Array);
|
const overlay = this.generateBlobURL(
|
||||||
|
"overlay",
|
||||||
|
readDocument(w2) as Uint8Array<ArrayBuffer>
|
||||||
|
);
|
||||||
result = `<div class='ls-imgdiff-wrap'>
|
result = `<div class='ls-imgdiff-wrap'>
|
||||||
<div class='overlay'>
|
<div class='overlay'>
|
||||||
<img class='img-base' src="${src}">
|
<img class='img-base' src="${src}">
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
title: string = "Conflicting changes";
|
title: string = "Conflicting changes";
|
||||||
|
|
||||||
pluginPickMode: boolean = false;
|
pluginPickMode: boolean = false;
|
||||||
localName: string = "Keep A";
|
localName: string = "Base";
|
||||||
remoteName: string = "Keep B";
|
remoteName: string = "Conflicted";
|
||||||
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
offEvent?: ReturnType<typeof eventHub.onEvent>;
|
||||||
|
|
||||||
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
constructor(app: App, filename: string, diff: diff_result, pluginPickMode?: boolean, remoteName?: string) {
|
||||||
@@ -36,8 +36,8 @@ export class ConflictResolveModal extends Modal {
|
|||||||
this.pluginPickMode = pluginPickMode || false;
|
this.pluginPickMode = pluginPickMode || false;
|
||||||
if (this.pluginPickMode) {
|
if (this.pluginPickMode) {
|
||||||
this.title = "Pick a version";
|
this.title = "Pick a version";
|
||||||
this.remoteName = `Use ${remoteName || "Remote"}`;
|
this.remoteName = `${remoteName || "Remote"}`;
|
||||||
this.localName = "Use Local";
|
this.localName = "Local";
|
||||||
}
|
}
|
||||||
// Send cancel signal for the previous merge dialogue
|
// Send cancel signal for the previous merge dialogue
|
||||||
// if not there, simply be ignored.
|
// if not there, simply be ignored.
|
||||||
@@ -85,20 +85,19 @@ export class ConflictResolveModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diff = diff.replace(/\n/g, "<br>");
|
|
||||||
div.innerHTML = diff;
|
|
||||||
const div2 = contentEl.createDiv("");
|
const div2 = contentEl.createDiv("");
|
||||||
const date1 =
|
const date1 =
|
||||||
new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
|
new Date(this.result.left.mtime).toLocaleString() + (this.result.left.deleted ? " (Deleted)" : "");
|
||||||
const date2 =
|
const date2 =
|
||||||
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
new Date(this.result.right.mtime).toLocaleString() + (this.result.right.deleted ? " (Deleted)" : "");
|
||||||
div2.innerHTML = `
|
div2.setHTMLUnsafe(`
|
||||||
<span class='deleted'>A:${date1}</span><br /><span class='added'>B:${date2}</span><br>
|
<span class='deleted'><span class='conflict-dev-name'>${this.localName}</span>: ${date1}</span><br>
|
||||||
`;
|
<span class='added'><span class='conflict-dev-name'>${this.remoteName}</span>: ${date2}</span><br>
|
||||||
contentEl.createEl("button", { text: this.localName }, (e) =>
|
`);
|
||||||
|
contentEl.createEl("button", { text: `Use ${this.localName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.right.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
contentEl.createEl("button", { text: this.remoteName }, (e) =>
|
contentEl.createEl("button", { text: `Use ${this.remoteName}` }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
e.addEventListener("click", () => this.sendResponse(this.result.left.rev))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
if (!this.pluginPickMode) {
|
if (!this.pluginPickMode) {
|
||||||
@@ -109,6 +108,13 @@ export class ConflictResolveModal extends Modal {
|
|||||||
contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) =>
|
contentEl.createEl("button", { text: !this.pluginPickMode ? "Not now" : "Cancel" }, (e) =>
|
||||||
e.addEventListener("click", () => this.sendResponse(CANCELLED))
|
e.addEventListener("click", () => this.sendResponse(CANCELLED))
|
||||||
).style.marginRight = "4px";
|
).style.marginRight = "4px";
|
||||||
|
diff = diff.replace(/\n/g, "<br>");
|
||||||
|
// div.innerHTML = diff;
|
||||||
|
if (diff.length > 100 * 1024) {
|
||||||
|
div.setText("(Too large diff to display)");
|
||||||
|
} else {
|
||||||
|
div.setHTMLUnsafe(diff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResponse(result: MergeDialogResult) {
|
sendResponse(result: MergeDialogResult) {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
import { VIEW_TYPE_GLOBAL_HISTORY, GlobalHistoryView } from "./GlobalHistory/GlobalHistoryView.ts";
|
||||||
|
|
||||||
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianGlobalHistory extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-global-history",
|
id: "livesync-global-history",
|
||||||
name: "Show vault history",
|
name: "Show vault history",
|
||||||
@@ -17,6 +17,9 @@ export class ModuleObsidianGlobalHistory extends AbstractObsidianModule implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
showGlobalHistory() {
|
showGlobalHistory() {
|
||||||
void this.core.$$showView(VIEW_TYPE_GLOBAL_HISTORY);
|
void this.services.API.showWindow(VIEW_TYPE_GLOBAL_HISTORY);
|
||||||
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import {
|
|||||||
type diff_result,
|
type diff_result,
|
||||||
} from "../../lib/src/common/types.ts";
|
} from "../../lib/src/common/types.ts";
|
||||||
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
import { ConflictResolveModal } from "./InteractiveConflictResolving/ConflictResolveModal.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } 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";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleInteractiveConflictResolver extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-conflictcheck",
|
id: "livesync-conflictcheck",
|
||||||
name: "Pick a file to resolve conflict",
|
name: "Pick a file to resolve conflict",
|
||||||
@@ -34,7 +35,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
|
async _anyResolveConflictByUI(filename: FilePathWithPrefix, conflictCheckResult: diff_result): Promise<boolean> {
|
||||||
// UI for resolving conflicts should one-by-one.
|
// UI for resolving conflicts should one-by-one.
|
||||||
return await serialized(`conflict-resolve-ui`, async () => {
|
return await serialized(`conflict-resolve-ui`, async () => {
|
||||||
this._log("Merge:open conflict dialog", LOG_LEVEL_VERBOSE);
|
this._log("Merge:open conflict dialog", LOG_LEVEL_VERBOSE);
|
||||||
@@ -68,7 +69,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
// 2. As usual, delete the conflicted revision and if there are no conflicts, write the resolved content to the storage.
|
||||||
if (
|
if (
|
||||||
(await this.core.$$resolveConflictByDeletingRev(filename, delRev, "UI Concatenated")) ==
|
(await this.services.conflict.resolveByDeletingRevision(filename, delRev, "UI Concatenated")) ==
|
||||||
MISSING_OR_ERROR
|
MISSING_OR_ERROR
|
||||||
) {
|
) {
|
||||||
this._log(
|
this._log(
|
||||||
@@ -80,7 +81,7 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
} else if (typeof toDelete === "string") {
|
} else if (typeof toDelete === "string") {
|
||||||
// Select one of the conflicted revision to delete.
|
// Select one of the conflicted revision to delete.
|
||||||
if (
|
if (
|
||||||
(await this.core.$$resolveConflictByDeletingRev(filename, toDelete, "UI Selected")) ==
|
(await this.services.conflict.resolveByDeletingRevision(filename, toDelete, "UI Selected")) ==
|
||||||
MISSING_OR_ERROR
|
MISSING_OR_ERROR
|
||||||
) {
|
) {
|
||||||
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
this._log(`Merge: Something went wrong: ${filename}, (${toDelete})`, LOG_LEVEL_NOTICE);
|
||||||
@@ -93,11 +94,11 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
// In here, some merge has been processed.
|
// In here, some merge has been processed.
|
||||||
// So we have to run replication if configured.
|
// So we have to run replication if configured.
|
||||||
// TODO: Make this is as a event request
|
// TODO: Make this is as a event request
|
||||||
if (this.settings.syncAfterMerge && !this.core.$$isSuspended()) {
|
if (this.settings.syncAfterMerge && !this.services.appLifecycle.isSuspended()) {
|
||||||
await this.core.$$replicateByEvent();
|
await this.services.replication.replicateByEvent();
|
||||||
}
|
}
|
||||||
// And, check it again.
|
// And, check it again.
|
||||||
await this.core.$$queueConflictCheck(filename);
|
await this.services.conflict.queueCheckFor(filename);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -120,14 +121,14 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
const target = await this.core.confirm.askSelectString("File to resolve conflict", notesList);
|
||||||
if (target) {
|
if (target) {
|
||||||
const targetItem = notes.find((e) => e.dispPath == target)!;
|
const targetItem = notes.find((e) => e.dispPath == target)!;
|
||||||
await this.core.$$queueConflictCheck(targetItem.path);
|
await this.services.conflict.queueCheckFor(targetItem.path);
|
||||||
await this.core.$$waitForAllConflictProcessed();
|
await this.services.conflict.ensureAllProcessed();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $allScanStat(): Promise<boolean> {
|
async _allScanStat(): Promise<boolean> {
|
||||||
const notes: { path: string; mtime: number }[] = [];
|
const notes: { path: string; mtime: number }[] = [];
|
||||||
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
this._log(`Checking conflicted files`, LOG_LEVEL_VERBOSE);
|
||||||
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
for await (const doc of this.localDatabase.findAllDocs({ conflicts: true })) {
|
||||||
@@ -157,4 +158,9 @@ export class ModuleInteractiveConflictResolver extends AbstractObsidianModule im
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnScanningStartupIssues(this._allScanStat.bind(this));
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.conflict.handleResolveByUserInteraction(this._anyResolveConflictByUI.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
} from "../../lib/src/mock_and_interop/stores.ts";
|
} from "../../lib/src/mock_and_interop/stores.ts";
|
||||||
import { eventHub } from "../../lib/src/hub/hub.ts";
|
import { eventHub } from "../../lib/src/hub/hub.ts";
|
||||||
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
import { EVENT_FILE_RENAMED, EVENT_LAYOUT_READY, EVENT_LEAF_ACTIVE_CHANGED } from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { addIcon, normalizePath, Notice } from "../../deps.ts";
|
import { addIcon, normalizePath, Notice } from "../../deps.ts";
|
||||||
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, setGlobalLogFunction } from "octagonal-wheels/common/logger";
|
||||||
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
import { QueueProcessor } from "octagonal-wheels/concurrency/processor";
|
||||||
@@ -28,6 +28,7 @@ import { LogPaneView, VIEW_TYPE_LOG } from "./Log/LogPaneView.ts";
|
|||||||
import { serialized } from "octagonal-wheels/concurrency/lock";
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
||||||
import { $msg } from "src/lib/src/common/i18n.ts";
|
import { $msg } from "src/lib/src/common/i18n.ts";
|
||||||
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
import { P2PLogCollector } from "../../lib/src/replication/trystero/P2PReplicatorCore.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
// This module cannot be a core module because it depends on the Obsidian UI.
|
// This module cannot be a core module because it depends on the Obsidian UI.
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ const recentLogProcessor = new QueueProcessor(
|
|||||||
|
|
||||||
const showDebugLog = false;
|
const showDebugLog = false;
|
||||||
export const MARK_DONE = "\u{2009}\u{2009}";
|
export const MARK_DONE = "\u{2009}\u{2009}";
|
||||||
export class ModuleLog extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleLog extends AbstractObsidianModule {
|
||||||
registerView = this.plugin.registerView.bind(this.plugin);
|
registerView = this.plugin.registerView.bind(this.plugin);
|
||||||
|
|
||||||
statusBar?: HTMLElement;
|
statusBar?: HTMLElement;
|
||||||
@@ -178,7 +179,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
});
|
});
|
||||||
|
|
||||||
const statusBarLabels = reactive(() => {
|
const statusBarLabels = reactive(() => {
|
||||||
const scheduleMessage = this.core.$$isReloadingScheduled()
|
const scheduleMessage = this.services.appLifecycle.isReloadingScheduled()
|
||||||
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
|
? `WARNING! RESTARTING OBSIDIAN IS SCHEDULED!\n`
|
||||||
: "";
|
: "";
|
||||||
const { message } = statusLineLabel();
|
const { message } = statusLineLabel();
|
||||||
@@ -199,7 +200,7 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
statusBarLabels.onChanged((label) => applyToDisplay(label.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
eventHub.onEvent(EVENT_LEAF_ACTIVE_CHANGED, () => this.onActiveLeafChange());
|
||||||
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
eventHub.onceEvent(EVENT_LAYOUT_READY, () => this.onActiveLeafChange());
|
||||||
|
|
||||||
@@ -219,15 +220,15 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
const thisFile = this.app.workspace.getActiveFile();
|
const thisFile = this.app.workspace.getActiveFile();
|
||||||
if (!thisFile) return "";
|
if (!thisFile) return "";
|
||||||
// Case Sensitivity
|
// Case Sensitivity
|
||||||
if (this.core.$$shouldCheckCaseInsensitive()) {
|
if (this.services.setting.shouldCheckCaseInsensitively()) {
|
||||||
const f = this.core.storageAccess
|
const f = this.core.storageAccess
|
||||||
.getFiles()
|
.getFiles()
|
||||||
.map((e) => e.path)
|
.map((e) => e.path)
|
||||||
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
|
.filter((e) => e.toLowerCase() == thisFile.path.toLowerCase());
|
||||||
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
if (f.length > 1) return "Not synchronised: There are multiple files with the same name";
|
||||||
}
|
}
|
||||||
if (!(await this.core.$$isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
|
if (!(await this.services.vault.isTargetFile(thisFile.path))) return "Not synchronised: not a target file";
|
||||||
if (this.core.$$isFileSizeExceeded(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
if (this.services.vault.isFileSizeTooLarge(thisFile.stat.size)) return "Not synchronised: File size exceeded";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
async setFileStatus() {
|
async setFileStatus() {
|
||||||
@@ -287,14 +288,14 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$allStartOnUnload(): Promise<boolean> {
|
private _allStartOnUnload(): Promise<boolean> {
|
||||||
if (this.statusDiv) {
|
if (this.statusDiv) {
|
||||||
this.statusDiv.remove();
|
this.statusDiv.remove();
|
||||||
}
|
}
|
||||||
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
document.querySelectorAll(`.livesync-status`)?.forEach((e) => e.remove());
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
addIcon(
|
addIcon(
|
||||||
"view-log",
|
"view-log",
|
||||||
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
|
`<g transform="matrix(1.28 0 0 1.28 -131 -411)" fill="currentColor" fill-rule="evenodd">
|
||||||
@@ -303,23 +304,23 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
</g>`
|
</g>`
|
||||||
);
|
);
|
||||||
this.addRibbonIcon("view-log", $msg("moduleLog.showLog"), () => {
|
this.addRibbonIcon("view-log", $msg("moduleLog.showLog"), () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
||||||
}).addClass("livesync-ribbon-showlog");
|
}).addClass("livesync-ribbon-showlog");
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "view-log",
|
id: "view-log",
|
||||||
name: "Show log",
|
name: "Show log",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.core.$$showView(VIEW_TYPE_LOG);
|
void this.services.API.showWindow(VIEW_TYPE_LOG);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
this.registerView(VIEW_TYPE_LOG, (leaf) => new LogPaneView(leaf, this.plugin));
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
$everyOnloadAfterLoadSettings(): Promise<boolean> {
|
private _everyOnloadAfterLoadSettings(): Promise<boolean> {
|
||||||
logStore
|
logStore
|
||||||
.pipeTo(
|
.pipeTo(
|
||||||
new QueueProcessor((logs) => logs.forEach((e) => this.core.$$addLog(e.message, e.level, e.key)), {
|
new QueueProcessor((logs) => logs.forEach((e) => this.__addLog(e.message, e.level, e.key)), {
|
||||||
suspended: false,
|
suspended: false,
|
||||||
batchSize: 20,
|
batchSize: 20,
|
||||||
concurrentLimit: 1,
|
concurrentLimit: 1,
|
||||||
@@ -366,17 +367,17 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$$addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
__addLog(message: any, level: LOG_LEVEL = LOG_LEVEL_INFO, key = ""): void {
|
||||||
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) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const vaultName = this.core.$$getVaultName();
|
const vaultName = this.services.vault.getVaultName();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const timestamp = now.toLocaleString();
|
const timestamp = now.toLocaleString();
|
||||||
const messageContent =
|
const messageContent =
|
||||||
@@ -437,4 +438,10 @@ export class ModuleLog extends AbstractObsidianModule implements IObsidianModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
services.appLifecycle.handleOnSettingLoaded(this._everyOnloadAfterLoadSettings.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
services.appLifecycle.handleOnBeforeUnload(this._allStartOnUnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import { type TFile } from "obsidian";
|
|||||||
import { eventHub } from "../../common/events.ts";
|
import { eventHub } from "../../common/events.ts";
|
||||||
import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts";
|
import { EVENT_REQUEST_SHOW_HISTORY } from "../../common/obsidianEvents.ts";
|
||||||
import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
|
import type { FilePathWithPrefix, LoadedEntry, DocumentID } from "../../lib/src/common/types.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
|
import { DocumentHistoryModal } from "./DocumentHistory/DocumentHistoryModal.ts";
|
||||||
import { getPath } from "../../common/utils.ts";
|
import { getPath } from "../../common/utils.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
|
|
||||||
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianDocumentHistory extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-history",
|
id: "livesync-history",
|
||||||
name: "Show history",
|
name: "Show history",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
const file = this.core.$$getActiveFilePath();
|
const file = this.services.vault.getActiveFilePath();
|
||||||
if (file) this.showHistory(file, undefined);
|
if (file) this.showHistory(file, undefined);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -51,4 +51,7 @@ export class ModuleObsidianDocumentHistory extends AbstractObsidianModule implem
|
|||||||
this.showHistory(targetId.path, targetId.id);
|
this.showHistory(targetId.path, targetId.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.core, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, EVENT_SETTING_SAVED, eventHub } from "../../common/events.ts";
|
||||||
import {
|
import {
|
||||||
type BucketSyncSetting,
|
type BucketSyncSetting,
|
||||||
ChunkAlgorithmNames,
|
ChunkAlgorithmNames,
|
||||||
@@ -11,20 +11,20 @@ import {
|
|||||||
SALT_OF_PASSPHRASE,
|
SALT_OF_PASSPHRASE,
|
||||||
} from "../../lib/src/common/types";
|
} from "../../lib/src/common/types";
|
||||||
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT } from "octagonal-wheels/common/logger";
|
||||||
import { $msg, setLang } from "../../lib/src/common/i18n";
|
import { $msg, setLang } from "../../lib/src/common/i18n.ts";
|
||||||
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb";
|
import { isCloudantURI } from "../../lib/src/pouchdb/utils_couchdb.ts";
|
||||||
import { getLanguage } from "obsidian";
|
import { getLanguage } from "obsidian";
|
||||||
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "../../lib/src/common/rosetta.ts";
|
||||||
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
import { decryptString, encryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
||||||
export class ModuleObsidianSettings extends AbstractObsidianModule implements IObsidianModule {
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
async $everyOnLayoutReady(): Promise<boolean> {
|
export class ModuleObsidianSettings extends AbstractObsidianModule {
|
||||||
|
async _everyOnLayoutReady(): Promise<boolean> {
|
||||||
let isChanged = false;
|
let isChanged = false;
|
||||||
if (this.settings.displayLanguage == "") {
|
if (this.settings.displayLanguage == "") {
|
||||||
const obsidianLanguage = getLanguage();
|
const obsidianLanguage = getLanguage();
|
||||||
if (
|
if (
|
||||||
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
|
||||||
obsidianLanguage != this.settings.displayLanguage && // Check if the language is different from the current setting
|
obsidianLanguage != this.settings.displayLanguage // Check if the language is different from the current setting
|
||||||
this.settings.displayLanguage != ""
|
|
||||||
) {
|
) {
|
||||||
// Check if the current setting is not empty (Means migrated or installed).
|
// Check if the current setting is not empty (Means migrated or installed).
|
||||||
this.settings.displayLanguage = obsidianLanguage as I18N_LANGS;
|
this.settings.displayLanguage = obsidianLanguage as I18N_LANGS;
|
||||||
@@ -33,7 +33,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
} else if (this.settings.displayLanguage == "") {
|
} else if (this.settings.displayLanguage == "") {
|
||||||
this.settings.displayLanguage = "def";
|
this.settings.displayLanguage = "def";
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
@@ -47,7 +47,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
this.settings.displayLanguage = "def";
|
this.settings.displayLanguage = "def";
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
}
|
}
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -62,13 +62,13 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return methodFunc();
|
return methodFunc();
|
||||||
}
|
}
|
||||||
|
|
||||||
$$saveDeviceAndVaultName(): void {
|
_saveDeviceAndVaultName(): void {
|
||||||
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
|
||||||
localStorage.setItem(lsKey, this.core.$$getDeviceAndVaultName() || "");
|
localStorage.setItem(lsKey, this.services.setting.getDeviceAndVaultName() || "");
|
||||||
}
|
}
|
||||||
|
|
||||||
usedPassphrase = "";
|
usedPassphrase = "";
|
||||||
$$clearUsedPassphrase(): void {
|
private _clearUsedPassphrase(): void {
|
||||||
this.usedPassphrase = "";
|
this.usedPassphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return `${"appId" in this.app ? this.app.appId : ""}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$saveSettingData() {
|
async _saveSettingData() {
|
||||||
this.core.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
const settings = { ...this.settings };
|
const settings = { ...this.settings };
|
||||||
settings.deviceAndVaultName = "";
|
settings.deviceAndVaultName = "";
|
||||||
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
if (this.usedPassphrase == "" && !(await this.getPassphrase(settings))) {
|
||||||
@@ -141,6 +141,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),
|
||||||
@@ -174,7 +175,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
async _decryptSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
const passphrase = await this.getPassphrase(settings);
|
const passphrase = await this.getPassphrase(settings);
|
||||||
if (passphrase === false) {
|
if (passphrase === false) {
|
||||||
this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT);
|
this._log("No passphrase found for data.json! Verify configuration before syncing.", LOG_LEVEL_URGENT);
|
||||||
@@ -234,7 +235,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
* @param settings
|
* @param settings
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
$$adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
_adjustSettings(settings: ObsidianLiveSyncSettings): Promise<ObsidianLiveSyncSettings> {
|
||||||
// Adjust settings as needed
|
// Adjust settings as needed
|
||||||
|
|
||||||
// Delete this feature to avoid problems on mobile.
|
// Delete this feature to avoid problems on mobile.
|
||||||
@@ -264,7 +265,7 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
return Promise.resolve(settings);
|
return Promise.resolve(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$loadSettings(): Promise<void> {
|
async _loadSettings(): Promise<void> {
|
||||||
const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings;
|
const settings = Object.assign({}, DEFAULT_SETTINGS, await this.core.loadData()) as ObsidianLiveSyncSettings;
|
||||||
|
|
||||||
if (typeof settings.isConfigured == "undefined") {
|
if (typeof settings.isConfigured == "undefined") {
|
||||||
@@ -277,17 +278,17 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.settings = await this.core.$$decryptSettings(settings);
|
this.settings = await this.services.setting.decryptSettings(settings);
|
||||||
|
|
||||||
setLang(this.settings.displayLanguage);
|
setLang(this.settings.displayLanguage);
|
||||||
|
|
||||||
await this.core.$$adjustSettings(this.settings);
|
await this.services.setting.adjustSettings(this.settings);
|
||||||
|
|
||||||
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-vaultanddevicename-" + this.services.vault.getVaultName();
|
||||||
if (this.settings.deviceAndVaultName != "") {
|
if (this.settings.deviceAndVaultName != "") {
|
||||||
if (!localStorage.getItem(lsKey)) {
|
if (!localStorage.getItem(lsKey)) {
|
||||||
this.core.$$setDeviceAndVaultName(this.settings.deviceAndVaultName);
|
this.services.setting.setDeviceAndVaultName(this.settings.deviceAndVaultName);
|
||||||
this.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
this.settings.deviceAndVaultName = "";
|
this.settings.deviceAndVaultName = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,8 +299,8 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
);
|
);
|
||||||
this.settings.customChunkSize = 0;
|
this.settings.customChunkSize = 0;
|
||||||
}
|
}
|
||||||
this.core.$$setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
this.services.setting.setDeviceAndVaultName(localStorage.getItem(lsKey) || "");
|
||||||
if (this.core.$$getDeviceAndVaultName() == "") {
|
if (this.services.setting.getDeviceAndVaultName() == "") {
|
||||||
if (this.settings.usePluginSync) {
|
if (this.settings.usePluginSync) {
|
||||||
this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE);
|
this._log("Device name missing. Disabling plug-in sync.", LOG_LEVEL_NOTICE);
|
||||||
this.settings.usePluginSync = false;
|
this.settings.usePluginSync = false;
|
||||||
@@ -309,4 +310,14 @@ export class ModuleObsidianSettings extends AbstractObsidianModule implements IO
|
|||||||
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
// this.core.ignoreFiles = this.settings.ignoreFiles.split(",").map(e => e.trim());
|
||||||
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleLayoutReady(this._everyOnLayoutReady.bind(this));
|
||||||
|
services.setting.handleClearUsedPassphrase(this._clearUsedPassphrase.bind(this));
|
||||||
|
services.setting.handleDecryptSettings(this._decryptSettings.bind(this));
|
||||||
|
services.setting.handleAdjustSettings(this._adjustSettings.bind(this));
|
||||||
|
services.setting.handleLoadSettings(this._loadSettings.bind(this));
|
||||||
|
services.setting.handleSaveDeviceAndVaultName(this._saveDeviceAndVaultName.bind(this));
|
||||||
|
services.setting.handleSaveSettingData(this._saveSettingData.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { isObjectDifferent } from "octagonal-wheels/object";
|
import { isObjectDifferent } from "octagonal-wheels/object";
|
||||||
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
import { EVENT_SETTING_SAVED, eventHub } from "../../common/events";
|
||||||
@@ -8,8 +8,8 @@ import { parseYaml, stringifyYaml } from "../../deps";
|
|||||||
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
import { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
||||||
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
const SETTING_HEADER = "````yaml:livesync-setting\n";
|
||||||
const SETTING_FOOTER = "\n````";
|
const SETTING_FOOTER = "\n````";
|
||||||
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule {
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: "livesync-export-config",
|
id: "livesync-export-config",
|
||||||
name: "Write setting markdown manually",
|
name: "Write setting markdown manually",
|
||||||
@@ -18,7 +18,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
return this.settings.settingSyncFile != "";
|
return this.settings.settingSyncFile != "";
|
||||||
}
|
}
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -160,7 +160,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
result == APPLY_AND_FETCH
|
result == APPLY_AND_FETCH
|
||||||
) {
|
) {
|
||||||
this.core.settings = settingToApply;
|
this.core.settings = settingToApply;
|
||||||
await this.core.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
if (result == APPLY_ONLY) {
|
if (result == APPLY_ONLY) {
|
||||||
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
this._log("Loaded settings have been applied!", LOG_LEVEL_NOTICE);
|
||||||
return;
|
return;
|
||||||
@@ -171,7 +171,7 @@ export class ModuleObsidianSettingsAsMarkdown extends AbstractObsidianModule imp
|
|||||||
if (result == APPLY_AND_FETCH) {
|
if (result == APPLY_AND_FETCH) {
|
||||||
await this.core.rebuilder.scheduleFetch();
|
await this.core.rebuilder.scheduleFetch();
|
||||||
}
|
}
|
||||||
this.core.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -242,4 +242,7 @@ We can perform a command in this file.
|
|||||||
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
this._log(`Markdown setting: ${filename} has been updated!`, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
import { ObsidianLiveSyncSettingTab } from "./SettingDialogue/ObsidianLiveSyncSettingTab.ts";
|
||||||
import { type IObsidianModule, AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
// import { PouchDB } from "../../lib/src/pouchdb/pouchdb-browser";
|
||||||
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
import { EVENT_REQUEST_OPEN_SETTING_WIZARD, EVENT_REQUEST_OPEN_SETTINGS, eventHub } from "../../common/events.ts";
|
||||||
|
|
||||||
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleObsidianSettingDialogue extends AbstractObsidianModule {
|
||||||
settingTab!: ObsidianLiveSyncSettingTab;
|
settingTab!: ObsidianLiveSyncSettingTab;
|
||||||
|
|
||||||
$everyOnloadStart(): Promise<boolean> {
|
_everyOnloadStart(): Promise<boolean> {
|
||||||
this.settingTab = new ObsidianLiveSyncSettingTab(this.app, this.plugin);
|
this.settingTab = new ObsidianLiveSyncSettingTab(this.app, this.plugin);
|
||||||
this.plugin.addSettingTab(this.settingTab);
|
this.plugin.addSettingTab(this.settingTab);
|
||||||
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTINGS, () => this.openSetting());
|
eventHub.onEvent(EVENT_REQUEST_OPEN_SETTINGS, () => this.openSetting());
|
||||||
@@ -29,4 +29,7 @@ export class ModuleObsidianSettingDialogue extends AbstractObsidianModule implem
|
|||||||
get appId() {
|
get appId() {
|
||||||
return `${"appId" in this.app ? this.app.appId : ""}`;
|
return `${"appId" in this.app ? this.app.appId : ""}`;
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: typeof this.plugin, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnInitialise(this._everyOnloadStart.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ import {
|
|||||||
EVENT_REQUEST_SHOW_SETUP_QR,
|
EVENT_REQUEST_SHOW_SETUP_QR,
|
||||||
eventHub,
|
eventHub,
|
||||||
} from "../../common/events.ts";
|
} from "../../common/events.ts";
|
||||||
import { AbstractObsidianModule, type IObsidianModule } from "../AbstractObsidianModule.ts";
|
import { AbstractObsidianModule } from "../AbstractObsidianModule.ts";
|
||||||
import { decodeAnyArray, encodeAnyArray } from "../../common/utils.ts";
|
import { decodeAnyArray, encodeAnyArray } from "../../common/utils.ts";
|
||||||
import qrcode from "qrcode-generator";
|
import qrcode from "qrcode-generator";
|
||||||
import { $msg } from "../../lib/src/common/i18n.ts";
|
import { $msg } from "../../lib/src/common/i18n.ts";
|
||||||
import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
import { performDoctorConsultation, RebuildOptions } from "@/lib/src/common/configForDoc.ts";
|
||||||
import { encryptString, decryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
import { encryptString, decryptString } from "@/lib/src/encryption/stringEncryption.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsidianModule {
|
export class ModuleSetupObsidian extends AbstractObsidianModule {
|
||||||
$everyOnload(): Promise<boolean> {
|
private _everyOnload(): Promise<boolean> {
|
||||||
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
this.registerObsidianProtocolHandler("setuplivesync", async (conf: any) => {
|
||||||
if (conf.settings) {
|
if (conf.settings) {
|
||||||
await this.setupWizard(conf.settings);
|
await this.setupWizard(conf.settings);
|
||||||
@@ -182,7 +183,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings;
|
const newSettings = JSON.parse(JSON.stringify(tryingSettings)) as ObsidianLiveSyncSettings;
|
||||||
const remoteConfig = await this.core.$$fetchRemotePreferredTweakValues(newSettings);
|
const remoteConfig = await this.services.tweakValue.fetchRemotePreferred(newSettings);
|
||||||
if (remoteConfig) {
|
if (remoteConfig) {
|
||||||
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
this._log("Remote configuration found.", LOG_LEVEL_NOTICE);
|
||||||
const resultSettings = {
|
const resultSettings = {
|
||||||
@@ -282,16 +283,16 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
);
|
);
|
||||||
if (setupType == setupJustImport) {
|
if (setupType == setupJustImport) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
} else if (setupType == setupAsNew) {
|
} else if (setupType == setupAsNew) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
await this.core.rebuilder.$fetchLocal();
|
await this.core.rebuilder.$fetchLocal();
|
||||||
} else if (setupType == setupAsMerge) {
|
} else if (setupType == setupAsMerge) {
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
await this.core.rebuilder.$fetchLocal(true);
|
await this.core.rebuilder.$fetchLocal(true);
|
||||||
} else if (setupType == setupAgain) {
|
} else if (setupType == setupAgain) {
|
||||||
@@ -308,7 +309,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
}
|
}
|
||||||
this.core.settings = newSettingW;
|
this.core.settings = newSettingW;
|
||||||
await this.core.saveSettings();
|
await this.core.saveSettings();
|
||||||
this.core.$$clearUsedPassphrase();
|
this.services.setting.clearUsedPassphrase();
|
||||||
await this.core.rebuilder.$rebuildEverything();
|
await this.core.rebuilder.$rebuildEverything();
|
||||||
} else {
|
} else {
|
||||||
// Explicitly cancel the operation or the dialog was closed.
|
// Explicitly cancel the operation or the dialog was closed.
|
||||||
@@ -345,4 +346,7 @@ export class ModuleSetupObsidian extends AbstractObsidianModule implements IObsi
|
|||||||
this._log(ex, LOG_LEVEL_VERBOSE);
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onBindFunction(core: LiveSyncCore, services: typeof core.services): void {
|
||||||
|
services.appLifecycle.handleOnLoaded(this._everyOnload.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +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 { balanceChunkPurgedDBs } from "@/lib/src/pouchdb/chunks.ts";
|
import { testCrypt } from "octagonal-wheels/encryption/encryption";
|
||||||
import { purgeUnreferencedChunks } from "@/lib/src/pouchdb/chunks.ts";
|
|
||||||
import { testCrypt } from "../../../lib/src/encryption/e2ee_v2.ts";
|
|
||||||
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";
|
||||||
@@ -38,7 +36,6 @@ import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
|||||||
import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
|
import { fireAndForget, yieldNextAnimationFrame } from "octagonal-wheels/promises";
|
||||||
import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts";
|
import { confirmWithMessage } from "../../coreObsidian/UILib/dialogs.ts";
|
||||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB, eventHub } from "../../../common/events.ts";
|
import { EVENT_REQUEST_RELOAD_SETTING_TAB, eventHub } from "../../../common/events.ts";
|
||||||
import { skipIfDuplicated } from "octagonal-wheels/concurrency/lock";
|
|
||||||
import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
|
import { JournalSyncMinio } from "../../../lib/src/replication/journal/objectstore/JournalSyncMinio.ts";
|
||||||
import { paneChangeLog } from "./PaneChangeLog.ts";
|
import { paneChangeLog } from "./PaneChangeLog.ts";
|
||||||
import {
|
import {
|
||||||
@@ -89,6 +86,9 @@ export function createStub(name: string, key: string, value: string, panel: stri
|
|||||||
|
|
||||||
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
||||||
plugin: ObsidianLiveSyncPlugin;
|
plugin: ObsidianLiveSyncPlugin;
|
||||||
|
get services() {
|
||||||
|
return this.plugin.services;
|
||||||
|
}
|
||||||
selectedScreen = "";
|
selectedScreen = "";
|
||||||
|
|
||||||
_editingSettings?: AllSettings;
|
_editingSettings?: AllSettings;
|
||||||
@@ -142,8 +142,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
return await Promise.resolve();
|
return await Promise.resolve();
|
||||||
}
|
}
|
||||||
if (key == "deviceAndVaultName") {
|
if (key == "deviceAndVaultName") {
|
||||||
this.plugin.$$setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
|
this.services.setting.setDeviceAndVaultName(this.editingSettings?.[key] ?? "");
|
||||||
this.plugin.$$saveDeviceAndVaultName();
|
this.services.setting.saveDeviceAndVaultName();
|
||||||
return await Promise.resolve();
|
return await Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
const ret = { ...OnDialogSettingsDefault };
|
const ret = { ...OnDialogSettingsDefault };
|
||||||
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
|
ret.configPassphrase = localStorage.getItem("ls-setting-passphrase") || "";
|
||||||
ret.preset = "";
|
ret.preset = "";
|
||||||
ret.deviceAndVaultName = this.plugin.$$getDeviceAndVaultName();
|
ret.deviceAndVaultName = this.services.setting.getDeviceAndVaultName();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
computeAllLocalSettings(): Partial<OnDialogSettings> {
|
computeAllLocalSettings(): Partial<OnDialogSettings> {
|
||||||
@@ -298,7 +298,11 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
|
async testConnection(settingOverride: Partial<ObsidianLiveSyncSettings> = {}): Promise<void> {
|
||||||
const trialSetting = { ...this.editingSettings, ...settingOverride };
|
const trialSetting = { ...this.editingSettings, ...settingOverride };
|
||||||
const replicator = await this.plugin.$anyNewReplicator(trialSetting);
|
const replicator = await this.services.replicator.getNewReplicator(trialSetting);
|
||||||
|
if (!replicator) {
|
||||||
|
Logger("No replicator available for the current settings.", LOG_LEVEL_NOTICE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await replicator.tryConnectRemote(trialSetting);
|
await replicator.tryConnectRemote(trialSetting);
|
||||||
const status = await replicator.getRemoteStatus(trialSetting);
|
const status = await replicator.getRemoteStatus(trialSetting);
|
||||||
if (status) {
|
if (status) {
|
||||||
@@ -549,10 +553,14 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
const settingForCheck: RemoteDBSettings = {
|
const settingForCheck: RemoteDBSettings = {
|
||||||
...this.editingSettings,
|
...this.editingSettings,
|
||||||
};
|
};
|
||||||
const replicator = this.plugin.$anyNewReplicator(settingForCheck);
|
const replicator = this.services.replicator.getNewReplicator(settingForCheck);
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
|
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return true;
|
||||||
|
|
||||||
const db = await replicator.connectRemoteCouchDBWithSetting(settingForCheck, this.plugin.$$isMobile(), true);
|
const db = await replicator.connectRemoteCouchDBWithSetting(
|
||||||
|
settingForCheck,
|
||||||
|
this.services.API.isMobile(),
|
||||||
|
true
|
||||||
|
);
|
||||||
if (typeof db === "string") {
|
if (typeof db === "string") {
|
||||||
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
Logger($msg("obsidianLiveSyncSettingTab.logCheckPassphraseFailed", { db }), LOG_LEVEL_NOTICE);
|
||||||
return false;
|
return false;
|
||||||
@@ -591,8 +599,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
this.editingSettings.passphrase = "";
|
this.editingSettings.passphrase = "";
|
||||||
}
|
}
|
||||||
this.applyAllSettings();
|
this.applyAllSettings();
|
||||||
await this.plugin.$allSuspendAllSync();
|
await this.services.setting.suspendAllSync();
|
||||||
await this.plugin.$allSuspendExtraSync();
|
await this.services.setting.suspendExtraSync();
|
||||||
this.reloadAllSettings();
|
this.reloadAllSettings();
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
Logger($msg("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
|
Logger($msg("obsidianLiveSyncSettingTab.logRebuildNote"), LOG_LEVEL_NOTICE);
|
||||||
@@ -641,12 +649,12 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
await this.applyAllSettings();
|
await this.applyAllSettings();
|
||||||
if (result == OPTION_FETCH) {
|
if (result == OPTION_FETCH) {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// await rebuildDB("localOnly");
|
// await rebuildDB("localOnly");
|
||||||
} else if (result == OPTION_REBUILD_BOTH) {
|
} else if (result == OPTION_REBUILD_BOTH) {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
||||||
this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
} else if (result == OPTION_ONLY_SETTING) {
|
} else if (result == OPTION_ONLY_SETTING) {
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -861,70 +869,8 @@ export class ObsidianLiveSyncSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryRunGC() {
|
|
||||||
await skipIfDuplicated("cleanup", async () => {
|
|
||||||
const replicator = this.plugin.$$getReplicator();
|
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
|
||||||
const remoteDBConn = await replicator.connectRemoteCouchDBWithSetting(
|
|
||||||
this.plugin.settings,
|
|
||||||
this.plugin.$$isMobile()
|
|
||||||
);
|
|
||||||
if (typeof remoteDBConn == "string") {
|
|
||||||
Logger(remoteDBConn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await purgeUnreferencedChunks(remoteDBConn.db, true, this.plugin.settings, false);
|
|
||||||
await purgeUnreferencedChunks(this.plugin.localDatabase.localDatabase, true);
|
|
||||||
this.plugin.localDatabase.clearCaches();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async dbGC() {
|
|
||||||
// Lock the remote completely once.
|
|
||||||
await skipIfDuplicated("cleanup", async () => {
|
|
||||||
const replicator = this.plugin.$$getReplicator();
|
|
||||||
if (!(replicator instanceof LiveSyncCouchDBReplicator)) return;
|
|
||||||
await this.plugin.$$getReplicator().markRemoteLocked(this.plugin.settings, true, true);
|
|
||||||
const remoteDBConnection = await replicator.connectRemoteCouchDBWithSetting(
|
|
||||||
this.plugin.settings,
|
|
||||||
this.plugin.$$isMobile()
|
|
||||||
);
|
|
||||||
if (typeof remoteDBConnection == "string") {
|
|
||||||
Logger(remoteDBConnection);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await purgeUnreferencedChunks(remoteDBConnection.db, false, this.plugin.settings, true);
|
|
||||||
await purgeUnreferencedChunks(this.plugin.localDatabase.localDatabase, false);
|
|
||||||
this.plugin.localDatabase.clearCaches();
|
|
||||||
await balanceChunkPurgedDBs(this.plugin.localDatabase.localDatabase, remoteDBConnection.db);
|
|
||||||
this.plugin.localDatabase.refreshSettings();
|
|
||||||
Logger(
|
|
||||||
"The remote database has been cleaned up! Other devices will be cleaned up on the next synchronisation."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { versionNumberString2Number } from "../../../lib/src/string_and_binary/c
|
|||||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
import { fireAndForget } from "octagonal-wheels/promises";
|
||||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
|
import { visibleOnly } from "./SettingPane.ts";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const manifestVersion: string = MANIFEST_VERSION || "-";
|
const manifestVersion: string = MANIFEST_VERSION || "-";
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -10,8 +11,34 @@ const updateInformation: string = UPDATE_INFO || "";
|
|||||||
|
|
||||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||||
export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement): void {
|
export function paneChangeLog(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement): void {
|
||||||
const informationDivEl = this.createEl(paneEl, "div", { text: "" });
|
const cx = this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
cls: "op-warn-info",
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
visibleOnly(() => !this.isConfiguredAs("versionUpFlash", ""))
|
||||||
|
);
|
||||||
|
|
||||||
|
this.createEl(
|
||||||
|
cx,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
text: this.editingSettings.versionUpFlash,
|
||||||
|
},
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
this.createEl(cx, "button", { text: $msg("obsidianLiveSyncSettingTab.btnGotItAndUpdated") }, (e) => {
|
||||||
|
e.addClass("mod-cta");
|
||||||
|
e.addEventListener("click", () => {
|
||||||
|
fireAndForget(async () => {
|
||||||
|
this.editingSettings.versionUpFlash = "";
|
||||||
|
await this.saveAllDirtySettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const informationDivEl = this.createEl(paneEl, "div", { text: "" });
|
||||||
const tmpDiv = createDiv();
|
const tmpDiv = createDiv();
|
||||||
// tmpDiv.addClass("sls-header-button");
|
// tmpDiv.addClass("sls-header-button");
|
||||||
tmpDiv.addClass("op-warn-info");
|
tmpDiv.addClass("op-warn-info");
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { addPrefix, shouldBeIgnored, stripAllPrefixes } from "../../../lib/src/s
|
|||||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||||
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
||||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||||
import { EVENT_REQUEST_RUN_DOCTOR, eventHub } from "../../../common/events.ts";
|
import { EVENT_REQUEST_RUN_DOCTOR, EVENT_REQUEST_RUN_FIX_INCOMPLETE, eventHub } from "../../../common/events.ts";
|
||||||
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
|
import { ICHeader, ICXHeader, PSCHeader } from "../../../common/types.ts";
|
||||||
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
|
import { HiddenFileSync } from "../../../features/HiddenFileSync/CmdHiddenFileSync.ts";
|
||||||
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
|
import { EVENT_REQUEST_SHOW_HISTORY } from "../../../common/obsidianEvents.ts";
|
||||||
@@ -50,6 +50,19 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, "you wanted(Thank you)!");
|
eventHub.emitEvent(EVENT_REQUEST_RUN_DOCTOR, "you wanted(Thank you)!");
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName($msg("Setting.TroubleShooting.ScanBrokenFiles"))
|
||||||
|
.setDesc($msg("Setting.TroubleShooting.ScanBrokenFiles.Desc"))
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Scan for Broken files")
|
||||||
|
.setCta()
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(() => {
|
||||||
|
this.closeSetting();
|
||||||
|
eventHub.emitEvent(EVENT_REQUEST_RUN_FIX_INCOMPLETE);
|
||||||
|
})
|
||||||
|
);
|
||||||
new Setting(paneEl).setName("Prepare the 'report' to create an issue").addButton((button) =>
|
new Setting(paneEl).setName("Prepare the 'report' to create an issue").addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Copy Report to clipboard")
|
.setButtonText("Copy Report to clipboard")
|
||||||
@@ -143,7 +156,7 @@ export function paneHatch(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElement,
|
|||||||
}
|
}
|
||||||
const obsidianInfo = {
|
const obsidianInfo = {
|
||||||
navigator: navigator.userAgent,
|
navigator: navigator.userAgent,
|
||||||
fileSystem: this.plugin.$$isStorageInsensitive() ? "insensitive" : "sensitive",
|
fileSystem: this.plugin.services.vault.isStorageInsensitive() ? "insensitive" : "sensitive",
|
||||||
};
|
};
|
||||||
const msgConfig = `# ---- Obsidian info ----
|
const msgConfig = `# ---- Obsidian info ----
|
||||||
${stringifyYaml(obsidianInfo)}
|
${stringifyYaml(obsidianInfo)}
|
||||||
@@ -169,10 +182,10 @@ ${stringifyYaml({
|
|||||||
|
|
||||||
void addPanel(paneEl, "Scram Switches").then((paneEl) => {
|
void addPanel(paneEl, "Scram Switches").then((paneEl) => {
|
||||||
new Setting(paneEl).autoWireToggle("suspendFileWatching");
|
new Setting(paneEl).autoWireToggle("suspendFileWatching");
|
||||||
this.addOnSaved("suspendFileWatching", () => this.plugin.$$askReload());
|
this.addOnSaved("suspendFileWatching", () => this.services.appLifecycle.askRestart());
|
||||||
|
|
||||||
new Setting(paneEl).autoWireToggle("suspendParseReplicationResult");
|
new Setting(paneEl).autoWireToggle("suspendParseReplicationResult");
|
||||||
this.addOnSaved("suspendParseReplicationResult", () => this.plugin.$$askReload());
|
this.addOnSaved("suspendParseReplicationResult", () => this.services.appLifecycle.askRestart());
|
||||||
});
|
});
|
||||||
|
|
||||||
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
void addPanel(paneEl, "Recovery and Repair").then((paneEl) => {
|
||||||
@@ -190,7 +203,7 @@ ${stringifyYaml({
|
|||||||
);
|
);
|
||||||
infoGroupEl.appendChild(
|
infoGroupEl.appendChild(
|
||||||
this.createEl(infoGroupEl, "div", {
|
this.createEl(infoGroupEl, "div", {
|
||||||
text: `Database: Modified: ${!fileOnDB ? `Missing:` : `${new Date(fileOnDB.mtime).toLocaleString()}, Size:${fileOnDB.size}`}`,
|
text: `Database: Modified: ${!fileOnDB ? `Missing:` : `${new Date(fileOnDB.mtime).toLocaleString()}, Size:${fileOnDB.size} (actual size:${readAsBlob(fileOnDB).size})`}`,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -371,15 +384,16 @@ ${stringifyYaml({
|
|||||||
? await this.plugin.storageAccess.statHidden(path)
|
? await this.plugin.storageAccess.statHidden(path)
|
||||||
: false;
|
: false;
|
||||||
const fileOnStorage = stat != null ? stat : false;
|
const fileOnStorage = stat != null ? stat : false;
|
||||||
if (!(await this.plugin.$$isTargetFile(path))) return incProc();
|
if (!(await this.services.vault.isTargetFile(path))) return incProc();
|
||||||
const releaser = await semaphore.acquire(1);
|
const releaser = await semaphore.acquire(1);
|
||||||
if (fileOnStorage && this.plugin.$$isFileSizeExceeded(fileOnStorage.size))
|
if (fileOnStorage && this.services.vault.isFileSizeTooLarge(fileOnStorage.size))
|
||||||
return incProc();
|
return incProc();
|
||||||
try {
|
try {
|
||||||
const isHiddenFile = path.startsWith(".");
|
const isHiddenFile = path.startsWith(".");
|
||||||
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
|
const dbPath = isHiddenFile ? addPrefix(path, ICHeader) : path;
|
||||||
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
|
const fileOnDB = await this.plugin.localDatabase.getDBEntry(dbPath);
|
||||||
if (fileOnDB && this.plugin.$$isFileSizeExceeded(fileOnDB.size)) return incProc();
|
if (fileOnDB && this.services.vault.isFileSizeTooLarge(fileOnDB.size))
|
||||||
|
return incProc();
|
||||||
|
|
||||||
if (!fileOnDB && fileOnStorage) {
|
if (!fileOnDB && fileOnStorage) {
|
||||||
Logger(`Compare: Not found on the local database: ${path}`, LOG_LEVEL_NOTICE);
|
Logger(`Compare: Not found on the local database: ${path}`, LOG_LEVEL_NOTICE);
|
||||||
@@ -423,7 +437,7 @@ ${stringifyYaml({
|
|||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
for await (const docName of this.plugin.localDatabase.findAllDocNames()) {
|
for await (const docName of this.plugin.localDatabase.findAllDocNames()) {
|
||||||
if (!docName.startsWith("f:")) {
|
if (!docName.startsWith("f:")) {
|
||||||
const idEncoded = await this.plugin.$$path2id(docName as FilePathWithPrefix);
|
const idEncoded = await this.services.path.path2id(docName as FilePathWithPrefix);
|
||||||
const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID);
|
const doc = await this.plugin.localDatabase.getRaw(docName as DocumentID);
|
||||||
if (!doc) continue;
|
if (!doc) continue;
|
||||||
if (doc.type != "newnote" && doc.type != "plain") {
|
if (doc.type != "newnote" && doc.type != "plain") {
|
||||||
@@ -464,7 +478,7 @@ ${stringifyYaml({
|
|||||||
if ((await this.plugin.localDatabase.putRaw(doc)).ok) {
|
if ((await this.plugin.localDatabase.putRaw(doc)).ok) {
|
||||||
Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE);
|
Logger(`Old ${docName} has been deleted`, LOG_LEVEL_NOTICE);
|
||||||
}
|
}
|
||||||
await this.plugin.$$queueConflictCheckIfOpen(docName as FilePathWithPrefix);
|
await this.services.conflict.queueCheckForIfOpen(docName as FilePathWithPrefix);
|
||||||
} else {
|
} else {
|
||||||
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
Logger(`Converting ${docName} Failed!`, LOG_LEVEL_NOTICE);
|
||||||
Logger(ret, LOG_LEVEL_VERBOSE);
|
Logger(ret, LOG_LEVEL_VERBOSE);
|
||||||
@@ -499,7 +513,7 @@ ${stringifyYaml({
|
|||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
this.editingSettings.isConfigured = false;
|
this.editingSettings.isConfigured = false;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function paneMaintenance(
|
|||||||
(e) => {
|
(e) => {
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.plugin.$$markRemoteResolved();
|
await this.services.remote.markResolved();
|
||||||
this.display();
|
this.display();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@ export function paneMaintenance(
|
|||||||
(e) => {
|
(e) => {
|
||||||
e.addEventListener("click", () => {
|
e.addEventListener("click", () => {
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
await this.plugin.$$markRemoteUnlocked();
|
await this.services.remote.markUnlocked();
|
||||||
this.display();
|
this.display();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -78,7 +78,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.setWarning()
|
.setWarning()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.$$markRemoteLocked();
|
await this.services.remote.markLocked();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addOnUpdate(this.onlyOnCouchDBOrMinIO);
|
.addOnUpdate(this.onlyOnCouchDBOrMinIO);
|
||||||
@@ -93,7 +93,7 @@ export function paneMaintenance(
|
|||||||
.setWarning()
|
.setWarning()
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -158,36 +158,40 @@ export function paneMaintenance(
|
|||||||
)
|
)
|
||||||
.addOnUpdate(this.onlyOnMinIO);
|
.addOnUpdate(this.onlyOnMinIO);
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, "Garbage Collection (Beta)", (e) => e, this.onlyOnP2POrCouchDB).then((paneEl) => {
|
void addPanel(paneEl, "Garbage Collection (Beta2)", (e) => e, this.onlyOnP2POrCouchDB).then((paneEl) => {
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
.setName("Remove all orphaned chunks")
|
.setName("Scan garbage")
|
||||||
.setDesc("Remove all orphaned chunks from the local database.")
|
.setDesc("Scan for garbage chunks in the database.")
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Remove")
|
.setButtonText("Scan")
|
||||||
.setWarning()
|
// .setWarning()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin
|
await this.plugin
|
||||||
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||||
?.removeUnusedChunks();
|
?.trackChanges(false, true);
|
||||||
})
|
})
|
||||||
);
|
|
||||||
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName("Resurrect deleted chunks")
|
|
||||||
.setDesc(
|
|
||||||
"If you have deleted chunks before fully synchronised and missed some chunks, you possibly can resurrect them."
|
|
||||||
)
|
)
|
||||||
|
.addButton((button) =>
|
||||||
|
button.setButtonText("Rescan").onClick(async () => {
|
||||||
|
await this.plugin
|
||||||
|
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||||
|
?.trackChanges(true, true);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName("Collect garbage")
|
||||||
|
.setDesc("Remove all unused chunks from the local database.")
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText("Try resurrect")
|
.setButtonText("Collect")
|
||||||
.setWarning()
|
.setWarning()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin
|
await this.plugin
|
||||||
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||||
?.resurrectChunks();
|
?.performGC(true);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
@@ -205,6 +209,41 @@ export function paneMaintenance(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
void addPanel(paneEl, "Garbage Collection (Old and Experimental)", (e) => e, this.onlyOnP2POrCouchDB).then(
|
||||||
|
(paneEl) => {
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName("Remove all orphaned chunks")
|
||||||
|
.setDesc("Remove all orphaned chunks from the local database.")
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Remove")
|
||||||
|
.setWarning()
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.plugin
|
||||||
|
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||||
|
?.removeUnusedChunks();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName("Resurrect deleted chunks")
|
||||||
|
.setDesc(
|
||||||
|
"If you have deleted chunks before fully synchronised and missed some chunks, you possibly can resurrect them."
|
||||||
|
)
|
||||||
|
.addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText("Try resurrect")
|
||||||
|
.setWarning()
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.plugin
|
||||||
|
.getAddOn<LocalDatabaseMaintenance>(LocalDatabaseMaintenance.name)
|
||||||
|
?.resurrectChunks();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
void addPanel(paneEl, "Rebuilding Operations (Local)").then((paneEl) => {
|
void addPanel(paneEl, "Rebuilding Operations (Local)").then((paneEl) => {
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
.setName("Fetch from remote")
|
.setName("Fetch from remote")
|
||||||
@@ -216,7 +255,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG3_HR, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
@@ -255,7 +294,7 @@ export function paneMaintenance(
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
await this.plugin.storageAccess.writeFileAuto(FLAGMD_REDFLAG2_HR, "");
|
||||||
this.plugin.$$performRestart();
|
this.services.appLifecycle.performRestart();
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.addButton((button) =>
|
.addButton((button) =>
|
||||||
@@ -366,8 +405,8 @@ export function paneMaintenance(
|
|||||||
.setWarning()
|
.setWarning()
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.plugin.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
await this.plugin.$$initializeDatabase();
|
await this.services.databaseEvents.initialiseDatabase();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
|||||||
|
|
||||||
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
|
this.addOnSaved("additionalSuffixOfDatabaseName", async (key) => {
|
||||||
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
|
Logger("Suffix has been changed. Reopening database...", LOG_LEVEL_NOTICE);
|
||||||
await this.plugin.$$initializeDatabase();
|
await this.services.databaseEvents.initialiseDatabase();
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireDropDown("hashAlg", {
|
new Setting(paneEl).autoWireDropDown("hashAlg", {
|
||||||
@@ -82,6 +82,7 @@ export function panePatches(this: ObsidianLiveSyncSettingTab, paneEl: HTMLElemen
|
|||||||
void addPanel(paneEl, "Edge case addressing (Behaviour)").then((paneEl) => {
|
void addPanel(paneEl, "Edge case addressing (Behaviour)").then((paneEl) => {
|
||||||
new Setting(paneEl).autoWireToggle("doNotSuspendOnFetching");
|
new Setting(paneEl).autoWireToggle("doNotSuspendOnFetching");
|
||||||
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("doNotDeleteFolder");
|
new Setting(paneEl).setClass("wizardHidden").autoWireToggle("doNotDeleteFolder");
|
||||||
|
new Setting(paneEl).autoWireToggle("processSizeMismatchedFiles");
|
||||||
});
|
});
|
||||||
|
|
||||||
void addPanel(paneEl, "Edge case addressing (Processing)").then((paneEl) => {
|
void addPanel(paneEl, "Edge case addressing (Processing)").then((paneEl) => {
|
||||||
|
|||||||
@@ -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)) {
|
||||||
@@ -199,12 +233,16 @@ export function paneRemoteConfig(
|
|||||||
) {
|
) {
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.okCorsOrigins"));
|
addResult($msg("obsidianLiveSyncSettingTab.okCorsOrigins"));
|
||||||
} else {
|
} else {
|
||||||
|
const fixedValue = [
|
||||||
|
...new Set([
|
||||||
|
...ConfiguredOrigins.map((e) => e.trim()),
|
||||||
|
"app://obsidian.md",
|
||||||
|
"capacitor://localhost",
|
||||||
|
"http://localhost",
|
||||||
|
]),
|
||||||
|
].join(",");
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.errCorsOrigins"));
|
addResult($msg("obsidianLiveSyncSettingTab.errCorsOrigins"));
|
||||||
addConfigFixButton(
|
addConfigFixButton($msg("obsidianLiveSyncSettingTab.msgSetCorsOrigins"), "cors/origins", fixedValue);
|
||||||
$msg("obsidianLiveSyncSettingTab.msgSetCorsOrigins"),
|
|
||||||
"cors/origins",
|
|
||||||
"app://obsidian.md,capacitor://localhost,http://localhost"
|
|
||||||
);
|
|
||||||
isSuccessful = false;
|
isSuccessful = false;
|
||||||
}
|
}
|
||||||
addResult($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
addResult($msg("obsidianLiveSyncSettingTab.msgConnectionCheck"), ["ob-btn-config-head"]);
|
||||||
@@ -283,280 +321,281 @@ export function paneRemoteConfig(
|
|||||||
},
|
},
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, "Peer-to-Peer", undefined, this.onlyOnOnlyP2P).then((paneEl) => {
|
});
|
||||||
const syncWarnP2P = this.createEl(paneEl, "div", {
|
|
||||||
text: "",
|
void addPanel(paneEl, "Peer-to-Peer", undefined, this.onlyOnOnlyP2P).then((paneEl) => {
|
||||||
});
|
const syncWarnP2P = this.createEl(paneEl, "div", {
|
||||||
const p2pMessage = `This feature is a Work In Progress, and configurable on \`P2P Replicator\` Pane.
|
text: "",
|
||||||
|
});
|
||||||
|
const p2pMessage = `This feature is a Work In Progress, and configurable on \`P2P Replicator\` Pane.
|
||||||
The pane also can be launched by \`P2P Replicator\` command from the Command Palette.
|
The pane also can be launched by \`P2P Replicator\` command from the Command Palette.
|
||||||
`;
|
`;
|
||||||
|
|
||||||
void MarkdownRenderer.render(this.plugin.app, p2pMessage, syncWarnP2P, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, p2pMessage, syncWarnP2P, "/", this.plugin);
|
||||||
syncWarnP2P.addClass("op-warn-info");
|
syncWarnP2P.addClass("op-warn-info");
|
||||||
new Setting(paneEl).setName("Apply Settings").setClass("wizardHidden").addApplyButton(["remoteType"]);
|
new Setting(paneEl).setName("Apply Settings").setClass("wizardHidden").addApplyButton(["remoteType"]);
|
||||||
// .addOnUpdate(onlyOnMinIO);
|
// .addOnUpdate(onlyOnMinIO);
|
||||||
// new Setting(paneEl).addButton((button) =>
|
// new Setting(paneEl).addButton((button) =>
|
||||||
// button
|
// button
|
||||||
// .setButtonText("Open P2P Replicator")
|
// .setButtonText("Open P2P Replicator")
|
||||||
// .onClick(() => {
|
// .onClick(() => {
|
||||||
// const addOn = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
// const addOn = this.plugin.getAddOn<P2PReplicator>(P2PReplicator.name);
|
||||||
// void addOn?.openPane();
|
// void addOn?.openPane();
|
||||||
// this.closeSetting();
|
// this.closeSetting();
|
||||||
// })
|
// })
|
||||||
// );
|
// );
|
||||||
});
|
});
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), undefined, this.onlyOnMinIO).then(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleMinioS3R2"), undefined, this.onlyOnMinIO).then(
|
||||||
(paneEl) => {
|
(paneEl) => {
|
||||||
const syncWarnMinio = this.createEl(paneEl, "div", {
|
const syncWarnMinio = this.createEl(paneEl, "div", {
|
||||||
text: "",
|
text: "",
|
||||||
});
|
});
|
||||||
const ObjectStorageMessage = $msg("obsidianLiveSyncSettingTab.msgObjectStorageWarning");
|
const ObjectStorageMessage = $msg("obsidianLiveSyncSettingTab.msgObjectStorageWarning");
|
||||||
|
|
||||||
void MarkdownRenderer.render(this.plugin.app, ObjectStorageMessage, syncWarnMinio, "/", this.plugin);
|
void MarkdownRenderer.render(this.plugin.app, ObjectStorageMessage, syncWarnMinio, "/", this.plugin);
|
||||||
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).autoWireText("accessKey", { holdValue: true });
|
new Setting(paneEl).autoWireToggle("forcePathStyle", { holdValue: true });
|
||||||
|
new Setting(paneEl).autoWireText("accessKey", { holdValue: true });
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("secretKey", {
|
new Setting(paneEl).autoWireText("secretKey", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
isPassword: true,
|
isPassword: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
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", {
|
new Setting(paneEl).autoWireText("bucketPrefix", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
placeHolder: "vaultname/",
|
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", {
|
||||||
holdValue: true,
|
holdValue: true,
|
||||||
placeHolder: "x-custom-header: value\n x-custom-header2: value2",
|
placeHolder: "x-custom-header: value\n x-custom-header2: value2",
|
||||||
});
|
});
|
||||||
new Setting(paneEl).setName($msg("obsidianLiveSyncSettingTab.nameTestConnection")).addButton((button) =>
|
new Setting(paneEl).setName($msg("obsidianLiveSyncSettingTab.nameTestConnection")).addButton((button) =>
|
||||||
|
button
|
||||||
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
||||||
|
.setDisabled(false)
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.testConnection(this.editingSettings);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||||
|
.setClass("wizardHidden")
|
||||||
|
.addApplyButton([
|
||||||
|
"remoteType",
|
||||||
|
"endpoint",
|
||||||
|
"region",
|
||||||
|
"accessKey",
|
||||||
|
"secretKey",
|
||||||
|
"bucket",
|
||||||
|
"useCustomRequestHandler",
|
||||||
|
"bucketCustomHeaders",
|
||||||
|
"bucketPrefix",
|
||||||
|
])
|
||||||
|
.addOnUpdate(this.onlyOnMinIO);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, this.onlyOnCouchDB).then(
|
||||||
|
(paneEl) => {
|
||||||
|
if (this.services.API.isMobile()) {
|
||||||
|
this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSWarning"),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||||
|
).addClass("op-warn");
|
||||||
|
} else {
|
||||||
|
this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSInfo"),
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
||||||
|
).addClass("op-warn-info");
|
||||||
|
}
|
||||||
|
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_URI", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireToggle("useJWT", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_USER", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => !this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_PASSWORD", {
|
||||||
|
holdValue: true,
|
||||||
|
isPassword: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => !this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const algorithms = {
|
||||||
|
["HS256"]: "HS256",
|
||||||
|
["HS512"]: "HS512",
|
||||||
|
["ES256"]: "ES256",
|
||||||
|
["ES512"]: "ES512",
|
||||||
|
} as const;
|
||||||
|
new Setting(paneEl).autoWireDropDown("jwtAlgorithm", {
|
||||||
|
options: algorithms,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireTextArea("jwtKey", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let generatedKeyDivEl: HTMLDivElement;
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setDesc("Generate ES256 Keypair for testing")
|
||||||
|
.addButton((button) =>
|
||||||
|
button.setButtonText("Generate").onClick(async () => {
|
||||||
|
const crypto = await getWebCrypto();
|
||||||
|
const keyPair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, [
|
||||||
|
"sign",
|
||||||
|
"verify",
|
||||||
|
]);
|
||||||
|
const pubKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
||||||
|
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||||
|
const encodedPublicKey = await arrayBufferToBase64Single(pubKey);
|
||||||
|
const encodedPrivateKey = await arrayBufferToBase64Single(privateKey);
|
||||||
|
|
||||||
|
const privateKeyPem = `> -----BEGIN PRIVATE KEY-----\n> ${encodedPrivateKey}\n> -----END PRIVATE KEY-----`;
|
||||||
|
const publicKeyPem = `> -----BEGIN PUBLIC KEY-----\\n${encodedPublicKey}\\n-----END PUBLIC KEY-----`;
|
||||||
|
|
||||||
|
const title = $msg("Setting.GenerateKeyPair.Title");
|
||||||
|
const msg = $msg("Setting.GenerateKeyPair.Desc", {
|
||||||
|
public_key: publicKeyPem,
|
||||||
|
private_key: privateKeyPem,
|
||||||
|
});
|
||||||
|
await MarkdownRenderer.render(
|
||||||
|
this.plugin.app,
|
||||||
|
"## " + title + "\n\n" + msg,
|
||||||
|
generatedKeyDivEl,
|
||||||
|
"/",
|
||||||
|
this.plugin
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addOnUpdate(
|
||||||
|
combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
generatedKeyDivEl = this.createEl(
|
||||||
|
paneEl,
|
||||||
|
"div",
|
||||||
|
{ text: "" },
|
||||||
|
(el) => {},
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(paneEl).autoWireText("jwtKid", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("jwtSub", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireNumeric("jwtExpDuration", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: combineOnUpdate(
|
||||||
|
this.enableOnlySyncDisabled,
|
||||||
|
visibleOnly(() => this.editingSettings.useJWT)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireText("couchDB_DBNAME", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true });
|
||||||
|
new Setting(paneEl).autoWireToggle("useRequestAPI", {
|
||||||
|
holdValue: true,
|
||||||
|
onUpdate: this.enableOnlySyncDisabled,
|
||||||
|
});
|
||||||
|
new Setting(paneEl)
|
||||||
|
.setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
|
||||||
|
.setClass("wizardHidden")
|
||||||
|
.setDesc($msg("obsidianLiveSyncSettingTab.descTestDatabaseConnection"))
|
||||||
|
.addButton((button) =>
|
||||||
button
|
button
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
await this.testConnection(this.editingSettings);
|
await this.testConnection();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.addApplyButton([
|
|
||||||
"remoteType",
|
|
||||||
"endpoint",
|
|
||||||
"region",
|
|
||||||
"accessKey",
|
|
||||||
"secretKey",
|
|
||||||
"bucket",
|
|
||||||
"useCustomRequestHandler",
|
|
||||||
"bucketCustomHeaders",
|
|
||||||
"bucketPrefix",
|
|
||||||
])
|
|
||||||
.addOnUpdate(this.onlyOnMinIO);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleCouchDB"), undefined, this.onlyOnCouchDB).then(
|
new Setting(paneEl)
|
||||||
(paneEl) => {
|
.setName($msg("obsidianLiveSyncSettingTab.nameValidateDatabaseConfig"))
|
||||||
if (this.plugin.$$isMobile()) {
|
.setDesc($msg("obsidianLiveSyncSettingTab.descValidateDatabaseConfig"))
|
||||||
this.createEl(
|
.addButton((button) =>
|
||||||
paneEl,
|
button
|
||||||
"div",
|
.setButtonText($msg("obsidianLiveSyncSettingTab.btnCheck"))
|
||||||
{
|
.setDisabled(false)
|
||||||
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSWarning"),
|
.onClick(async () => {
|
||||||
},
|
await checkConfig(checkResultDiv);
|
||||||
undefined,
|
|
||||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
|
||||||
).addClass("op-warn");
|
|
||||||
} else {
|
|
||||||
this.createEl(
|
|
||||||
paneEl,
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
text: $msg("obsidianLiveSyncSettingTab.msgNonHTTPSInfo"),
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
visibleOnly(() => !this.editingSettings.couchDB_URI.startsWith("https://"))
|
|
||||||
).addClass("op-warn-info");
|
|
||||||
}
|
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_URI", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireToggle("useJWT", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_USER", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => !this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireText("couchDB_PASSWORD", {
|
|
||||||
holdValue: true,
|
|
||||||
isPassword: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => !this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
const algorithms = {
|
|
||||||
["HS256"]: "HS256",
|
|
||||||
["HS512"]: "HS512",
|
|
||||||
["ES256"]: "ES256",
|
|
||||||
["ES512"]: "ES512",
|
|
||||||
} as const;
|
|
||||||
new Setting(paneEl).autoWireDropDown("jwtAlgorithm", {
|
|
||||||
options: algorithms,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireTextArea("jwtKey", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
),
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
let generatedKeyDivEl: HTMLDivElement;
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setDesc("Generate ES256 Keypair for testing")
|
|
||||||
.addButton((button) =>
|
|
||||||
button.setButtonText("Generate").onClick(async () => {
|
|
||||||
const crypto = await getWebCrypto();
|
|
||||||
const keyPair = await crypto.subtle.generateKey(
|
|
||||||
{ name: "ECDSA", namedCurve: "P-256" },
|
|
||||||
true,
|
|
||||||
["sign", "verify"]
|
|
||||||
);
|
|
||||||
const pubKey = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
||||||
const privateKey = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
|
||||||
const encodedPublicKey = await arrayBufferToBase64Single(pubKey);
|
|
||||||
const encodedPrivateKey = await arrayBufferToBase64Single(privateKey);
|
|
||||||
|
|
||||||
const privateKeyPem = `> -----BEGIN PRIVATE KEY-----\n> ${encodedPrivateKey}\n> -----END PRIVATE KEY-----`;
|
|
||||||
const publicKeyPem = `> -----BEGIN PUBLIC KEY-----\\n${encodedPublicKey}\\n-----END PUBLIC KEY-----`;
|
|
||||||
|
|
||||||
const title = $msg("Setting.GenerateKeyPair.Title");
|
|
||||||
const msg = $msg("Setting.GenerateKeyPair.Desc", {
|
|
||||||
public_key: publicKeyPem,
|
|
||||||
private_key: privateKeyPem,
|
|
||||||
});
|
|
||||||
await MarkdownRenderer.render(
|
|
||||||
this.plugin.app,
|
|
||||||
"## " + title + "\n\n" + msg,
|
|
||||||
generatedKeyDivEl,
|
|
||||||
"/",
|
|
||||||
this.plugin
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.addOnUpdate(
|
|
||||||
combineOnUpdate(
|
|
||||||
this.enableOnlySyncDisabled,
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
generatedKeyDivEl = this.createEl(
|
|
||||||
paneEl,
|
|
||||||
"div",
|
|
||||||
{ text: "" },
|
|
||||||
(el) => {},
|
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
|
||||||
);
|
);
|
||||||
|
checkResultDiv = this.createEl(paneEl, "div", {
|
||||||
|
text: "",
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(paneEl).autoWireText("jwtKid", {
|
new Setting(paneEl)
|
||||||
holdValue: true,
|
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
||||||
onUpdate: combineOnUpdate(
|
.setClass("wizardHidden")
|
||||||
this.enableOnlySyncDisabled,
|
.addApplyButton([
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
"remoteType",
|
||||||
),
|
"couchDB_URI",
|
||||||
});
|
"couchDB_USER",
|
||||||
new Setting(paneEl).autoWireText("jwtSub", {
|
"couchDB_PASSWORD",
|
||||||
holdValue: true,
|
"couchDB_DBNAME",
|
||||||
onUpdate: combineOnUpdate(
|
"jwtAlgorithm",
|
||||||
this.enableOnlySyncDisabled,
|
"jwtExpDuration",
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
"jwtKey",
|
||||||
),
|
"jwtSub",
|
||||||
});
|
"jwtKid",
|
||||||
new Setting(paneEl).autoWireNumeric("jwtExpDuration", {
|
"useJWT",
|
||||||
holdValue: true,
|
"couchDB_CustomHeaders",
|
||||||
onUpdate: combineOnUpdate(
|
"useRequestAPI",
|
||||||
this.enableOnlySyncDisabled,
|
])
|
||||||
visibleOnly(() => this.editingSettings.useJWT)
|
.addOnUpdate(this.onlyOnCouchDB);
|
||||||
),
|
}
|
||||||
});
|
);
|
||||||
new Setting(paneEl).autoWireText("couchDB_DBNAME", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl).autoWireTextArea("couchDB_CustomHeaders", { holdValue: true });
|
|
||||||
new Setting(paneEl).autoWireToggle("useRequestAPI", {
|
|
||||||
holdValue: true,
|
|
||||||
onUpdate: this.enableOnlySyncDisabled,
|
|
||||||
});
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameTestDatabaseConnection"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.setDesc($msg("obsidianLiveSyncSettingTab.descTestDatabaseConnection"))
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnTest"))
|
|
||||||
.setDisabled(false)
|
|
||||||
.onClick(async () => {
|
|
||||||
await this.testConnection();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameValidateDatabaseConfig"))
|
|
||||||
.setDesc($msg("obsidianLiveSyncSettingTab.descValidateDatabaseConfig"))
|
|
||||||
.addButton((button) =>
|
|
||||||
button
|
|
||||||
.setButtonText($msg("obsidianLiveSyncSettingTab.btnCheck"))
|
|
||||||
.setDisabled(false)
|
|
||||||
.onClick(async () => {
|
|
||||||
await checkConfig(checkResultDiv);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
checkResultDiv = this.createEl(paneEl, "div", {
|
|
||||||
text: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
new Setting(paneEl)
|
|
||||||
.setName($msg("obsidianLiveSyncSettingTab.nameApplySettings"))
|
|
||||||
.setClass("wizardHidden")
|
|
||||||
.addApplyButton([
|
|
||||||
"remoteType",
|
|
||||||
"couchDB_URI",
|
|
||||||
"couchDB_USER",
|
|
||||||
"couchDB_PASSWORD",
|
|
||||||
"couchDB_DBNAME",
|
|
||||||
"jwtAlgorithm",
|
|
||||||
"jwtExpDuration",
|
|
||||||
"jwtKey",
|
|
||||||
"jwtSub",
|
|
||||||
"jwtKid",
|
|
||||||
"useJWT",
|
|
||||||
"couchDB_CustomHeaders",
|
|
||||||
"useRequestAPI",
|
|
||||||
])
|
|
||||||
.addOnUpdate(this.onlyOnCouchDB);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => {}, this.onlyOnCouchDB).then(
|
void addPanel(paneEl, $msg("obsidianLiveSyncSettingTab.titleNotification"), () => {}, this.onlyOnCouchDB).then(
|
||||||
(paneEl) => {
|
(paneEl) => {
|
||||||
paneEl.addClass("wizardHidden");
|
paneEl.addClass("wizardHidden");
|
||||||
@@ -591,7 +630,8 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
.setDisabled(false)
|
.setDisabled(false)
|
||||||
.onClick(async () => {
|
.onClick(async () => {
|
||||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||||
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
|
const newTweaks =
|
||||||
|
await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
|
||||||
if (newTweaks.result !== false) {
|
if (newTweaks.result !== false) {
|
||||||
if (this.inWizard) {
|
if (this.inWizard) {
|
||||||
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
||||||
@@ -609,15 +649,15 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
}
|
}
|
||||||
)) == "no"
|
)) == "no"
|
||||||
) {
|
) {
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
await this.plugin.rebuilder.scheduleFetch();
|
await this.plugin.rebuilder.scheduleFetch();
|
||||||
await this.plugin.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,7 +728,7 @@ The pane also can be launched by \`P2P Replicator\` command from the Command Pal
|
|||||||
)) == "yes"
|
)) == "yes"
|
||||||
) {
|
) {
|
||||||
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
const trialSetting = { ...this.initialSettings, ...this.editingSettings };
|
||||||
const newTweaks = await this.plugin.$$checkAndAskUseRemoteConfiguration(trialSetting);
|
const newTweaks = await this.services.tweakValue.checkAndAskUseRemoteConfiguration(trialSetting);
|
||||||
if (newTweaks.result !== false) {
|
if (newTweaks.result !== false) {
|
||||||
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
this.editingSettings = { ...this.editingSettings, ...newTweaks.result };
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function paneSetup(
|
|||||||
text.setButtonText($msg("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
|
text.setButtonText($msg("obsidianLiveSyncSettingTab.btnEnable")).onClick(async () => {
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -91,10 +91,10 @@ export function paneSetup(
|
|||||||
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
this.editingSettings = { ...this.editingSettings, ...DEFAULT_SETTINGS };
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
this.plugin.settings = { ...DEFAULT_SETTINGS };
|
this.plugin.settings = { ...DEFAULT_SETTINGS };
|
||||||
await this.plugin.$$saveSettingData();
|
await this.services.setting.saveSettingData();
|
||||||
await this.plugin.$$resetLocalDatabase();
|
await this.services.database.resetDatabase();
|
||||||
// await this.plugin.initializeDatabase();
|
// await this.plugin.initializeDatabase();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setWarning();
|
.setWarning();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
import { Logger } from "../../../lib/src/common/logger.ts";
|
import { Logger } from "../../../lib/src/common/logger.ts";
|
||||||
import { $msg } from "../../../lib/src/common/i18n.ts";
|
import { $msg } from "../../../lib/src/common/i18n.ts";
|
||||||
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
import { LiveSyncSetting as Setting } from "./LiveSyncSetting.ts";
|
||||||
import { fireAndForget } from "octagonal-wheels/promises";
|
|
||||||
import { EVENT_REQUEST_COPY_SETUP_URI, eventHub } from "../../../common/events.ts";
|
import { EVENT_REQUEST_COPY_SETUP_URI, eventHub } from "../../../common/events.ts";
|
||||||
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
import type { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab.ts";
|
||||||
import type { PageFunctions } from "./SettingPane.ts";
|
import type { PageFunctions } from "./SettingPane.ts";
|
||||||
@@ -17,30 +16,6 @@ export function paneSyncSettings(
|
|||||||
paneEl: HTMLElement,
|
paneEl: HTMLElement,
|
||||||
{ addPanel, addPane }: PageFunctions
|
{ addPanel, addPane }: PageFunctions
|
||||||
): void {
|
): void {
|
||||||
if (this.editingSettings.versionUpFlash != "") {
|
|
||||||
const c = this.createEl(
|
|
||||||
paneEl,
|
|
||||||
"div",
|
|
||||||
{
|
|
||||||
text: this.editingSettings.versionUpFlash,
|
|
||||||
cls: "op-warn sls-setting-hidden",
|
|
||||||
},
|
|
||||||
(el) => {
|
|
||||||
this.createEl(el, "button", { text: $msg("obsidianLiveSyncSettingTab.btnGotItAndUpdated") }, (e) => {
|
|
||||||
e.addClass("mod-cta");
|
|
||||||
e.addEventListener("click", () => {
|
|
||||||
fireAndForget(async () => {
|
|
||||||
this.editingSettings.versionUpFlash = "";
|
|
||||||
await this.saveAllDirtySettings();
|
|
||||||
c.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
visibleOnly(() => !this.isConfiguredAs("versionUpFlash", ""))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createEl(paneEl, "div", {
|
this.createEl(paneEl, "div", {
|
||||||
text: $msg("obsidianLiveSyncSettingTab.msgSelectAndApplyPreset"),
|
text: $msg("obsidianLiveSyncSettingTab.msgSelectAndApplyPreset"),
|
||||||
cls: "wizardOnly",
|
cls: "wizardOnly",
|
||||||
@@ -130,7 +105,7 @@ export function paneSyncSettings(
|
|||||||
if (!this.editingSettings.isConfigured) {
|
if (!this.editingSettings.isConfigured) {
|
||||||
this.editingSettings.isConfigured = true;
|
this.editingSettings.isConfigured = true;
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
await this.rebuildDB("localOnly");
|
await this.rebuildDB("localOnly");
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
if (
|
if (
|
||||||
@@ -149,13 +124,13 @@ export function paneSyncSettings(
|
|||||||
await this.confirmRebuild();
|
await this.confirmRebuild();
|
||||||
} else {
|
} else {
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
this.plugin.$$askReload();
|
this.services.appLifecycle.askRestart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.saveAllDirtySettings();
|
await this.saveAllDirtySettings();
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -194,7 +169,7 @@ export function paneSyncSettings(
|
|||||||
}
|
}
|
||||||
await this.saveSettings(["liveSync", "periodicReplication"]);
|
await this.saveSettings(["liveSync", "periodicReplication"]);
|
||||||
|
|
||||||
await this.plugin.$$realizeSettingSyncMode();
|
await this.services.setting.realiseSetting();
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(paneEl)
|
new Setting(paneEl)
|
||||||
@@ -314,21 +289,21 @@ export function paneSyncSettings(
|
|||||||
button.setButtonText("Merge").onClick(async () => {
|
button.setButtonText("Merge").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("MERGE");
|
await this.services.setting.enableOptionalFeature("MERGE");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.addButton((button) => {
|
.addButton((button) => {
|
||||||
button.setButtonText("Fetch").onClick(async () => {
|
button.setButtonText("Fetch").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("FETCH");
|
await this.services.setting.enableOptionalFeature("FETCH");
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.addButton((button) => {
|
.addButton((button) => {
|
||||||
button.setButtonText("Overwrite").onClick(async () => {
|
button.setButtonText("Overwrite").onClick(async () => {
|
||||||
this.closeSetting();
|
this.closeSetting();
|
||||||
// this.resetEditingSettings();
|
// this.resetEditingSettings();
|
||||||
await this.plugin.$anyConfigureOptionalSyncFeature("OVERWRITE");
|
await this.services.setting.enableOptionalFeature("OVERWRITE");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import { versionNumberString2Number } from "../../lib/src/string_and_binary/conv
|
|||||||
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
import { cancelAllPeriodicTask, cancelAllTasks } from "octagonal-wheels/concurrency/task";
|
||||||
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
import { stopAllRunningProcessors } from "octagonal-wheels/concurrency/processor";
|
||||||
import { AbstractModule } from "../AbstractModule.ts";
|
import { AbstractModule } from "../AbstractModule.ts";
|
||||||
import type { ICoreModule } from "../ModuleTypes.ts";
|
|
||||||
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
|
import { EVENT_PLATFORM_UNLOADED } from "../../lib/src/PlatformAPIs/base/APIBase.ts";
|
||||||
|
import type { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import type { LiveSyncCore } from "../../main.ts";
|
||||||
|
|
||||||
export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
export class ModuleLiveSyncMain extends AbstractModule {
|
||||||
async $$onLiveSyncReady() {
|
async _onLiveSyncReady() {
|
||||||
if (!(await this.core.$everyOnLayoutReady())) return;
|
if (!(await this.core.services.appLifecycle.onLayoutReady())) return false;
|
||||||
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
eventHub.emitEvent(EVENT_LAYOUT_READY);
|
||||||
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
if (this.settings.suspendFileWatching || this.settings.suspendParseReplicationResult) {
|
||||||
const ANSWER_KEEP = $msg("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
|
const ANSWER_KEEP = $msg("moduleLiveSyncMain.optionKeepLiveSyncDisabled");
|
||||||
@@ -36,46 +37,49 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
this.settings.suspendFileWatching = false;
|
this.settings.suspendFileWatching = false;
|
||||||
this.settings.suspendParseReplicationResult = false;
|
this.settings.suspendParseReplicationResult = false;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
await this.core.$$scheduleAppReload();
|
this.services.appLifecycle.scheduleRestart();
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const isInitialized = await this.core.$$initializeDatabase(false, false);
|
const isInitialized = await this.services.databaseEvents.initialiseDatabase(false, false);
|
||||||
if (!isInitialized) {
|
if (!isInitialized) {
|
||||||
//TODO:stop all sync.
|
//TODO:stop all sync.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!(await this.core.$everyOnFirstInitialize())) return;
|
if (!(await this.core.services.appLifecycle.onFirstInitialise())) return false;
|
||||||
await this.core.$$realizeSettingSyncMode();
|
// await this.core.$$realizeSettingSyncMode();
|
||||||
|
await this.services.setting.realiseSetting();
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleLiveSyncMain.logAdditionalSafetyScan"), LOG_LEVEL_VERBOSE);
|
||||||
if (!(await this.core.$allScanStat())) {
|
if (!(await this.services.appLifecycle.onScanningStartupIssues())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logSafetyScanFailed"), LOG_LEVEL_NOTICE);
|
||||||
} else {
|
} else {
|
||||||
this._log($msg("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
|
this._log($msg("moduleLiveSyncMain.logSafetyScanCompleted"), LOG_LEVEL_VERBOSE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$wireUpEvents(): void {
|
_wireUpEvents() {
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
this.localDatabase.settings = settings;
|
this.localDatabase.settings = settings;
|
||||||
setLang(settings.displayLanguage);
|
setLang(settings.displayLanguage);
|
||||||
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
eventHub.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||||
});
|
});
|
||||||
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
eventHub.onEvent(EVENT_SETTING_SAVED, (settings: ObsidianLiveSyncSettings) => {
|
||||||
fireAndForget(() => this.core.$$realizeSettingSyncMode());
|
fireAndForget(() => this.core.services.setting.realiseSetting());
|
||||||
});
|
});
|
||||||
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncLoad(): Promise<void> {
|
async _onLiveSyncLoad(): Promise<boolean> {
|
||||||
this.$$wireUpEvents();
|
await this.services.appLifecycle.onWireUpEvents();
|
||||||
// debugger;
|
// debugger;
|
||||||
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
eventHub.emitEvent(EVENT_PLUGIN_LOADED, this.core);
|
||||||
this._log($msg("moduleLiveSyncMain.logLoadingPlugin"));
|
this._log($msg("moduleLiveSyncMain.logLoadingPlugin"));
|
||||||
if (!(await this.core.$everyOnloadStart())) {
|
if (!(await this.services.appLifecycle.onInitialise())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// this.addUIs();
|
// this.addUIs();
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -84,12 +88,12 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
|
||||||
|
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
this._log($msg("moduleLiveSyncMain.logPluginVersion", { manifestVersion, packageVersion }));
|
||||||
await this.core.$$loadSettings();
|
await this.services.setting.loadSettings();
|
||||||
if (!(await this.core.$everyOnloadAfterLoadSettings())) {
|
if (!(await this.services.appLifecycle.onSettingLoaded())) {
|
||||||
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
this._log($msg("moduleLiveSyncMain.logPluginInitCancelled"), LOG_LEVEL_NOTICE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
const lsKey = "obsidian-live-sync-ver" + this.core.$$getVaultName();
|
const lsKey = "obsidian-live-sync-ver" + this.services.vault.getVaultName();
|
||||||
const last_version = localStorage.getItem(lsKey);
|
const last_version = localStorage.getItem(lsKey);
|
||||||
|
|
||||||
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
|
||||||
@@ -113,22 +117,23 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
localStorage.setItem(lsKey, `${VER}`);
|
localStorage.setItem(lsKey, `${VER}`);
|
||||||
await this.core.$$openDatabase();
|
await this.services.database.openDatabase();
|
||||||
this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
|
// this.core.$$realizeSettingSyncMode = this.core.$$realizeSettingSyncMode.bind(this);
|
||||||
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
|
// this.$$parseReplicationResult = this.$$parseReplicationResult.bind(this);
|
||||||
// this.$$replicate = this.$$replicate.bind(this);
|
// this.$$replicate = this.$$replicate.bind(this);
|
||||||
this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
// this.core.$$onLiveSyncReady = this.core.$$onLiveSyncReady.bind(this);
|
||||||
await this.core.$everyOnload();
|
await this.core.services.appLifecycle.onLoaded();
|
||||||
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
await Promise.all(this.core.addOns.map((e) => e.onload()));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$onLiveSyncUnload(): Promise<void> {
|
async _onLiveSyncUnload(): Promise<void> {
|
||||||
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
|
eventHub.emitEvent(EVENT_PLUGIN_UNLOADED);
|
||||||
await this.core.$allStartOnUnload();
|
await this.services.appLifecycle.onBeforeUnload();
|
||||||
cancelAllPeriodicTask();
|
cancelAllPeriodicTask();
|
||||||
cancelAllTasks();
|
cancelAllTasks();
|
||||||
stopAllRunningProcessors();
|
stopAllRunningProcessors();
|
||||||
await this.core.$allOnUnload();
|
await this.services.appLifecycle.onUnload();
|
||||||
this._unloaded = true;
|
this._unloaded = true;
|
||||||
for (const addOn of this.core.addOns) {
|
for (const addOn of this.core.addOns) {
|
||||||
addOn.onunload();
|
addOn.onunload();
|
||||||
@@ -143,49 +148,68 @@ export class ModuleLiveSyncMain extends AbstractModule implements ICoreModule {
|
|||||||
eventHub.emitEvent(EVENT_PLATFORM_UNLOADED);
|
eventHub.emitEvent(EVENT_PLATFORM_UNLOADED);
|
||||||
eventHub.offAll();
|
eventHub.offAll();
|
||||||
this._log($msg("moduleLiveSyncMain.logUnloadingPlugin"));
|
this._log($msg("moduleLiveSyncMain.logUnloadingPlugin"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$realizeSettingSyncMode(): Promise<void> {
|
private async _realizeSettingSyncMode(): Promise<void> {
|
||||||
await this.core.$everyBeforeSuspendProcess();
|
await this.services.appLifecycle.onSuspending();
|
||||||
await this.core.$everyBeforeRealizeSetting();
|
await this.services.setting.onBeforeRealiseSetting();
|
||||||
this.localDatabase.refreshSettings();
|
this.localDatabase.refreshSettings();
|
||||||
await this.core.$everyCommitPendingFileEvent();
|
await this.services.fileProcessing.commitPendingFileEvents();
|
||||||
await this.core.$everyRealizeSettingSyncMode();
|
await this.services.setting.onRealiseSetting();
|
||||||
// disable all sync temporary.
|
// disable all sync temporary.
|
||||||
if (this.core.$$isSuspended()) return;
|
if (this.services.appLifecycle.isSuspended()) return;
|
||||||
await this.core.$everyOnResumeProcess();
|
await this.services.appLifecycle.onResuming();
|
||||||
await this.core.$everyAfterResumeProcess();
|
await this.services.appLifecycle.onResumed();
|
||||||
await this.core.$everyAfterRealizeSetting();
|
await this.services.setting.onSettingRealised();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$isReloadingScheduled(): boolean {
|
_isReloadingScheduled(): boolean {
|
||||||
return this.core._totalProcessingCount !== undefined;
|
return this.core._totalProcessingCount !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
isReady = false;
|
isReady = false;
|
||||||
|
|
||||||
$$isReady(): boolean {
|
_isReady(): boolean {
|
||||||
return this.isReady;
|
return this.isReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$markIsReady(): void {
|
_markIsReady(): void {
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$$resetIsReady(): void {
|
_resetIsReady(): void {
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_suspended = false;
|
_suspended = false;
|
||||||
$$isSuspended(): boolean {
|
_isSuspended(): boolean {
|
||||||
return this._suspended || !this.settings?.isConfigured;
|
return this._suspended || !this.settings?.isConfigured;
|
||||||
}
|
}
|
||||||
$$setSuspended(value: boolean) {
|
|
||||||
|
_setSuspended(value: boolean) {
|
||||||
this._suspended = value;
|
this._suspended = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_unloaded = false;
|
_unloaded = false;
|
||||||
$$isUnloaded(): boolean {
|
_isUnloaded(): boolean {
|
||||||
return this._unloaded;
|
return this._unloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBindFunction(core: LiveSyncCore, services: InjectableServiceHub): void {
|
||||||
|
super.onBindFunction(core, services);
|
||||||
|
services.appLifecycle.handleIsSuspended(this._isSuspended.bind(this));
|
||||||
|
services.appLifecycle.handleSetSuspended(this._setSuspended.bind(this));
|
||||||
|
services.appLifecycle.handleIsReady(this._isReady.bind(this));
|
||||||
|
services.appLifecycle.handleMarkIsReady(this._markIsReady.bind(this));
|
||||||
|
services.appLifecycle.handleResetIsReady(this._resetIsReady.bind(this));
|
||||||
|
services.appLifecycle.handleHasUnloaded(this._isUnloaded.bind(this));
|
||||||
|
services.appLifecycle.handleIsReloadingScheduled(this._isReloadingScheduled.bind(this));
|
||||||
|
services.appLifecycle.handleOnReady(this._onLiveSyncReady.bind(this));
|
||||||
|
services.appLifecycle.handleOnWireUpEvents(this._wireUpEvents.bind(this));
|
||||||
|
services.appLifecycle.handleOnLoad(this._onLiveSyncLoad.bind(this));
|
||||||
|
services.appLifecycle.handleOnAppUnload(this._onLiveSyncUnload.bind(this));
|
||||||
|
services.setting.handleRealiseSetting(this._realizeSettingSyncMode.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
src/modules/services/ObsidianServices.ts
Normal file
76
src/modules/services/ObsidianServices.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
InjectableAPIService,
|
||||||
|
InjectableAppLifecycleService,
|
||||||
|
InjectableConflictService,
|
||||||
|
InjectableDatabaseService,
|
||||||
|
InjectableFileProcessingService,
|
||||||
|
InjectablePathService,
|
||||||
|
InjectableRemoteService,
|
||||||
|
InjectableReplicationService,
|
||||||
|
InjectableReplicatorService,
|
||||||
|
InjectableSettingService,
|
||||||
|
InjectableTestService,
|
||||||
|
InjectableTweakValueService,
|
||||||
|
InjectableVaultService,
|
||||||
|
} from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
import { InjectableServiceHub } from "../../lib/src/services/InjectableServices.ts";
|
||||||
|
// All Services will be migrated to be based on Plain Services, not Injectable Services.
|
||||||
|
// This is a migration step.
|
||||||
|
|
||||||
|
export class ObsidianAPIService extends InjectableAPIService {}
|
||||||
|
export class ObsidianPathService extends InjectablePathService {}
|
||||||
|
export class ObsidianDatabaseService extends InjectableDatabaseService {}
|
||||||
|
|
||||||
|
// InjectableReplicatorService
|
||||||
|
export class ObsidianReplicatorService extends InjectableReplicatorService {}
|
||||||
|
// InjectableFileProcessingService
|
||||||
|
export class ObsidianFileProcessingService extends InjectableFileProcessingService {}
|
||||||
|
// InjectableReplicationService
|
||||||
|
export class ObsidianReplicationService extends InjectableReplicationService {}
|
||||||
|
// InjectableRemoteService
|
||||||
|
export class ObsidianRemoteService extends InjectableRemoteService {}
|
||||||
|
// InjectableConflictService
|
||||||
|
export class ObsidianConflictService extends InjectableConflictService {}
|
||||||
|
// InjectableAppLifecycleService
|
||||||
|
export class ObsidianAppLifecycleService extends InjectableAppLifecycleService {}
|
||||||
|
// InjectableSettingService
|
||||||
|
export class ObsidianSettingService extends InjectableSettingService {}
|
||||||
|
// InjectableTweakValueService
|
||||||
|
export class ObsidianTweakValueService extends InjectableTweakValueService {}
|
||||||
|
// InjectableVaultService
|
||||||
|
export class ObsidianVaultService extends InjectableVaultService {}
|
||||||
|
// InjectableTestService
|
||||||
|
export class ObsidianTestService extends InjectableTestService {}
|
||||||
|
|
||||||
|
// InjectableServiceHub
|
||||||
|
|
||||||
|
export class ObsidianServiceHub extends InjectableServiceHub {
|
||||||
|
protected _api: ObsidianAPIService = new ObsidianAPIService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _path: ObsidianPathService = new ObsidianPathService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _database: ObsidianDatabaseService = new ObsidianDatabaseService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _replicator: ObsidianReplicatorService = new ObsidianReplicatorService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _fileProcessing: ObsidianFileProcessingService = new ObsidianFileProcessingService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _replication: ObsidianReplicationService = new ObsidianReplicationService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _remote: ObsidianRemoteService = new ObsidianRemoteService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _conflict: ObsidianConflictService = new ObsidianConflictService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _appLifecycle: ObsidianAppLifecycleService = new ObsidianAppLifecycleService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _setting: ObsidianSettingService = new ObsidianSettingService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _tweakValue: ObsidianTweakValueService = new ObsidianTweakValueService(
|
||||||
|
this._serviceBackend,
|
||||||
|
this._throughHole
|
||||||
|
);
|
||||||
|
protected _vault: ObsidianVaultService = new ObsidianVaultService(this._serviceBackend, this._throughHole);
|
||||||
|
protected _test: ObsidianTestService = new ObsidianTestService(this._serviceBackend, this._throughHole);
|
||||||
|
}
|
||||||
40
styles.css
40
styles.css
@@ -12,6 +12,11 @@
|
|||||||
background-color: var(--text-muted);
|
background-color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.conflict-dev-name {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
.op-scrollable {
|
.op-scrollable {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
/* min-height: 280px; */
|
/* min-height: 280px; */
|
||||||
@@ -105,10 +110,10 @@
|
|||||||
div.sls-setting-menu-btn {
|
div.sls-setting-menu-btn {
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
background-color: var(--background-secondary-alt);
|
background-color: var(--background-secondary-alt);
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 8px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 12px;
|
margin-right: 2px;
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
outline: none;
|
outline: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -135,9 +140,9 @@ div.sls-setting-menu-btn {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: rgba(var(--background-primary), 0.3);
|
backdrop-filter: blur(15px);
|
||||||
backdrop-filter: blur(4px);
|
padding: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,6 +390,18 @@ span.ls-mark-cr::after {
|
|||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||||
|
top: calc(var(--bases-header-height) + var(--header-height));
|
||||||
|
padding: 5px;
|
||||||
|
padding-right:18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-mobile div.workspace-leaf-content[data-type=bases] .livesync-status {
|
||||||
|
top: calc(var(--bases-header-height) + var(--view-header-height));
|
||||||
|
padding: 6px;
|
||||||
|
padding-right:18px;
|
||||||
|
}
|
||||||
|
|
||||||
.livesync-status div {
|
.livesync-status div {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
-webkit-filter: grayscale(100%);
|
-webkit-filter: grayscale(100%);
|
||||||
@@ -405,6 +422,7 @@ span.ls-mark-cr::after {
|
|||||||
filter: unset;
|
filter: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.menu-setting-poweruser-disabled .sls-setting-poweruser {
|
.menu-setting-poweruser-disabled .sls-setting-poweruser {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -419,13 +437,11 @@ span.ls-mark-cr::after {
|
|||||||
|
|
||||||
.sls-setting-panel-title {
|
.sls-setting-panel-title {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
}
|
font-size: medium;
|
||||||
|
top: 2.5em;
|
||||||
.sls-setting-panel-title {
|
background-color: var(--background-secondary-alt);
|
||||||
top: 2em;
|
border-radius: 10px;
|
||||||
background-color: rgba(var(--background-primary), 0.3);
|
padding: 0.5em 1.0em;
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
border-radius: 30%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sls-dialogue-note-wrapper {
|
.sls-dialogue-note-wrapper {
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ const terserOption = {
|
|||||||
evaluate: true,
|
evaluate: true,
|
||||||
dead_code: true,
|
dead_code: true,
|
||||||
// directives: true,
|
// directives: true,
|
||||||
inline: 3,
|
inline: false,
|
||||||
join_vars: true,
|
join_vars: true,
|
||||||
loops: true,
|
loops: true,
|
||||||
passes: 4,
|
passes: 4,
|
||||||
reduce_vars: true,
|
reduce_vars: true,
|
||||||
reduce_funcs: true,
|
reduce_funcs: false,
|
||||||
arrows: true,
|
arrows: true,
|
||||||
collapse_vars: true,
|
collapse_vars: true,
|
||||||
comparisons: true,
|
comparisons: true,
|
||||||
|
|||||||
166
updates.md
166
updates.md
@@ -1,113 +1,131 @@
|
|||||||
## ~~0.25.5~~ 0.25.6
|
# 0.25
|
||||||
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
|
||||||
|
|
||||||
9th August, 2025
|
Since 19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
||||||
|
|
||||||
|
The head note of 0.25 is now in [updates_old.md](https://github.com/vrtmrz/obsidian-livesync/blob/main/updates_old.md). Because 0.25 got a lot of updates, thankfully, compatibility is kept and we do not need breaking changes! In other words, when get enough stabled. The next version will be v1.0.0. Even though it my hope.
|
||||||
|
|
||||||
|
## 0.25.22
|
||||||
|
|
||||||
|
15th October, 2025
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Storage scanning no longer occurs when `Suspend file watching` is enabled (including boot-sequence).
|
- Fixed a bug that caused wrong event bindings and flag inversion (#727)
|
||||||
- This change improves safety when troubleshooting or fetching the remote database.
|
- This caused following issues:
|
||||||
- `Fetch chunks on demand` is now working again (if you installed 0.25.5, other versions are not affected).
|
- In some cases, settings changes were not applied or saved correctly.
|
||||||
|
- Automatic synchronisation did not begin correctly.
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
|
- Too large diffs are not shown in the file comparison view, due to performance reasons.
|
||||||
|
|
||||||
- Saving notes and files now consumes less memory.
|
### Notes
|
||||||
- Data is no longer fully buffered in memory and written at once; instead, it is now written in each over-2MB increments.
|
- The checking algorithm implemented in 0.25.20 is also raised as PR (#237). And completely I merged it manually.
|
||||||
- Chunk caching is now more efficient.
|
- Sorry for lacking merging this PR, and let me say thanks to the great contribution, @bioluks !
|
||||||
- Chunks are now managed solely by their count (still maintained as LRU). If memory usage becomes excessive, they will be automatically released by the system-runtime.
|
- Known issues:
|
||||||
- Reverse-indexing is also no longer used. It is performed as scanning caches and act also as a WeakRef thinning.
|
- Sync on Editor save seems not to work correctly in some cases.
|
||||||
- Both of them (may) are effective for #692, #680, and some more.
|
- I am investigating this issue. If you have any information, please let me know.
|
||||||
|
|
||||||
### Changed
|
## 0.25.21
|
||||||
|
|
||||||
- `Incubate Chunks in Document` (also known as `Eden`) is now fully sunset.
|
13th October, 2025
|
||||||
- Existing chunks can still be read, but new ones will no longer be created.
|
|
||||||
- The `Compute revisions for chunks` setting has also been removed.
|
This release including 0.25.21.beta1 and 0.25.21.beta2.
|
||||||
- This feature is now always enabled and is no longer configurable (restoring the original behaviour).
|
|
||||||
- As mentioned, `Memory cache size (by total characters)` has been removed.
|
Apologies for taking a little time. I was seriously tackling this.
|
||||||
- The `Memory cache size (by total items)` setting is now the only option available (but it has 10x ratio compared to the previous version).
|
(Of course, being caught up in an unfamiliar structure due to personnel changes on my workplace played a part, but fortunately I have returned to a place where I can do research and development rather than production. Completely beside the point, though).
|
||||||
|
Now then, this time, moving away from 'convention over configuration', I have changed to a mechanism for manually binding events. This makes it much easier to leverage IDE assistance.
|
||||||
|
And, also, we are ready to separate `Features` and `APIs` from `Module`. Features are still in the module, but APIs will be moved to a Service layer. This will make it easier to maintain and extend the codebase in the future.
|
||||||
|
|
||||||
|
If you have found any issues, please let me know. I am now on the following:
|
||||||
|
- GitHub [Issues](https://github.com/vrtmrz/obsidian-livesync/issues) Excellent! May the other contributors will help you too.
|
||||||
|
- Twitter [@vorotamoroz](https://twitter.com/vorotamoroz) Quickest!
|
||||||
|
- Matrix [@vrtmrz:matrix.org](https://matrix.to/#/@vrtmrz:matrix.org) Also quick, and if you need to keep it private!
|
||||||
|
I am creating rooms too, but I'm struggling to figure out how to use them effectively because I cannot tell the difference of use-case between them and discussions. However, if you want to use Discord, this is a answer; We should on E2E encrypted platform.
|
||||||
|
## 0.25.21.beta2
|
||||||
|
|
||||||
|
8th October, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed wrong event type bindings (which caused some events not to be handled correctly).
|
||||||
|
- Fixed detected a timing issue in StorageEventManager
|
||||||
|
- When multiple events for the same file are fired in quick succession, metadata has been kept older information. This induces unexpected wrong notifications and write prevention.
|
||||||
|
|
||||||
|
## 0.25.21.beta1
|
||||||
|
|
||||||
|
6th October, 2025
|
||||||
|
|
||||||
### Refactored
|
### Refactored
|
||||||
|
|
||||||
- A significant refactoring of the core codebase is underway.
|
- Event handling now does not rely on 'convention over configuration'.
|
||||||
- This is part of our ongoing efforts to improve code maintainability, readability, and to unify interfaces.
|
- Services.ts now have a proper event handler registration system.
|
||||||
- Previously, complex files posed a risk due to a low bus factor. Fortunately, as our devices have become faster and more capable, we can now write code that is clearer and more maintainable (And not so much costs on performance).
|
|
||||||
- Hashing functions have been refactored into the `HashManager` class and its derived classes.
|
|
||||||
- Chunk splitting functions have been refactored into the `ContentSplitterCore` class and its derived classes.
|
|
||||||
- Change tracking functions have been refactored into the `ChangeManager` class.
|
|
||||||
- Chunk read/write functions have been refactored into the `ChunkManager` class.
|
|
||||||
- Fetching chunks on demand is now handled separately from the `ChunkManager` and chunk reading functions. Chunks are queued by the `ChunkManager` and then processed by the `ChunkFetcher`, simplifying the process and reducing unnecessary complexity.
|
|
||||||
- Then, local database access via `LiveSyncLocalDB` has been refactored to use the new classes.
|
|
||||||
- References to external sources from `commonlib` have been corrected.
|
|
||||||
- Type definitions in `types.ts` have been refined.
|
|
||||||
- Unit tests are being added incrementally.
|
|
||||||
- I am using `Deno` for testing, to simplify testing and coverage reporting.
|
|
||||||
- While this is not identical to the Obsidian environment, `jest` may also have limitations. It is certainly better than having no tests.
|
|
||||||
- In other words, recent manual scenario testing has highlighted some shortcomings.
|
|
||||||
- `pouchdb-test`, used for testing PouchDB with Deno, has been added, utilising the `memory` adapter.
|
|
||||||
|
|
||||||
Side note: Although class-oriented programming is sometimes considered an outdated style, However, I have come to re-evaluate it as valuable from the perspectives of maintainability and readability.
|
## 0.25.20
|
||||||
|
|
||||||
|
26th September, 2025
|
||||||
## 0.25.4
|
|
||||||
|
|
||||||
29th July, 2025
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- The PBKDF2Salt is no longer corrupted when attempting replication while the device is offline. (#686)
|
- Chunk fetching no longer reports errors when the fetched chunk could not be saved (#710).
|
||||||
- If this issue has already occurred, please use `Maintenance` -> `Rebuilding Operations (Remote Only)` -> `Overwrite Remote` and `Send` to resolve it.
|
- Just using the fetched chunk temporarily.
|
||||||
- Please perform this operation on the device that is most reliable.
|
- Chunk fetching reports errors when the fetched chunk is surely corrupted (#710, #712).
|
||||||
- I am so sorry for the inconvenience; there are no patching workarounds. The rebuilding operation is the only solution.
|
- It no longer detects files that the plug-in has modified.
|
||||||
- This issue only affects the encryption of the remote database and does not impact the local databases on any devices.
|
- It may reduce unnecessary file comparisons and unexpected file states.
|
||||||
- (Preventing synchronisation is by design and expected behaviour, even if it is sometimes inconvenient. This is also why we should avoid using workarounds; it is, admittedly, an excuse).
|
|
||||||
- In any case, we can unlock the remote from the warning dialogue on receiving devices. We are performing replication, instead of simple synchronisation at the expense of a little complexity (I would love to express thank you again for your every effort to manage and maintain the settings! Your all understanding saves our notes).
|
|
||||||
- This process may require considerable time and bandwidth (as usual), so please wait patiently and ensure a stable network connection.
|
|
||||||
|
|
||||||
### Side note
|
### Improved
|
||||||
|
|
||||||
The PBKDF2Salt will be referred to as the `Security Seed`, and it is used to derive the encryption key for replication. Therefore, it should be stored on the server prior to synchronisation. We apologise for the lack of explanation in previous updates!
|
- Now checking the remote database configuration respecting the CouchDB version (#714).
|
||||||
|
|
||||||
## 0.25.3
|
## 0.25.19
|
||||||
|
|
||||||
22nd July, 2025
|
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
|
### Fixed
|
||||||
|
|
||||||
- Now the `Doctor` at migration will save the configuration.
|
- 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.
|
||||||
|
|
||||||
## 0.25.2 ~~0.25.1~~
|
### Refactored
|
||||||
|
|
||||||
(0.25.1 was missed due to a mistake in the versioning process).
|
- Removed some unnecessary intermediate files.
|
||||||
19th July, 2025
|
|
||||||
|
|
||||||
### Refined and New Features
|
## 0.25.17
|
||||||
|
|
||||||
- Fetching the remote database on `RedFlag` now also retrieves remote configurations optionally.
|
16th September, 2025
|
||||||
- This is beneficial if we have already set up another device and wish to use the same configuration. We will see a much less frequent `Unmatched` dialogue.
|
|
||||||
- The setup wizard using Set-up URI and QR code has been improved.
|
|
||||||
- The message is now more user-friendly.
|
|
||||||
- The obsolete method (manual setting application) has been removed.
|
|
||||||
- The `Cancel` button has been added to the setup wizard.
|
|
||||||
- We can now fetch the remote configuration from the server if it exists, which is useful for adding new devices.
|
|
||||||
- Mostly same as a `RedFlag` fetching remote configuration.
|
|
||||||
- We can also use the `Doctor` to check and fix the imported (and fetched) configuration before applying it.
|
|
||||||
|
|
||||||
### Changes
|
### Fixed
|
||||||
|
|
||||||
- The Set-up URI is now encrypted with a new encryption algorithm (mostly the same as `V2`).
|
- No longer information-level logs have produced during toggling `Show only notifications` in the settings (#708).
|
||||||
- The new Set-up URI is not compatible with version 0.24.x or earlier.
|
- Ignoring filters for Hidden file sync now works correctly (#709).
|
||||||
|
|
||||||
## 0.25.0
|
### Refactored
|
||||||
|
|
||||||
19th July, 2025 (beta1 in 0.25.0-beta1, 13th July, 2025)
|
- Removed some unnecessary intermediate files.
|
||||||
|
|
||||||
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.
|
## 0.25.16
|
||||||
|
|
||||||
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).
|
4th September, 2025
|
||||||
|
|
||||||
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.
|
### 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).
|
||||||
|
|
||||||
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).
|
||||||
|
|||||||
260
updates_old.md
260
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.
|
||||||
|
|
||||||
@@ -9,6 +8,243 @@ I have now rewritten the E2EE code to be more robust and easier to understand. I
|
|||||||
|
|
||||||
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.
|
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.15
|
||||||
|
|
||||||
|
3rd September, 2025
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Now we can configure `forcePathStyle` for bucket synchronisation (#707).
|
||||||
|
|
||||||
|
## 0.25.14
|
||||||
|
|
||||||
|
2nd September, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Opening IndexedDB handling has been ensured.
|
||||||
|
- Migration check of corrupted files detection has been fixed.
|
||||||
|
- Now informs us about conflicted files as non-recoverable, but noted so.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
15th August, 2025
|
||||||
|
|
||||||
|
**Since the release of 0.25.6, there are two large problem. Please update immediately.**
|
||||||
|
|
||||||
|
- We may have corrupted some documents during the migration process. **Please check your documents on the wizard.**
|
||||||
|
- Due to a chunk ID assignment issue, some data has not been encrypted. **Please rebuild the database using Rebuild Everything** if you have enabled E2EE.
|
||||||
|
|
||||||
|
**_So, If you have enabled E2EE, please perform `Rebuild everything`. If not, please check your documents on the wizard._**
|
||||||
|
|
||||||
|
In next version, insecure chunk detection will be implemented.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Off-loaded chunking have been fixed to ensure proper functionality (#693).
|
||||||
|
- Chunk document ID assignment has been fixed.
|
||||||
|
- Replication prevention message during version up detection has been improved (#686).
|
||||||
|
- `Keep A` and `Keep B` on Conflict resolving dialogue has been renamed to `Use Base` and `Use Conflicted` (#691).
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Metadata and content-size unmatched documents are now detected and reported, prevented to be applied to the storage.
|
||||||
|
- This behaviour can be configured in `Patch` -> `Edge case addressing (Behaviour)` -> `Process files even if seems to be corrupted`
|
||||||
|
- Note: this toggle is for the direct-database-manipulation users.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- `Scan for Broken files` has been implemented on `Hatch` -> `TroubleShooting`.
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- Off-loaded processes have been refactored for the better maintainability.
|
||||||
|
- Files prefixed `bg.worker` are now work on the worker threads.
|
||||||
|
- Files prefixed `bgWorker.` are now also controls these worker threads. (I know what you want to say... I will rename them).
|
||||||
|
- Removed unused code.
|
||||||
|
|
||||||
|
## ~~0.25.5~~ 0.25.6
|
||||||
|
|
||||||
|
(0.25.5 has been withdrawn due to a bug in the `Fetch chunks on demand` feature).
|
||||||
|
|
||||||
|
9th August, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Storage scanning no longer occurs when `Suspend file watching` is enabled (including boot-sequence).
|
||||||
|
- This change improves safety when troubleshooting or fetching the remote database.
|
||||||
|
- `Fetch chunks on demand` is now working again (if you installed 0.25.5, other versions are not affected).
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Saving notes and files now consumes less memory.
|
||||||
|
- Data is no longer fully buffered in memory and written at once; instead, it is now written in each over-2MB increments.
|
||||||
|
- Chunk caching is now more efficient.
|
||||||
|
- Chunks are now managed solely by their count (still maintained as LRU). If memory usage becomes excessive, they will be automatically released by the system-runtime.
|
||||||
|
- Reverse-indexing is also no longer used. It is performed as scanning caches and act also as a WeakRef thinning.
|
||||||
|
- Both of them (may) are effective for #692, #680, and some more.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `Incubate Chunks in Document` (also known as `Eden`) is now fully sunset.
|
||||||
|
- Existing chunks can still be read, but new ones will no longer be created.
|
||||||
|
- The `Compute revisions for chunks` setting has also been removed.
|
||||||
|
- This feature is now always enabled and is no longer configurable (restoring the original behaviour).
|
||||||
|
- As mentioned, `Memory cache size (by total characters)` has been removed.
|
||||||
|
- The `Memory cache size (by total items)` setting is now the only option available (but it has 10x ratio compared to the previous version).
|
||||||
|
|
||||||
|
### Refactored
|
||||||
|
|
||||||
|
- A significant refactoring of the core codebase is underway.
|
||||||
|
- This is part of our ongoing efforts to improve code maintainability, readability, and to unify interfaces.
|
||||||
|
- Previously, complex files posed a risk due to a low bus factor. Fortunately, as our devices have become faster and more capable, we can now write code that is clearer and more maintainable (And not so much costs on performance).
|
||||||
|
- Hashing functions have been refactored into the `HashManager` class and its derived classes.
|
||||||
|
- Chunk splitting functions have been refactored into the `ContentSplitterCore` class and its derived classes.
|
||||||
|
- Change tracking functions have been refactored into the `ChangeManager` class.
|
||||||
|
- Chunk read/write functions have been refactored into the `ChunkManager` class.
|
||||||
|
- Fetching chunks on demand is now handled separately from the `ChunkManager` and chunk reading functions. Chunks are queued by the `ChunkManager` and then processed by the `ChunkFetcher`, simplifying the process and reducing unnecessary complexity.
|
||||||
|
- Then, local database access via `LiveSyncLocalDB` has been refactored to use the new classes.
|
||||||
|
- References to external sources from `commonlib` have been corrected.
|
||||||
|
- Type definitions in `types.ts` have been refined.
|
||||||
|
- Unit tests are being added incrementally.
|
||||||
|
- I am using `Deno` for testing, to simplify testing and coverage reporting.
|
||||||
|
- While this is not identical to the Obsidian environment, `jest` may also have limitations. It is certainly better than having no tests.
|
||||||
|
- In other words, recent manual scenario testing has highlighted some shortcomings.
|
||||||
|
- `pouchdb-test`, used for testing PouchDB with Deno, has been added, utilising the `memory` adapter.
|
||||||
|
|
||||||
|
Side note: Although class-oriented programming is sometimes considered an outdated style, However, I have come to re-evaluate it as valuable from the perspectives of maintainability and readability.
|
||||||
|
|
||||||
|
## 0.25.4
|
||||||
|
|
||||||
|
29th July, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The PBKDF2Salt is no longer corrupted when attempting replication while the device is offline. (#686)
|
||||||
|
- If this issue has already occurred, please use `Maintenance` -> `Rebuilding Operations (Remote Only)` -> `Overwrite Remote` and `Send` to resolve it.
|
||||||
|
- Please perform this operation on the device that is most reliable.
|
||||||
|
- I am so sorry for the inconvenience; there are no patching workarounds. The rebuilding operation is the only solution.
|
||||||
|
- This issue only affects the encryption of the remote database and does not impact the local databases on any devices.
|
||||||
|
- (Preventing synchronisation is by design and expected behaviour, even if it is sometimes inconvenient. This is also why we should avoid using workarounds; it is, admittedly, an excuse).
|
||||||
|
- In any case, we can unlock the remote from the warning dialogue on receiving devices. We are performing replication, instead of simple synchronisation at the expense of a little complexity (I would love to express thank you again for your every effort to manage and maintain the settings! Your all understanding saves our notes).
|
||||||
|
- This process may require considerable time and bandwidth (as usual), so please wait patiently and ensure a stable network connection.
|
||||||
|
|
||||||
|
### Side note
|
||||||
|
|
||||||
|
The PBKDF2Salt will be referred to as the `Security Seed`, and it is used to derive the encryption key for replication. Therefore, it should be stored on the server prior to synchronisation. We apologise for the lack of explanation in previous updates!
|
||||||
|
|
||||||
|
## 0.25.3
|
||||||
|
|
||||||
|
22nd July, 2025
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Now the `Doctor` at migration will save the configuration.
|
||||||
|
|
||||||
|
## 0.25.2 ~~0.25.1~~
|
||||||
|
|
||||||
|
(0.25.1 was missed due to a mistake in the versioning process).
|
||||||
|
19th July, 2025
|
||||||
|
|
||||||
|
### Refined and New Features
|
||||||
|
|
||||||
|
- Fetching the remote database on `RedFlag` now also retrieves remote configurations optionally.
|
||||||
|
- This is beneficial if we have already set up another device and wish to use the same configuration. We will see a much less frequent `Unmatched` dialogue.
|
||||||
|
- The setup wizard using Set-up URI and QR code has been improved.
|
||||||
|
- The message is now more user-friendly.
|
||||||
|
- The obsolete method (manual setting application) has been removed.
|
||||||
|
- The `Cancel` button has been added to the setup wizard.
|
||||||
|
- We can now fetch the remote configuration from the server if it exists, which is useful for adding new devices.
|
||||||
|
- Mostly same as a `RedFlag` fetching remote configuration.
|
||||||
|
- We can also use the `Doctor` to check and fix the imported (and fetched) configuration before applying it.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## 0.25.0
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- The encryption algorithm now uses HKDF with a master key.
|
- The encryption algorithm now uses HKDF with a master key.
|
||||||
@@ -38,8 +274,6 @@ As a result, this is the first time in a while that forward compatibility has be
|
|||||||
- `couchdb_utils.ts` has been separated into several explicitly named files.
|
- `couchdb_utils.ts` has been separated into several explicitly named files.
|
||||||
- Some missing functions in `bgWorker.mock.ts` have been added.
|
- Some missing functions in `bgWorker.mock.ts` have been added.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 0.24.0
|
## 0.24.0
|
||||||
|
|
||||||
I know that we have been waiting for a long time. It is finally released!
|
I know that we have been waiting for a long time. It is finally released!
|
||||||
@@ -226,7 +460,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
|
||||||
@@ -304,8 +537,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.
|
||||||
@@ -314,8 +547,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.
|
||||||
|
|
||||||
@@ -337,8 +570,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
|
||||||
|
|
||||||
@@ -379,7 +612,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
|
||||||
@@ -460,7 +692,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.
|
||||||
@@ -476,7 +707,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