Compare commits

...

19 Commits

Author SHA1 Message Date
vorotamoroz
b337a05b5a bump 2023-11-27 07:13:15 +00:00
vorotamoroz
9ea6bee9d1 - Fixed:
- No longer files are broken while rebuilding.
    - Now, Large binary files can be written correctly on a mobile platform.
    - Any decoding errors now make zero-byte files.
  - Modified:
    - All files are processed sequentially for each.
2023-11-27 06:55:55 +00:00
vorotamoroz
9747c26d50 bump 2023-11-25 02:22:26 +09:00
vorotamoroz
bb4b764586 - Fixed:
- No more infinity loops on larger files.
    - Show message on decode error.
  - Refactored:
    - Fixed to avoid obsolete global variables.
2023-11-25 02:21:44 +09:00
vorotamoroz
279b4b41e5 bump 2023-11-24 10:32:46 +00:00
vorotamoroz
b644fb791d - Changes and performance improvements:
- Now the saving files are processed by Blob.
    - The V2-Format has been reverted.
    - New encoding format has been enabled in default.
    - WARNING: Since this version, the compatibilities with older Filesystem LiveSync have been lost.
2023-11-24 10:31:58 +00:00
vorotamoroz
ac9428e96b Fixed
- To better replication, path obfuscation is now deterministic even if with E2EE.
2023-11-15 08:44:03 +00:00
vorotamoroz
280d9e1dd9 Fixed: Fixed the issue of TOML editing. 2023-11-07 01:07:58 +00:00
vorotamoroz
f7209e566c bump 2023-10-24 10:07:29 +01:00
vorotamoroz
4a9ab2d1de Fixed:
- No longer enumerating file names is broken.
2023-10-24 10:07:17 +01:00
vorotamoroz
cb74b5ee93 - Fixed
- Now empty file could be decoded.
    - Local files are no longer pre-saved before fetching from a remote database.
    - No longer deadlock while applying customisation sync.
    - Configuration with multiple files is now able to be applied correctly.
    - Deleting folder propagation now works without enabling the use of a trash bin.
2023-10-24 09:54:56 +01:00
vorotamoroz
60eecd7001 bump 2023-10-17 12:00:59 +09:00
vorotamoroz
4bd7b54bcd Fixed:
- Now the files which having digit or character prefixes in the path will not be ignored.
2023-10-17 12:00:19 +09:00
vorotamoroz
8923c73d1b bump 2023-10-14 23:08:34 +09:00
vorotamoroz
11e64b13e2 The text-input-dialogue is no longer broken. 2023-10-14 23:07:51 +09:00
vorotamoroz
983d9248ed bump 2023-10-13 04:23:59 +01:00
vorotamoroz
7240e84328 - New feature:
- We can launch Customization sync from the Ribbon if we enable it.
- Fixed:
  - Setup URI is now back to the previous spec; be encrypted by V1.
  - The Settings dialogue is now registered at the beginning of the start-up process.
- Improved:
  - Enumerating documents has been faster.
2023-10-13 04:22:24 +01:00
vorotamoroz
0d55ae2532 Merge pull request #298 from LiamSwayne/patch-1
Grammar fix
2023-10-04 13:07:42 +09:00
Liam Swayne
dbd284f5dd grammar fix 2023-10-03 22:26:30 -04:00
22 changed files with 550 additions and 406 deletions

View File

@@ -66,7 +66,7 @@ Synchronization status is shown in statusbar.
- ↓ Downloaded chunks and metadata - ↓ Downloaded chunks and metadata
- ⏳ Number of pending processes - ⏳ Number of pending processes
- 🧩 Number of files waiting for their chunks. - 🧩 Number of files waiting for their chunks.
If you have deleted or renamed files, please wait until ⏳ icon disappeared. If you have deleted or renamed files, please wait until ⏳ icon disappears.
## Hints ## Hints

View File

@@ -3,8 +3,8 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": { "metadata": {
"id": "view-in-github", "colab_type": "text",
"colab_type": "text" "id": "view-in-github"
}, },
"source": [ "source": [
"<a href=\"https://colab.research.google.com/gist/vrtmrz/37c3efd7842e49947aaaa7f665e5020a/deploy_couchdb_to_flyio_v2_with_swap.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" "<a href=\"https://colab.research.google.com/gist/vrtmrz/37c3efd7842e49947aaaa7f665e5020a/deploy_couchdb_to_flyio_v2_with_swap.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
@@ -12,15 +12,16 @@
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {
"id": "HiRV7G8Gk1Rs"
},
"source": [ "source": [
"History:\n", "History:\n",
"- 18, May, 2023: Initial.\n", "- 18, May, 2023: Initial.\n",
"- 19, Jun., 2023: Patched for enabling swap.\n", "- 19, Jun., 2023: Patched for enabling swap.\n",
"- 22, Aug., 2023: Generating Setup-URI implemented." "- 22, Aug., 2023: Generating Setup-URI implemented.\n",
], "- 7, Nov., 2023: Fixed the issue of TOML editing."
"metadata": { ]
"id": "HiRV7G8Gk1Rs"
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -45,7 +46,7 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"# Delete once\n", "# Delete once (Do not care about `cannot remove './fly.toml': No such file or directory`)\n",
"!rm ./fly.toml" "!rm ./fly.toml"
] ]
}, },
@@ -78,15 +79,15 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": null,
"# Check the toml once.\n",
"!cat fly.toml"
],
"metadata": { "metadata": {
"id": "2RSoO9o-i2TT" "id": "2RSoO9o-i2TT"
}, },
"execution_count": null, "outputs": [],
"outputs": [] "source": [
"# Check the toml once.\n",
"!cat fly.toml"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -96,52 +97,45 @@
}, },
"outputs": [], "outputs": [],
"source": [ "source": [
"# Modify fly.toml\n", "# Modify the TOML and generate Dockerfile\n",
"## Port modification\n", "!pip install mergedeep\n",
"!sed -i 's/8080/5984/g' fly.toml\n", "from mergedeep import merge\n",
"## Add user into.\n", "import toml\n",
"!echo -e \"\\n[env]\\n COUCHDB_USER = \\\"${couchUser}\\\"\" >> ./fly.toml\n", "fly = toml.load('fly.toml')\n",
"## Set the location of an ini file which to save configurations persistently via erlang flags.\n", "override = {\n",
"!echo -e \"\\nERL_FLAGS=\\\"-couch_ini /opt/couchdb/etc/default.ini /opt/couchdb/etc/default.d/ /opt/couchdb/etc/local.d /opt/couchdb/etc/local.ini /opt/couchdb/data/persistence.ini\\\"\" >> ./fly.toml\n", " \"http_service\":{\n",
"## Mounting volumes to store data and ini file.\n", " \"internal_port\":5984\n",
"!echo -e \"\\n[mounts]\\n source=\\\"couchdata\\\"\\n destination=\\\"/opt/couchdb/data\\\"\" >> ./fly.toml\n", " },\n",
"!cat fly.toml" " \"build\":{\n",
] " \"dockerfile\":\"./Dockerfile\"\n",
}, " },\n",
{ " \"mounts\":{\n",
"cell_type": "code", " \"source\":\"couchdata\",\n",
"source": [ " \"destination\":\"/opt/couchdb/data\"\n",
" },\n",
" \"env\":{\n",
" \"COUCHDB_USER\":os.environ['couchUser'],\n",
" \"ERL_FLAGS\":\"-couch_ini /opt/couchdb/etc/default.ini /opt/couchdb/etc/default.d/ /opt/couchdb/etc/local.d /opt/couchdb/etc/local.ini /opt/couchdb/data/persistence.ini\",\n",
" }\n",
"}\n",
"out = merge(fly,override)\n",
"with open('fly.toml', 'wt') as fp:\n",
" toml.dump(out, fp)\n",
" fp.close()\n",
"\n",
"# Make the Dockerfile to modify the permission of the ini file. If you want to use a specific version, you should change `latest` here.\n", "# Make the Dockerfile to modify the permission of the ini file. If you want to use a specific version, you should change `latest` here.\n",
"!echo -e \"\\n[build]\\n dockerfile = \\\"./Dockerfile\\\"\" >> ./fly.toml" "dockerfile = '''FROM couchdb:latest\n",
], "RUN sed -i '2itouch /opt/couchdb/data/persistence.ini && chmod +w /opt/couchdb/data/persistence.ini && fallocate -l 512M /swapfile && chmod 0600 /swapfile && mkswap /swapfile && echo 10 > /proc/sys/vm/swappiness && swapon /swapfile && echo 1 > /proc/sys/vm/overcommit_memory' /docker-entrypoint.sh\n",
"metadata": { "'''\n",
"id": "LQPsZ_dYxkTu" "with open(\"./Dockerfile\",\"wt\") as fp:\n",
}, " fp.write(dockerfile)\n",
"execution_count": null, " fp.close()\n",
"outputs": [] "\n",
}, "!echo ------\n",
{ "!cat fly.toml\n",
"cell_type": "code", "!echo ------\n",
"source": [ "!cat Dockerfile"
"!echo -e \"FROM couchdb:latest\\nRUN sed -i '2itouch /opt/couchdb/data/persistence.ini && chmod +w /opt/couchdb/data/persistence.ini && fallocate -l 512M /swapfile && chmod 0600 /swapfile && mkswap /swapfile && echo 10 > /proc/sys/vm/swappiness && swapon /swapfile && echo 1 > /proc/sys/vm/overcommit_memory' /docker-entrypoint.sh\" > ./Dockerfile" ]
],
"metadata": {
"id": "44cBeGJ9on5i"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Check dockerfile\n",
"!cat ./Dockerfile"
],
"metadata": {
"id": "ai2R3BbpxRSe"
},
"execution_count": null,
"outputs": []
}, },
{ {
"cell_type": "code", "cell_type": "code",
@@ -189,20 +183,27 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null,
"metadata": {
"id": "cGlSzVqlQG_z"
},
"outputs": [],
"source": [ "source": [
"# Finish setting up the CouchDB\n", "# Finish setting up the CouchDB\n",
"# Please repeat until the request is completed without error messages\n", "# Please repeat until the request is completed without error messages\n",
"# i.e., You have to redo this block while \"curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to xxxx\" is showing.\n", "# i.e., You have to redo this block while \"curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to xxxx\" is showing.\n",
"#\n",
"# Note: A few minutes might be required to be booted.\n",
"!curl -X POST \"${couchHost}/_cluster_setup\" -H \"Content-Type: application/json\" -d \"{\\\"action\\\":\\\"enable_single_node\\\",\\\"username\\\":\\\"${couchUser}\\\",\\\"password\\\":\\\"${couchPwd}\\\",\\\"bind_address\\\":\\\"0.0.0.0\\\",\\\"port\\\":5984,\\\"singlenode\\\":true}\" --user \"${couchUser}:${couchPwd}\"" "!curl -X POST \"${couchHost}/_cluster_setup\" -H \"Content-Type: application/json\" -d \"{\\\"action\\\":\\\"enable_single_node\\\",\\\"username\\\":\\\"${couchUser}\\\",\\\"password\\\":\\\"${couchPwd}\\\",\\\"bind_address\\\":\\\"0.0.0.0\\\",\\\"port\\\":5984,\\\"singlenode\\\":true}\" --user \"${couchUser}:${couchPwd}\""
], ]
"metadata": {
"id": "cGlSzVqlQG_z"
},
"execution_count": null,
"outputs": []
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null,
"metadata": {
"id": "JePzrsHypY18"
},
"outputs": [],
"source": [ "source": [
"# Please repeat until all lines are completed without error messages\n", "# Please repeat until all lines are completed without error messages\n",
"!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/chttpd/require_valid_user\" -H \"Content-Type: application/json\" -d '\"true\"' --user \"${couchUser}:${couchPwd}\"\n", "!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/chttpd/require_valid_user\" -H \"Content-Type: application/json\" -d '\"true\"' --user \"${couchUser}:${couchPwd}\"\n",
@@ -214,28 +215,28 @@
"!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/couchdb/max_document_size\" -H \"Content-Type: application/json\" -d '\"50000000\"' --user \"${couchUser}:${couchPwd}\"\n", "!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/couchdb/max_document_size\" -H \"Content-Type: application/json\" -d '\"50000000\"' --user \"${couchUser}:${couchPwd}\"\n",
"!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/cors/credentials\" -H \"Content-Type: application/json\" -d '\"true\"' --user \"${couchUser}:${couchPwd}\"\n", "!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/cors/credentials\" -H \"Content-Type: application/json\" -d '\"true\"' --user \"${couchUser}:${couchPwd}\"\n",
"!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/cors/origins\" -H \"Content-Type: application/json\" -d '\"app://obsidian.md,capacitor://localhost,http://localhost\"' --user \"${couchUser}:${couchPwd}\"" "!curl -X PUT \"${couchHost}/_node/nonode@nohost/_config/cors/origins\" -H \"Content-Type: application/json\" -d '\"app://obsidian.md,capacitor://localhost,http://localhost\"' --user \"${couchUser}:${couchPwd}\""
], ]
"metadata": {
"id": "JePzrsHypY18"
},
"execution_count": null,
"outputs": []
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {
"id": "YfSOomsoXbGS"
},
"source": [ "source": [
"Now, our CouchDB has been surely installed and configured. Cheers!\n", "Now, our CouchDB has been surely installed and configured. Cheers!\n",
"\n", "\n",
"In the steps that follow, create a setup-URI.\n", "In the steps that follow, create a setup-URI.\n",
"\n", "\n",
"This URI could be imported directly into Self-hosted LiveSync, to configure the use of the CouchDB which we configured now." "This URI could be imported directly into Self-hosted LiveSync, to configure the use of the CouchDB which we configured now."
], ]
"metadata": {
"id": "YfSOomsoXbGS"
}
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null,
"metadata": {
"id": "416YncOqXdNn"
},
"outputs": [],
"source": [ "source": [
"# Database config\n", "# Database config\n",
"import random, string\n", "import random, string\n",
@@ -250,39 +251,39 @@
"\n", "\n",
"print(\"Your database:\"+os.environ['database'])\n", "print(\"Your database:\"+os.environ['database'])\n",
"print(\"Your passphrase:\"+os.environ['passphrase'])" "print(\"Your passphrase:\"+os.environ['passphrase'])"
], ]
"metadata": {
"id": "416YncOqXdNn"
},
"execution_count": null,
"outputs": []
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": null,
"# Install deno for make setup uri\n",
"!curl -fsSL https://deno.land/x/install/install.sh | sh"
],
"metadata": { "metadata": {
"id": "C4d7C0HAXgsr" "id": "C4d7C0HAXgsr"
}, },
"execution_count": null, "outputs": [],
"outputs": [] "source": [
"# Install deno for make setup uri\n",
"!curl -fsSL https://deno.land/x/install/install.sh | sh"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"source": [ "execution_count": null,
"# Fetch module for encrypting a Setup URI\n",
"!curl -o encrypt.ts https://gist.githubusercontent.com/vrtmrz/f9d1d95ee2ca3afa1a924a2c6759b854/raw/d7a070d864a6f61403d8dc74208238d5741aeb5a/encrypt.ts"
],
"metadata": { "metadata": {
"id": "hQL_Dx-PXise" "id": "hQL_Dx-PXise"
}, },
"execution_count": null, "outputs": [],
"outputs": [] "source": [
"# Fetch module for encrypting a Setup URI\n",
"!curl -o encrypt.ts https://gist.githubusercontent.com/vrtmrz/f9d1d95ee2ca3afa1a924a2c6759b854/raw/d7a070d864a6f61403d8dc74208238d5741aeb5a/encrypt.ts"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null,
"metadata": {
"id": "o0gX_thFXlIZ"
},
"outputs": [],
"source": [ "source": [
"# Make buttons!\n", "# Make buttons!\n",
"from IPython.display import HTML\n", "from IPython.display import HTML\n",
@@ -294,29 +295,24 @@
"else:\n", "else:\n",
" result = \"Failed to encrypt the setup URI\"\n", " result = \"Failed to encrypt the setup URI\"\n",
"result" "result"
], ]
"metadata": {
"id": "o0gX_thFXlIZ"
},
"execution_count": null,
"outputs": []
} }
], ],
"metadata": { "metadata": {
"colab": { "colab": {
"provenance": [], "include_colab_link": true,
"private_outputs": true, "private_outputs": true,
"include_colab_link": true "provenance": []
}, },
"gpuClass": "standard",
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3",
"name": "python3" "name": "python3"
}, },
"language_info": { "language_info": {
"name": "python" "name": "python"
}, }
"gpuClass": "standard"
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 0
} }

View File

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

18
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.20.2", "version": "0.21.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.20.2", "version": "0.21.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
@@ -36,7 +36,7 @@
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.28.0", "eslint-plugin-import": "^2.28.0",
"events": "^3.3.0", "events": "^3.3.0",
"obsidian": "^1.3.5", "obsidian": "^1.4.11",
"postcss": "^8.4.27", "postcss": "^8.4.27",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.1", "pouchdb-adapter-http": "^8.0.1",
@@ -3223,9 +3223,9 @@
} }
}, },
"node_modules/obsidian": { "node_modules/obsidian": {
"version": "1.4.0", "version": "1.4.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.0.tgz", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz",
"integrity": "sha512-fsZMPlxgflGSBSP6P4BjQi5+0MqZl3h6FEDEZ3CNnweNdDw0doyqN3FMO/PGWfuxPT77WicVwUxekuI3e6eCGg==", "integrity": "sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/codemirror": "5.60.8", "@types/codemirror": "5.60.8",
@@ -6913,9 +6913,9 @@
} }
}, },
"obsidian": { "obsidian": {
"version": "1.4.0", "version": "1.4.11",
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.0.tgz", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.4.11.tgz",
"integrity": "sha512-fsZMPlxgflGSBSP6P4BjQi5+0MqZl3h6FEDEZ3CNnweNdDw0doyqN3FMO/PGWfuxPT77WicVwUxekuI3e6eCGg==", "integrity": "sha512-BCVYTvaXxElJMl6MMbDdY/CGK+aq18SdtDY/7vH8v6BxCBQ6KF4kKxL0vG9UZ0o5qh139KpUoJHNm+6O5dllKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/codemirror": "5.60.8", "@types/codemirror": "5.60.8",

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-livesync", "name": "obsidian-livesync",
"version": "0.20.2", "version": "0.21.2",
"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",
@@ -17,9 +17,9 @@
"@types/diff-match-patch": "^1.0.32", "@types/diff-match-patch": "^1.0.32",
"@types/node": "^20.2.5", "@types/node": "^20.2.5",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"@types/pouchdb-browser": "^6.1.3",
"@types/pouchdb-adapter-http": "^6.1.3", "@types/pouchdb-adapter-http": "^6.1.3",
"@types/pouchdb-adapter-idb": "^6.1.4", "@types/pouchdb-adapter-idb": "^6.1.4",
"@types/pouchdb-browser": "^6.1.3",
"@types/pouchdb-core": "^7.0.11", "@types/pouchdb-core": "^7.0.11",
"@types/pouchdb-mapreduce": "^6.1.7", "@types/pouchdb-mapreduce": "^6.1.7",
"@types/pouchdb-replication": "^6.4.4", "@types/pouchdb-replication": "^6.4.4",
@@ -33,7 +33,7 @@
"eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.28.0", "eslint-plugin-import": "^2.28.0",
"events": "^3.3.0", "events": "^3.3.0",
"obsidian": "^1.3.5", "obsidian": "^1.4.11",
"postcss": "^8.4.27", "postcss": "^8.4.27",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"pouchdb-adapter-http": "^8.0.1", "pouchdb-adapter-http": "^8.0.1",
@@ -60,4 +60,4 @@
"xxhash-wasm": "0.4.2", "xxhash-wasm": "0.4.2",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
} }
} }

View File

@@ -1,13 +1,13 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Notice, type PluginManifest, parseYaml, normalizePath } from "./deps"; import { Notice, type PluginManifest, parseYaml, normalizePath } from "./deps";
import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry } from "./lib/src/types"; import type { EntryDoc, LoadedEntry, InternalFileEntry, FilePathWithPrefix, FilePath, DocumentID, AnyEntry, SavingEntry } from "./lib/src/types";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "./lib/src/types"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE } from "./lib/src/types";
import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types"; import { ICXHeader, PERIODIC_PLUGIN_SWEEP, } from "./types";
import { delay, getDocData } from "./lib/src/utils"; import { createTextBlob, delay, getDocData } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { WrappedNotice } from "./lib/src/wrapper"; import { WrappedNotice } from "./lib/src/wrapper";
import { readString, crc32CKHash, decodeBinary, encodeBinary } from "./lib/src/strbin"; import { readString, crc32CKHash, decodeBinary, arrayBufferToBase64 } from "./lib/src/strbin";
import { serialized } from "./lib/src/lock"; import { serialized } from "./lib/src/lock";
import { LiveSyncCommands } from "./LiveSyncCommands"; import { LiveSyncCommands } from "./LiveSyncCommands";
import { stripAllPrefixes } from "./lib/src/path"; import { stripAllPrefixes } from "./lib/src/path";
@@ -111,6 +111,7 @@ function deserialize2(str: string): PluginDataEx {
data data
} }
) )
tokens.nextLine();
} while (filename); } while (filename);
return result; return result;
} }
@@ -352,11 +353,13 @@ export class ConfigSync extends LiveSyncCommands {
} }
} }
Logger(`All files enumerated`, logLevel, "get-plugins"); Logger(`All files enumerated`, logLevel, "get-plugins");
pluginIsEnumerating.set(false);
this.createMissingConfigurationEntry(); this.createMissingConfigurationEntry();
} finally { } finally {
pluginIsEnumerating.set(false); pluginIsEnumerating.set(false);
} }
}); });
pluginIsEnumerating.set(false);
}); });
// return entries; // return entries;
} }
@@ -415,9 +418,9 @@ export class ConfigSync extends LiveSyncCommands {
await this.ensureDirectoryEx(path); await this.ensureDirectoryEx(path);
if (!content) { if (!content) {
const dt = decodeBinary(f.data); const dt = decodeBinary(f.data);
await this.app.vault.adapter.writeBinary(path, dt); await this.vaultAccess.adapterWrite(path, dt);
} else { } else {
await this.app.vault.adapter.write(path, content); await this.vaultAccess.adapterWrite(path, content);
} }
Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`); Logger(`Applying ${f.filename} of ${data.displayName || data.name}.. Done`);
@@ -537,16 +540,16 @@ export class ConfigSync extends LiveSyncCommands {
recentProcessedInternalFiles = [] as string[]; recentProcessedInternalFiles = [] as string[];
async makeEntryFromFile(path: FilePath): Promise<false | PluginDataExFile> { async makeEntryFromFile(path: FilePath): Promise<false | PluginDataExFile> {
const stat = await this.app.vault.adapter.stat(path); const stat = await this.vaultAccess.adapterStat(path);
let version: string | undefined; let version: string | undefined;
let displayName: string | undefined; let displayName: string | undefined;
if (!stat) { if (!stat) {
return false; return false;
} }
const contentBin = await this.app.vault.adapter.readBinary(path); const contentBin = await this.vaultAccess.adapterReadBinary(path);
let content: string[]; let content: string[];
try { try {
content = await encodeBinary(contentBin, this.settings.useV1); content = await arrayBufferToBase64(contentBin);
if (path.toLowerCase().endsWith("/manifest.json")) { if (path.toLowerCase().endsWith("/manifest.json")) {
const v = readString(new Uint8Array(contentBin)); const v = readString(new Uint8Array(contentBin));
try { try {
@@ -649,10 +652,10 @@ export class ConfigSync extends LiveSyncCommands {
return return
} }
const content = serialize(dt); const content = createTextBlob(serialize(dt));
try { try {
const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false); const old = await this.localDatabase.getDBEntryMeta(prefixedFileName, null, false);
let saveData: LoadedEntry; let saveData: SavingEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
_id: id, _id: id,
@@ -661,7 +664,7 @@ export class ConfigSync extends LiveSyncCommands {
mtime, mtime,
ctime: mtime, ctime: mtime,
datatype: "newnote", datatype: "newnote",
size: content.length, size: content.size,
children: [], children: [],
deleted: false, deleted: false,
type: "newnote", type: "newnote",
@@ -676,7 +679,7 @@ export class ConfigSync extends LiveSyncCommands {
...old, ...old,
data: content, data: content,
mtime, mtime,
size: content.length, size: content.size,
datatype: "newnote", datatype: "newnote",
children: [], children: [],
deleted: false, deleted: false,
@@ -698,7 +701,7 @@ export class ConfigSync extends LiveSyncCommands {
async watchVaultRawEventsAsync(path: FilePath) { async watchVaultRawEventsAsync(path: FilePath) {
if (!this.settings.usePluginSync) return false; if (!this.settings.usePluginSync) return false;
if (!this.isTargetPath(path)) return false; if (!this.isTargetPath(path)) return false;
const stat = await this.app.vault.adapter.stat(path); const stat = await this.vaultAccess.adapterStat(path);
// Make sure that target is a file. // Make sure that target is a file.
if (stat && stat.type != "file") if (stat && stat.type != "file")
return false; return false;

View File

@@ -1,7 +1,7 @@
import { normalizePath, type PluginManifest } from "./deps"; import { normalizePath, type PluginManifest } from "./deps";
import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type InternalFileEntry, type FilePathWithPrefix, type FilePath, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE, MODE_SELECTIVE, MODE_PAUSED, type SavingEntry } from "./lib/src/types";
import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types"; import { type InternalFileInfo, ICHeader, ICHeaderEnd } from "./types";
import { Parallels, delay, isDocContentSame } from "./lib/src/utils"; import { Parallels, createBinaryBlob, delay, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { scheduleTask, isInternalMetadata, PeriodicProcessor } from "./utils"; import { scheduleTask, isInternalMetadata, PeriodicProcessor } from "./utils";
@@ -103,7 +103,7 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE); Logger(`Hidden file skipped: ${path} is synchronized in customization sync.`, LOG_LEVEL_VERBOSE);
return; return;
} }
const stat = await this.app.vault.adapter.stat(path); const stat = await this.vaultAccess.adapterStat(path);
// sometimes folder is coming. // sometimes folder is coming.
if (stat && stat.type != "file") if (stat && stat.type != "file")
return; return;
@@ -171,12 +171,12 @@ export class HiddenFileSync extends LiveSyncCommands {
if (result) { if (result) {
Logger(`Object merge:${path}`, LOG_LEVEL_INFO); Logger(`Object merge:${path}`, LOG_LEVEL_INFO);
const filename = stripAllPrefixes(path); const filename = stripAllPrefixes(path);
const isExists = await this.app.vault.adapter.exists(filename); const isExists = await this.plugin.vaultAccess.adapterExists(filename);
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
} }
await this.app.vault.adapter.write(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.app.vault.adapter.stat(filename); const stat = await this.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }); await this.storeInternalFileToDatabase({ path: filename, ...stat });
await this.extractInternalFileFromDatabase(filename); await this.extractInternalFileFromDatabase(filename);
await this.localDatabase.removeRaw(id, revB); await this.localDatabase.removeRaw(id, revB);
@@ -408,10 +408,10 @@ export class HiddenFileSync extends LiveSyncCommands {
const id = await this.path2id(file.path, ICHeader); const id = await this.path2id(file.path, ICHeader);
const prefixedFileName = addPrefix(file.path, ICHeader); const prefixedFileName = addPrefix(file.path, ICHeader);
const contentBin = await this.app.vault.adapter.readBinary(file.path); const contentBin = await this.plugin.vaultAccess.adapterReadBinary(file.path);
let content: string[]; let content: Blob;
try { try {
content = await encodeBinary(contentBin, this.settings.useV1); content = createBinaryBlob(contentBin);
} catch (ex) { } catch (ex) {
Logger(`The file ${file.path} could not be encoded`); Logger(`The file ${file.path} could not be encoded`);
Logger(ex, LOG_LEVEL_VERBOSE); Logger(ex, LOG_LEVEL_VERBOSE);
@@ -421,7 +421,7 @@ export class HiddenFileSync extends LiveSyncCommands {
return await serialized("file-" + prefixedFileName, async () => { return await serialized("file-" + prefixedFileName, async () => {
try { try {
const old = await this.localDatabase.getDBEntry(prefixedFileName, null, false, false); const old = await this.localDatabase.getDBEntry(prefixedFileName, null, false, false);
let saveData: LoadedEntry; let saveData: SavingEntry;
if (old === false) { if (old === false) {
saveData = { saveData = {
_id: id, _id: id,
@@ -511,7 +511,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
async extractInternalFileFromDatabase(filename: FilePath, force = false) { async extractInternalFileFromDatabase(filename: FilePath, force = false) {
const isExists = await this.app.vault.adapter.exists(filename); const isExists = await this.plugin.vaultAccess.adapterExists(filename);
const prefixedFileName = addPrefix(filename, ICHeader); const prefixedFileName = addPrefix(filename, ICHeader);
if (await this.plugin.isIgnoredByIgnoreFiles(filename)) { if (await this.plugin.isIgnoredByIgnoreFiles(filename)) {
return; return;
@@ -534,7 +534,7 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden) Deleted on DB, but the file is already not found on storage.`);
} else { } else {
Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`); Logger(`STORAGE <x- DB:${filename}: deleted (hidden).`);
await this.app.vault.adapter.remove(filename); await this.plugin.vaultAccess.adapterRemove(filename);
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -547,7 +547,7 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
await this.app.vault.adapter.writeBinary(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -558,13 +558,13 @@ export class HiddenFileSync extends LiveSyncCommands {
Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`); Logger(`STORAGE <-- DB:${filename}: written (hidden,new${force ? ", force" : ""})`);
return true; return true;
} else { } else {
const contentBin = await this.app.vault.adapter.readBinary(filename); const contentBin = await this.plugin.vaultAccess.adapterReadBinary(filename);
const content = await encodeBinary(contentBin, this.settings.useV1); const content = await encodeBinary(contentBin);
if (isDocContentSame(content, fileOnDB.data) && !force) { if (isDocContentSame(content, fileOnDB.data) && !force) {
// Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE); // Logger(`STORAGE <-- DB:${filename}: skipped (hidden) Not changed`, LOG_LEVEL_VERBOSE);
return true; return true;
} }
await this.app.vault.adapter.writeBinary(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime }); await this.plugin.vaultAccess.adapterWrite(filename, decodeBinary(fileOnDB.data), { mtime: fileOnDB.mtime, ctime: fileOnDB.ctime });
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
await this.app.vault.adapter.reconcileInternalFile(filename); await this.app.vault.adapter.reconcileInternalFile(filename);
@@ -613,12 +613,12 @@ export class HiddenFileSync extends LiveSyncCommands {
} }
} }
if (!keep && result) { if (!keep && result) {
const isExists = await this.app.vault.adapter.exists(filename); const isExists = await this.plugin.vaultAccess.adapterExists(filename);
if (!isExists) { if (!isExists) {
await this.ensureDirectoryEx(filename); await this.ensureDirectoryEx(filename);
} }
await this.app.vault.adapter.write(filename, result); await this.plugin.vaultAccess.adapterWrite(filename, result);
const stat = await this.app.vault.adapter.stat(filename); const stat = await this.plugin.vaultAccess.adapterStat(filename);
await this.storeInternalFileToDatabase({ path: filename, ...stat }, true); await this.storeInternalFileToDatabase({ path: filename, ...stat }, true);
try { try {
//@ts-ignore internalAPI //@ts-ignore internalAPI
@@ -657,7 +657,7 @@ export class HiddenFileSync extends LiveSyncCommands {
const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => { const files = filenames.filter(path => synchronisedInConfigSync.every(filterFile => !path.toLowerCase().startsWith(filterFile))).map(async (e) => {
return { return {
path: e as FilePath, path: e as FilePath,
stat: await this.app.vault.adapter.stat(e) stat: await this.plugin.vaultAccess.adapterStat(e)
}; };
}); });
const result: InternalFileInfo[] = []; const result: InternalFileInfo[] = [];

View File

@@ -1,8 +1,8 @@
import { normalizePath, type PluginManifest } from "./deps"; import { normalizePath, type PluginManifest } from "./deps";
import type { DocumentID, EntryDoc, FilePathWithPrefix, LoadedEntry } from "./lib/src/types"; import type { DocumentID, EntryDoc, FilePathWithPrefix, LoadedEntry, SavingEntry } from "./lib/src/types";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "./lib/src/types"; import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "./lib/src/types";
import { type PluginDataEntry, PERIODIC_PLUGIN_SWEEP, type PluginList, type DevicePluginList, PSCHeader, PSCHeaderEnd } from "./types"; import { type PluginDataEntry, PERIODIC_PLUGIN_SWEEP, type PluginList, type DevicePluginList, PSCHeader, PSCHeaderEnd } from "./types";
import { getDocData, isDocContentSame } from "./lib/src/utils"; import { createTextBlob, getDocData, isDocContentSame } from "./lib/src/utils";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { isPluginMetadata, PeriodicProcessor } from "./utils"; import { isPluginMetadata, PeriodicProcessor } from "./utils";
@@ -186,18 +186,17 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
} }
Logger(`Reading plugin:${m.name}(${m.id})`, LOG_LEVEL_VERBOSE); Logger(`Reading plugin:${m.name}(${m.id})`, LOG_LEVEL_VERBOSE);
const path = normalizePath(m.dir) + "/"; const path = normalizePath(m.dir) + "/";
const adapter = this.app.vault.adapter;
const files = ["manifest.json", "main.js", "styles.css", "data.json"]; const files = ["manifest.json", "main.js", "styles.css", "data.json"];
const pluginData: { [key: string]: string; } = {}; const pluginData: { [key: string]: string; } = {};
for (const file of files) { for (const file of files) {
const thePath = path + file; const thePath = path + file;
if (await adapter.exists(thePath)) { if (await this.plugin.vaultAccess.adapterExists(thePath)) {
pluginData[file] = await adapter.read(thePath); pluginData[file] = await this.plugin.vaultAccess.adapterRead(thePath);
} }
} }
let mtime = 0; let mtime = 0;
if (await adapter.exists(path + "/data.json")) { if (await this.plugin.vaultAccess.adapterExists(path + "/data.json")) {
mtime = (await adapter.stat(path + "/data.json")).mtime; mtime = (await this.plugin.vaultAccess.adapterStat(path + "/data.json")).mtime;
} }
const p: PluginDataEntry = { const p: PluginDataEntry = {
@@ -211,13 +210,14 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
mtime: mtime, mtime: mtime,
type: "plugin", type: "plugin",
}; };
const d: LoadedEntry = { const blob = createTextBlob(JSON.stringify(p));
const d: SavingEntry = {
_id: p._id, _id: p._id,
path: p._id as string as FilePathWithPrefix, path: p._id as string as FilePathWithPrefix,
data: JSON.stringify(p), data: blob,
ctime: mtime, ctime: mtime,
mtime: mtime, mtime: mtime,
size: 0, size: blob.size,
children: [], children: [],
datatype: "plain", datatype: "plain",
type: "plain" type: "plain"
@@ -268,7 +268,6 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
async applyPluginData(plugin: PluginDataEntry) { async applyPluginData(plugin: PluginDataEntry) {
await serialized("plugin-" + plugin.manifest.id, async () => { await serialized("plugin-" + plugin.manifest.id, async () => {
const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/";
const adapter = this.app.vault.adapter;
// @ts-ignore // @ts-ignore
const stat = this.app.plugins.enabledPlugins.has(plugin.manifest.id) == true; const stat = this.app.plugins.enabledPlugins.has(plugin.manifest.id) == true;
if (stat) { if (stat) {
@@ -277,7 +276,7 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL_NOTICE); Logger(`Unload plugin:${plugin.manifest.id}`, LOG_LEVEL_NOTICE);
} }
if (plugin.dataJson) if (plugin.dataJson)
await adapter.write(pluginTargetFolderPath + "data.json", plugin.dataJson); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "data.json", plugin.dataJson);
Logger("wrote:" + pluginTargetFolderPath + "data.json", LOG_LEVEL_NOTICE); Logger("wrote:" + pluginTargetFolderPath + "data.json", LOG_LEVEL_NOTICE);
if (stat) { if (stat) {
// @ts-ignore // @ts-ignore
@@ -298,14 +297,13 @@ export class PluginAndTheirSettings extends LiveSyncCommands {
} }
const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/"; const pluginTargetFolderPath = normalizePath(plugin.manifest.dir) + "/";
const adapter = this.app.vault.adapter; if ((await this.plugin.vaultAccess.adapterExists(pluginTargetFolderPath)) === false) {
if ((await adapter.exists(pluginTargetFolderPath)) === false) { await this.app.vault.adapter.mkdir(pluginTargetFolderPath);
await adapter.mkdir(pluginTargetFolderPath);
} }
await adapter.write(pluginTargetFolderPath + "main.js", plugin.mainJs); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "main.js", plugin.mainJs);
await adapter.write(pluginTargetFolderPath + "manifest.json", plugin.manifestJson); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "manifest.json", plugin.manifestJson);
if (plugin.styleCss) if (plugin.styleCss)
await adapter.write(pluginTargetFolderPath + "styles.css", plugin.styleCss); await this.plugin.vaultAccess.adapterWrite(pluginTargetFolderPath + "styles.css", plugin.styleCss);
if (stat) { if (stat) {
// @ts-ignore // @ts-ignore
await this.app.plugins.loadPlugin(plugin.manifest.id); await this.app.plugins.loadPlugin(plugin.manifest.id);

View File

@@ -60,7 +60,7 @@ export class SetupLiveSync extends LiveSyncCommands {
delete setting[k]; delete setting[k];
} }
} }
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, false)); const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false));
const uri = `${configURIBase}${encryptedSetting}`; const uri = `${configURIBase}${encryptedSetting}`;
await navigator.clipboard.writeText(uri); await navigator.clipboard.writeText(uri);
Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE); Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);
@@ -70,7 +70,7 @@ export class SetupLiveSync extends LiveSyncCommands {
if (encryptingPassphrase === false) if (encryptingPassphrase === false)
return; return;
const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" }; const setting = { ...this.settings, configPassphraseStore: "", encryptedCouchDBConnection: "", encryptedPassphrase: "" };
const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false, false)); const encryptedSetting = encodeURIComponent(await encrypt(JSON.stringify(setting), encryptingPassphrase, false));
const uri = `${configURIBase}${encryptedSetting}`; const uri = `${configURIBase}${encryptedSetting}`;
await navigator.clipboard.writeText(uri); await navigator.clipboard.writeText(uri);
Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE); Logger("Setup URI copied to clipboard", LOG_LEVEL_NOTICE);

View File

@@ -200,11 +200,11 @@ export class DocumentHistoryModal extends Modal {
Logger("Path is not valid to write content.", LOG_LEVEL_INFO); Logger("Path is not valid to write content.", LOG_LEVEL_INFO);
} }
if (this.currentDoc?.datatype == "plain") { if (this.currentDoc?.datatype == "plain") {
await this.app.vault.adapter.write(pathToWrite, getDocData(this.currentDoc.data)); await this.plugin.vaultAccess.adapterWrite(pathToWrite, getDocData(this.currentDoc.data));
await focusFile(pathToWrite); await focusFile(pathToWrite);
this.close(); this.close();
} else if (this.currentDoc?.datatype == "newnote") { } else if (this.currentDoc?.datatype == "newnote") {
await this.app.vault.adapter.writeBinary(pathToWrite, decodeBinary(this.currentDoc.data)); await this.plugin.vaultAccess.adapterWrite(pathToWrite, decodeBinary(this.currentDoc.data));
await focusFile(pathToWrite); await focusFile(pathToWrite);
this.close(); this.close();
} else { } else {

View File

@@ -2,12 +2,11 @@
import ObsidianLiveSyncPlugin from "./main"; import ObsidianLiveSyncPlugin from "./main";
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types"; import type { AnyEntry, FilePathWithPrefix } from "./lib/src/types";
import { getDocData, isDocContentSame } from "./lib/src/utils"; import { createBinaryBlob, getDocData, isDocContentSame } from "./lib/src/utils";
import { diff_match_patch } from "./deps"; import { diff_match_patch } from "./deps";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { isPlainText, stripAllPrefixes } from "./lib/src/path"; import { isPlainText, stripAllPrefixes } from "./lib/src/path";
import { TFile } from "./deps"; import { TFile } from "./deps";
import { encodeBinary } from "./lib/src/strbin";
export let plugin: ObsidianLiveSyncPlugin; export let plugin: ObsidianLiveSyncPlugin;
let showDiffInfo = false; let showDiffInfo = false;
@@ -108,15 +107,15 @@
} }
if (rev == docA._rev) { if (rev == docA._rev) {
if (checkStorageDiff) { if (checkStorageDiff) {
const abs = plugin.app.vault.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA))); const abs = plugin.vaultAccess.getAbstractFileByPath(stripAllPrefixes(plugin.getPath(docA)));
if (abs instanceof TFile) { if (abs instanceof TFile) {
let result = false; let result = false;
if (isPlainText(docA.path)) { if (isPlainText(docA.path)) {
const data = await plugin.app.vault.read(abs); const data = await plugin.vaultAccess.adapterRead(abs);
result = isDocContentSame(data, doc.data); result = isDocContentSame(data, doc.data);
} else { } else {
const data = await plugin.app.vault.readBinary(abs); const data = await plugin.vaultAccess.adapterReadBinary(abs);
const dataEEncoded = await encodeBinary(data, plugin.settings.useV1); const dataEEncoded = createBinaryBlob(data);
result = isDocContentSame(dataEEncoded, doc.data); result = isDocContentSame(dataEEncoded, doc.data);
} }
if (result) { if (result) {

View File

@@ -14,6 +14,9 @@ export abstract class LiveSyncCommands {
get localDatabase() { get localDatabase() {
return this.plugin.localDatabase; return this.plugin.localDatabase;
} }
get vaultAccess() {
return this.plugin.vaultAccess;
}
id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix { id2path(id: DocumentID, entry?: EntryHasPath, stripPrefix?: boolean): FilePathWithPrefix {
return this.plugin.id2path(id, entry, stripPrefix); return this.plugin.id2path(id, entry, stripPrefix);
} }

View File

@@ -1832,15 +1832,6 @@ ${stringifyYaml(pluginConfig)}`;
await this.plugin.saveSettings(); await this.plugin.saveSettings();
}) })
); );
new Setting(containerHatchEl)
.setName("Use binary and encryption version 1")
.setDesc("Use the previous data format for other products which shares the remote database.")
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.useV1).onChange(async (value) => {
this.plugin.settings.useV1 = value;
await this.plugin.saveSettings();
})
);
addScreenElement("50", containerHatchEl); addScreenElement("50", containerHatchEl);

View File

@@ -207,21 +207,21 @@
const local = list.find((e) => e.term == thisTerm); const local = list.find((e) => e.term == thisTerm);
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
if (selectedItem && (await applyData(selectedItem))) { if (selectedItem && (await applyData(selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(true, local.documentPath)); addOn.updatePluginList(true, local?.documentPath);
} }
} }
async function compareSelected() { async function compareSelected() {
const local = list.find((e) => e.term == thisTerm); const local = list.find((e) => e.term == thisTerm);
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
if (local && selectedItem && (await compareData(local, selectedItem))) { if (local && selectedItem && (await compareData(local, selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.updatePluginList(true, local.documentPath)); addOn.updatePluginList(true, local.documentPath);
} }
} }
async function deleteSelected() { async function deleteSelected() {
const selectedItem = list.find((e) => e.term == selected); const selectedItem = list.find((e) => e.term == selected);
// const deletedPath = selectedItem.documentPath; // const deletedPath = selectedItem.documentPath;
if (selectedItem && (await deleteData(selectedItem))) { if (selectedItem && (await deleteData(selectedItem))) {
scheduleTask("update-plugin-list", 250, () => addOn.reloadPluginList(true)); addOn.reloadPluginList(true);
} }
} }
async function duplicateItem() { async function duplicateItem() {

118
src/SerializedFileAccess.ts Normal file
View File

@@ -0,0 +1,118 @@
import { type App, TFile, type DataWriteOptions, TFolder, TAbstractFile } from "./deps";
import { serialized } from "./lib/src/lock";
import type { FilePath } from "./lib/src/types";
function getFileLockKey(file: TFile | TFolder | string) {
return `fl:${typeof (file) == "string" ? file : file.path}`;
}
function toArrayBuffer(arr: Uint8Array | ArrayBuffer | DataView): ArrayBufferLike {
if (arr instanceof Uint8Array) {
return arr.buffer;
}
if (arr instanceof DataView) {
return arr.buffer;
}
return arr;
}
export class SerializedFileAccess {
app: App
constructor(app: App) {
this.app = app;
}
async adapterStat(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.stat(path));
}
async adapterExists(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.exists(path));
}
async adapterRemove(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.remove(path));
}
async adapterRead(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.read(path));
}
async adapterReadBinary(file: TFile | string) {
const path = file instanceof TFile ? file.path : file;
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.readBinary(path));
}
async adapterWrite(file: TFile | string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
const path = file instanceof TFile ? file.path : file;
if (typeof (data) === "string") {
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.write(path, data, options));
} else {
return await serialized(getFileLockKey(path), () => this.app.vault.adapter.writeBinary(path, toArrayBuffer(data), options));
}
}
async vaultCacheRead(file: TFile) {
return await serialized(getFileLockKey(file), () => this.app.vault.cachedRead(file));
}
async vaultRead(file: TFile) {
return await serialized(getFileLockKey(file), () => this.app.vault.read(file));
}
async vaultReadBinary(file: TFile) {
return await serialized(getFileLockKey(file), () => this.app.vault.readBinary(file));
}
async vaultModify(file: TFile, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions) {
if (typeof (data) === "string") {
return await serialized(getFileLockKey(file), () => this.app.vault.modify(file, data, options));
} else {
return await serialized(getFileLockKey(file), () => this.app.vault.modifyBinary(file, toArrayBuffer(data), options));
}
}
async vaultCreate(path: string, data: string | ArrayBuffer | Uint8Array, options?: DataWriteOptions): Promise<TFile> {
if (typeof (data) === "string") {
return await serialized(getFileLockKey(path), () => this.app.vault.create(path, data, options));
} else {
return await serialized(getFileLockKey(path), () => this.app.vault.createBinary(path, toArrayBuffer(data), options));
}
}
async delete(file: TFile | TFolder, force = false) {
return await serialized(getFileLockKey(file), () => this.app.vault.delete(file, force));
}
async trash(file: TFile | TFolder, force = false) {
return await serialized(getFileLockKey(file), () => this.app.vault.trash(file, force));
}
getAbstractFileByPath(path: FilePath | string): TAbstractFile | null {
// Disabled temporary.
return this.app.vault.getAbstractFileByPath(path);
// // Hidden API but so useful.
// // @ts-ignore
// if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
// // @ts-ignore
// return app.vault.getAbstractFileByPathInsensitive(path);
// } else {
// return app.vault.getAbstractFileByPath(path);
// }
}
touchedFiles: string[] = [];
touch(file: TFile | FilePath) {
const f = file instanceof TFile ? file : this.getAbstractFileByPath(file) as TFile;
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
this.touchedFiles.unshift(key);
this.touchedFiles = this.touchedFiles.slice(0, 100);
}
recentlyTouched(file: TFile) {
const key = `${file.path}-${file.stat.mtime}-${file.stat.size}`;
if (this.touchedFiles.indexOf(key) == -1) return false;
return true;
}
clearTouched() {
this.touchedFiles = [];
}
}

View File

@@ -1,9 +1,9 @@
import type { SerializedFileAccess } from "./SerializedFileAccess";
import { Plugin, TAbstractFile, TFile, TFolder } from "./deps"; import { Plugin, TAbstractFile, TFile, TFolder } from "./deps";
import { isPlainText, shouldBeIgnored } from "./lib/src/path"; import { isPlainText, shouldBeIgnored } from "./lib/src/path";
import { getGlobalStore } from "./lib/src/store"; import { getGlobalStore } from "./lib/src/store";
import { type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types"; import { type FilePath, type ObsidianLiveSyncSettings } from "./lib/src/types";
import { type FileEventItem, type FileEventType, type FileInfo, type InternalFileInfo, type queueItem } from "./types"; import { type FileEventItem, type FileEventType, type FileInfo, type InternalFileInfo, type queueItem } from "./types";
import { recentlyTouched } from "./utils";
export abstract class StorageEventManager { export abstract class StorageEventManager {
@@ -16,6 +16,7 @@ type LiveSyncForStorageEventManager = Plugin &
{ {
settings: ObsidianLiveSyncSettings settings: ObsidianLiveSyncSettings
ignoreFiles: string[], ignoreFiles: string[],
vaultAccess: SerializedFileAccess
} & { } & {
isTargetFile: (file: string | TAbstractFile) => Promise<boolean>, isTargetFile: (file: string | TAbstractFile) => Promise<boolean>,
procFileEvent: (applyBatch?: boolean) => Promise<any>, procFileEvent: (applyBatch?: boolean) => Promise<any>,
@@ -105,15 +106,14 @@ export class StorageEventManagerObsidian extends StorageEventManager {
let cache: null | string | ArrayBuffer; let cache: null | string | ArrayBuffer;
// new file or something changed, cache the changes. // new file or something changed, cache the changes.
if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) { if (file instanceof TFile && (type == "CREATE" || type == "CHANGED")) {
if (recentlyTouched(file)) { if (this.plugin.vaultAccess.recentlyTouched(file)) {
continue; continue;
} }
if (!isPlainText(file.name)) { if (!isPlainText(file.name)) {
cache = await this.plugin.app.vault.readBinary(file); cache = await this.plugin.vaultAccess.vaultReadBinary(file);
} else { } else {
// cache = await this.app.vault.read(file); cache = await this.plugin.vaultAccess.vaultCacheRead(file);
cache = await this.plugin.app.vault.cachedRead(file); if (!cache) cache = await this.plugin.vaultAccess.vaultRead(file);
if (!cache) cache = await this.plugin.app.vault.read(file);
} }
} }
if (type == "DELETE" || type == "RENAME") { if (type == "DELETE" || type == "RENAME") {

View File

@@ -58,8 +58,7 @@ export class InputStringDialog extends Modal {
onOpen() { onOpen() {
const { contentEl } = this; const { contentEl } = this;
this.titleEl.setText(this.title); this.titleEl.setText(this.title);
// For enter to submit const formEl = contentEl.createDiv();
const formEl = contentEl.createEl("form");
new Setting(formEl).setName(this.key).setClass(this.isPassword ? "password-input" : "normal-input").addText((text) => new Setting(formEl).setName(this.key).setClass(this.isPassword ? "password-input" : "normal-input").addText((text) =>
text.onChange((value) => { text.onChange((value) => {
this.result = value; this.result = value;

Submodule src/lib updated: 609c7aecf3...7e79c27035

View File

@@ -2,21 +2,21 @@ const isDebug = false;
import { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "./deps"; import { type Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch } from "./deps";
import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, type RequestUrlParam, type RequestUrlResponse, requestUrl, type MarkdownFileInfo } from "./deps"; import { debounce, Notice, Plugin, TFile, addIcon, TFolder, normalizePath, TAbstractFile, Editor, MarkdownView, type RequestUrlParam, type RequestUrlResponse, requestUrl, type MarkdownFileInfo } from "./deps";
import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type diff_check_result, type diff_result_leaf, type EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, type ConfigPassphraseStore, type CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, type AnyEntry, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT, LOG_LEVEL_VERBOSE, } from "./lib/src/types"; import { type EntryDoc, type LoadedEntry, type ObsidianLiveSyncSettings, type diff_check_result, type diff_result_leaf, type EntryBody, LOG_LEVEL, VER, DEFAULT_SETTINGS, type diff_result, FLAGMD_REDFLAG, SYNCINFO_ID, SALT_OF_PASSPHRASE, type ConfigPassphraseStore, type CouchDBConnection, FLAGMD_REDFLAG2, FLAGMD_REDFLAG3, PREFIXMD_LOGFILE, type DatabaseConnectingStatus, type EntryHasPath, type DocumentID, type FilePathWithPrefix, type FilePath, type AnyEntry, LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_URGENT, LOG_LEVEL_VERBOSE, type SavingEntry, } from "./lib/src/types";
import { type InternalFileInfo, type queueItem, type CacheData, type FileEventItem, FileWatchEventQueueMax } from "./types"; import { type InternalFileInfo, type queueItem, type CacheData, type FileEventItem, FileWatchEventQueueMax } from "./types";
import { arrayToChunkedArray, getDocData, isDocContentSame } from "./lib/src/utils"; import { arrayToChunkedArray, createBinaryBlob, createTextBlob, getDocData, isDocContentSame } from "./lib/src/utils";
import { Logger, setGlobalLogFunction } from "./lib/src/logger"; import { Logger, setGlobalLogFunction } from "./lib/src/logger";
import { PouchDB } from "./lib/src/pouchdb-browser.js"; import { PouchDB } from "./lib/src/pouchdb-browser.js";
import { ConflictResolveModal } from "./ConflictResolveModal"; import { ConflictResolveModal } from "./ConflictResolveModal";
import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab"; import { ObsidianLiveSyncSettingTab } from "./ObsidianLiveSyncSettingTab";
import { DocumentHistoryModal } from "./DocumentHistoryModal"; import { DocumentHistoryModal } from "./DocumentHistoryModal";
import { applyPatch, cancelAllPeriodicTask, cancelAllTasks, cancelTask, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, flattenObject, path2id, scheduleTask, tryParseJSON, createFile, modifyFile, isValidPath, getAbstractFileByPath, touch, recentlyTouched, isInternalMetadata, isPluginMetadata, stripInternalMetadataPrefix, isChunk, askSelectString, askYesNo, askString, PeriodicProcessor, clearTouched, getPath, getPathWithoutPrefix, getPathFromTFile, performRebuildDB, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject, isCustomisationSyncMetadata } from "./utils"; import { applyPatch, cancelAllPeriodicTask, cancelAllTasks, cancelTask, generatePatchObj, id2path, isObjectMargeApplicable, isSensibleMargeApplicable, flattenObject, path2id, scheduleTask, tryParseJSON, isValidPath, isInternalMetadata, isPluginMetadata, stripInternalMetadataPrefix, isChunk, askSelectString, askYesNo, askString, PeriodicProcessor, getPath, getPathWithoutPrefix, getPathFromTFile, performRebuildDB, memoIfNotExist, memoObject, retrieveMemoObject, disposeMemoObject, isCustomisationSyncMetadata } from "./utils";
import { encrypt, tryDecrypt } from "./lib/src/e2ee_v2"; import { encrypt, tryDecrypt } from "./lib/src/e2ee_v2";
import { balanceChunkPurgedDBs, enableEncryption, isCloudantURI, isErrorOfMissingDoc, isValidRemoteCouchDBURI, purgeUnreferencedChunks } from "./lib/src/utils_couchdb"; import { balanceChunkPurgedDBs, enableEncryption, isCloudantURI, isErrorOfMissingDoc, isValidRemoteCouchDBURI, purgeUnreferencedChunks } from "./lib/src/utils_couchdb";
import { getGlobalStore, ObservableStore, observeStores } from "./lib/src/store"; import { getGlobalStore, ObservableStore, observeStores } from "./lib/src/store";
import { lockStore, logMessageStore, logStore, type LogEntry } from "./lib/src/stores"; import { lockStore, logMessageStore, logStore, type LogEntry } from "./lib/src/stores";
import { setNoticeClass } from "./lib/src/wrapper"; import { setNoticeClass } from "./lib/src/wrapper";
import { versionNumberString2Number, writeString, decodeBinary, encodeBinary, readString } from "./lib/src/strbin"; import { versionNumberString2Number, writeString, decodeBinary, readString } from "./lib/src/strbin";
import { addPrefix, isAcceptedAll, isPlainText, shouldBeIgnored, stripAllPrefixes } from "./lib/src/path"; import { addPrefix, isAcceptedAll, isPlainText, shouldBeIgnored, stripAllPrefixes } from "./lib/src/path";
import { isLockAcquired, serialized, skipIfDuplicated } from "./lib/src/lock"; import { isLockAcquired, serialized, skipIfDuplicated } from "./lib/src/lock";
import { Semaphore } from "./lib/src/semaphore"; import { Semaphore } from "./lib/src/semaphore";
@@ -33,6 +33,7 @@ import { GlobalHistoryView, VIEW_TYPE_GLOBAL_HISTORY } from "./GlobalHistoryView
import { LogPaneView, VIEW_TYPE_LOG } from "./LogPaneView"; import { LogPaneView, VIEW_TYPE_LOG } from "./LogPaneView";
import { mapAllTasksWithConcurrencyLimit, processAllTasksWithConcurrencyLimit } from "./lib/src/task"; import { mapAllTasksWithConcurrencyLimit, processAllTasksWithConcurrencyLimit } from "./lib/src/task";
import { LRUCache } from "./lib/src/LRUCache"; import { LRUCache } from "./lib/src/LRUCache";
import { SerializedFileAccess } from "./SerializedFileAccess";
setNoticeClass(Notice); setNoticeClass(Notice);
@@ -86,6 +87,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
return !this.last_successful_post; return !this.last_successful_post;
} }
vaultAccess: SerializedFileAccess = new SerializedFileAccess(this.app);
_unloaded = false; _unloaded = false;
@@ -204,7 +206,7 @@ export default class ObsidianLiveSyncPlugin extends Plugin
const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf); const db: PouchDB.Database<EntryDoc> = new PouchDB<EntryDoc>(uri, conf);
if (passphrase !== "false" && typeof passphrase === "string") { if (passphrase !== "false" && typeof passphrase === "string") {
enableEncryption(db, passphrase, useDynamicIterationCount, false, this.settings.useV1); enableEncryption(db, passphrase, useDynamicIterationCount, false);
} }
if (skipInfo) { if (skipInfo) {
return { db: db, info: { db_name: "", doc_count: 0, update_seq: "" } }; return { db: db, info: { db_name: "", doc_count: 0, update_seq: "" } };
@@ -293,36 +295,36 @@ export default class ObsidianLiveSyncPlugin extends Plugin
} }
isRedFlagRaised(): boolean { isRedFlagRaised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
isRedFlag2Raised(): boolean { isRedFlag2Raised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
async deleteRedFlag2() { async deleteRedFlag2() {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG2));
if (redflag != null) { if (redflag != null && redflag instanceof TFile) {
await app.vault.delete(redflag, true); await this.vaultAccess.delete(redflag, true);
} }
} }
isRedFlag3Raised(): boolean { isRedFlag3Raised(): boolean {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3));
if (redflag != null) { if (redflag != null) {
return true; return true;
} }
return false; return false;
} }
async deleteRedFlag3() { async deleteRedFlag3() {
const redflag = getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3)); const redflag = this.vaultAccess.getAbstractFileByPath(normalizePath(FLAGMD_REDFLAG3));
if (redflag != null) { if (redflag != null && redflag instanceof TFile) {
await app.vault.delete(redflag, true); await this.vaultAccess.delete(redflag, true);
} }
} }
@@ -404,10 +406,6 @@ export default class ObsidianLiveSyncPlugin extends Plugin
Logger(`Checking expired file history done`); Logger(`Checking expired file history done`);
} }
async onLayoutReady() { async onLayoutReady() {
if (this.settings.useV1 === undefined) {
this.settings.useV1 = await this.askEnableV2();
await this.saveSettingData();
}
this.registerFileWatchEvents(); this.registerFileWatchEvents();
if (!this.localDatabase.isReady) { if (!this.localDatabase.isReady) {
Logger(`Something went wrong! The local database is not ready`, LOG_LEVEL_NOTICE); Logger(`Something went wrong! The local database is not ready`, LOG_LEVEL_NOTICE);
@@ -515,58 +513,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
return ret == CHOICE_V1; return ret == CHOICE_V1;
} }
async onload() { addUIs() {
logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
Logger("loading plugin");
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this.manifestVersion = manifestVersion;
this.packageVersion = packageVersion;
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsKey);
await this.loadSettings();
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates) {
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL_NOTICE);
}
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.openDatabase();
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 1000, false);
this.watchWindowVisibility = debounce(this.watchWindowVisibility.bind(this), 1000, false);
this.watchOnline = debounce(this.watchOnline.bind(this), 500, false);
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
addIcon( addIcon(
"replicate", "replicate",
`<g transform="matrix(1.15 0 0 1.15 -8.31 -9.52)" fill="currentColor" fill-rule="evenodd"> `<g transform="matrix(1.15 0 0 1.15 -8.31 -9.52)" fill="currentColor" fill-rule="evenodd">
@@ -583,14 +530,23 @@ Note: We can always able to read V1 format. It will be progressively converted.
<path d="m106 346v44h70v-44zm45 16h-20v-8h20z"/> <path d="m106 346v44h70v-44zm45 16h-20v-8h20z"/>
</g>` </g>`
); );
await Promise.all(this.addOns.map(e => e.onload())); addIcon(
"custom-sync",
`<g transform="rotate(-90 75 218)" fill="currentColor" fill-rule="evenodd">
<path d="m272 166-9.38 9.38 9.38 9.38 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.058 1.91 1.94 1.91 5.04 0 6.98l-9.38 9.38 5.86 5.86-11.7 11.7c-8.34 8.35-21.4 9.68-31.3 3.19l-3.84 3.98c-8.45 8.7-20.1 13.6-32.2 13.6h-5.55v-9.95h5.55c9.43-0.0182 18.5-3.84 25-10.6l3.95-4.09c-6.54-9.86-5.23-23 3.14-31.3l11.7-11.7 5.86 5.86 9.38-9.38c1.96-1.93 5.11-1.9 7.03 0.0564 1.91 1.93 1.91 5.04 2e-3 6.98z"/>
</g>`
);
this.addRibbonIcon("replicate", "Replicate", async () => { this.addRibbonIcon("replicate", "Replicate", async () => {
await this.replicate(true); await this.replicate(true);
}); }).addClass("livesync-ribbon-replicate");
this.addRibbonIcon("view-log", "Show log", () => { this.addRibbonIcon("view-log", "Show log", () => {
this.showView(VIEW_TYPE_LOG); this.showView(VIEW_TYPE_LOG);
}); }).addClass("livesync-ribbon-showlog");
this.addRibbonIcon("custom-sync", "Show Customization sync", () => {
this.addOnConfigSync.showPluginSyncModal();
}).addClass("livesync-ribbon-showcustom");
this.addCommand({ this.addCommand({
id: "view-log", id: "view-log",
name: "Show log", name: "Show log",
@@ -598,8 +554,6 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.showView(VIEW_TYPE_LOG); this.showView(VIEW_TYPE_LOG);
} }
}); });
this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
this.addCommand({ this.addCommand({
id: "livesync-replicate", id: "livesync-replicate",
@@ -611,8 +565,13 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.addCommand({ this.addCommand({
id: "livesync-dump", id: "livesync-dump",
name: "Dump information of this doc ", name: "Dump information of this doc ",
editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => { // editorCallback: (editor: Editor, view: MarkdownView | MarkdownFileInfo) => {
const file = view.file; // const file = view.file;
// if (!file) return;
// this.localDatabase.getDBEntry(getPathFromTFile(file), {}, true, false);
// },
callback: () => {
const file = this.app.workspace.getActiveFile();
if (!file) return; if (!file) return;
this.localDatabase.getDBEntry(getPathFromTFile(file), {}, true, false); this.localDatabase.getDBEntry(getPathFromTFile(file), {}, true, false);
}, },
@@ -672,8 +631,6 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
}) })
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
this.addCommand({ this.addCommand({
id: "livesync-filehistory", id: "livesync-filehistory",
name: "Pick a file to show history", name: "Pick a file to show history",
@@ -709,6 +666,13 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.replicator.terminateSync(); this.replicator.terminateSync();
}, },
}) })
this.addCommand({
id: "livesync-global-history",
name: "Show vault history",
callback: () => {
this.showGlobalHistory()
}
})
this.registerView( this.registerView(
VIEW_TYPE_GLOBAL_HISTORY, VIEW_TYPE_GLOBAL_HISTORY,
@@ -718,13 +682,66 @@ Note: We can always able to read V1 format. It will be progressively converted.
VIEW_TYPE_LOG, VIEW_TYPE_LOG,
(leaf) => new LogPaneView(leaf, this) (leaf) => new LogPaneView(leaf, this)
); );
this.addCommand({ }
id: "livesync-global-history",
name: "Show vault history", async onload() {
callback: () => { logStore.subscribe(e => this.addLog(e.message, e.level, e.key));
this.showGlobalHistory() Logger("loading plugin");
} this.addSettingTab(new ObsidianLiveSyncSettingTab(this.app, this));
}) this.addUIs();
//@ts-ignore
const manifestVersion: string = MANIFEST_VERSION || "0.0.0";
//@ts-ignore
const packageVersion: string = PACKAGE_VERSION || "0.0.0";
this.manifestVersion = manifestVersion;
this.packageVersion = packageVersion;
Logger(`Self-hosted LiveSync v${manifestVersion} ${packageVersion} `);
const lsKey = "obsidian-live-sync-ver" + this.getVaultName();
const last_version = localStorage.getItem(lsKey);
await this.loadSettings();
this.statusBar = this.addStatusBarItem();
this.statusBar.addClass("syncstatusbar");
const lastVersion = ~~(versionNumberString2Number(manifestVersion) / 1000);
if (lastVersion > this.settings.lastReadUpdates) {
Logger("Self-hosted LiveSync has undergone a major upgrade. Please open the setting dialog, and check the information pane.", LOG_LEVEL_NOTICE);
}
//@ts-ignore
if (this.app.isMobile) {
this.isMobile = true;
this.settings.disableRequestURI = true;
}
if (last_version && Number(last_version) < VER) {
this.settings.liveSync = false;
this.settings.syncOnSave = false;
this.settings.syncOnEditorSave = false;
this.settings.syncOnStart = false;
this.settings.syncOnFileOpen = false;
this.settings.syncAfterMerge = false;
this.settings.periodicReplication = false;
this.settings.versionUpFlash = "Self-hosted LiveSync has been upgraded and some behaviors have changed incompatibly. All automatic synchronization is now disabled temporary. Ensure that other devices are also upgraded, and enable synchronization again.";
this.saveSettings();
}
localStorage.setItem(lsKey, `${VER}`);
await this.openDatabase();
this.watchWorkspaceOpen = debounce(this.watchWorkspaceOpen.bind(this), 1000, false);
this.watchWindowVisibility = debounce(this.watchWindowVisibility.bind(this), 1000, false);
this.watchOnline = debounce(this.watchOnline.bind(this), 500, false);
this.parseReplicationResult = this.parseReplicationResult.bind(this);
this.loadQueuedFiles = this.loadQueuedFiles.bind(this);
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
await Promise.all(this.addOns.map(e => e.onload()));
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
this.triggerRealizeSettingSyncMode = debounce(this.triggerRealizeSettingSyncMode.bind(this), 1000);
} }
async showView(viewType: string) { async showView(viewType: string) {
const leaves = this.app.workspace.getLeavesOfType(viewType); const leaves = this.app.workspace.getLeavesOfType(viewType);
@@ -812,7 +829,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) { async encryptConfigurationItem(src: string, settings: ObsidianLiveSyncSettings) {
if (this.usedPassphrase != "") { if (this.usedPassphrase != "") {
return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE, false, true); return await encrypt(src, this.usedPassphrase + SALT_OF_PASSPHRASE, false);
} }
const passphrase = await this.getPassphrase(settings); const passphrase = await this.getPassphrase(settings);
@@ -820,7 +837,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
Logger("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL_URGENT); Logger("Could not determine passphrase to save data.json! You probably make the configuration sure again!", LOG_LEVEL_URGENT);
return ""; return "";
} }
const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE, false, true); const dec = await encrypt(src, passphrase + SALT_OF_PASSPHRASE, false);
if (dec) { if (dec) {
this.usedPassphrase = passphrase; this.usedPassphrase = passphrase;
return dec; return dec;
@@ -1049,7 +1066,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path); await this.addOnHiddenFileSync.watchVaultRawEventsAsync(file.path);
await this.addOnConfigSync.watchVaultRawEventsAsync(file.path); await this.addOnConfigSync.watchVaultRawEventsAsync(file.path);
} else { } else {
const targetFile = this.app.vault.getAbstractFileByPath(file.path); const targetFile = this.vaultAccess.getAbstractFileByPath(file.path);
if (!(targetFile instanceof TFile)) { if (!(targetFile instanceof TFile)) {
Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO); Logger(`Target file was not found: ${file.path}`, LOG_LEVEL_INFO);
continue; continue;
@@ -1168,7 +1185,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
if (this.settings?.writeLogToTheFile) { if (this.settings?.writeLogToTheFile) {
const time = now.toISOString().split("T")[0]; const time = now.toISOString().split("T")[0];
const logDate = `${PREFIXMD_LOGFILE}${time}.md`; const logDate = `${PREFIXMD_LOGFILE}${time}.md`;
const file = this.app.vault.getAbstractFileByPath(normalizePath(logDate)); const file = this.vaultAccess.getAbstractFileByPath(normalizePath(logDate));
if (!file) { if (!file) {
this.app.vault.adapter.append(normalizePath(logDate), "```\n"); this.app.vault.adapter.append(normalizePath(logDate), "```\n");
} }
@@ -1290,13 +1307,15 @@ Note: We can always able to read V1 format. It will be progressively converted.
try { try {
let outFile; let outFile;
if (mode == "create") { if (mode == "create") {
outFile = await createFile(normalizePath(path), writeData, { ctime: doc.ctime, mtime: doc.mtime, }); const normalizedPath = normalizePath(path);
await this.vaultAccess.vaultCreate(normalizedPath, writeData, { ctime: doc.ctime, mtime: doc.mtime, });
outFile = this.vaultAccess.getAbstractFileByPath(normalizedPath) as TFile;
} else { } else {
await modifyFile(file, writeData, { ctime: doc.ctime, mtime: doc.mtime }); await this.vaultAccess.vaultModify(file, writeData, { ctime: doc.ctime, mtime: doc.mtime });
outFile = getAbstractFileByPath(getPathFromTFile(file)) as TFile; outFile = this.vaultAccess.getAbstractFileByPath(getPathFromTFile(file)) as TFile;
} }
Logger(msg + path); Logger(msg + path);
touch(outFile); this.vaultAccess.touch(outFile);
this.app.vault.trigger(mode, outFile); this.app.vault.trigger(mode, outFile);
} catch (ex) { } catch (ex) {
@@ -1311,15 +1330,15 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
const dir = file.parent; const dir = file.parent;
if (this.settings.trashInsteadDelete) { if (this.settings.trashInsteadDelete) {
await this.app.vault.trash(file, false); await this.vaultAccess.trash(file, false);
} else { } else {
await this.app.vault.delete(file); await this.vaultAccess.delete(file, true);
} }
Logger(`xxx <- STORAGE (deleted) ${file.path}`); Logger(`xxx <- STORAGE (deleted) ${file.path}`);
Logger(`files: ${dir.children.length}`); Logger(`files: ${dir.children.length}`);
if (dir.children.length == 0) { if (dir.children.length == 0) {
if (!this.settings.doNotDeleteFolder) { if (!this.settings.doNotDeleteFolder) {
Logger(`All files under the parent directory (${dir}) have been deleted, so delete this one.`); Logger(`All files under the parent directory (${dir.path}) have been deleted, so delete this one.`);
await this.deleteVaultItem(dir); await this.deleteVaultItem(dir);
} }
} }
@@ -1378,7 +1397,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
async handleDBChangedAsync(change: EntryBody) { async handleDBChangedAsync(change: EntryBody) {
const targetFile = getAbstractFileByPath(this.getPathWithoutPrefix(change)); const targetFile = this.vaultAccess.getAbstractFileByPath(this.getPathWithoutPrefix(change));
if (targetFile == null) { if (targetFile == null) {
if (change._deleted || change.deleted) { if (change._deleted || change.deleted) {
return; return;
@@ -1502,7 +1521,7 @@ Note: We can always able to read V1 format. It will be progressively converted.
} }
if ((!isInternalMetadata(doc._id)) && skipOldFile) { if ((!isInternalMetadata(doc._id)) && skipOldFile) {
const info = getAbstractFileByPath(stripAllPrefixes(path)); const info = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(path));
if (info && info instanceof TFile) { if (info && info instanceof TFile) {
const localMtime = ~~(info.stat.mtime / 1000); const localMtime = ~~(info.stat.mtime / 1000);
@@ -1609,6 +1628,9 @@ Note: We can always able to read V1 format. It will be progressively converted.
this.replicator.openReplication(this.settings, true, false); this.replicator.openReplication(this.settings, true, false);
} }
const q = activeDocument.querySelector(`.livesync-ribbon-showcustom`);
q?.toggleClass("sls-hidden", !this.settings.usePluginSync);
this.periodicSyncProcessor.enable(this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0); this.periodicSyncProcessor.enable(this.settings.periodicReplication ? this.settings.periodicReplicationInterval * 1000 : 0);
@@ -2241,12 +2263,12 @@ Or if you are sure know what had been happened, we can unlock the database from
// remove conflicted revision. // remove conflicted revision.
await this.localDatabase.deleteDBEntry(path, { rev: conflictedRev }); await this.localDatabase.deleteDBEntry(path, { rev: conflictedRev });
const file = getAbstractFileByPath(stripAllPrefixes(path)) as TFile; const file = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(path)) as TFile;
if (file) { if (file) {
await this.app.vault.modify(file, p); await this.vaultAccess.vaultModify(file, p);
await this.updateIntoDB(file); await this.updateIntoDB(file);
} else { } else {
const newFile = await this.app.vault.create(path, p); const newFile = await this.vaultAccess.vaultCreate(path, p);
await this.updateIntoDB(newFile); await this.updateIntoDB(newFile);
} }
await this.pullFile(path); await this.pullFile(path);
@@ -2319,12 +2341,12 @@ Or if you are sure know what had been happened, we can unlock the database from
// delete conflicted revision and write a new file, store it again. // delete conflicted revision and write a new file, store it again.
const p = conflictCheckResult.diff.map((e) => e[1]).join(""); const p = conflictCheckResult.diff.map((e) => e[1]).join("");
await this.localDatabase.deleteDBEntry(filename, { rev: testDoc._conflicts[0] }); await this.localDatabase.deleteDBEntry(filename, { rev: testDoc._conflicts[0] });
const file = getAbstractFileByPath(stripAllPrefixes(filename)) as TFile; const file = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(filename)) as TFile;
if (file) { if (file) {
await this.app.vault.modify(file, p); await this.vaultAccess.vaultModify(file, p);
await this.updateIntoDB(file); await this.updateIntoDB(file);
} else { } else {
const newFile = await this.app.vault.create(filename, p); const newFile = await this.vaultAccess.vaultCreate(filename, p);
await this.updateIntoDB(newFile); await this.updateIntoDB(newFile);
} }
await this.pullFile(filename); await this.pullFile(filename);
@@ -2366,7 +2388,7 @@ Or if you are sure know what had been happened, we can unlock the database from
const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[]; const checkFiles = JSON.parse(JSON.stringify(this.conflictedCheckFiles)) as FilePath[];
for (const filename of checkFiles) { for (const filename of checkFiles) {
try { try {
const file = getAbstractFileByPath(filename); const file = this.vaultAccess.getAbstractFileByPath(filename);
if (file != null && file instanceof TFile) { if (file != null && file instanceof TFile) {
await this.showIfConflicted(getPathFromTFile(file)); await this.showIfConflicted(getPathFromTFile(file));
} }
@@ -2401,7 +2423,7 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
async pullFile(filename: FilePathWithPrefix, fileList?: TFile[], force?: boolean, rev?: string, waitForReady = true) { async pullFile(filename: FilePathWithPrefix, fileList?: TFile[], force?: boolean, rev?: string, waitForReady = true) {
const targetFile = getAbstractFileByPath(stripAllPrefixes(filename)); const targetFile = this.vaultAccess.getAbstractFileByPath(stripAllPrefixes(filename));
if (!await this.isTargetFile(filename)) return; if (!await this.isTargetFile(filename)) return;
if (targetFile == null) { if (targetFile == null) {
//have to create; //have to create;
@@ -2432,7 +2454,7 @@ Or if you are sure know what had been happened, we can unlock the database from
throw new Error(`Missing doc:${(file as any).path}`) throw new Error(`Missing doc:${(file as any).path}`)
} }
if (!(file instanceof TFile) && "path" in file) { if (!(file instanceof TFile) && "path" in file) {
const w = getAbstractFileByPath((file as any).path); const w = this.vaultAccess.getAbstractFileByPath((file as any).path);
if (w instanceof TFile) { if (w instanceof TFile) {
file = w; file = w;
} else { } else {
@@ -2480,15 +2502,15 @@ Or if you are sure know what had been happened, we can unlock the database from
if (shouldBeIgnored(file.path)) { if (shouldBeIgnored(file.path)) {
return true; return true;
} }
let content: string | string[]; let content: Blob;
let datatype: "plain" | "newnote" = "newnote"; let datatype: "plain" | "newnote" = "newnote";
if (!cache) { if (!cache) {
if (!isPlainText(file.name)) { if (!isPlainText(file.name)) {
Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE); Logger(`Reading : ${file.path}`, LOG_LEVEL_VERBOSE);
const contentBin = await this.app.vault.readBinary(file); const contentBin = await this.vaultAccess.vaultReadBinary(file);
Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
try { try {
content = await encodeBinary(contentBin, this.settings.useV1); content = createBinaryBlob(contentBin);
} catch (ex) { } catch (ex) {
Logger(`The file ${file.path} could not be encoded`); Logger(`The file ${file.path} could not be encoded`);
Logger(ex, LOG_LEVEL_VERBOSE); Logger(ex, LOG_LEVEL_VERBOSE);
@@ -2496,14 +2518,14 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
datatype = "newnote"; datatype = "newnote";
} else { } else {
content = await this.app.vault.read(file); content = createTextBlob(await this.vaultAccess.vaultRead(file));
datatype = "plain"; datatype = "plain";
} }
} else { } else {
if (cache instanceof ArrayBuffer) { if (cache instanceof ArrayBuffer) {
Logger(`Processing: ${file.path}`, LOG_LEVEL_VERBOSE); Logger(`Cache Processing: ${file.path}`, LOG_LEVEL_VERBOSE);
try { try {
content = await encodeBinary(cache, this.settings.useV1); content = createBinaryBlob(cache);
} catch (ex) { } catch (ex) {
Logger(`The file ${file.path} could not be encoded`); Logger(`The file ${file.path} could not be encoded`);
Logger(ex, LOG_LEVEL_VERBOSE); Logger(ex, LOG_LEVEL_VERBOSE);
@@ -2511,13 +2533,13 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
datatype = "newnote" datatype = "newnote"
} else { } else {
content = cache; content = createTextBlob(cache);
datatype = "plain"; datatype = "plain";
} }
} }
const fullPath = getPathFromTFile(file); const fullPath = getPathFromTFile(file);
const id = await this.path2id(fullPath); const id = await this.path2id(fullPath);
const d: LoadedEntry = { const d: SavingEntry = {
_id: id, _id: id,
path: getPathFromTFile(file), path: getPathFromTFile(file),
data: content, data: content,
@@ -2531,7 +2553,7 @@ Or if you are sure know what had been happened, we can unlock the database from
//upsert should locked //upsert should locked
const msg = `DB <- STORAGE (${datatype}) `; const msg = `DB <- STORAGE (${datatype}) `;
const isNotChanged = await serialized("file-" + fullPath, async () => { const isNotChanged = await serialized("file-" + fullPath, async () => {
if (recentlyTouched(file)) { if (this.vaultAccess.recentlyTouched(file)) {
return true; return true;
} }
try { try {
@@ -2555,7 +2577,11 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
return false; return false;
}); });
if (isNotChanged) return true; if (isNotChanged) {
this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) }));
Logger(msg + " Skip " + fullPath, LOG_LEVEL_VERBOSE);
return true;
}
const ret = await this.localDatabase.putDBEntry(d, initialScan); const ret = await this.localDatabase.putDBEntry(d, initialScan);
this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) })); this.queuedFiles = this.queuedFiles.map((e) => ({ ...e, ...(e.entry._id == d._id ? { done: true } : {}) }));
@@ -2584,7 +2610,7 @@ Or if you are sure know what had been happened, we can unlock the database from
} }
async resetLocalDatabase() { async resetLocalDatabase() {
clearTouched(); this.vaultAccess.clearTouched();
await this.localDatabase.resetDatabase(); await this.localDatabase.resetDatabase();
} }
@@ -2624,10 +2650,6 @@ Or if you are sure know what had been happened, we can unlock the database from
return files.filter(file => !ignorePatterns.some(e => file.path.match(e))).filter(file => !targetFiles || (targetFiles && targetFiles.indexOf(file.path) !== -1)) return files.filter(file => !ignorePatterns.some(e => file.path.match(e))).filter(file => !targetFiles || (targetFiles && targetFiles.indexOf(file.path) !== -1))
} }
async applyMTimeToFile(file: InternalFileInfo) {
await this.app.vault.adapter.append(file.path, "", { ctime: file.ctime, mtime: file.mtime });
}
async resolveConflictByNewerEntry(path: FilePathWithPrefix) { async resolveConflictByNewerEntry(path: FilePathWithPrefix) {
const id = await this.path2id(path); const id = await this.path2id(path);
const doc = await this.localDatabase.getRaw<AnyEntry>(id, { conflicts: true }); const doc = await this.localDatabase.getRaw<AnyEntry>(id, { conflicts: true });
@@ -2654,7 +2676,7 @@ Or if you are sure know what had been happened, we can unlock the database from
ignoreFiles = [] as string[] ignoreFiles = [] as string[]
async readIgnoreFile(path: string) { async readIgnoreFile(path: string) {
try { try {
const file = await this.app.vault.adapter.read(path); const file = await this.vaultAccess.adapterRead(path);
const gitignore = file.split(/\r?\n/g); const gitignore = file.split(/\r?\n/g);
this.ignoreFileCache.set(path, gitignore); this.ignoreFileCache.set(path, gitignore);
return gitignore; return gitignore;

View File

@@ -1,4 +1,4 @@
import { type DataWriteOptions, normalizePath, TFile, Platform, TAbstractFile, App, Plugin, type RequestUrlParam, requestUrl } from "./deps"; import { normalizePath, TFile, Platform, TAbstractFile, App, Plugin, type RequestUrlParam, requestUrl } from "./deps";
import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path"; import { path2id_base, id2path_base, isValidFilenameInLinux, isValidFilenameInDarwin, isValidFilenameInWidows, isValidFilenameInAndroid, stripAllPrefixes } from "./lib/src/path";
import { Logger } from "./lib/src/logger"; import { Logger } from "./lib/src/logger";
@@ -303,20 +303,6 @@ export function flattenObject(obj: Record<string | number | symbol, any>, path:
return ret; return ret;
} }
export function modifyFile(file: TFile, data: string | ArrayBuffer, options?: DataWriteOptions) {
if (typeof (data) === "string") {
return app.vault.modify(file, data, options);
} else {
return app.vault.modifyBinary(file, data, options);
}
}
export function createFile(path: string, data: string | ArrayBuffer, options?: DataWriteOptions): Promise<TFile> {
if (typeof (data) === "string") {
return app.vault.create(path, data, options);
} else {
return app.vault.createBinary(path, data, options);
}
}
export function isValidPath(filename: string) { export function isValidPath(filename: string) {
if (Platform.isDesktop) { if (Platform.isDesktop) {
@@ -332,38 +318,10 @@ export function isValidPath(filename: string) {
return isValidFilenameInWidows(filename); return isValidFilenameInWidows(filename);
} }
let touchedFiles: string[] = [];
export function getAbstractFileByPath(path: FilePath): TAbstractFile | null {
// Disabled temporary.
return app.vault.getAbstractFileByPath(path);
// // Hidden API but so useful.
// // @ts-ignore
// if ("getAbstractFileByPathInsensitive" in app.vault && (app.vault.adapter?.insensitive ?? false)) {
// // @ts-ignore
// return app.vault.getAbstractFileByPathInsensitive(path);
// } else {
// return app.vault.getAbstractFileByPath(path);
// }
}
export function trimPrefix(target: string, prefix: string) { export function trimPrefix(target: string, prefix: string) {
return target.startsWith(prefix) ? target.substring(prefix.length) : target; return target.startsWith(prefix) ? target.substring(prefix.length) : target;
} }
export function touch(file: TFile | FilePath) {
const f = file instanceof TFile ? file : getAbstractFileByPath(file) as TFile;
const key = `${f.path}-${f.stat.mtime}-${f.stat.size}`;
touchedFiles.unshift(key);
touchedFiles = touchedFiles.slice(0, 100);
}
export function recentlyTouched(file: TFile) {
const key = `${file.path}-${file.stat.mtime}-${file.stat.size}`;
if (touchedFiles.indexOf(key) == -1) return false;
return true;
}
export function clearTouched() {
touchedFiles = [];
}
/** /**
* returns is internal chunk of file * returns is internal chunk of file
@@ -481,7 +439,7 @@ export const requestToCouchDB = async (baseUri: string, username: string, passwo
export async function performRebuildDB(plugin: ObsidianLiveSyncPlugin, method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") { export async function performRebuildDB(plugin: ObsidianLiveSyncPlugin, method: "localOnly" | "remoteOnly" | "rebuildBothByThisDevice") {
if (method == "localOnly") { if (method == "localOnly") {
await plugin.addOnSetup.fetchLocalWithKeepLocal(); await plugin.addOnSetup.fetchLocal();
} }
if (method == "remoteOnly") { if (method == "remoteOnly") {
await plugin.addOnSetup.rebuildRemote(); await plugin.addOnSetup.rebuildRemote();

View File

@@ -1,43 +1,31 @@
### 0.20.0 ### 0.21.0
The E2EE encryption V2 format has been reverted. That was probably the cause of the glitch.
Instead, to maintain efficiency, files are treated with Blob until just before saving. Along with this, the old-fashioned encryption format has also been discontinued.
There are both forward and backwards compatibilities, with recent versions. However, unfortunately, we lost compatibility with filesystem-livesync or some.
It will be addressed soon. Please be patient if you are using filesystem-livesync with E2EE.
At 0.20.0, Self-hosted LiveSync has changed the binary file format and encrypting format, for efficient synchronisation.
The dialogue will be shown and asks us to decide whether to keep v1 or use v2. Once we have enabled v2, all subsequent edits will be saved in v2. Therefore, devices running 0.19 or below cannot understand this and they might say that decryption error. Please update all devices.
Then we will have an impressive performance.
Of course, these are very impactful changes. If you have any questions or troubled things, please feel free to open an issue and mention me.
Note: if you want to roll it back to v1, please enable `Use binary and encryption version 1` on the `Hatch` pane and perform the `rebuild everything` once.
Extra but notable information:
This format change gives us the ability to detect some `marks` in the binary files as same as text files. Therefore, we can split binary files and some specific sort of them (i.e., PDF files) at the specific character. It means that editing the middle of files could be detected with marks.
Now only a few chunks are transferred, even if we add a comment to the PDF or put new files into the ZIP archives.
#### Version history #### Version history
- 0.20.2 - 0.21.2
- New feature: - IMPORTANT NOTICE: **0.21.1 CONTAINS A BUG WHILE REBUILDING THE DATABASE. IF YOU HAVE BEEN REBUILT, PLEASE MAKE SURE THAT ALL FILES ARE SANE.**
- We can delete all data of customization sync from the `Delete all customization sync data` on the `Hatch` pane. - This has been fixed in this version.
- Fixed: - Fixed:
- Prevent keep restarting on iOS by yielding microtasks. - No longer files are broken while rebuilding.
- 0.20.1 - Now, Large binary files can be written correctly on a mobile platform.
- Any decoding errors now make zero-byte files.
- Modified:
- All files are processed sequentially for each.
- 0.21.1
- Fixed: - Fixed:
- No more UI freezing and keep restarting on iOS. - No more infinity loops on larger files.
- Diff of Non-markdown documents are now shown correctly. - Show message on decode error.
- Improved: - Refactored:
- Performance has been a bit improved. - Fixed to avoid obsolete global variables.
- Customization sync has gotten faster. - 0.21.0
- However, We lost forward compatibility again (only for this feature). Please update all devices. - Changes and performance improvements:
- Misc - Now the saving files are processed by Blob.
- Terser configuration has been more aggressive. - The V2-Format has been reverted.
- 0.20.0 - New encoding format has been enabled in default.
- Improved: - WARNING: Since this version, the compatibilities with older Filesystem LiveSync have been lost.
- A New binary file handling implemented
- A new encrypted format has been implemented
- Now the chunk sizes will be adjusted for efficient sync
- Fixed:
- levels of exception in some logs have been fixed
- Tidied:
- Some Lint warnings have been suppressed.
... To continue on to `updates_old.md`. ... To continue on to `updates_old.md`.

View File

@@ -1,3 +1,72 @@
## 0.20.0
At 0.20.0, Self-hosted LiveSync has changed the binary file format and encrypting format, for efficient synchronisation.
The dialogue will be shown and asks us to decide whether to keep v1 or use v2. Once we have enabled v2, all subsequent edits will be saved in v2. Therefore, devices running 0.19 or below cannot understand this and they might say that decryption error. Please update all devices.
Then we will have an impressive performance.
Of course, these are very impactful changes. If you have any questions or troubled things, please feel free to open an issue and mention me.
Note: if you want to roll it back to v1, please enable `Use binary and encryption version 1` on the `Hatch` pane and perform the `rebuild everything` once.
Extra but notable information:
This format change gives us the ability to detect some `marks` in the binary files as same as text files. Therefore, we can split binary files and some specific sort of them (i.e., PDF files) at the specific character. It means that editing the middle of files could be detected with marks.
Now only a few chunks are transferred, even if we add a comment to the PDF or put new files into the ZIP archives.
- 0.20.7
- Fixed
- To better replication, path obfuscation is now deterministic even if with E2EE.
Note: Compatible with previous database without any conversion. Only new files will be obfuscated in deterministic.
- 0.20.6
- Fixed
- Now empty file could be decoded.
- Local files are no longer pre-saved before fetching from a remote database.
- No longer deadlock while applying customisation sync.
- Configuration with multiple files is now able to be applied correctly.
- Deleting folder propagation now works without enabling the use of a trash bin.
- 0.20.5
- Fixed
- Now the files which having digit or character prefixes in the path will not be ignored.
- 0.20.4
- Fixed
- The text-input-dialogue is no longer broken.
- Finally, we can use the Setup URI again on mobile.
- 0.20.3
- New feature:
- We can launch Customization sync from the Ribbon if we enabled it.
- Fixed:
- Setup URI is now back to the previous spec; be encrypted by V1.
- It may avoid the trouble with iOS 17.
- The Settings dialogue is now registered at the beginning of the start-up process.
- We can change the configuration even though LiveSync could not be launched in normal.
- Improved:
- Enumerating documents has been faster.
- 0.20.2
- New feature:
- We can delete all data of customization sync from the `Delete all customization sync data` on the `Hatch` pane.
- Fixed:
- Prevent keep restarting on iOS by yielding microtasks.
- 0.20.1
- Fixed:
- No more UI freezing and keep restarting on iOS.
- Diff of Non-markdown documents are now shown correctly.
- Improved:
- Performance has been a bit improved.
- Customization sync has gotten faster.
- However, We lost forward compatibility again (only for this feature). Please update all devices.
- Misc
- Terser configuration has been more aggressive.
- 0.20.0
- Improved:
- A New binary file handling implemented
- A new encrypted format has been implemented
- Now the chunk sizes will be adjusted for efficient sync
- Fixed:
- levels of exception in some logs have been fixed
- Tidied:
- Some Lint warnings have been suppressed.
### 0.19.0 ### 0.19.0
#### Customization sync #### Customization sync