Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d1b451f4c | ||
|
|
69537f7002 | ||
|
|
17de78240f | ||
|
|
8cbcdf5323 | ||
|
|
6ba5abc0a6 | ||
|
|
d820285d1e | ||
|
|
a6002ba340 | ||
|
|
1f31303906 | ||
|
|
0fcedf397f | ||
|
|
085d8448d2 | ||
|
|
0d9da32b00 | ||
|
|
22880d6cf5 | ||
|
|
a084ec7728 | ||
|
|
eeeb4893c7 | ||
|
|
ed1ac66ac0 | ||
|
|
c33a9ecfa4 | ||
|
|
cabca54d80 | ||
|
|
8dde6c59d2 |
@@ -64,7 +64,8 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
|
|||||||
- Tutorials / Examples:
|
- Tutorials / Examples:
|
||||||
- [Cloudflare R2](./docs/remote_services/s3_cloudflare_r2/README.md)
|
- [Cloudflare R2](./docs/remote_services/s3_cloudflare_r2/README.md)
|
||||||
- [BackBlaze B2](./docs/remote_services/s3_backblaze_b2/README.md)
|
- [BackBlaze B2](./docs/remote_services/s3_backblaze_b2/README.md)
|
||||||
- [腾讯云 COS Tencent Cloud COS](./docs/remote_services/s3_tencent_cloud_cos/README.md)
|
- [Storj](./docs/remote_services/s3_storj_io/README.md)
|
||||||
|
- [腾讯云 COS](./docs/remote_services/s3_tencent_cloud_cos/README.zh-cn.md) | [Tencent Cloud COS](./docs/remote_services/s3_tencent_cloud_cos/README.md)
|
||||||
- [MinIO](./docs/remote_services/s3_minio/README.md)
|
- [MinIO](./docs/remote_services/s3_minio/README.md)
|
||||||
- Prepare your S3 (-compatible) service information: [endpoint, region](https://docs.aws.amazon.com/general/latest/gr/s3.html), [access key id, secret access key](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html), bucket name. The bucket should be empty and solely for syncing a vault.
|
- Prepare your S3 (-compatible) service information: [endpoint, region](https://docs.aws.amazon.com/general/latest/gr/s3.html), [access key id, secret access key](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/getting-your-credentials.html), bucket name. The bucket should be empty and solely for syncing a vault.
|
||||||
- If you are using AWS S3, create [policy and user](./docs/remote_services/s3_general/s3_user_policy.md).
|
- If you are using AWS S3, create [policy and user](./docs/remote_services/s3_general/s3_user_policy.md).
|
||||||
@@ -95,8 +96,9 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
|
|||||||
- Tutorials / Examples:
|
- Tutorials / Examples:
|
||||||
- [ownCloud](./docs/remote_services/webdav_owncloud/README.md)
|
- [ownCloud](./docs/remote_services/webdav_owncloud/README.md)
|
||||||
- [InfiniCloud](./docs/remote_services/webdav_infinicloud_teracloud/README.md)
|
- [InfiniCloud](./docs/remote_services/webdav_infinicloud_teracloud/README.md)
|
||||||
- [AList](./docs/remote_services/webdav_alist/README.md)
|
- [Synology webdav server](./docs/remote_services/webdav_synology_webdav_server/README.md) | [群晖 webdav server](./docs/remote_services/webdav_synology_webdav_server/README.zh-cn.md)
|
||||||
- [坚果云 JianGuoYun/NutStore](./docs/remote_services/webdav_jianguoyun/README.md)
|
- [AList(中文)](./docs/remote_services/webdav_alist/README.zh-cn.md) | [AList (English)](./docs/remote_services/webdav_alist/README.md)
|
||||||
|
- [坚果云](./docs/remote_services/webdav_jianguoyun/README.zh-cn.md) | [JianGuoYun/NutStore](./docs/remote_services/webdav_jianguoyun/README.md)
|
||||||
- Very old version of Obsidian needs [configuring CORS](./docs/remote_services/webdav_general/webav_cors.md).
|
- Very old version of Obsidian needs [configuring CORS](./docs/remote_services/webdav_general/webav_cors.md).
|
||||||
- Your data would be synced to a `${vaultName}` sub folder on your webdav server.
|
- Your data would be synced to a `${vaultName}` sub folder on your webdav server.
|
||||||
- Password-based end-to-end encryption is also supported. But please be aware that **the vault name itself is not encrypted**.
|
- Password-based end-to-end encryption is also supported. But please be aware that **the vault name itself is not encrypted**.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Storj
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<https://www.storj.io/>
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Register an account. Login.
|
||||||
|
2. Create a bucket.
|
||||||
|
3. Create S3 Credentials in Access Management. Allow all permissions for the bucket. Remember the access key and secret key and the end point. The end point is likely to be [`https://gateway.storjshare.io`](https://docs.storj.io/dcs/api/s3/s3-compatible-gateway).
|
||||||
|

|
||||||
|

|
||||||
|
4. Input your credentials into remotely-save settings. Region [should be `global`](https://docs.storj.io/dcs/api/s3/s3-compatibility).
|
||||||
|

|
||||||
|
5. Check connectivity.
|
||||||
|
6. Sync!
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a3484f74f45fbdae6110de285edeff10c196c13160cb97f079da77843ae92e80
|
||||||
|
size 386188
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:42593d98e1b154f4174a932088e72c25db9a976b5613f4d6ae21df1445b4559c
|
||||||
|
size 278219
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7843c31153054eb4762dbaf3027c1042b0d7cb4dd774bf584788cd7a97ba4044
|
||||||
|
size 571188
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
# 腾讯云 COS Tencent Cloud COS
|
# Tencent Cloud COS
|
||||||
|
|
||||||
## 链接 Link
|
English | [中文](./README.zh-cn.md)
|
||||||
|
|
||||||
|
## Link
|
||||||
|
|
||||||
- 中国区 <https://console.cloud.tencent.com/cos>
|
|
||||||
- international <https://console.tencentcloud.com/cos>
|
- international <https://console.tencentcloud.com/cos>
|
||||||
|
|
||||||
## 步骤 Steps
|
## 步骤 Steps
|
||||||
|
|
||||||
注意这里用中国区示例,国际区配置应该类似。The example shows the steps of China version. International version should be similar.
|
The example shows the steps of China version. International version should be similar.
|
||||||
|
|
||||||
1. 在“存储桶列表”页,[“创建存储桶”](https://console.cloud.tencent.com/cos/bucket?action=create)。注意创建**私有读写**,建议打开服务端加密。Create a bucket with **private read-write permissions** and recommendly enable server-side-encryption.
|
1. Create a bucket with **private read-write permissions** and recommendly enable server-side-encryption.
|
||||||
2. 在桶列表页,点击刚刚存储的桶,进入概览页。可以见到桶名称(一般来说是之前指定的英文加账号数字),地域,访问域名。记录下来。In bucket list page, enter the bucket overview page of the bucket you just created. You should see the bucket name (your texts with the number of your account id), region, and access address.
|
2. In bucket list page, enter the bucket overview page of the bucket you just created. You should see the bucket name (your texts with the number of your account id), region, and access address.
|
||||||

|

|
||||||
3. 在[“访问管理页”](https://console.cloud.tencent.com/cam/capi) ,“API 密钥管理”,“创建密钥”,要记录 SecretID 和 SecretKey。In CAM page, create api key, and note down the SecretID and SecretKey.
|
3. In CAM page, create api key, and note down the SecretID and SecretKey.
|
||||||

|

|
||||||
4. **把桶名称从访问域名移除,才是你即将输入的服务地址!假如你在腾讯云网站看到访问域名是 `https://<bucket-name-with-number>.cos.<region>.myqcloud.com`,那么“服务地址”是 `https://cos.<region>.myqcloud.com`. Remove the bucket name from your access address to obtain your endpoint address! If your access address on the website is `https://<bucket-name-with-number>.cos.<region>.myqcloud.com`, then the endpoint address you are going to use is `https://cos.<region>.myqcloud.com`.**
|
4. **Remove the bucket name from your access address to obtain your endpoint address! If your access address on the website is `https://<bucket-name-with-number>.cos.<region>.myqcloud.com`, then the endpoint address you are going to use is `https://cos.<region>.myqcloud.com`.**
|
||||||
5. 在 remotely-save 设置,输入服务地址,SecretID,SecretKey,和 桶名称。In remotely-save settings page, enter your endpoint adress, SecretID, SecretKey,and bucket name.
|
5. In remotely-save settings page, enter your endpoint adress, SecretID, SecretKey,and bucket name.
|
||||||

|

|
||||||
6. 检查连接。Check Connectivity.
|
6. Check Connectivity.
|
||||||

|

|
||||||
7. 可以同步了!Sync!
|
7. Sync!
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# 腾讯云 COS
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- 中国区 <https://console.cloud.tencent.com/cos>
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
注意这里用中国区示例,国际区配置应该类似。
|
||||||
|
|
||||||
|
1. 在“存储桶列表”页,[“创建存储桶”](https://console.cloud.tencent.com/cos/bucket?action=create)。注意创建**私有读写**,建议打开服务端加密。
|
||||||
|
2. 在桶列表页,点击刚刚存储的桶,进入概览页。可以见到桶名称(一般来说是之前指定的英文加账号数字),地域,访问域名。记录下来。
|
||||||
|

|
||||||
|
3. 在[“访问管理页”](https://console.cloud.tencent.com/cam/capi) ,“API 密钥管理”,“创建密钥”,要记录 SecretID 和 SecretKey。
|
||||||
|

|
||||||
|
4. **把桶名称从访问域名移除,才是你即将输入的服务地址!假如你在腾讯云网站看到访问域名是 `https://<bucket-name-with-number>.cos.<region>.myqcloud.com`,那么“服务地址”是 `https://cos.<region>.myqcloud.com`.**
|
||||||
|
5. 在 remotely-save 设置,输入服务地址,SecretID,SecretKey,和 桶名称。
|
||||||
|

|
||||||
|
6. 检查连接。
|
||||||
|

|
||||||
|
7. 可以同步了!
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
# AList
|
# AList
|
||||||
|
|
||||||
## 链接 Links
|
English | [中文](./README.zh-cn.md)
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
- English official website: <https://alist.nn.ci/> and <https://alist.nn.ci/guide/webdav.html>
|
- English official website: <https://alist.nn.ci/> and <https://alist.nn.ci/guide/webdav.html>
|
||||||
- 中文官网:<https://alist.nn.ci/zh/> 和 <https://alist.nn.ci/zh/guide/webdav.html>
|
|
||||||
|
|
||||||
## 步骤 Steps
|
## Steps
|
||||||
|
|
||||||
1. 安装和使用 AList。获取账号名和密码。在网页上登录。Install and run AList. Get the account and password. Login using the web page.
|
1. Install and run AList. Get the account and password. Login using the web page.
|
||||||
2. 新建挂载,检查挂载路径。如图所示是 `/alisttest davpath`。Add new storage. Pay attention to the mount path. The screenshot shows the mount path as `/alisttest davpath`.
|
2. Add new storage. Pay attention to the mount path. The screenshot shows the mount path as `/alisttest davpath`.
|
||||||

|

|
||||||

|

|
||||||
3. 从而构建 webdav 网址如下,**http(s)://域名** + **端口** + **`/dav`** + **挂载路径**,其中挂载路径中假如有空格,换成 `%20`:Construct the webdav address as: **http(s)://domain** + **port** + **`/dav`** + **mount path**, and the space inside the mount path should be replaced with `%20`:
|
3. Construct the webdav address as: **http(s)://domain** + **port** + **`/dav`** + **mount path**, and the space inside the mount path should be replaced with `%20`:
|
||||||
```
|
```
|
||||||
http[s]://domain:port/dav/[mountpath url encoded]
|
http[s]://domain:port/dav/[mountpath url encoded]
|
||||||
http://127.0.0.1:5244/dav/alisttest%20davpath
|
http://127.0.0.1:5244/dav/alisttest%20davpath
|
||||||
```
|
```
|
||||||
4. 在 remotely-save 设置,输入**带域名端口`/dav`和挂载路径的网址**、账号、密码。In remotely-save setting page, select webdav type, then input the **full address with mount path**/account/password.
|
4. In remotely-save setting page, select webdav type, then input the **full address with mount path**/account/password.
|
||||||

|

|
||||||
5. 在 remotely-save 设置,检查连接。In remotely-save setting page, click "Check Connectivity".
|
5. In remotely-save setting page, click "Check Connectivity".
|
||||||
6. 同步文件!Sync!
|
6. Sync!
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# AList
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- 中文官网:<https://alist.nn.ci/zh/> 和 <https://alist.nn.ci/zh/guide/webdav.html>
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. 安装和使用 AList。获取账号名和密码。在网页上登录。
|
||||||
|
2. 新建挂载,检查挂载路径。如图所示是 `/alisttest davpath`。
|
||||||
|

|
||||||
|

|
||||||
|
3. 从而构建 webdav 网址如下,**http(s)://域名** + **端口** + **`/dav`** + **挂载路径**,其中挂载路径中假如有空格,换成 `%20`:
|
||||||
|
```
|
||||||
|
http[s]://domain:port/dav/[mountpath url encoded]
|
||||||
|
http://127.0.0.1:5244/dav/alisttest%20davpath
|
||||||
|
```
|
||||||
|
4. 在 remotely-save 设置,输入**带域名端口`/dav`和挂载路径的网址**、账号、密码。
|
||||||
|

|
||||||
|
5. 在 remotely-save 设置,检查连接。
|
||||||
|
6. 同步文件!
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
# 坚果云 JianGuoYun/NutStore
|
# JianGuoYun/NutStore
|
||||||
|
|
||||||
## 链接 Link
|
English | [中文](./README.zh-cn.md)
|
||||||
|
|
||||||
|
## Link
|
||||||
|
|
||||||
<https://www.jianguoyun.com/>
|
<https://www.jianguoyun.com/>
|
||||||
|
|
||||||
## 注意!!!Attentions!!!
|
## Attentions!!!
|
||||||
|
|
||||||
坚果云有限制 api 数量等设定。本插件会产生若干查询,如果文件较多很容易触发 api 上限,从而工作不正常。这不是插件 bug,也没有办法解决。
|
|
||||||
|
|
||||||
JianGuoYun/NutStore has api limits. The plugin may generate many queries, and it's possible to reach the api limits if there are many files, then do not work properly. It's not a bug and there's no way to fix this situation.
|
JianGuoYun/NutStore has api limits. The plugin may generate many queries, and it's possible to reach the api limits if there are many files, then do not work properly. It's not a bug and there's no way to fix this situation.
|
||||||
|
|
||||||
## 步骤 Steps
|
## Steps
|
||||||
|
|
||||||
1. **知悉坚果云有 api 限制,本插件可能因此工作不正常。Be aware that JianGuoYun/NutStore has api limits, and the plugin may not work properly because of this.**
|
1. **Be aware that JianGuoYun/NutStore has api limits, and the plugin may not work properly because of this.**
|
||||||
2. 注册账号,登录。Register an account.
|
2. Register an account.
|
||||||
3. 去“个人信息”->“安全”,“添加应用”,从而获取了 webDAV 账号(应该是 email)和 WebDAV 密码(一串特殊的字符,不等于网站密码)。Go to "settings"->"Security", click "Add Application", then obtain the WebDAV account (email), and WebDAV password (a string different from web site password).
|
3. Go to "settings"->"Security", click "Add Application", then obtain the WebDAV account (email), and WebDAV password (a string different from web site password).
|
||||||

|

|
||||||
4. 在 remotely-save 设置,输入网址、账号、密码、**“发送到服务器的 Depth Header”设置为“只支持 depth='1'”**。Input the WebDAV address, account, password, **Depth Header Sent To Servers="only supports depth='1'"** in remotely-save settings.
|
4. Input the WebDAV address, account, password, **Depth Header Sent To Servers="only supports depth='1'"** in remotely-save settings.
|
||||||

|

|
||||||
5. 在 remotely-save 设置,检查连接。In remotely-save setting page, click "Check Connectivity".
|
5. In remotely-save setting page, click "Check Connectivity".
|
||||||
6. 同步文件!Sync!
|
6. Sync!
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 坚果云
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
<https://www.jianguoyun.com/>
|
||||||
|
|
||||||
|
## 注意!!!
|
||||||
|
|
||||||
|
坚果云有限制 api 数量等设定。本插件会产生若干查询,如果文件较多很容易触发 api 上限,从而工作不正常。这不是插件 bug,也没有办法解决。
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. **知悉坚果云有 api 限制,本插件可能因此工作不正常。**
|
||||||
|
2. 注册账号,登录。
|
||||||
|
3. 去“个人信息”->“安全”,“添加应用”,从而获取了 webDAV 账号(应该是 email)和 WebDAV 密码(一串特殊的字符,不等于网站密码)。
|
||||||
|

|
||||||
|
4. 在 remotely-save 设置,输入网址、账号、密码、**“发送到服务器的 Depth Header”设置为“只支持 depth='1'”**。
|
||||||
|

|
||||||
|
5. 在 remotely-save 设置,检查连接。
|
||||||
|
6. 同步文件!
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Synology Webdav Server
|
||||||
|
|
||||||
|
English | [中文](./README.zh-cn.md)
|
||||||
|
|
||||||
|
## Link
|
||||||
|
|
||||||
|
<https://kb.synology.com/en-global/DSM/tutorial/How_to_access_files_on_Synology_NAS_with_WebDAV>
|
||||||
|
|
||||||
|
## Attention
|
||||||
|
|
||||||
|
The tutorial author (the author of Remotely Save) is NOT an expert of NAS / Synology. Please read the doc carefully and change it to accommodate your needs by yourself.
|
||||||
|
|
||||||
|
**It's dangerous to expose your NAS into public Internet if you don't know how to set up firewalls and other protections.**
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
Synology DSM 7 is used in this tutorial.
|
||||||
|
|
||||||
|
1. Create a new shared folder if you don't have one. For this tutorial, a new shared folder `share2` is created. You should assing a proper user to read / write the shared folder.
|
||||||
|

|
||||||
|
|
||||||
|
2. Assuming you want to sync your vault into sub folder `哈哈哈/sub folder`, please create the sub folder(s) correspondingly inide the shared folder `share2`.
|
||||||
|
|
||||||
|
3. Install webdav server from package center.
|
||||||
|

|
||||||
|
|
||||||
|
4. Enter the webdav server settings.
|
||||||
|
|
||||||
|
5. If you know how to configure https certificates correctly, you are strongly recommend to enable https.
|
||||||
|
|
||||||
|
For the demonstration purpose, this tutorial enable http server for the later steps.
|
||||||
|
|
||||||
|
Also "Enable DavDepthInfinity", which could speed up the plugin runnings greatly.
|
||||||
|
|
||||||
|
"Apply".
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. In Remotely Save settings, you should input your address as:
|
||||||
|
|
||||||
|
`http(s)://<your synology ip or domain>:<port>/<shared folder>/<sub folders>`
|
||||||
|
|
||||||
|
For example, in the tutorial, the proper url should be:
|
||||||
|
|
||||||
|
`http://<ip>:5000/share2/哈哈哈/sub folder`
|
||||||
|
|
||||||
|
Username and password should be the user you configured before with read / write permissions to `share2`.
|
||||||
|
|
||||||
|
Depth header should be "supports depth="infinity"".
|
||||||
|
|
||||||
|
Check connectivity!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
7. Sync!
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 群晖 Webdav Server
|
||||||
|
|
||||||
|
[English](./README.md) | 中文
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
<https://kb.synology.cn/zh-cn/DSM/tutorial/How_to_access_files_on_Synology_NAS_with_WebDAV>
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
教程作者(Remotely Save 作者)**不是** NAS、群晖专家。请仔细阅读文档,并自行改动以适应您自身需求。
|
||||||
|
|
||||||
|
**没有设置防火墙和其他保护措施的话,将 NAS 暴露到公网上非常危险。**
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
本教程有用到群晖 DSM 7。
|
||||||
|
|
||||||
|
1. 创建共享文件夹。本教程示例创建了 `share2`。你需要允许某个账号对此的读写权限。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. 假设之后你想同步你的库到子文件夹,`哈哈哈/sub folder`,请先在共享文件夹 `share2` 底下创建好。
|
||||||
|
|
||||||
|
3. 从套件中心安装 webdav server 。
|
||||||
|

|
||||||
|
|
||||||
|
4. 进入 webdav server 设置。
|
||||||
|
|
||||||
|
5. 如果你知道如何正确配置 https 证书的话,强烈建议开启 https。
|
||||||
|
|
||||||
|
本教程简化示例,开启了 http。
|
||||||
|
|
||||||
|
也设置“Enable DavDepthInfinity”,这可以加速插件连接速度。
|
||||||
|
|
||||||
|
“Apply”。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. 在 Remotely Save 设置页,你的地址应如下格式输入:
|
||||||
|
|
||||||
|
`http(s)://<your synology ip or domain>:<port>/<shared folder>/<sub folders>`
|
||||||
|
|
||||||
|
比如说,本教程里,正确的地址类似于:
|
||||||
|
|
||||||
|
`http://<ip>:5000/share2/哈哈哈/sub folder`
|
||||||
|
|
||||||
|
用户名和密码是你之前配置了允许读写 `share2` 的那个账号。
|
||||||
|
|
||||||
|
Depth 设置应为“supports depth="infinity"”。
|
||||||
|
|
||||||
|
检查连接!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
7. 同步!
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:71e61c25db793c00d4406ea0f46745941e16e268a41b7328b03a183ab0706ec8
|
||||||
|
size 59700
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0f0725099989b5496048c0e3cb284b4f6537d66866b4618c8e73ee758cb2b642
|
||||||
|
size 174986
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7bc6d03a9deb0e8babd2ed102e7b85ee1062797abf1146ae71f80237a852ff10
|
||||||
|
size 522084
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ba3781f24b5d65ee6915d496f7542c569116141cdced24ca505f4373d9efa9df
|
||||||
|
size 536163
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Sync Algorithm V3
|
||||||
|
|
||||||
|
Drafted on 20240117.
|
||||||
|
|
||||||
|
An absolutely better sync algorithm. Better for tracking deletions and better for subbranching.
|
||||||
|
|
||||||
|
## Huge Thanks
|
||||||
|
|
||||||
|
Basically a combination of algorithm v2 + [synclone](https://github.com/Jwink3101/syncrclone) + [rsinc](https://github.com/ConorWilliams/rsinc) + (some of rclone [bisync](https://rclone.org/bisync/)). All of the later three are released under MIT License so no worries about the licenses.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Must have
|
||||||
|
|
||||||
|
1. true deletion detection
|
||||||
|
2. deletion protection (blocking) with a setting
|
||||||
|
3. transaction from the old algorithm
|
||||||
|
4. user warning show up, **new algorithm needs all clients to be updated!** (deliberately corrput the metadata file??)
|
||||||
|
5. filters
|
||||||
|
6. conflict warning
|
||||||
|
7. partial sync
|
||||||
|
|
||||||
|
Nice to have
|
||||||
|
|
||||||
|
1. true time and hash
|
||||||
|
2. conflict rename
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
We have _five_ input sources: local all files, remote all files, _local previous succeeded sync history_, local deletions, remote deletions.
|
||||||
|
|
||||||
|
Init run, consuming local deletions and remote deletions :
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
Later runs, use the first, second, third sources **only**.
|
||||||
|
|
||||||
|
TBD
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.3.37",
|
"version": "0.3.40",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.3.37",
|
"version": "0.3.40",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.3.25",
|
"version": "0.3.40",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev2": "node esbuild.config.mjs --watch",
|
"dev2": "node esbuild.config.mjs --watch",
|
||||||
|
|||||||
+11
-5
@@ -48,9 +48,10 @@ export interface DropboxConfig {
|
|||||||
|
|
||||||
export type WebdavAuthType = "digest" | "basic";
|
export type WebdavAuthType = "digest" | "basic";
|
||||||
export type WebdavDepthType =
|
export type WebdavDepthType =
|
||||||
| "auto_unknown"
|
| "auto" // deprecated on 20240116
|
||||||
| "auto_1"
|
| "auto_unknown" // deprecated on 20240116
|
||||||
| "auto_infinity"
|
| "auto_1" // deprecated on 20240116
|
||||||
|
| "auto_infinity" // deprecated on 20240116
|
||||||
| "manual_1"
|
| "manual_1"
|
||||||
| "manual_infinity";
|
| "manual_infinity";
|
||||||
|
|
||||||
@@ -59,9 +60,14 @@ export interface WebdavConfig {
|
|||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
authType: WebdavAuthType;
|
authType: WebdavAuthType;
|
||||||
manualRecursive: boolean; // deprecated in 0.3.6, use depth
|
|
||||||
depth?: WebdavDepthType;
|
depth?: WebdavDepthType;
|
||||||
remoteBaseDir?: string;
|
remoteBaseDir?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
manualRecursive: boolean; // deprecated in 0.3.6, use depth
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnedriveConfig {
|
export interface OnedriveConfig {
|
||||||
@@ -112,7 +118,7 @@ export interface RemotelySavePluginSettings {
|
|||||||
|
|
||||||
export interface RemoteItem {
|
export interface RemoteItem {
|
||||||
key: string;
|
key: string;
|
||||||
lastModified: number;
|
lastModified?: number;
|
||||||
size: number;
|
size: number;
|
||||||
remoteType: SUPPORTED_SERVICES_TYPE;
|
remoteType: SUPPORTED_SERVICES_TYPE;
|
||||||
etag?: string;
|
etag?: string;
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ export const exportQrCodeUri = async (
|
|||||||
currentVaultName: string,
|
currentVaultName: string,
|
||||||
pluginVersion: string
|
pluginVersion: string
|
||||||
) => {
|
) => {
|
||||||
const settings2 = cloneDeep(settings);
|
const settings2: Partial<RemotelySavePluginSettings> = cloneDeep(settings);
|
||||||
delete settings2.dropbox;
|
delete settings2.dropbox;
|
||||||
delete settings2.onedrive;
|
delete settings2.onedrive;
|
||||||
delete settings2.vaultRandomID;
|
delete settings2.vaultRandomID;
|
||||||
|
|||||||
+10
-9
@@ -40,14 +40,16 @@
|
|||||||
"command_exportsyncplans_json": "export sync plans in json format",
|
"command_exportsyncplans_json": "export sync plans in json format",
|
||||||
"command_exportlogsindb": "export logs saved in db",
|
"command_exportlogsindb": "export logs saved in db",
|
||||||
|
|
||||||
"statusbar_time_years": "{{time}} years",
|
"statusbar_time_years": "Synced {{time}} years ago",
|
||||||
"statusbar_time_months": "{{time}} months",
|
"statusbar_time_months": "Synced {{time}} months ago",
|
||||||
"statusbar_time_weeks": "{{time}} weeks",
|
"statusbar_time_weeks": "Synced {{time}} weeks ago",
|
||||||
"statusbar_time_days": "{{time}} days",
|
"statusbar_time_days": "Synced {{time}} days ago",
|
||||||
"statusbar_time_hours": "{{time}} hours",
|
"statusbar_time_hours": "Synced {{time}} hours ago",
|
||||||
"statusbar_time_minutes": "{{time}} minutes",
|
"statusbar_time_minutes": "Synced {{time}} minutes ago",
|
||||||
"statusbar_time_lessminute": "less than a minute",
|
"statusbar_time_lessminute": "Synced less than a minute ago",
|
||||||
"statusbar_lastsync": "Synced {{time}} ago",
|
"statusbar_lastsync": "Synced {{time}} ago",
|
||||||
|
"statusbar_syncing": "Syncing...",
|
||||||
|
"statusbar_now": "Synced just now",
|
||||||
"statusbar_lastsync_label": "Last successful Sync on {{date}}",
|
"statusbar_lastsync_label": "Last successful Sync on {{date}}",
|
||||||
"statusbar_lastsync_never": "Never Synced",
|
"statusbar_lastsync_never": "Never Synced",
|
||||||
"statusbar_lastsync_never_label": "Never Synced before",
|
"statusbar_lastsync_never_label": "Never Synced before",
|
||||||
@@ -224,8 +226,7 @@
|
|||||||
"settings_webdav_auth": "Auth Type",
|
"settings_webdav_auth": "Auth Type",
|
||||||
"settings_webdav_auth_desc": "If no password, this option would be ignored.",
|
"settings_webdav_auth_desc": "If no password, this option would be ignored.",
|
||||||
"settings_webdav_depth": "Depth Header Sent To Servers",
|
"settings_webdav_depth": "Depth Header Sent To Servers",
|
||||||
"settings_webdav_depth_desc": "Webdav servers should be configured to allow requests with header Depth being '1' or 'Infinity'. The plugin needs to know this info. If you are not sure what's this, choose \"auto\".",
|
"settings_webdav_depth_desc": "Webdav servers should be configured to allow requests with header Depth being '1' or 'Infinity'. If you are not sure what's this, choose \"depth='1'\". If you are sure your server supports depth='infinity', please choose that to get way better performance.",
|
||||||
"settings_webdav_depth_auto": "auto detect",
|
|
||||||
"settings_webdav_depth_1": "only supports depth='1'",
|
"settings_webdav_depth_1": "only supports depth='1'",
|
||||||
"settings_webdav_depth_inf": "supports depth='infinity'",
|
"settings_webdav_depth_inf": "supports depth='infinity'",
|
||||||
"settings_webdav_connect_succ": "Great! The webdav server can be accessed.",
|
"settings_webdav_connect_succ": "Great! The webdav server can be accessed.",
|
||||||
|
|||||||
+10
-9
@@ -40,14 +40,16 @@
|
|||||||
"command_exportsyncplans_json": "导出同步计划为 json 格式(export sync plans in json format)",
|
"command_exportsyncplans_json": "导出同步计划为 json 格式(export sync plans in json format)",
|
||||||
"command_exportlogsindb": "从数据库导出终端日志(export logs saved in db)",
|
"command_exportlogsindb": "从数据库导出终端日志(export logs saved in db)",
|
||||||
|
|
||||||
"statusbar_time_years": "{{time}} 年前",
|
"statusbar_time_years": "{{time}} 年前同步",
|
||||||
"statusbar_time_months": "{{time}} 月前",
|
"statusbar_time_months": "{{time}} 月前同步",
|
||||||
"statusbar_time_weeks": "{{time}} 周前",
|
"statusbar_time_weeks": "{{time}} 周前同步",
|
||||||
"statusbar_time_days": "{{time}} 天前",
|
"statusbar_time_days": "{{time}} 天前同步",
|
||||||
"statusbar_time_hours": "{{time}} 小时前",
|
"statusbar_time_hours": "{{time}} 小时前同步",
|
||||||
"statusbar_time_minutes": "{{time}} 分钟前",
|
"statusbar_time_minutes": "{{time}} 分钟前同步",
|
||||||
"statusbar_time_lessminute": "一分钟之内",
|
"statusbar_time_lessminute": "一分钟之内同步",
|
||||||
"statusbar_lastsync": "上一次同步于:{{time}}",
|
"statusbar_lastsync": "上一次同步于:{{time}}",
|
||||||
|
"statusbar_syncing": "正在同步",
|
||||||
|
"statusbar_now": "刚同步完",
|
||||||
"statusbar_lastsync_label": "上一次同步于:{{date}}",
|
"statusbar_lastsync_label": "上一次同步于:{{date}}",
|
||||||
"statusbar_lastsync_never": "没触发过同步",
|
"statusbar_lastsync_never": "没触发过同步",
|
||||||
"statusbar_lastsync_never_label": "没触发过同步",
|
"statusbar_lastsync_never_label": "没触发过同步",
|
||||||
@@ -224,8 +226,7 @@
|
|||||||
"settings_webdav_auth": "鉴权类型",
|
"settings_webdav_auth": "鉴权类型",
|
||||||
"settings_webdav_auth_desc": "如果不填写密码,本设置会被忽略。",
|
"settings_webdav_auth_desc": "如果不填写密码,本设置会被忽略。",
|
||||||
"settings_webdav_depth": "发送到服务器的 Depth header",
|
"settings_webdav_depth": "发送到服务器的 Depth header",
|
||||||
"settings_webdav_depth_desc": "Webdav 服务器会被设为接收 Depth header 为“1”或“Infinity”请求。本插件需要得知此信息。如果您不清楚如何设置,可以选择“自动检测”",
|
"settings_webdav_depth_desc": "Webdav 服务器会被设为接收 Depth header 为“1”或“Infinity”请求。如果您不清楚如何设置,可以选择 depth='1'。如果那你确认服务器支持 depth='infinity',请选择它,会获得更加好的性能。",
|
||||||
"settings_webdav_depth_auto": "自动检测",
|
|
||||||
"settings_webdav_depth_1": "只支持 depth='1'",
|
"settings_webdav_depth_1": "只支持 depth='1'",
|
||||||
"settings_webdav_depth_inf": "支持 depth='infinity'",
|
"settings_webdav_depth_inf": "支持 depth='infinity'",
|
||||||
"settings_webdav_connect_succ": "很好!可以连接上 Webdav 服务器。",
|
"settings_webdav_connect_succ": "很好!可以连接上 Webdav 服务器。",
|
||||||
|
|||||||
+10
-9
@@ -40,14 +40,16 @@
|
|||||||
"command_exportsyncplans_json": "匯出同步計劃為 json 格式(export sync plans in json format)",
|
"command_exportsyncplans_json": "匯出同步計劃為 json 格式(export sync plans in json format)",
|
||||||
"command_exportlogsindb": "從資料庫匯出終端日誌(export logs saved in db)",
|
"command_exportlogsindb": "從資料庫匯出終端日誌(export logs saved in db)",
|
||||||
|
|
||||||
"statusbar_time_years": "{{time}} 年前",
|
"statusbar_time_years": "{{time}} 年前同步",
|
||||||
"statusbar_time_months": "{{time}} 月前",
|
"statusbar_time_months": "{{time}} 月前同步",
|
||||||
"statusbar_time_weeks": "{{time}} 周前",
|
"statusbar_time_weeks": "{{time}} 周前同步",
|
||||||
"statusbar_time_days": "{{time}} 天前",
|
"statusbar_time_days": "{{time}} 天前同步",
|
||||||
"statusbar_time_hours": "{{time}} 小時前",
|
"statusbar_time_hours": "{{time}} 小時前同步",
|
||||||
"statusbar_time_minutes": "{{time}} 分鐘前",
|
"statusbar_time_minutes": "{{time}} 分鐘前同步",
|
||||||
"statusbar_time_lessminute": "一分鐘之內",
|
"statusbar_time_lessminute": "一分鐘之內同步",
|
||||||
"statusbar_lastsync": "上一次同步於:{{time}}",
|
"statusbar_lastsync": "上一次同步於:{{time}}",
|
||||||
|
"statusbar_syncing": "正在同步",
|
||||||
|
"statusbar_now": "剛同步完",
|
||||||
"statusbar_lastsync_label": "上一次同步於:{{date}}",
|
"statusbar_lastsync_label": "上一次同步於:{{date}}",
|
||||||
"statusbar_lastsync_never": "沒觸發過同步",
|
"statusbar_lastsync_never": "沒觸發過同步",
|
||||||
"statusbar_lastsync_never_label": "沒觸發過同步",
|
"statusbar_lastsync_never_label": "沒觸發過同步",
|
||||||
@@ -224,8 +226,7 @@
|
|||||||
"settings_webdav_auth": "鑑權型別",
|
"settings_webdav_auth": "鑑權型別",
|
||||||
"settings_webdav_auth_desc": "如果不填寫密碼,本設定會被忽略。",
|
"settings_webdav_auth_desc": "如果不填寫密碼,本設定會被忽略。",
|
||||||
"settings_webdav_depth": "傳送到伺服器的 Depth header",
|
"settings_webdav_depth": "傳送到伺服器的 Depth header",
|
||||||
"settings_webdav_depth_desc": "Webdav 伺服器會被設為接收 Depth header 為“1”或“Infinity”請求。本外掛需要得知此資訊。如果您不清楚如何設定,可以選擇“自動檢測”",
|
"settings_webdav_depth_desc": "Webdav 伺服器會被設為接收 Depth header 為“1”或“Infinity”請求。如果您不清楚如何設定,可以選擇 depth='1'。如果那你確認伺服器支援 depth='infinity',請選擇它,會獲得更加好的效能。",
|
||||||
"settings_webdav_depth_auto": "自動檢測",
|
|
||||||
"settings_webdav_depth_1": "只支援 depth='1'",
|
"settings_webdav_depth_1": "只支援 depth='1'",
|
||||||
"settings_webdav_depth_inf": "支援 depth='infinity'",
|
"settings_webdav_depth_inf": "支援 depth='infinity'",
|
||||||
"settings_webdav_connect_succ": "很好!可以連線上 Webdav 伺服器。",
|
"settings_webdav_connect_succ": "很好!可以連線上 Webdav 伺服器。",
|
||||||
|
|||||||
+6
-4
@@ -436,8 +436,8 @@ export const insertRenameRecordByVault = async (
|
|||||||
vaultRandomID: string
|
vaultRandomID: string
|
||||||
) => {
|
) => {
|
||||||
// log.info(fileOrFolder);
|
// log.info(fileOrFolder);
|
||||||
let k1: FileFolderHistoryRecord;
|
let k1: FileFolderHistoryRecord | undefined;
|
||||||
let k2: FileFolderHistoryRecord;
|
let k2: FileFolderHistoryRecord | undefined;
|
||||||
const actionWhen = Date.now();
|
const actionWhen = Date.now();
|
||||||
if (fileOrFolder instanceof TFile) {
|
if (fileOrFolder instanceof TFile) {
|
||||||
k1 = {
|
k1 = {
|
||||||
@@ -473,9 +473,11 @@ export const insertRenameRecordByVault = async (
|
|||||||
// TAbstractFile does not contain these info
|
// TAbstractFile does not contain these info
|
||||||
// but from API_VER_STAT_FOLDER we can manually stat them by path.
|
// but from API_VER_STAT_FOLDER we can manually stat them by path.
|
||||||
const s = await statFix(fileOrFolder.vault, fileOrFolder.path);
|
const s = await statFix(fileOrFolder.vault, fileOrFolder.path);
|
||||||
|
if (s !== undefined && s !== null) {
|
||||||
ctime = s.ctime;
|
ctime = s.ctime;
|
||||||
mtime = s.mtime;
|
mtime = s.mtime;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
k1 = {
|
k1 = {
|
||||||
key: key,
|
key: key,
|
||||||
ctime: ctime,
|
ctime: ctime,
|
||||||
@@ -500,8 +502,8 @@ export const insertRenameRecordByVault = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k1.key}`, k1),
|
db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k1!.key}`, k1),
|
||||||
db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k2.key}`, k2),
|
db.fileHistoryTbl.setItem(`${vaultRandomID}\t${k2!.key}`, k2),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+105
-63
@@ -66,7 +66,6 @@ import type { LangType, LangTypeAndAuto, TransItemType } from "./i18n";
|
|||||||
|
|
||||||
import { DeletionOnRemote, MetadataOnRemote } from "./metadataOnRemote";
|
import { DeletionOnRemote, MetadataOnRemote } from "./metadataOnRemote";
|
||||||
import { SyncAlgoV2Modal } from "./syncAlgoV2Notice";
|
import { SyncAlgoV2Modal } from "./syncAlgoV2Notice";
|
||||||
import { applyPresetRulesInplace } from "./presetRules";
|
|
||||||
|
|
||||||
import { applyLogWriterInplace, log } from "./moreOnLog";
|
import { applyLogWriterInplace, log } from "./moreOnLog";
|
||||||
import AggregateError from "aggregate-error";
|
import AggregateError from "aggregate-error";
|
||||||
@@ -133,18 +132,18 @@ const getIconSvg = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default class RemotelySavePlugin extends Plugin {
|
export default class RemotelySavePlugin extends Plugin {
|
||||||
settings: RemotelySavePluginSettings;
|
settings!: RemotelySavePluginSettings;
|
||||||
db: InternalDBs;
|
db!: InternalDBs;
|
||||||
syncStatus: SyncStatusType;
|
syncStatus!: SyncStatusType;
|
||||||
statusBarElement: HTMLSpanElement;
|
statusBarElement!: HTMLSpanElement;
|
||||||
oauth2Info: OAuth2Info;
|
oauth2Info!: OAuth2Info;
|
||||||
currLogLevel: string;
|
currLogLevel!: string;
|
||||||
currSyncMsg?: string;
|
currSyncMsg?: string;
|
||||||
syncRibbon?: HTMLElement;
|
syncRibbon?: HTMLElement;
|
||||||
autoRunIntervalID?: number;
|
autoRunIntervalID?: number;
|
||||||
syncOnSaveIntervalID?: number;
|
syncOnSaveIntervalID?: number;
|
||||||
i18n: I18n;
|
i18n!: I18n;
|
||||||
vaultRandomID: string;
|
vaultRandomID!: string;
|
||||||
debugServerTemp?: string;
|
debugServerTemp?: string;
|
||||||
|
|
||||||
async syncRun(triggerSource: SyncTriggerSourceType = "manual") {
|
async syncRun(triggerSource: SyncTriggerSourceType = "manual") {
|
||||||
@@ -175,7 +174,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
|
|
||||||
let originLabel = `${this.manifest.name}`;
|
let originLabel = `${this.manifest.name}`;
|
||||||
if (this.syncRibbon !== undefined) {
|
if (this.syncRibbon !== undefined) {
|
||||||
originLabel = this.syncRibbon.getAttribute("aria-label");
|
originLabel = this.syncRibbon.getAttribute("aria-label") as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -204,6 +203,10 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change status to "syncing..." on statusbar
|
||||||
|
if (this.statusBarElement !== undefined) {
|
||||||
|
this.updateLastSuccessSyncMsg(-1);
|
||||||
|
}
|
||||||
//log.info(`huh ${this.settings.password}`)
|
//log.info(`huh ${this.settings.password}`)
|
||||||
if (this.settings.currLogLevel === "info") {
|
if (this.settings.currLogLevel === "info") {
|
||||||
getNotice(
|
getNotice(
|
||||||
@@ -286,7 +289,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.db,
|
this.db,
|
||||||
this.vaultRandomID
|
this.vaultRandomID
|
||||||
);
|
);
|
||||||
let localConfigDirContents: ObsConfigDirFileType[] = undefined;
|
let localConfigDirContents: ObsConfigDirFileType[] | undefined =
|
||||||
|
undefined;
|
||||||
if (this.settings.syncConfigDir) {
|
if (this.settings.syncConfigDir) {
|
||||||
localConfigDirContents = await listFilesInObsFolder(
|
localConfigDirContents = await listFilesInObsFolder(
|
||||||
this.app.vault.configDir,
|
this.app.vault.configDir,
|
||||||
@@ -312,11 +316,11 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
client.serviceType,
|
client.serviceType,
|
||||||
triggerSource,
|
triggerSource,
|
||||||
this.app.vault,
|
this.app.vault,
|
||||||
this.settings.syncConfigDir,
|
this.settings.syncConfigDir ?? false,
|
||||||
this.app.vault.configDir,
|
this.app.vault.configDir,
|
||||||
this.settings.syncUnderscoreItems,
|
this.settings.syncUnderscoreItems ?? false,
|
||||||
this.settings.skipSizeLargerThan,
|
this.settings.skipSizeLargerThan ?? -1,
|
||||||
this.settings.ignorePaths,
|
this.settings.ignorePaths ?? [],
|
||||||
this.settings.password
|
this.settings.password
|
||||||
);
|
);
|
||||||
log.info(plan.mixedStates); // for debugging
|
log.info(plan.mixedStates); // for debugging
|
||||||
@@ -332,6 +336,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
getNotice(t("syncrun_step7"));
|
getNotice(t("syncrun_step7"));
|
||||||
}
|
}
|
||||||
this.syncStatus = "syncing";
|
this.syncStatus = "syncing";
|
||||||
|
|
||||||
await doActualSync(
|
await doActualSync(
|
||||||
client,
|
client,
|
||||||
this.db,
|
this.db,
|
||||||
@@ -350,7 +355,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
new SizesConflictModal(
|
new SizesConflictModal(
|
||||||
self.app,
|
self.app,
|
||||||
self,
|
self,
|
||||||
this.settings.skipSizeLargerThan,
|
this.settings.skipSizeLargerThan ?? -1,
|
||||||
ss,
|
ss,
|
||||||
this.settings.password !== ""
|
this.settings.password !== ""
|
||||||
).open();
|
).open();
|
||||||
@@ -397,7 +402,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.manifest.id
|
this.manifest.id
|
||||||
}-${Date.now()}: finish sync, triggerSource=${triggerSource}`
|
}-${Date.now()}: finish sync, triggerSource=${triggerSource}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
const msg = t("syncrun_abort", {
|
const msg = t("syncrun_abort", {
|
||||||
manifestID: this.manifest.id,
|
manifestID: this.manifest.id,
|
||||||
theDate: `${Date.now()}`,
|
theDate: `${Date.now()}`,
|
||||||
@@ -412,7 +417,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
getNotice(e.message, 10 * 1000);
|
getNotice(e.message, 10 * 1000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
getNotice(error.message, 10 * 1000);
|
getNotice(error?.message ?? "error while sync", 10 * 1000);
|
||||||
}
|
}
|
||||||
this.syncStatus = "idle";
|
this.syncStatus = "idle";
|
||||||
if (this.syncRibbon !== undefined) {
|
if (this.syncRibbon !== undefined) {
|
||||||
@@ -442,10 +447,9 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.currSyncMsg = "";
|
this.currSyncMsg = "";
|
||||||
|
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
await this.checkIfPresetRulesFollowed();
|
|
||||||
|
|
||||||
// lang should be load early, but after settings
|
// lang should be load early, but after settings
|
||||||
this.i18n = new I18n(this.settings.lang, async (lang: LangTypeAndAuto) => {
|
this.i18n = new I18n(this.settings.lang!, async (lang: LangTypeAndAuto) => {
|
||||||
this.settings.lang = lang;
|
this.settings.lang = lang;
|
||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
});
|
});
|
||||||
@@ -475,8 +479,11 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
vaultBasePath,
|
vaultBasePath,
|
||||||
vaultRandomIDFromOldConfigFile
|
vaultRandomIDFromOldConfigFile
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
new Notice(err.message, 10 * 1000);
|
new Notice(
|
||||||
|
err?.message ?? "error of prepareDBAndVaultRandomID",
|
||||||
|
10 * 1000
|
||||||
|
);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,14 +573,18 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.registerObsidianProtocolHandler(
|
this.registerObsidianProtocolHandler(
|
||||||
COMMAND_CALLBACK_DROPBOX,
|
COMMAND_CALLBACK_DROPBOX,
|
||||||
async (inputParams) => {
|
async (inputParams) => {
|
||||||
if (inputParams.code !== undefined) {
|
if (
|
||||||
|
inputParams.code !== undefined &&
|
||||||
|
this.oauth2Info?.verifier !== undefined
|
||||||
|
) {
|
||||||
if (this.oauth2Info.helperModal !== undefined) {
|
if (this.oauth2Info.helperModal !== undefined) {
|
||||||
this.oauth2Info.helperModal.contentEl.empty();
|
const k = this.oauth2Info.helperModal.contentEl;
|
||||||
|
k.empty();
|
||||||
|
|
||||||
t("protocol_dropbox_connecting")
|
t("protocol_dropbox_connecting")
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.forEach((val) => {
|
.forEach((val) => {
|
||||||
this.oauth2Info.helperModal.contentEl.createEl("p", {
|
k.createEl("p", {
|
||||||
text: val,
|
text: val,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -596,7 +607,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
const self = this;
|
const self = this;
|
||||||
setConfigBySuccessfullAuthInplaceDropbox(
|
setConfigBySuccessfullAuthInplaceDropbox(
|
||||||
this.settings.dropbox,
|
this.settings.dropbox,
|
||||||
authRes,
|
authRes!,
|
||||||
() => self.saveSettings()
|
() => self.saveSettings()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -655,14 +666,18 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.registerObsidianProtocolHandler(
|
this.registerObsidianProtocolHandler(
|
||||||
COMMAND_CALLBACK_ONEDRIVE,
|
COMMAND_CALLBACK_ONEDRIVE,
|
||||||
async (inputParams) => {
|
async (inputParams) => {
|
||||||
if (inputParams.code !== undefined) {
|
if (
|
||||||
|
inputParams.code !== undefined &&
|
||||||
|
this.oauth2Info?.verifier !== undefined
|
||||||
|
) {
|
||||||
if (this.oauth2Info.helperModal !== undefined) {
|
if (this.oauth2Info.helperModal !== undefined) {
|
||||||
this.oauth2Info.helperModal.contentEl.empty();
|
const k = this.oauth2Info.helperModal.contentEl;
|
||||||
|
k.empty();
|
||||||
|
|
||||||
t("protocol_onedrive_connecting")
|
t("protocol_onedrive_connecting")
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.forEach((val) => {
|
.forEach((val) => {
|
||||||
this.oauth2Info.helperModal.contentEl.createEl("p", {
|
k.createEl("p", {
|
||||||
text: val,
|
text: val,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -746,7 +761,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
if (!Platform.isMobileApp && this.settings.enableStatusBarInfo === true) {
|
if (!Platform.isMobileApp && this.settings.enableStatusBarInfo === true) {
|
||||||
const statusBarItem = this.addStatusBarItem();
|
const statusBarItem = this.addStatusBarItem();
|
||||||
this.statusBarElement = statusBarItem.createEl("span");
|
this.statusBarElement = statusBarItem.createEl("span");
|
||||||
this.statusBarElement.setAttribute("aria-label-position", "top");
|
this.statusBarElement.setAttribute("data-tooltip-position", "top");
|
||||||
|
|
||||||
this.updateLastSuccessSyncMsg(
|
this.updateLastSuccessSyncMsg(
|
||||||
await getLastSuccessSyncByVault(this.db, this.vaultRandomID)
|
await getLastSuccessSyncByVault(this.db, this.vaultRandomID)
|
||||||
@@ -824,7 +839,13 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.syncRibbon = undefined;
|
this.syncRibbon = undefined;
|
||||||
if (this.oauth2Info !== undefined) {
|
if (this.oauth2Info !== undefined) {
|
||||||
this.oauth2Info.helperModal = undefined;
|
this.oauth2Info.helperModal = undefined;
|
||||||
this.oauth2Info = undefined;
|
this.oauth2Info = {
|
||||||
|
verifier: "",
|
||||||
|
helperModal: undefined,
|
||||||
|
authDiv: undefined,
|
||||||
|
revokeDiv: undefined,
|
||||||
|
revokeAuthSetting: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,10 +871,18 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.onedrive.remoteBaseDir = "";
|
this.settings.onedrive.remoteBaseDir = "";
|
||||||
}
|
}
|
||||||
if (this.settings.webdav.manualRecursive === undefined) {
|
if (this.settings.webdav.manualRecursive === undefined) {
|
||||||
this.settings.webdav.manualRecursive = false;
|
this.settings.webdav.manualRecursive = true;
|
||||||
}
|
}
|
||||||
if (this.settings.webdav.depth === undefined) {
|
if (
|
||||||
this.settings.webdav.depth = "auto_unknown";
|
this.settings.webdav.depth === undefined ||
|
||||||
|
this.settings.webdav.depth === "auto" ||
|
||||||
|
this.settings.webdav.depth === "auto_1" ||
|
||||||
|
this.settings.webdav.depth === "auto_infinity" ||
|
||||||
|
this.settings.webdav.depth === "auto_unknown"
|
||||||
|
) {
|
||||||
|
// auto is deprecated as of 20240116
|
||||||
|
this.settings.webdav.depth = "manual_1";
|
||||||
|
this.settings.webdav.manualRecursive = true;
|
||||||
}
|
}
|
||||||
if (this.settings.webdav.remoteBaseDir === undefined) {
|
if (this.settings.webdav.remoteBaseDir === undefined) {
|
||||||
this.settings.webdav.remoteBaseDir = "";
|
this.settings.webdav.remoteBaseDir = "";
|
||||||
@@ -892,13 +921,6 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
await this.saveSettings();
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkIfPresetRulesFollowed() {
|
|
||||||
const res = applyPresetRulesInplace(this.settings);
|
|
||||||
if (res.changed) {
|
|
||||||
await this.saveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.saveData(normalConfigToMessy(this.settings));
|
await this.saveData(normalConfigToMessy(this.settings));
|
||||||
}
|
}
|
||||||
@@ -932,7 +954,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
let dropboxExpired = false;
|
let dropboxExpired = false;
|
||||||
if (
|
if (
|
||||||
this.settings.dropbox.refreshToken !== "" &&
|
this.settings.dropbox.refreshToken !== "" &&
|
||||||
current >= this.settings.dropbox.credentialsShouldBeDeletedAtTime
|
current >= this.settings!.dropbox!.credentialsShouldBeDeletedAtTime!
|
||||||
) {
|
) {
|
||||||
dropboxExpired = true;
|
dropboxExpired = true;
|
||||||
this.settings.dropbox = cloneDeep(DEFAULT_DROPBOX_CONFIG);
|
this.settings.dropbox = cloneDeep(DEFAULT_DROPBOX_CONFIG);
|
||||||
@@ -942,7 +964,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
let onedriveExpired = false;
|
let onedriveExpired = false;
|
||||||
if (
|
if (
|
||||||
this.settings.onedrive.refreshToken !== "" &&
|
this.settings.onedrive.refreshToken !== "" &&
|
||||||
current >= this.settings.onedrive.credentialsShouldBeDeletedAtTime
|
current >= this.settings!.onedrive!.credentialsShouldBeDeletedAtTime!
|
||||||
) {
|
) {
|
||||||
onedriveExpired = true;
|
onedriveExpired = true;
|
||||||
this.settings.onedrive = cloneDeep(DEFAULT_ONEDRIVE_CONFIG);
|
this.settings.onedrive = cloneDeep(DEFAULT_ONEDRIVE_CONFIG);
|
||||||
@@ -1059,6 +1081,19 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.syncOnSaveAfterMilliseconds > 0
|
this.settings.syncOnSaveAfterMilliseconds > 0
|
||||||
) {
|
) {
|
||||||
let runScheduled = false;
|
let runScheduled = false;
|
||||||
|
let needToRunAgain = false;
|
||||||
|
|
||||||
|
const scheduleSyncOnSave = (scheduleTimeFromNow: number) => {
|
||||||
|
log.info(
|
||||||
|
`schedule a run for ${scheduleTimeFromNow} milliseconds later`
|
||||||
|
);
|
||||||
|
runScheduled = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.syncRun("auto_sync_on_save");
|
||||||
|
runScheduled = false;
|
||||||
|
}, scheduleTimeFromNow);
|
||||||
|
};
|
||||||
|
|
||||||
this.app.workspace.onLayoutReady(() => {
|
this.app.workspace.onLayoutReady(() => {
|
||||||
const intervalID = window.setInterval(() => {
|
const intervalID = window.setInterval(() => {
|
||||||
const currentFile = this.app.workspace.getActiveFile();
|
const currentFile = this.app.workspace.getActiveFile();
|
||||||
@@ -1069,29 +1104,28 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
// then schedule a run for syncOnSaveAfterMilliseconds after it was modified
|
// then schedule a run for syncOnSaveAfterMilliseconds after it was modified
|
||||||
const lastModified = currentFile.stat.mtime;
|
const lastModified = currentFile.stat.mtime;
|
||||||
const currentTime = Date.now();
|
const currentTime = Date.now();
|
||||||
// log.debug(
|
|
||||||
// `Checking if file was modified within last ${
|
|
||||||
// this.settings.syncOnSaveAfterMilliseconds / 1000
|
|
||||||
// } seconds, last modified: ${
|
|
||||||
// (currentTime - lastModified) / 1000
|
|
||||||
// } seconds ago`
|
|
||||||
// );
|
|
||||||
if (
|
if (
|
||||||
currentTime - lastModified <
|
currentTime - lastModified <
|
||||||
this.settings.syncOnSaveAfterMilliseconds
|
this.settings!.syncOnSaveAfterMilliseconds!
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!needToRunAgain &&
|
||||||
|
!runScheduled &&
|
||||||
|
this.syncStatus === "idle"
|
||||||
) {
|
) {
|
||||||
if (!runScheduled) {
|
|
||||||
const scheduleTimeFromNow =
|
const scheduleTimeFromNow =
|
||||||
this.settings.syncOnSaveAfterMilliseconds -
|
this.settings!.syncOnSaveAfterMilliseconds! -
|
||||||
(currentTime - lastModified);
|
(currentTime - lastModified);
|
||||||
log.info(
|
scheduleSyncOnSave(scheduleTimeFromNow);
|
||||||
`schedule a run for ${scheduleTimeFromNow} milliseconds later`
|
} else if (
|
||||||
);
|
needToRunAgain &&
|
||||||
runScheduled = true;
|
!runScheduled &&
|
||||||
setTimeout(() => {
|
this.syncStatus === "idle"
|
||||||
this.syncRun("auto_sync_on_save");
|
) {
|
||||||
runScheduled = false;
|
scheduleSyncOnSave(this.settings!.syncOnSaveAfterMilliseconds!);
|
||||||
}, scheduleTimeFromNow);
|
needToRunAgain = false;
|
||||||
|
} else {
|
||||||
|
needToRunAgain = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,6 +1161,10 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
let lastSyncMsg = t("statusbar_lastsync_never");
|
let lastSyncMsg = t("statusbar_lastsync_never");
|
||||||
let lastSyncLabelMsg = t("statusbar_lastsync_never_label");
|
let lastSyncLabelMsg = t("statusbar_lastsync_never_label");
|
||||||
|
|
||||||
|
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis === -1) {
|
||||||
|
lastSyncMsg = t("statusbar_syncing");
|
||||||
|
}
|
||||||
|
|
||||||
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis > 0) {
|
if (lastSuccessSyncMillis !== undefined && lastSuccessSyncMillis > 0) {
|
||||||
const deltaTime = Date.now() - lastSuccessSyncMillis;
|
const deltaTime = Date.now() - lastSuccessSyncMillis;
|
||||||
|
|
||||||
@@ -1137,6 +1175,8 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
const days = Math.floor(deltaTime / 86400000);
|
const days = Math.floor(deltaTime / 86400000);
|
||||||
const hours = Math.floor(deltaTime / 3600000);
|
const hours = Math.floor(deltaTime / 3600000);
|
||||||
const minutes = Math.floor(deltaTime / 60000);
|
const minutes = Math.floor(deltaTime / 60000);
|
||||||
|
const seconds = Math.floor(deltaTime / 1000);
|
||||||
|
|
||||||
let timeText = "";
|
let timeText = "";
|
||||||
|
|
||||||
if (years > 0) {
|
if (years > 0) {
|
||||||
@@ -1151,8 +1191,10 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
timeText = t("statusbar_time_hours", { time: hours });
|
timeText = t("statusbar_time_hours", { time: hours });
|
||||||
} else if (minutes > 0) {
|
} else if (minutes > 0) {
|
||||||
timeText = t("statusbar_time_minutes", { time: minutes });
|
timeText = t("statusbar_time_minutes", { time: minutes });
|
||||||
} else {
|
} else if (seconds > 30) {
|
||||||
timeText = t("statusbar_time_lessminute");
|
timeText = t("statusbar_time_lessminute");
|
||||||
|
} else {
|
||||||
|
timeText = t("statusbar_now");
|
||||||
}
|
}
|
||||||
|
|
||||||
let dateText = new Date(lastSuccessSyncMillis).toLocaleTimeString(
|
let dateText = new Date(lastSuccessSyncMillis).toLocaleTimeString(
|
||||||
@@ -1165,7 +1207,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
lastSyncMsg = t("statusbar_lastsync", { time: timeText });
|
lastSyncMsg = timeText;
|
||||||
lastSyncLabelMsg = t("statusbar_lastsync_label", { date: dateText });
|
lastSyncLabelMsg = t("statusbar_lastsync_label", { date: dateText });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export interface MetadataOnRemote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isEqualMetadataOnRemote = (
|
export const isEqualMetadataOnRemote = (
|
||||||
a: MetadataOnRemote,
|
a: MetadataOnRemote | undefined,
|
||||||
b: MetadataOnRemote
|
b: MetadataOnRemote | undefined
|
||||||
) => {
|
) => {
|
||||||
const m1 = a === undefined ? { deletions: [] } : a;
|
const m1 = a === undefined ? { deletions: [] } : a;
|
||||||
const m2 = b === undefined ? { deletions: [] } : b;
|
const m2 = b === undefined ? { deletions: [] } : b;
|
||||||
|
|||||||
+12
-5
@@ -126,8 +126,12 @@ export const base64ToArrayBuffer = (b64text: string) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const hexStringToTypedArray = (hex: string) => {
|
export const hexStringToTypedArray = (hex: string) => {
|
||||||
|
const f = hex.match(/[\da-f]{2}/gi);
|
||||||
|
if (f === null) {
|
||||||
|
throw Error(`input ${hex} is not hex, no way to transform`);
|
||||||
|
}
|
||||||
return new Uint8Array(
|
return new Uint8Array(
|
||||||
hex.match(/[\da-f]{2}/gi).map(function (h) {
|
f.map(function (h) {
|
||||||
return parseInt(h, 16);
|
return parseInt(h, 16);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -236,7 +240,7 @@ export const setToString = (a: Set<string>, delimiter: string = ",") => {
|
|||||||
export const extractSvgSub = (x: string, subEl: string = "rect") => {
|
export const extractSvgSub = (x: string, subEl: string = "rect") => {
|
||||||
const parser = new window.DOMParser();
|
const parser = new window.DOMParser();
|
||||||
const dom = parser.parseFromString(x, "image/svg+xml");
|
const dom = parser.parseFromString(x, "image/svg+xml");
|
||||||
const svg = dom.querySelector("svg");
|
const svg = dom.querySelector("svg")!;
|
||||||
svg.setAttribute("viewbox", "0 0 10 10");
|
svg.setAttribute("viewbox", "0 0 10 10");
|
||||||
return svg.innerHTML;
|
return svg.innerHTML;
|
||||||
};
|
};
|
||||||
@@ -317,7 +321,7 @@ export const getTypeName = (obj: any) => {
|
|||||||
* @param x
|
* @param x
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const atWhichLevel = (x: string) => {
|
export const atWhichLevel = (x: string | undefined) => {
|
||||||
if (
|
if (
|
||||||
x === undefined ||
|
x === undefined ||
|
||||||
x === "" ||
|
x === "" ||
|
||||||
@@ -413,11 +417,14 @@ export const toText = (x: any) => {
|
|||||||
*/
|
*/
|
||||||
export const statFix = async (vault: Vault, path: string) => {
|
export const statFix = async (vault: Vault, path: string) => {
|
||||||
const s = await vault.adapter.stat(path);
|
const s = await vault.adapter.stat(path);
|
||||||
|
if (s === undefined || s === null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
if (s.ctime === undefined || s.ctime === null || Number.isNaN(s.ctime)) {
|
if (s.ctime === undefined || s.ctime === null || Number.isNaN(s.ctime)) {
|
||||||
s.ctime = undefined;
|
s.ctime = undefined as any; // force assignment
|
||||||
}
|
}
|
||||||
if (s.mtime === undefined || s.mtime === null || Number.isNaN(s.mtime)) {
|
if (s.mtime === undefined || s.mtime === null || Number.isNaN(s.mtime)) {
|
||||||
s.mtime = undefined;
|
s.mtime = undefined as any; // force assignment
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(s.size === undefined || s.size === null || Number.isNaN(s.size)) &&
|
(s.size === undefined || s.size === null || Number.isNaN(s.size)) &&
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export const listFilesInObsFolder = async (
|
|||||||
const CHUNK_SIZE = 10;
|
const CHUNK_SIZE = 10;
|
||||||
const contents: ObsConfigDirFileType[] = [];
|
const contents: ObsConfigDirFileType[] = [];
|
||||||
while (q.length > 0) {
|
while (q.length > 0) {
|
||||||
const itemsToFetch = [];
|
const itemsToFetch: string[] = [];
|
||||||
while (q.length > 0) {
|
while (q.length > 0) {
|
||||||
itemsToFetch.push(q.pop());
|
itemsToFetch.push(q.pop()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
||||||
@@ -63,8 +63,11 @@ export const listFilesInObsFolder = async (
|
|||||||
const r = singleChunk.map(async (x) => {
|
const r = singleChunk.map(async (x) => {
|
||||||
const statRes = await statFix(vault, x);
|
const statRes = await statFix(vault, x);
|
||||||
|
|
||||||
|
if (statRes === undefined || statRes === null) {
|
||||||
|
throw Error("something goes wrong while listing hidden folder");
|
||||||
|
}
|
||||||
const isFolder = statRes.type === "folder";
|
const isFolder = statRes.type === "folder";
|
||||||
let children: ListedFiles = undefined;
|
let children: ListedFiles | undefined = undefined;
|
||||||
if (isFolder) {
|
if (isFolder) {
|
||||||
children = await vault.adapter.list(x);
|
children = await vault.adapter.list(x);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import type {
|
|
||||||
RemotelySavePluginSettings,
|
|
||||||
WebdavConfig,
|
|
||||||
WebdavDepthType,
|
|
||||||
} from "./baseTypes";
|
|
||||||
|
|
||||||
const RULES = {
|
|
||||||
webdav: {
|
|
||||||
depth: [
|
|
||||||
{
|
|
||||||
url: "^https://(.+).teracloud.jp/.+",
|
|
||||||
depth: "auto_1",
|
|
||||||
manualRecursive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: "^https://dav.jianguoyun.com/dav/",
|
|
||||||
depth: "auto_1",
|
|
||||||
manualRecursive: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyWebdavPresetRulesInplace = (
|
|
||||||
webdav: Partial<WebdavConfig> | undefined
|
|
||||||
) => {
|
|
||||||
if (webdav === undefined) {
|
|
||||||
return {
|
|
||||||
changed: false,
|
|
||||||
webdav: webdav,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
for (const { url, depth, manualRecursive } of RULES.webdav.depth) {
|
|
||||||
if (
|
|
||||||
webdav.address !== undefined &&
|
|
||||||
new RegExp(url).test(webdav.address) &&
|
|
||||||
webdav.depth !== undefined &&
|
|
||||||
webdav.depth.startsWith("auto_") &&
|
|
||||||
webdav.depth !== depth
|
|
||||||
) {
|
|
||||||
webdav.depth = depth as WebdavDepthType;
|
|
||||||
webdav.manualRecursive = manualRecursive;
|
|
||||||
return {
|
|
||||||
changed: true,
|
|
||||||
webdav: webdav,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
changed: false,
|
|
||||||
webdav: webdav,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyPresetRulesInplace = (
|
|
||||||
settings: RemotelySavePluginSettings | undefined
|
|
||||||
) => {
|
|
||||||
if (settings === undefined) {
|
|
||||||
return {
|
|
||||||
changed: false,
|
|
||||||
settings: settings,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const webdavRes = applyWebdavPresetRulesInplace(settings.webdav);
|
|
||||||
return {
|
|
||||||
changed: webdavRes.changed,
|
|
||||||
settings: settings,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
+40
-40
@@ -43,10 +43,10 @@ export class RemoteClient {
|
|||||||
"remember to provide vault name and callback while init webdav client"
|
"remember to provide vault name and callback while init webdav client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const remoteBaseDir = webdavConfig.remoteBaseDir || vaultName;
|
const remoteBaseDir = webdavConfig!.remoteBaseDir || vaultName;
|
||||||
this.webdavConfig = webdavConfig;
|
this.webdavConfig = webdavConfig;
|
||||||
this.webdavClient = webdav.getWebdavClient(
|
this.webdavClient = webdav.getWebdavClient(
|
||||||
this.webdavConfig,
|
this.webdavConfig!,
|
||||||
remoteBaseDir,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
@@ -56,10 +56,10 @@ export class RemoteClient {
|
|||||||
"remember to provide vault name and callback while init dropbox client"
|
"remember to provide vault name and callback while init dropbox client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const remoteBaseDir = dropboxConfig.remoteBaseDir || vaultName;
|
const remoteBaseDir = dropboxConfig!.remoteBaseDir || vaultName;
|
||||||
this.dropboxConfig = dropboxConfig;
|
this.dropboxConfig = dropboxConfig;
|
||||||
this.dropboxClient = dropbox.getDropboxClient(
|
this.dropboxClient = dropbox.getDropboxClient(
|
||||||
this.dropboxConfig,
|
this.dropboxConfig!,
|
||||||
remoteBaseDir,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
@@ -69,10 +69,10 @@ export class RemoteClient {
|
|||||||
"remember to provide vault name and callback while init onedrive client"
|
"remember to provide vault name and callback while init onedrive client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const remoteBaseDir = onedriveConfig.remoteBaseDir || vaultName;
|
const remoteBaseDir = onedriveConfig!.remoteBaseDir || vaultName;
|
||||||
this.onedriveConfig = onedriveConfig;
|
this.onedriveConfig = onedriveConfig;
|
||||||
this.onedriveClient = onedrive.getOnedriveClient(
|
this.onedriveClient = onedrive.getOnedriveClient(
|
||||||
this.onedriveConfig,
|
this.onedriveConfig!,
|
||||||
remoteBaseDir,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
@@ -84,17 +84,17 @@ export class RemoteClient {
|
|||||||
getRemoteMeta = async (fileOrFolderPath: string) => {
|
getRemoteMeta = async (fileOrFolderPath: string) => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.getRemoteMeta(
|
return await s3.getRemoteMeta(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config,
|
this.s3Config!,
|
||||||
fileOrFolderPath
|
fileOrFolderPath
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.getRemoteMeta(this.webdavClient, fileOrFolderPath);
|
return await webdav.getRemoteMeta(this.webdavClient!, fileOrFolderPath);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.getRemoteMeta(this.dropboxClient, fileOrFolderPath);
|
return await dropbox.getRemoteMeta(this.dropboxClient!, fileOrFolderPath);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.getRemoteMeta(
|
return await onedrive.getRemoteMeta(
|
||||||
this.onedriveClient,
|
this.onedriveClient!,
|
||||||
fileOrFolderPath
|
fileOrFolderPath
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -104,7 +104,7 @@ export class RemoteClient {
|
|||||||
|
|
||||||
uploadToRemote = async (
|
uploadToRemote = async (
|
||||||
fileOrFolderPath: string,
|
fileOrFolderPath: string,
|
||||||
vault: Vault,
|
vault: Vault | undefined,
|
||||||
isRecursively: boolean = false,
|
isRecursively: boolean = false,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
remoteEncryptedKey: string = "",
|
remoteEncryptedKey: string = "",
|
||||||
@@ -114,8 +114,8 @@ export class RemoteClient {
|
|||||||
) => {
|
) => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.uploadToRemote(
|
return await s3.uploadToRemote(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config,
|
this.s3Config!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
isRecursively,
|
isRecursively,
|
||||||
@@ -126,7 +126,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.uploadToRemote(
|
return await webdav.uploadToRemote(
|
||||||
this.webdavClient,
|
this.webdavClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
isRecursively,
|
isRecursively,
|
||||||
@@ -137,7 +137,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.uploadToRemote(
|
return await dropbox.uploadToRemote(
|
||||||
this.dropboxClient,
|
this.dropboxClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
isRecursively,
|
isRecursively,
|
||||||
@@ -149,7 +149,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.uploadToRemote(
|
return await onedrive.uploadToRemote(
|
||||||
this.onedriveClient,
|
this.onedriveClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
isRecursively,
|
isRecursively,
|
||||||
@@ -167,15 +167,15 @@ export class RemoteClient {
|
|||||||
listAllFromRemote = async () => {
|
listAllFromRemote = async () => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.listAllFromRemote(
|
return await s3.listAllFromRemote(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config
|
this.s3Config!
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.listAllFromRemote(this.webdavClient);
|
return await webdav.listAllFromRemote(this.webdavClient!);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.listAllFromRemote(this.dropboxClient);
|
return await dropbox.listAllFromRemote(this.dropboxClient!);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.listAllFromRemote(this.onedriveClient);
|
return await onedrive.listAllFromRemote(this.onedriveClient!);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@@ -191,8 +191,8 @@ export class RemoteClient {
|
|||||||
) => {
|
) => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.downloadFromRemote(
|
return await s3.downloadFromRemote(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config,
|
this.s3Config!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
mtime,
|
mtime,
|
||||||
@@ -202,7 +202,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.downloadFromRemote(
|
return await webdav.downloadFromRemote(
|
||||||
this.webdavClient,
|
this.webdavClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
mtime,
|
mtime,
|
||||||
@@ -212,7 +212,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.downloadFromRemote(
|
return await dropbox.downloadFromRemote(
|
||||||
this.dropboxClient,
|
this.dropboxClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
mtime,
|
mtime,
|
||||||
@@ -222,7 +222,7 @@ export class RemoteClient {
|
|||||||
);
|
);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.downloadFromRemote(
|
return await onedrive.downloadFromRemote(
|
||||||
this.onedriveClient,
|
this.onedriveClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
vault,
|
vault,
|
||||||
mtime,
|
mtime,
|
||||||
@@ -242,29 +242,29 @@ export class RemoteClient {
|
|||||||
) => {
|
) => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.deleteFromRemote(
|
return await s3.deleteFromRemote(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config,
|
this.s3Config!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.deleteFromRemote(
|
return await webdav.deleteFromRemote(
|
||||||
this.webdavClient,
|
this.webdavClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.deleteFromRemote(
|
return await dropbox.deleteFromRemote(
|
||||||
this.dropboxClient,
|
this.dropboxClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.deleteFromRemote(
|
return await onedrive.deleteFromRemote(
|
||||||
this.onedriveClient,
|
this.onedriveClient!,
|
||||||
fileOrFolderPath,
|
fileOrFolderPath,
|
||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
@@ -277,17 +277,17 @@ export class RemoteClient {
|
|||||||
checkConnectivity = async (callbackFunc?: any) => {
|
checkConnectivity = async (callbackFunc?: any) => {
|
||||||
if (this.serviceType === "s3") {
|
if (this.serviceType === "s3") {
|
||||||
return await s3.checkConnectivity(
|
return await s3.checkConnectivity(
|
||||||
s3.getS3Client(this.s3Config),
|
s3.getS3Client(this.s3Config!),
|
||||||
this.s3Config,
|
this.s3Config!,
|
||||||
callbackFunc
|
callbackFunc
|
||||||
);
|
);
|
||||||
} else if (this.serviceType === "webdav") {
|
} else if (this.serviceType === "webdav") {
|
||||||
return await webdav.checkConnectivity(this.webdavClient, callbackFunc);
|
return await webdav.checkConnectivity(this.webdavClient!, callbackFunc);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.checkConnectivity(this.dropboxClient, callbackFunc);
|
return await dropbox.checkConnectivity(this.dropboxClient!, callbackFunc);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.checkConnectivity(
|
return await onedrive.checkConnectivity(
|
||||||
this.onedriveClient,
|
this.onedriveClient!,
|
||||||
callbackFunc
|
callbackFunc
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -297,9 +297,9 @@ export class RemoteClient {
|
|||||||
|
|
||||||
getUser = async () => {
|
getUser = async () => {
|
||||||
if (this.serviceType === "dropbox") {
|
if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.getUserDisplayName(this.dropboxClient);
|
return await dropbox.getUserDisplayName(this.dropboxClient!);
|
||||||
} else if (this.serviceType === "onedrive") {
|
} else if (this.serviceType === "onedrive") {
|
||||||
return await onedrive.getUserDisplayName(this.onedriveClient);
|
return await onedrive.getUserDisplayName(this.onedriveClient!);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ export class RemoteClient {
|
|||||||
|
|
||||||
revokeAuth = async () => {
|
revokeAuth = async () => {
|
||||||
if (this.serviceType === "dropbox") {
|
if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.revokeAuth(this.dropboxClient);
|
return await dropbox.revokeAuth(this.dropboxClient!);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-10
@@ -24,7 +24,7 @@ import { log } from "./moreOnLog";
|
|||||||
|
|
||||||
export const DEFAULT_DROPBOX_CONFIG: DropboxConfig = {
|
export const DEFAULT_DROPBOX_CONFIG: DropboxConfig = {
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
clientID: process.env.DEFAULT_DROPBOX_APP_KEY,
|
clientID: process.env.DEFAULT_DROPBOX_APP_KEY ?? "",
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
accessTokenExpiresInSeconds: 0,
|
accessTokenExpiresInSeconds: 0,
|
||||||
accessTokenExpiresAtTime: 0,
|
accessTokenExpiresAtTime: 0,
|
||||||
@@ -76,7 +76,7 @@ const fromDropboxItemToRemoteItem = (
|
|||||||
| files.DeletedMetadataReference,
|
| files.DeletedMetadataReference,
|
||||||
remoteBaseDir: string
|
remoteBaseDir: string
|
||||||
): RemoteItem => {
|
): RemoteItem => {
|
||||||
let key = getNormPath(x.path_display, remoteBaseDir);
|
let key = getNormPath(x.path_display!, remoteBaseDir);
|
||||||
if (x[".tag"] === "folder" && !key.endsWith("/")) {
|
if (x[".tag"] === "folder" && !key.endsWith("/")) {
|
||||||
key = `${key}/`;
|
key = `${key}/`;
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,8 @@ const fromDropboxItemToRemoteItem = (
|
|||||||
remoteType: "dropbox",
|
remoteType: "dropbox",
|
||||||
etag: `${x.id}\t${x.content_hash}`,
|
etag: `${x.id}\t${x.content_hash}`,
|
||||||
} as RemoteItem;
|
} as RemoteItem;
|
||||||
} else if (x[".tag"] === "deleted") {
|
} else {
|
||||||
|
// x[".tag"] === "deleted"
|
||||||
throw Error("do not support deleted tag");
|
throw Error("do not support deleted tag");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -188,7 +189,7 @@ export const getAuthUrlAndVerifier = async (
|
|||||||
: `obsidian://${COMMAND_CALLBACK_DROPBOX}`;
|
: `obsidian://${COMMAND_CALLBACK_DROPBOX}`;
|
||||||
const authUrl = (
|
const authUrl = (
|
||||||
await auth.getAuthenticationUrl(
|
await auth.getAuthenticationUrl(
|
||||||
callback,
|
callback as any,
|
||||||
undefined,
|
undefined,
|
||||||
"code",
|
"code",
|
||||||
"offline",
|
"offline",
|
||||||
@@ -282,9 +283,7 @@ export const setConfigBySuccessfullAuthInplace = async (
|
|||||||
|
|
||||||
if (authRes.refresh_token !== undefined) {
|
if (authRes.refresh_token !== undefined) {
|
||||||
config.refreshToken = authRes.refresh_token;
|
config.refreshToken = authRes.refresh_token;
|
||||||
}
|
config.accountID = authRes.account_id!;
|
||||||
if (authRes.refresh_token !== undefined) {
|
|
||||||
config.accountID = authRes.account_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (saveUpdatedConfigFunc !== undefined) {
|
if (saveUpdatedConfigFunc !== undefined) {
|
||||||
@@ -307,7 +306,7 @@ interface ErrSubType {
|
|||||||
async function retryReq<T>(
|
async function retryReq<T>(
|
||||||
reqFunc: () => Promise<DropboxResponse<T>>,
|
reqFunc: () => Promise<DropboxResponse<T>>,
|
||||||
extraHint: string = ""
|
extraHint: string = ""
|
||||||
): Promise<DropboxResponse<T>> {
|
): Promise<DropboxResponse<T> | undefined> {
|
||||||
const waitSeconds = [1, 2, 4, 8]; // hard code exponential backoff
|
const waitSeconds = [1, 2, 4, 8]; // hard code exponential backoff
|
||||||
for (let idx = 0; idx < waitSeconds.length; ++idx) {
|
for (let idx = 0; idx < waitSeconds.length; ++idx) {
|
||||||
try {
|
try {
|
||||||
@@ -369,7 +368,7 @@ export class WrappedDropboxClient {
|
|||||||
dropboxConfig: DropboxConfig;
|
dropboxConfig: DropboxConfig;
|
||||||
remoteBaseDir: string;
|
remoteBaseDir: string;
|
||||||
saveUpdatedConfigFunc: () => Promise<any>;
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
dropbox: Dropbox;
|
dropbox!: Dropbox;
|
||||||
vaultFolderExists: boolean;
|
vaultFolderExists: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
dropboxConfig: DropboxConfig,
|
dropboxConfig: DropboxConfig,
|
||||||
@@ -507,6 +506,9 @@ export const getRemoteMeta = async (
|
|||||||
path: remotePath,
|
path: remotePath,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (rsp === undefined) {
|
||||||
|
throw Error("dropbox.filesGetMetadata undefinded");
|
||||||
|
}
|
||||||
if (rsp.status !== 200) {
|
if (rsp.status !== 200) {
|
||||||
throw Error(JSON.stringify(rsp));
|
throw Error(JSON.stringify(rsp));
|
||||||
}
|
}
|
||||||
@@ -516,7 +518,7 @@ export const getRemoteMeta = async (
|
|||||||
export const uploadToRemote = async (
|
export const uploadToRemote = async (
|
||||||
client: WrappedDropboxClient,
|
client: WrappedDropboxClient,
|
||||||
fileOrFolderPath: string,
|
fileOrFolderPath: string,
|
||||||
vault: Vault,
|
vault: Vault | undefined,
|
||||||
isRecursively: boolean = false,
|
isRecursively: boolean = false,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
remoteEncryptedKey: string = "",
|
remoteEncryptedKey: string = "",
|
||||||
@@ -611,6 +613,11 @@ export const uploadToRemote = async (
|
|||||||
localContent = rawContent;
|
localContent = rawContent;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (vault === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`the vault variable is not passed but we want to read ${fileOrFolderPath} for Dropbox`
|
||||||
|
);
|
||||||
|
}
|
||||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||||
}
|
}
|
||||||
let remoteContent = localContent;
|
let remoteContent = localContent;
|
||||||
@@ -700,6 +707,9 @@ const downloadFromRemoteRaw = async (
|
|||||||
}),
|
}),
|
||||||
`downloadFromRemoteRaw=${remotePath}`
|
`downloadFromRemoteRaw=${remotePath}`
|
||||||
);
|
);
|
||||||
|
if (rsp === undefined) {
|
||||||
|
throw Error(`unknown rsp from dropbox download: ${rsp}`);
|
||||||
|
}
|
||||||
if ((rsp.result as any).fileBlob !== undefined) {
|
if ((rsp.result as any).fileBlob !== undefined) {
|
||||||
// we get a Blob
|
// we get a Blob
|
||||||
const content = (rsp.result as any).fileBlob as Blob;
|
const content = (rsp.result as any).fileBlob as Blob;
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`;
|
|||||||
|
|
||||||
export const DEFAULT_ONEDRIVE_CONFIG: OnedriveConfig = {
|
export const DEFAULT_ONEDRIVE_CONFIG: OnedriveConfig = {
|
||||||
accessToken: "",
|
accessToken: "",
|
||||||
clientID: process.env.DEFAULT_ONEDRIVE_CLIENT_ID,
|
clientID: process.env.DEFAULT_ONEDRIVE_CLIENT_ID ?? "",
|
||||||
authority: process.env.DEFAULT_ONEDRIVE_AUTHORITY,
|
authority: process.env.DEFAULT_ONEDRIVE_AUTHORITY ?? "",
|
||||||
refreshToken: "",
|
refreshToken: "",
|
||||||
accessTokenExpiresInSeconds: 0,
|
accessTokenExpiresInSeconds: 0,
|
||||||
accessTokenExpiresAtTime: 0,
|
accessTokenExpiresAtTime: 0,
|
||||||
@@ -199,7 +199,7 @@ export const setConfigBySuccessfullAuthInplace = async (
|
|||||||
config.accessTokenExpiresAtTime =
|
config.accessTokenExpiresAtTime =
|
||||||
Date.now() + authRes.expires_in - 5 * 60 * 1000;
|
Date.now() + authRes.expires_in - 5 * 60 * 1000;
|
||||||
config.accessTokenExpiresInSeconds = authRes.expires_in;
|
config.accessTokenExpiresInSeconds = authRes.expires_in;
|
||||||
config.refreshToken = authRes.refresh_token;
|
config.refreshToken = authRes.refresh_token!;
|
||||||
|
|
||||||
// manually set it expired after 80 days;
|
// manually set it expired after 80 days;
|
||||||
config.credentialsShouldBeDeletedAtTime =
|
config.credentialsShouldBeDeletedAtTime =
|
||||||
@@ -256,7 +256,9 @@ const getNormPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const constructFromDriveItemToRemoteItemError = (x: DriveItem) => {
|
const constructFromDriveItemToRemoteItemError = (x: DriveItem) => {
|
||||||
return `parentPath="${x.parentReference.path}", selfName="${x.name}"`;
|
return `parentPath="${
|
||||||
|
x.parentReference?.path ?? "(no parentReference or path)"
|
||||||
|
}", selfName="${x.name}"`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromDriveItemToRemoteItem = (
|
const fromDriveItemToRemoteItem = (
|
||||||
@@ -281,6 +283,14 @@ const fromDriveItemToRemoteItem = (
|
|||||||
// another possibile prefix
|
// another possibile prefix
|
||||||
const FOURTH_COMMON_PREFIX_RAW = `/drive/items/`;
|
const FOURTH_COMMON_PREFIX_RAW = `/drive/items/`;
|
||||||
|
|
||||||
|
if (
|
||||||
|
x.parentReference === undefined ||
|
||||||
|
x.parentReference === null ||
|
||||||
|
x.parentReference.path === undefined ||
|
||||||
|
x.parentReference.path === null
|
||||||
|
) {
|
||||||
|
throw Error("x.parentReference.path is undefinded or null");
|
||||||
|
}
|
||||||
const fullPathOriginal = `${x.parentReference.path}/${x.name}`;
|
const fullPathOriginal = `${x.parentReference.path}/${x.name}`;
|
||||||
const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX);
|
const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX);
|
||||||
const matchSecondPrefixRes = fullPathOriginal.match(
|
const matchSecondPrefixRes = fullPathOriginal.match(
|
||||||
@@ -309,6 +319,11 @@ const fromDriveItemToRemoteItem = (
|
|||||||
// it's something like
|
// it's something like
|
||||||
// /drive/items/<some_id>!<another_id>:/${remoteBaseDir}/<subfolder>
|
// /drive/items/<some_id>!<another_id>:/${remoteBaseDir}/<subfolder>
|
||||||
// with uri encoded!
|
// with uri encoded!
|
||||||
|
if (x.name === undefined || x.name === null) {
|
||||||
|
throw Error(
|
||||||
|
`OneDrive item no name variable while matching ${FOURTH_COMMON_PREFIX_RAW}`
|
||||||
|
);
|
||||||
|
}
|
||||||
const parPath = decodeURIComponent(x.parentReference.path);
|
const parPath = decodeURIComponent(x.parentReference.path);
|
||||||
key = parPath.substring(parPath.indexOf(":") + 1);
|
key = parPath.substring(parPath.indexOf(":") + 1);
|
||||||
if (key.startsWith(`/${remoteBaseDir}/`)) {
|
if (key.startsWith(`/${remoteBaseDir}/`)) {
|
||||||
@@ -337,8 +352,8 @@ const fromDriveItemToRemoteItem = (
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
lastModified: Date.parse(x.fileSystemInfo.lastModifiedDateTime),
|
lastModified: Date.parse(x!.fileSystemInfo!.lastModifiedDateTime!),
|
||||||
size: isFolder ? 0 : x.size,
|
size: isFolder ? 0 : x.size!,
|
||||||
remoteType: "onedrive",
|
remoteType: "onedrive",
|
||||||
etag: x.cTag || "", // do NOT use x.eTag because it changes if meta changes
|
etag: x.cTag || "", // do NOT use x.eTag because it changes if meta changes
|
||||||
};
|
};
|
||||||
@@ -381,7 +396,7 @@ class MyAuthProvider implements AuthenticationProvider {
|
|||||||
}
|
}
|
||||||
const r2 = r as AccessCodeResponseSuccessfulType;
|
const r2 = r as AccessCodeResponseSuccessfulType;
|
||||||
this.onedriveConfig.accessToken = r2.access_token;
|
this.onedriveConfig.accessToken = r2.access_token;
|
||||||
this.onedriveConfig.refreshToken = r2.refresh_token;
|
this.onedriveConfig.refreshToken = r2.refresh_token!;
|
||||||
this.onedriveConfig.accessTokenExpiresInSeconds = r2.expires_in;
|
this.onedriveConfig.accessTokenExpiresInSeconds = r2.expires_in;
|
||||||
this.onedriveConfig.accessTokenExpiresAtTime =
|
this.onedriveConfig.accessTokenExpiresAtTime =
|
||||||
currentTs + r2.expires_in * 1000 - 60 * 2 * 1000;
|
currentTs + r2.expires_in * 1000 - 60 * 2 * 1000;
|
||||||
@@ -680,7 +695,7 @@ export const getRemoteMeta = async (
|
|||||||
export const uploadToRemote = async (
|
export const uploadToRemote = async (
|
||||||
client: WrappedOnedriveClient,
|
client: WrappedOnedriveClient,
|
||||||
fileOrFolderPath: string,
|
fileOrFolderPath: string,
|
||||||
vault: Vault,
|
vault: Vault | undefined,
|
||||||
isRecursively: boolean = false,
|
isRecursively: boolean = false,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
remoteEncryptedKey: string = "",
|
remoteEncryptedKey: string = "",
|
||||||
@@ -784,6 +799,11 @@ export const uploadToRemote = async (
|
|||||||
localContent = rawContent;
|
localContent = rawContent;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (vault === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`the vault variable is not passed but we want to read ${fileOrFolderPath} for OneDrive`
|
||||||
|
);
|
||||||
|
}
|
||||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||||
}
|
}
|
||||||
let remoteContent = localContent;
|
let remoteContent = localContent;
|
||||||
@@ -842,7 +862,7 @@ export const uploadToRemote = async (
|
|||||||
`${uploadFile}:/createUploadSession`,
|
`${uploadFile}:/createUploadSession`,
|
||||||
k
|
k
|
||||||
);
|
);
|
||||||
const uploadUrl = s.uploadUrl;
|
const uploadUrl = s.uploadUrl!;
|
||||||
log.debug("uploadSession = ");
|
log.debug("uploadSession = ");
|
||||||
log.debug(s);
|
log.debug(s);
|
||||||
|
|
||||||
|
|||||||
+34
-21
@@ -55,7 +55,7 @@ import PQueue from "p-queue";
|
|||||||
* But this uses Obsidian requestUrl instead.
|
* But this uses Obsidian requestUrl instead.
|
||||||
*/
|
*/
|
||||||
class ObsHttpHandler extends FetchHttpHandler {
|
class ObsHttpHandler extends FetchHttpHandler {
|
||||||
requestTimeoutInMs: number;
|
requestTimeoutInMs: number | undefined;
|
||||||
constructor(options?: FetchHttpHandlerOptions) {
|
constructor(options?: FetchHttpHandlerOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.requestTimeoutInMs =
|
this.requestTimeoutInMs =
|
||||||
@@ -95,7 +95,7 @@ class ObsHttpHandler extends FetchHttpHandler {
|
|||||||
transformedHeaders[keyLower] = request.headers[key];
|
transformedHeaders[keyLower] = request.headers[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentType: string = undefined;
|
let contentType: string | undefined = undefined;
|
||||||
if (transformedHeaders["content-type"] !== undefined) {
|
if (transformedHeaders["content-type"] !== undefined) {
|
||||||
contentType = transformedHeaders["content-type"];
|
contentType = transformedHeaders["content-type"];
|
||||||
}
|
}
|
||||||
@@ -226,17 +226,17 @@ const fromS3ObjectToRemoteItem = (
|
|||||||
mtimeRecords: Record<string, number>,
|
mtimeRecords: Record<string, number>,
|
||||||
ctimeRecords: Record<string, number>
|
ctimeRecords: Record<string, number>
|
||||||
) => {
|
) => {
|
||||||
let mtime = x.LastModified.valueOf();
|
let mtime = x.LastModified!.valueOf();
|
||||||
if (x.Key in mtimeRecords) {
|
if (x.Key! in mtimeRecords) {
|
||||||
const m2 = mtimeRecords[x.Key];
|
const m2 = mtimeRecords[x.Key!];
|
||||||
if (m2 !== 0) {
|
if (m2 !== 0) {
|
||||||
mtime = m2;
|
mtime = m2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const r: RemoteItem = {
|
const r: RemoteItem = {
|
||||||
key: getLocalNoPrefixPath(x.Key, remotePrefix),
|
key: getLocalNoPrefixPath(x.Key!, remotePrefix),
|
||||||
lastModified: mtime,
|
lastModified: mtime,
|
||||||
size: x.Size,
|
size: x.Size!,
|
||||||
remoteType: "s3",
|
remoteType: "s3",
|
||||||
etag: x.ETag,
|
etag: x.ETag,
|
||||||
};
|
};
|
||||||
@@ -249,7 +249,7 @@ const fromS3HeadObjectToRemoteItem = (
|
|||||||
remotePrefix: string,
|
remotePrefix: string,
|
||||||
useAccurateMTime: boolean
|
useAccurateMTime: boolean
|
||||||
) => {
|
) => {
|
||||||
let mtime = x.LastModified.valueOf();
|
let mtime = x.LastModified!.valueOf();
|
||||||
if (useAccurateMTime && x.Metadata !== undefined) {
|
if (useAccurateMTime && x.Metadata !== undefined) {
|
||||||
const m2 = Math.round(
|
const m2 = Math.round(
|
||||||
parseFloat(x.Metadata.mtime || x.Metadata.MTime || "0")
|
parseFloat(x.Metadata.mtime || x.Metadata.MTime || "0")
|
||||||
@@ -317,6 +317,7 @@ export const getRemoteMeta = async (
|
|||||||
fileOrFolderPathWithRemotePrefix: string
|
fileOrFolderPathWithRemotePrefix: string
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
|
s3Config.remotePrefix !== undefined &&
|
||||||
s3Config.remotePrefix !== "" &&
|
s3Config.remotePrefix !== "" &&
|
||||||
!fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix)
|
!fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix)
|
||||||
) {
|
) {
|
||||||
@@ -332,8 +333,8 @@ export const getRemoteMeta = async (
|
|||||||
return fromS3HeadObjectToRemoteItem(
|
return fromS3HeadObjectToRemoteItem(
|
||||||
fileOrFolderPathWithRemotePrefix,
|
fileOrFolderPathWithRemotePrefix,
|
||||||
res,
|
res,
|
||||||
s3Config.remotePrefix,
|
s3Config.remotePrefix ?? "",
|
||||||
s3Config.useAccurateMTime
|
s3Config.useAccurateMTime ?? false
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -341,7 +342,7 @@ export const uploadToRemote = async (
|
|||||||
s3Client: S3Client,
|
s3Client: S3Client,
|
||||||
s3Config: S3Config,
|
s3Config: S3Config,
|
||||||
fileOrFolderPath: string,
|
fileOrFolderPath: string,
|
||||||
vault: Vault,
|
vault: Vault | undefined,
|
||||||
isRecursively: boolean = false,
|
isRecursively: boolean = false,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
remoteEncryptedKey: string = "",
|
remoteEncryptedKey: string = "",
|
||||||
@@ -354,7 +355,7 @@ export const uploadToRemote = async (
|
|||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
uploadFile = remoteEncryptedKey;
|
uploadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
uploadFile = getRemoteWithPrefixPath(uploadFile, s3Config.remotePrefix);
|
uploadFile = getRemoteWithPrefixPath(uploadFile, s3Config.remotePrefix ?? "");
|
||||||
const isFolder = fileOrFolderPath.endsWith("/");
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
if (isFolder && isRecursively) {
|
if (isFolder && isRecursively) {
|
||||||
@@ -407,8 +408,13 @@ export const uploadToRemote = async (
|
|||||||
mtime = rawContentMTime;
|
mtime = rawContentMTime;
|
||||||
ctime = rawContentCTime;
|
ctime = rawContentCTime;
|
||||||
} else {
|
} else {
|
||||||
|
if (vault === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`the vault variable is not passed but we want to read ${fileOrFolderPath} for S3`
|
||||||
|
);
|
||||||
|
}
|
||||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||||
const s = await vault?.adapter?.stat(fileOrFolderPath);
|
const s = await vault.adapter.stat(fileOrFolderPath);
|
||||||
if (s !== undefined && s !== null) {
|
if (s !== undefined && s !== null) {
|
||||||
mtime = s.mtime;
|
mtime = s.mtime;
|
||||||
ctime = s.ctime;
|
ctime = s.ctime;
|
||||||
@@ -500,12 +506,12 @@ const listFromRemoteRaw = async (
|
|||||||
if (rspHead.Metadata === undefined) {
|
if (rspHead.Metadata === undefined) {
|
||||||
// pass
|
// pass
|
||||||
} else {
|
} else {
|
||||||
mtimeRecords[content.Key] = Math.round(
|
mtimeRecords[content.Key!] = Math.round(
|
||||||
parseFloat(
|
parseFloat(
|
||||||
rspHead.Metadata.mtime || rspHead.Metadata.MTime || "0"
|
rspHead.Metadata.mtime || rspHead.Metadata.MTime || "0"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
ctimeRecords[content.Key] = Math.round(
|
ctimeRecords[content.Key!] = Math.round(
|
||||||
parseFloat(
|
parseFloat(
|
||||||
rspHead.Metadata.ctime || rspHead.Metadata.CTime || "0"
|
rspHead.Metadata.ctime || rspHead.Metadata.CTime || "0"
|
||||||
)
|
)
|
||||||
@@ -515,7 +521,7 @@ const listFromRemoteRaw = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTruncated = rsp.IsTruncated;
|
isTruncated = rsp.IsTruncated ?? false;
|
||||||
confCmd.ContinuationToken = rsp.NextContinuationToken;
|
confCmd.ContinuationToken = rsp.NextContinuationToken;
|
||||||
if (
|
if (
|
||||||
isTruncated &&
|
isTruncated &&
|
||||||
@@ -536,7 +542,7 @@ const listFromRemoteRaw = async (
|
|||||||
Contents: contents.map((x) =>
|
Contents: contents.map((x) =>
|
||||||
fromS3ObjectToRemoteItem(
|
fromS3ObjectToRemoteItem(
|
||||||
x,
|
x,
|
||||||
s3Config.remotePrefix,
|
s3Config.remotePrefix ?? "",
|
||||||
mtimeRecords,
|
mtimeRecords,
|
||||||
ctimeRecords
|
ctimeRecords
|
||||||
)
|
)
|
||||||
@@ -559,8 +565,11 @@ export const listAllFromRemote = async (
|
|||||||
* @returns Promise<ArrayBuffer>
|
* @returns Promise<ArrayBuffer>
|
||||||
*/
|
*/
|
||||||
const getObjectBodyToArrayBuffer = async (
|
const getObjectBodyToArrayBuffer = async (
|
||||||
b: Readable | ReadableStream | Blob
|
b: Readable | ReadableStream | Blob | undefined
|
||||||
) => {
|
) => {
|
||||||
|
if (b === undefined) {
|
||||||
|
throw Error(`ObjectBody is undefined and don't know how to deal with it`);
|
||||||
|
}
|
||||||
if (b instanceof Readable) {
|
if (b instanceof Readable) {
|
||||||
return (await new Promise((resolve, reject) => {
|
return (await new Promise((resolve, reject) => {
|
||||||
const chunks: Uint8Array[] = [];
|
const chunks: Uint8Array[] = [];
|
||||||
@@ -583,6 +592,7 @@ const downloadFromRemoteRaw = async (
|
|||||||
fileOrFolderPathWithRemotePrefix: string
|
fileOrFolderPathWithRemotePrefix: string
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
|
s3Config.remotePrefix !== undefined &&
|
||||||
s3Config.remotePrefix !== "" &&
|
s3Config.remotePrefix !== "" &&
|
||||||
!fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix)
|
!fileOrFolderPathWithRemotePrefix.startsWith(s3Config.remotePrefix)
|
||||||
) {
|
) {
|
||||||
@@ -626,7 +636,10 @@ export const downloadFromRemote = async (
|
|||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
downloadFile = remoteEncryptedKey;
|
downloadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
downloadFile = getRemoteWithPrefixPath(downloadFile, s3Config.remotePrefix);
|
downloadFile = getRemoteWithPrefixPath(
|
||||||
|
downloadFile,
|
||||||
|
s3Config.remotePrefix ?? ""
|
||||||
|
);
|
||||||
const remoteContent = await downloadFromRemoteRaw(
|
const remoteContent = await downloadFromRemoteRaw(
|
||||||
s3Client,
|
s3Client,
|
||||||
s3Config,
|
s3Config,
|
||||||
@@ -668,7 +681,7 @@ export const deleteFromRemote = async (
|
|||||||
}
|
}
|
||||||
remoteFileName = getRemoteWithPrefixPath(
|
remoteFileName = getRemoteWithPrefixPath(
|
||||||
remoteFileName,
|
remoteFileName,
|
||||||
s3Config.remotePrefix
|
s3Config.remotePrefix ?? ""
|
||||||
);
|
);
|
||||||
await s3Client.send(
|
await s3Client.send(
|
||||||
new DeleteObjectCommand({
|
new DeleteObjectCommand({
|
||||||
@@ -734,7 +747,7 @@ export const checkConnectivity = async (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return results.$metadata.httpStatusCode === 200;
|
return results.$metadata.httpStatusCode === 200;
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
log.debug(err);
|
log.debug(err);
|
||||||
if (callbackFunc !== undefined) {
|
if (callbackFunc !== undefined) {
|
||||||
if (s3Config.s3Endpoint.contains(s3Config.s3BucketName)) {
|
if (s3Config.s3Endpoint.contains(s3Config.s3BucketName)) {
|
||||||
|
|||||||
+59
-58
@@ -19,6 +19,15 @@ import type {
|
|||||||
// ResponseDataDetailed,
|
// ResponseDataDetailed,
|
||||||
} from "webdav";
|
} from "webdav";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/32850898/how-to-check-if-a-string-has-any-non-iso-8859-1-characters-with-javascript
|
||||||
|
* @param str
|
||||||
|
* @returns true if all are iso 8859 1 chars
|
||||||
|
*/
|
||||||
|
function onlyAscii(str: string) {
|
||||||
|
return !/[^\u0000-\u00ff]/g.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { getPatcher } from "webdav/dist/web/index.js";
|
import { getPatcher } from "webdav/dist/web/index.js";
|
||||||
if (VALID_REQURL) {
|
if (VALID_REQURL) {
|
||||||
@@ -36,22 +45,42 @@ if (VALID_REQURL) {
|
|||||||
method: options.method,
|
method: options.method,
|
||||||
body: options.data as string | ArrayBuffer,
|
body: options.data as string | ArrayBuffer,
|
||||||
headers: transformedHeaders,
|
headers: transformedHeaders,
|
||||||
|
throw: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let contentType: string | undefined =
|
let contentType: string | undefined =
|
||||||
r.headers["Content-Type"] ||
|
r.headers["Content-Type"] || r.headers["content-type"];
|
||||||
r.headers["content-type"] ||
|
if (options.headers !== undefined) {
|
||||||
|
contentType =
|
||||||
|
contentType ||
|
||||||
options.headers["Content-Type"] ||
|
options.headers["Content-Type"] ||
|
||||||
options.headers["content-type"] ||
|
options.headers["content-type"] ||
|
||||||
options.headers["Accept"] ||
|
options.headers["Accept"] ||
|
||||||
options.headers["accept"];
|
options.headers["accept"];
|
||||||
|
}
|
||||||
|
|
||||||
if (contentType !== undefined) {
|
if (contentType !== undefined) {
|
||||||
contentType = contentType.toLowerCase();
|
contentType = contentType.toLowerCase();
|
||||||
}
|
}
|
||||||
const rspHeaders = { ...r.headers };
|
const rspHeaders = { ...r.headers };
|
||||||
|
console.log("rspHeaders");
|
||||||
|
console.log(rspHeaders);
|
||||||
for (let key in rspHeaders) {
|
for (let key in rspHeaders) {
|
||||||
if (rspHeaders.hasOwnProperty(key)) {
|
if (rspHeaders.hasOwnProperty(key)) {
|
||||||
if (key === "content-disposition" || key === "Content-Disposition") {
|
// avoid the error:
|
||||||
|
// Failed to read the 'headers' property from 'ResponseInit': String contains non ISO-8859-1 code point.
|
||||||
|
// const possibleNonAscii = [
|
||||||
|
// "Content-Disposition",
|
||||||
|
// "X-Accel-Redirect",
|
||||||
|
// "X-Outfilename",
|
||||||
|
// "X-Sendfile"
|
||||||
|
// ];
|
||||||
|
// for (const p of possibleNonAscii) {
|
||||||
|
// if (key === p || key === p.toLowerCase()) {
|
||||||
|
// rspHeaders[key] = encodeURIComponent(rspHeaders[key]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (!onlyAscii(rspHeaders[key])) {
|
||||||
rspHeaders[key] = encodeURIComponent(rspHeaders[key]);
|
rspHeaders[key] = encodeURIComponent(rspHeaders[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +135,7 @@ if (VALID_REQURL) {
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let r2: Response = undefined;
|
let r2: Response | undefined = undefined;
|
||||||
if ([101, 103, 204, 205, 304].includes(r.status)) {
|
if ([101, 103, 204, 205, 304].includes(r.status)) {
|
||||||
// A null body status is a status that is 101, 103, 204, 205, or 304.
|
// A null body status is a status that is 101, 103, 204, 205, or 304.
|
||||||
// https://fetch.spec.whatwg.org/#statuses
|
// https://fetch.spec.whatwg.org/#statuses
|
||||||
@@ -138,8 +167,8 @@ export const DEFAULT_WEBDAV_CONFIG = {
|
|||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
authType: "basic",
|
authType: "basic",
|
||||||
manualRecursive: false,
|
manualRecursive: true,
|
||||||
depth: "auto_unknown",
|
depth: "manual_1",
|
||||||
remoteBaseDir: "",
|
remoteBaseDir: "",
|
||||||
} as WebdavConfig;
|
} as WebdavConfig;
|
||||||
|
|
||||||
@@ -193,7 +222,7 @@ const fromWebdavItemToRemoteItem = (x: FileStat, remoteBaseDir: string) => {
|
|||||||
export class WrappedWebdavClient {
|
export class WrappedWebdavClient {
|
||||||
webdavConfig: WebdavConfig;
|
webdavConfig: WebdavConfig;
|
||||||
remoteBaseDir: string;
|
remoteBaseDir: string;
|
||||||
client: WebDAVClient;
|
client!: WebDAVClient;
|
||||||
vaultFolderExists: boolean;
|
vaultFolderExists: boolean;
|
||||||
saveUpdatedConfigFunc: () => Promise<any>;
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
constructor(
|
constructor(
|
||||||
@@ -209,10 +238,12 @@ export class WrappedWebdavClient {
|
|||||||
|
|
||||||
init = async () => {
|
init = async () => {
|
||||||
// init client if not inited
|
// init client if not inited
|
||||||
|
if (this.client !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const headers = {
|
const headers = {
|
||||||
"Cache-Control": "no-cache",
|
"Cache-Control": "no-cache",
|
||||||
};
|
};
|
||||||
if (this.client === undefined) {
|
|
||||||
if (
|
if (
|
||||||
this.webdavConfig.username !== "" &&
|
this.webdavConfig.username !== "" &&
|
||||||
this.webdavConfig.password !== ""
|
this.webdavConfig.password !== ""
|
||||||
@@ -232,7 +263,6 @@ export class WrappedWebdavClient {
|
|||||||
headers: headers,
|
headers: headers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// check vault folder
|
// check vault folder
|
||||||
if (this.vaultFolderExists) {
|
if (this.vaultFolderExists) {
|
||||||
@@ -251,58 +281,21 @@ export class WrappedWebdavClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// adjust depth parameter
|
// adjust depth parameter
|
||||||
if (this.webdavConfig.depth === "auto_unknown") {
|
if (
|
||||||
let testPassed = false;
|
this.webdavConfig.depth === "auto" ||
|
||||||
try {
|
this.webdavConfig.depth === "auto_1" ||
|
||||||
const res = await this.client.customRequest(`/${this.remoteBaseDir}/`, {
|
this.webdavConfig.depth === "auto_infinity" ||
|
||||||
method: "PROPFIND",
|
this.webdavConfig.depth === "auto_unknown"
|
||||||
headers: {
|
) {
|
||||||
Depth: "infinity",
|
this.webdavConfig.depth = "manual_1";
|
||||||
Accept: "text/plain,application/xml",
|
|
||||||
},
|
|
||||||
// responseType: "text",
|
|
||||||
} as any);
|
|
||||||
if (res.status === 403) {
|
|
||||||
throw Error("not support Infinity, get 403");
|
|
||||||
} else {
|
|
||||||
testPassed = true;
|
|
||||||
this.webdavConfig.depth = "auto_infinity";
|
|
||||||
this.webdavConfig.manualRecursive = false;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
testPassed = false;
|
|
||||||
}
|
|
||||||
if (!testPassed) {
|
|
||||||
try {
|
|
||||||
const res = await this.client.customRequest(
|
|
||||||
`/${this.remoteBaseDir}/`,
|
|
||||||
{
|
|
||||||
method: "PROPFIND",
|
|
||||||
headers: {
|
|
||||||
Depth: "1",
|
|
||||||
Accept: "text/plain,application/xml",
|
|
||||||
},
|
|
||||||
// responseType: "text",
|
|
||||||
} as any
|
|
||||||
);
|
|
||||||
testPassed = true;
|
|
||||||
this.webdavConfig.depth = "auto_1";
|
|
||||||
this.webdavConfig.manualRecursive = true;
|
this.webdavConfig.manualRecursive = true;
|
||||||
} catch (error) {
|
|
||||||
testPassed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (testPassed) {
|
|
||||||
// the depth option has been changed
|
|
||||||
// save the setting
|
|
||||||
if (this.saveUpdatedConfigFunc !== undefined) {
|
if (this.saveUpdatedConfigFunc !== undefined) {
|
||||||
await this.saveUpdatedConfigFunc();
|
await this.saveUpdatedConfigFunc();
|
||||||
log.info(
|
log.info(
|
||||||
`webdav depth="auto_unknown" is changed to ${this.webdavConfig.depth}`
|
`webdav depth="auto_???" is changed to ${this.webdavConfig.depth}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +333,7 @@ export const getRemoteMeta = async (
|
|||||||
export const uploadToRemote = async (
|
export const uploadToRemote = async (
|
||||||
client: WrappedWebdavClient,
|
client: WrappedWebdavClient,
|
||||||
fileOrFolderPath: string,
|
fileOrFolderPath: string,
|
||||||
vault: Vault,
|
vault: Vault | undefined,
|
||||||
isRecursively: boolean = false,
|
isRecursively: boolean = false,
|
||||||
password: string = "",
|
password: string = "",
|
||||||
remoteEncryptedKey: string = "",
|
remoteEncryptedKey: string = "",
|
||||||
@@ -392,6 +385,11 @@ export const uploadToRemote = async (
|
|||||||
localContent = rawContent;
|
localContent = rawContent;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (vault == undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`the vault variable is not passed but we want to read ${fileOrFolderPath} for webdav`
|
||||||
|
);
|
||||||
|
}
|
||||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||||
}
|
}
|
||||||
let remoteContent = localContent;
|
let remoteContent = localContent;
|
||||||
@@ -420,7 +418,10 @@ export const listAllFromRemote = async (client: WrappedWebdavClient) => {
|
|||||||
|
|
||||||
let contents = [] as FileStat[];
|
let contents = [] as FileStat[];
|
||||||
if (
|
if (
|
||||||
|
client.webdavConfig.depth === "auto" ||
|
||||||
|
client.webdavConfig.depth === "auto_unknown" ||
|
||||||
client.webdavConfig.depth === "auto_1" ||
|
client.webdavConfig.depth === "auto_1" ||
|
||||||
|
client.webdavConfig.depth === "auto_infinity" /* don't trust auto now */ ||
|
||||||
client.webdavConfig.depth === "manual_1"
|
client.webdavConfig.depth === "manual_1"
|
||||||
) {
|
) {
|
||||||
// the remote doesn't support infinity propfind,
|
// the remote doesn't support infinity propfind,
|
||||||
@@ -428,9 +429,9 @@ export const listAllFromRemote = async (client: WrappedWebdavClient) => {
|
|||||||
const q = new Queue([`/${client.remoteBaseDir}`]);
|
const q = new Queue([`/${client.remoteBaseDir}`]);
|
||||||
const CHUNK_SIZE = 10;
|
const CHUNK_SIZE = 10;
|
||||||
while (q.length > 0) {
|
while (q.length > 0) {
|
||||||
const itemsToFetch = [];
|
const itemsToFetch: string[] = [];
|
||||||
while (q.length > 0) {
|
while (q.length > 0) {
|
||||||
itemsToFetch.push(q.pop());
|
itemsToFetch.push(q.pop()!);
|
||||||
}
|
}
|
||||||
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
const itemsToFetchChunks = chunk(itemsToFetch, CHUNK_SIZE);
|
||||||
// log.debug(itemsToFetchChunks);
|
// log.debug(itemsToFetchChunks);
|
||||||
@@ -582,7 +583,7 @@ export const checkConnectivity = async (
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await client.init();
|
await client.init();
|
||||||
const results = await getRemoteMeta(client, `/${client.remoteBaseDir}`);
|
const results = await getRemoteMeta(client, `/${client.remoteBaseDir}/`);
|
||||||
if (results === undefined) {
|
if (results === undefined) {
|
||||||
const err = "results is undefined";
|
const err = "results is undefined";
|
||||||
log.error(err);
|
log.error(err);
|
||||||
|
|||||||
+33
-46
@@ -43,7 +43,6 @@ import {
|
|||||||
import { messyConfigToNormal } from "./configPersist";
|
import { messyConfigToNormal } from "./configPersist";
|
||||||
import type { TransItemType } from "./i18n";
|
import type { TransItemType } from "./i18n";
|
||||||
import { checkHasSpecialCharForDir } from "./misc";
|
import { checkHasSpecialCharForDir } from "./misc";
|
||||||
import { applyWebdavPresetRulesInplace } from "./presetRules";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
applyLogWriterInplace,
|
applyLogWriterInplace,
|
||||||
@@ -416,7 +415,7 @@ class DropboxAuthModal extends Modal {
|
|||||||
const self = this;
|
const self = this;
|
||||||
setConfigBySuccessfullAuthInplace(
|
setConfigBySuccessfullAuthInplace(
|
||||||
this.plugin.settings.dropbox,
|
this.plugin.settings.dropbox,
|
||||||
authRes,
|
authRes!,
|
||||||
() => self.plugin.saveSettings()
|
() => self.plugin.saveSettings()
|
||||||
);
|
);
|
||||||
const client = new RemoteClient(
|
const client = new RemoteClient(
|
||||||
@@ -784,7 +783,7 @@ const getEyesElements = () => {
|
|||||||
|
|
||||||
const wrapTextWithPasswordHide = (text: TextComponent) => {
|
const wrapTextWithPasswordHide = (text: TextComponent) => {
|
||||||
const { eye, eyeOff } = getEyesElements();
|
const { eye, eyeOff } = getEyesElements();
|
||||||
const hider = text.inputEl.insertAdjacentElement("afterend", createSpan());
|
const hider = text.inputEl.insertAdjacentElement("afterend", createSpan())!;
|
||||||
// the init type of hider is "hidden" === eyeOff === password
|
// the init type of hider is "hidden" === eyeOff === password
|
||||||
hider.innerHTML = eyeOff;
|
hider.innerHTML = eyeOff;
|
||||||
hider.addEventListener("click", (e) => {
|
hider.addEventListener("click", (e) => {
|
||||||
@@ -1462,16 +1461,16 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setValue(this.plugin.settings.webdav.address)
|
.setValue(this.plugin.settings.webdav.address)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.webdav.address = value.trim();
|
this.plugin.settings.webdav.address = value.trim();
|
||||||
|
// deprecate auto on 20240116, force to manual_1
|
||||||
if (
|
if (
|
||||||
|
this.plugin.settings.webdav.depth === "auto" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_1" ||
|
this.plugin.settings.webdav.depth === "auto_1" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_infinity"
|
this.plugin.settings.webdav.depth === "auto_infinity" ||
|
||||||
|
this.plugin.settings.webdav.depth === "auto_unknown"
|
||||||
) {
|
) {
|
||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
this.plugin.settings.webdav.depth = "manual_1";
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: any more elegant way?
|
|
||||||
applyWebdavPresetRulesInplace(this.plugin.settings.webdav);
|
|
||||||
|
|
||||||
// normally saved
|
// normally saved
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
})
|
})
|
||||||
@@ -1487,11 +1486,14 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setValue(this.plugin.settings.webdav.username)
|
.setValue(this.plugin.settings.webdav.username)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.webdav.username = value.trim();
|
this.plugin.settings.webdav.username = value.trim();
|
||||||
|
// deprecate auto on 20240116, force to manual_1
|
||||||
if (
|
if (
|
||||||
|
this.plugin.settings.webdav.depth === "auto" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_1" ||
|
this.plugin.settings.webdav.depth === "auto_1" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_infinity"
|
this.plugin.settings.webdav.depth === "auto_infinity" ||
|
||||||
|
this.plugin.settings.webdav.depth === "auto_unknown"
|
||||||
) {
|
) {
|
||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
this.plugin.settings.webdav.depth = "manual_1";
|
||||||
}
|
}
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
@@ -1507,11 +1509,14 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setValue(this.plugin.settings.webdav.password)
|
.setValue(this.plugin.settings.webdav.password)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.webdav.password = value.trim();
|
this.plugin.settings.webdav.password = value.trim();
|
||||||
|
// deprecate auto on 20240116, force to manual_1
|
||||||
if (
|
if (
|
||||||
|
this.plugin.settings.webdav.depth === "auto" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_1" ||
|
this.plugin.settings.webdav.depth === "auto_1" ||
|
||||||
this.plugin.settings.webdav.depth === "auto_infinity"
|
this.plugin.settings.webdav.depth === "auto_infinity" ||
|
||||||
|
this.plugin.settings.webdav.depth === "auto_unknown"
|
||||||
) {
|
) {
|
||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
this.plugin.settings.webdav.depth = "manual_1";
|
||||||
}
|
}
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
@@ -1534,8 +1539,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.webdav.authType)
|
.setValue(this.plugin.settings.webdav.authType)
|
||||||
.onChange(async (val: WebdavAuthType) => {
|
.onChange(async (val) => {
|
||||||
this.plugin.settings.webdav.authType = val;
|
this.plugin.settings.webdav.authType = val as WebdavAuthType;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1544,28 +1549,13 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setName(t("settings_webdav_depth"))
|
.setName(t("settings_webdav_depth"))
|
||||||
.setDesc(t("settings_webdav_depth_desc"))
|
.setDesc(t("settings_webdav_depth_desc"))
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
dropdown.addOption("auto", t("settings_webdav_depth_auto"));
|
|
||||||
dropdown.addOption("manual_1", t("settings_webdav_depth_1"));
|
dropdown.addOption("manual_1", t("settings_webdav_depth_1"));
|
||||||
dropdown.addOption("manual_infinity", t("settings_webdav_depth_inf"));
|
dropdown.addOption("manual_infinity", t("settings_webdav_depth_inf"));
|
||||||
|
|
||||||
let initVal = "auto";
|
dropdown
|
||||||
const autoOptions: Set<WebdavDepthType> = new Set([
|
.setValue(this.plugin.settings.webdav.depth || "manual_1")
|
||||||
"auto_unknown",
|
.onChange(async (val) => {
|
||||||
"auto_1",
|
if (val === "manual_1") {
|
||||||
"auto_infinity",
|
|
||||||
]);
|
|
||||||
if (autoOptions.has(this.plugin.settings.webdav.depth)) {
|
|
||||||
initVal = "auto";
|
|
||||||
} else {
|
|
||||||
initVal = this.plugin.settings.webdav.depth || "auto";
|
|
||||||
}
|
|
||||||
|
|
||||||
type DepthOption = "auto" | "manual_1" | "manual_infinity";
|
|
||||||
dropdown.setValue(initVal).onChange(async (val: DepthOption) => {
|
|
||||||
if (val === "auto") {
|
|
||||||
this.plugin.settings.webdav.depth = "auto_unknown";
|
|
||||||
this.plugin.settings.webdav.manualRecursive = false;
|
|
||||||
} else if (val === "manual_1") {
|
|
||||||
this.plugin.settings.webdav.depth = "manual_1";
|
this.plugin.settings.webdav.depth = "manual_1";
|
||||||
this.plugin.settings.webdav.manualRecursive = true;
|
this.plugin.settings.webdav.manualRecursive = true;
|
||||||
} else if (val === "manual_infinity") {
|
} else if (val === "manual_infinity") {
|
||||||
@@ -1573,9 +1563,6 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.webdav.manualRecursive = false;
|
this.plugin.settings.webdav.manualRecursive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: any more elegant way?
|
|
||||||
applyWebdavPresetRulesInplace(this.plugin.settings.webdav);
|
|
||||||
|
|
||||||
// normally save
|
// normally save
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
@@ -1656,8 +1643,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
dropdown.addOption("onedrive", t("settings_chooseservice_onedrive"));
|
dropdown.addOption("onedrive", t("settings_chooseservice_onedrive"));
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.serviceType)
|
.setValue(this.plugin.settings.serviceType)
|
||||||
.onChange(async (val: SUPPORTED_SERVICES_TYPE) => {
|
.onChange(async (val) => {
|
||||||
this.plugin.settings.serviceType = val;
|
this.plugin.settings.serviceType = val as SUPPORTED_SERVICES_TYPE;
|
||||||
s3Div.toggleClass(
|
s3Div.toggleClass(
|
||||||
"s3-hide",
|
"s3-hide",
|
||||||
this.plugin.settings.serviceType !== "s3"
|
this.plugin.settings.serviceType !== "s3"
|
||||||
@@ -1815,11 +1802,11 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
// );
|
// );
|
||||||
if (
|
if (
|
||||||
currentTime - lastModified <
|
currentTime - lastModified <
|
||||||
this.plugin.settings.syncOnSaveAfterMilliseconds
|
this.plugin.settings.syncOnSaveAfterMilliseconds!
|
||||||
) {
|
) {
|
||||||
if (!runScheduled) {
|
if (!runScheduled) {
|
||||||
const scheduleTimeFromNow =
|
const scheduleTimeFromNow =
|
||||||
this.plugin.settings.syncOnSaveAfterMilliseconds -
|
this.plugin.settings.syncOnSaveAfterMilliseconds! -
|
||||||
(currentTime - lastModified);
|
(currentTime - lastModified);
|
||||||
log.info(
|
log.info(
|
||||||
`schedule a run for ${scheduleTimeFromNow} milliseconds later`
|
`schedule a run for ${scheduleTimeFromNow} milliseconds later`
|
||||||
@@ -1864,7 +1851,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setDesc(t("settings_enablestatusbar_info_desc"))
|
.setDesc(t("settings_enablestatusbar_info_desc"))
|
||||||
.addToggle((toggle) => {
|
.addToggle((toggle) => {
|
||||||
toggle
|
toggle
|
||||||
.setValue(this.plugin.settings.enableStatusBarInfo)
|
.setValue(this.plugin.settings.enableStatusBarInfo ?? false)
|
||||||
.onChange(async (val) => {
|
.onChange(async (val) => {
|
||||||
this.plugin.settings.enableStatusBarInfo = val;
|
this.plugin.settings.enableStatusBarInfo = val;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
@@ -1897,7 +1884,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
.addTextArea((textArea) => {
|
.addTextArea((textArea) => {
|
||||||
textArea
|
textArea
|
||||||
.setValue(`${this.plugin.settings.ignorePaths.join("\n")}`)
|
.setValue(`${(this.plugin.settings.ignorePaths ?? []).join("\n")}`)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.ignorePaths = value
|
this.plugin.settings.ignorePaths = value
|
||||||
.trim()
|
.trim()
|
||||||
@@ -1999,9 +1986,9 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
t("settings_deletetowhere_obsidian_trash")
|
t("settings_deletetowhere_obsidian_trash")
|
||||||
);
|
);
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.deleteToWhere)
|
.setValue(this.plugin.settings.deleteToWhere ?? "system")
|
||||||
.onChange(async (val: "system" | "obsidian") => {
|
.onChange(async (val) => {
|
||||||
this.plugin.settings.deleteToWhere = val;
|
this.plugin.settings.deleteToWhere = val as "system" | "obsidian";
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2044,7 +2031,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
dropdown.addOption("info", "info");
|
dropdown.addOption("info", "info");
|
||||||
dropdown.addOption("debug", "debug");
|
dropdown.addOption("debug", "debug");
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.currLogLevel)
|
.setValue(this.plugin.settings.currLogLevel ?? "info")
|
||||||
.onChange(async (val: string) => {
|
.onChange(async (val: string) => {
|
||||||
this.plugin.settings.currLogLevel = val;
|
this.plugin.settings.currLogLevel = val;
|
||||||
log.setLevel(val as any);
|
log.setLevel(val as any);
|
||||||
|
|||||||
+62
-81
@@ -186,7 +186,7 @@ export const parseRemoteItems = async (
|
|||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
const remoteStates = [] as FileOrFolderMixedState[];
|
const remoteStates = [] as FileOrFolderMixedState[];
|
||||||
let metadataFile: FileOrFolderMixedState = undefined;
|
let metadataFile: FileOrFolderMixedState | undefined = undefined;
|
||||||
if (remote === undefined) {
|
if (remote === undefined) {
|
||||||
return {
|
return {
|
||||||
remoteStates: remoteStates,
|
remoteStates: remoteStates,
|
||||||
@@ -212,8 +212,8 @@ export const parseRemoteItems = async (
|
|||||||
remoteType,
|
remoteType,
|
||||||
db,
|
db,
|
||||||
key,
|
key,
|
||||||
entry.lastModified,
|
entry.lastModified ?? Date.now(),
|
||||||
entry.etag,
|
entry.etag ?? "",
|
||||||
vaultRandomID
|
vaultRandomID
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ export const parseRemoteItems = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMetadataFile = async (
|
export const fetchMetadataFile = async (
|
||||||
metadataFile: FileOrFolderMixedState,
|
metadataFile: FileOrFolderMixedState | undefined,
|
||||||
client: RemoteClient,
|
client: RemoteClient,
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
@@ -283,7 +283,7 @@ export const fetchMetadataFile = async (
|
|||||||
const buf = await client.downloadFromRemote(
|
const buf = await client.downloadFromRemote(
|
||||||
metadataFile.key,
|
metadataFile.key,
|
||||||
vault,
|
vault,
|
||||||
metadataFile.mtimeRemote,
|
metadataFile.mtimeRemote ?? Date.now(),
|
||||||
password,
|
password,
|
||||||
metadataFile.remoteEncryptedKey,
|
metadataFile.remoteEncryptedKey,
|
||||||
true
|
true
|
||||||
@@ -325,8 +325,8 @@ const ensembleMixedStates = async (
|
|||||||
remoteStates: FileOrFolderMixedState[],
|
remoteStates: FileOrFolderMixedState[],
|
||||||
local: TAbstractFile[],
|
local: TAbstractFile[],
|
||||||
localConfigDirContents: ObsConfigDirFileType[] | undefined,
|
localConfigDirContents: ObsConfigDirFileType[] | undefined,
|
||||||
remoteDeleteHistory: DeletionOnRemote[],
|
remoteDeleteHistory: DeletionOnRemote[] | undefined,
|
||||||
localFileHistory: FileFolderHistoryRecord[],
|
localFileHistory: FileFolderHistoryRecord[] | undefined,
|
||||||
syncConfigDir: boolean,
|
syncConfigDir: boolean,
|
||||||
configDir: string,
|
configDir: string,
|
||||||
syncUnderscoreItems: boolean,
|
syncUnderscoreItems: boolean,
|
||||||
@@ -413,7 +413,10 @@ const ensembleMixedStates = async (
|
|||||||
if (syncConfigDir && localConfigDirContents !== undefined) {
|
if (syncConfigDir && localConfigDirContents !== undefined) {
|
||||||
for (const entry of localConfigDirContents) {
|
for (const entry of localConfigDirContents) {
|
||||||
const key = entry.key;
|
const key = entry.key;
|
||||||
let mtimeLocal = Math.max(entry.mtime ?? 0, entry.ctime ?? 0);
|
let mtimeLocal: number | undefined = Math.max(
|
||||||
|
entry.mtime ?? 0,
|
||||||
|
entry.ctime ?? 0
|
||||||
|
);
|
||||||
if (Number.isNaN(mtimeLocal) || mtimeLocal === 0) {
|
if (Number.isNaN(mtimeLocal) || mtimeLocal === 0) {
|
||||||
mtimeLocal = undefined;
|
mtimeLocal = undefined;
|
||||||
}
|
}
|
||||||
@@ -453,7 +456,7 @@ const ensembleMixedStates = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of remoteDeleteHistory) {
|
for (const entry of remoteDeleteHistory ?? []) {
|
||||||
const key = entry.key;
|
const key = entry.key;
|
||||||
const r = {
|
const r = {
|
||||||
key: key,
|
key: key,
|
||||||
@@ -485,7 +488,7 @@ const ensembleMixedStates = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of localFileHistory) {
|
for (const entry of localFileHistory ?? []) {
|
||||||
let key = entry.key;
|
let key = entry.key;
|
||||||
if (entry.keyType === "folder") {
|
if (entry.keyType === "folder") {
|
||||||
if (!entry.key.endsWith("/")) {
|
if (!entry.key.endsWith("/")) {
|
||||||
@@ -532,7 +535,7 @@ const ensembleMixedStates = async (
|
|||||||
changeLocalMtimeUsingMapping: true,
|
changeLocalMtimeUsingMapping: true,
|
||||||
};
|
};
|
||||||
if (results.hasOwnProperty(key)) {
|
if (results.hasOwnProperty(key)) {
|
||||||
let mtimeLocal = Math.max(
|
let mtimeLocal: number | undefined = Math.max(
|
||||||
r.mtimeLocal ?? 0,
|
r.mtimeLocal ?? 0,
|
||||||
results[key].mtimeLocal ?? 0
|
results[key].mtimeLocal ?? 0
|
||||||
);
|
);
|
||||||
@@ -624,13 +627,13 @@ const assignOperationToFileInplace = (
|
|||||||
|
|
||||||
// 1. mtimeLocal
|
// 1. mtimeLocal
|
||||||
if (r.existLocal) {
|
if (r.existLocal) {
|
||||||
const mtimeRemote = r.existRemote ? r.mtimeRemote : -1;
|
const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1;
|
||||||
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
||||||
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
||||||
if (
|
if (
|
||||||
r.mtimeLocal >= mtimeRemote &&
|
r.mtimeLocal! >= mtimeRemote &&
|
||||||
r.mtimeLocal >= deltimeLocal &&
|
r.mtimeLocal! >= deltimeLocal &&
|
||||||
r.mtimeLocal >= deltimeRemote
|
r.mtimeLocal! >= deltimeRemote
|
||||||
) {
|
) {
|
||||||
if (sizeLocalComp === undefined) {
|
if (sizeLocalComp === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -654,7 +657,7 @@ const assignOperationToFileInplace = (
|
|||||||
} else {
|
} else {
|
||||||
// limit the sizes
|
// limit the sizes
|
||||||
if (sizeLocalComp <= skipSizeLargerThan) {
|
if (sizeLocalComp <= skipSizeLargerThan) {
|
||||||
if (sizeRemoteComp <= skipSizeLargerThan) {
|
if (sizeRemoteComp! <= skipSizeLargerThan) {
|
||||||
r.decision = "uploadLocalToRemote";
|
r.decision = "uploadLocalToRemote";
|
||||||
r.decisionBranch = 18;
|
r.decisionBranch = 18;
|
||||||
} else {
|
} else {
|
||||||
@@ -662,7 +665,7 @@ const assignOperationToFileInplace = (
|
|||||||
r.decisionBranch = 19;
|
r.decisionBranch = 19;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sizeRemoteComp <= skipSizeLargerThan) {
|
if (sizeRemoteComp! <= skipSizeLargerThan) {
|
||||||
r.decision = "errorLocalTooLargeConflictRemote";
|
r.decision = "errorLocalTooLargeConflictRemote";
|
||||||
r.decisionBranch = 20;
|
r.decisionBranch = 20;
|
||||||
} else {
|
} else {
|
||||||
@@ -713,13 +716,13 @@ const assignOperationToFileInplace = (
|
|||||||
|
|
||||||
// 2. mtimeRemote
|
// 2. mtimeRemote
|
||||||
if (r.existRemote) {
|
if (r.existRemote) {
|
||||||
const mtimeLocal = r.existLocal ? r.mtimeLocal : -1;
|
const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1;
|
||||||
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
||||||
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
||||||
if (
|
if (
|
||||||
r.mtimeRemote > mtimeLocal &&
|
r.mtimeRemote! > mtimeLocal &&
|
||||||
r.mtimeRemote >= deltimeLocal &&
|
r.mtimeRemote! >= deltimeLocal &&
|
||||||
r.mtimeRemote >= deltimeRemote
|
r.mtimeRemote! >= deltimeRemote
|
||||||
) {
|
) {
|
||||||
// we have remote laregest mtime,
|
// we have remote laregest mtime,
|
||||||
// and the local not existing or smaller mtime
|
// and the local not existing or smaller mtime
|
||||||
@@ -771,8 +774,8 @@ const assignOperationToFileInplace = (
|
|||||||
|
|
||||||
// 3. deltimeLocal
|
// 3. deltimeLocal
|
||||||
if (r.deltimeLocal !== undefined && r.deltimeLocal !== 0) {
|
if (r.deltimeLocal !== undefined && r.deltimeLocal !== 0) {
|
||||||
const mtimeLocal = r.existLocal ? r.mtimeLocal : -1;
|
const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1;
|
||||||
const mtimeRemote = r.existRemote ? r.mtimeRemote : -1;
|
const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1;
|
||||||
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
const deltimeRemote = r.deltimeRemote !== undefined ? r.deltimeRemote : -1;
|
||||||
if (
|
if (
|
||||||
r.deltimeLocal >= mtimeLocal &&
|
r.deltimeLocal >= mtimeLocal &&
|
||||||
@@ -787,9 +790,9 @@ const assignOperationToFileInplace = (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const localTooLargeToDelete =
|
const localTooLargeToDelete =
|
||||||
r.existLocal && sizeLocalComp > skipSizeLargerThan;
|
r.existLocal && sizeLocalComp! > skipSizeLargerThan;
|
||||||
const remoteTooLargeToDelete =
|
const remoteTooLargeToDelete =
|
||||||
r.existRemote && sizeRemoteComp > skipSizeLargerThan;
|
r.existRemote && sizeRemoteComp! > skipSizeLargerThan;
|
||||||
if (localTooLargeToDelete) {
|
if (localTooLargeToDelete) {
|
||||||
if (remoteTooLargeToDelete) {
|
if (remoteTooLargeToDelete) {
|
||||||
r.decision = "skipUsingLocalDelTooLarge";
|
r.decision = "skipUsingLocalDelTooLarge";
|
||||||
@@ -824,8 +827,8 @@ const assignOperationToFileInplace = (
|
|||||||
|
|
||||||
// 4. deltimeRemote
|
// 4. deltimeRemote
|
||||||
if (r.deltimeRemote !== undefined && r.deltimeRemote !== 0) {
|
if (r.deltimeRemote !== undefined && r.deltimeRemote !== 0) {
|
||||||
const mtimeLocal = r.existLocal ? r.mtimeLocal : -1;
|
const mtimeLocal = r.existLocal ? r.mtimeLocal! : -1;
|
||||||
const mtimeRemote = r.existRemote ? r.mtimeRemote : -1;
|
const mtimeRemote = r.existRemote ? r.mtimeRemote! : -1;
|
||||||
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
const deltimeLocal = r.deltimeLocal !== undefined ? r.deltimeLocal : -1;
|
||||||
if (
|
if (
|
||||||
r.deltimeRemote >= mtimeLocal &&
|
r.deltimeRemote >= mtimeLocal &&
|
||||||
@@ -840,9 +843,9 @@ const assignOperationToFileInplace = (
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const localTooLargeToDelete =
|
const localTooLargeToDelete =
|
||||||
r.existLocal && sizeLocalComp > skipSizeLargerThan;
|
r.existLocal && sizeLocalComp! > skipSizeLargerThan;
|
||||||
const remoteTooLargeToDelete =
|
const remoteTooLargeToDelete =
|
||||||
r.existRemote && sizeRemoteComp > skipSizeLargerThan;
|
r.existRemote && sizeRemoteComp! > skipSizeLargerThan;
|
||||||
if (localTooLargeToDelete) {
|
if (localTooLargeToDelete) {
|
||||||
if (remoteTooLargeToDelete) {
|
if (remoteTooLargeToDelete) {
|
||||||
r.decision = "skipUsingRemoteDelTooLarge";
|
r.decision = "skipUsingRemoteDelTooLarge";
|
||||||
@@ -905,7 +908,13 @@ const assignOperationToFolderInplace = async (
|
|||||||
// if it was created after deletion, we should keep it as is
|
// if it was created after deletion, we should keep it as is
|
||||||
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
if (requireApiVersion(API_VER_STAT_FOLDER)) {
|
||||||
if (r.existLocal) {
|
if (r.existLocal) {
|
||||||
const { ctime, mtime } = await statFix(vault, r.key);
|
let ctime = 0;
|
||||||
|
let mtime = 0;
|
||||||
|
const s = await statFix(vault, r.key);
|
||||||
|
if (s !== undefined && s !== null) {
|
||||||
|
ctime = s.ctime;
|
||||||
|
mtime = s.mtime;
|
||||||
|
}
|
||||||
const cmtime = Math.max(ctime ?? 0, mtime ?? 0);
|
const cmtime = Math.max(ctime ?? 0, mtime ?? 0);
|
||||||
if (
|
if (
|
||||||
!Number.isNaN(cmtime) &&
|
!Number.isNaN(cmtime) &&
|
||||||
@@ -936,9 +945,9 @@ const assignOperationToFolderInplace = async (
|
|||||||
if (
|
if (
|
||||||
r.existLocal &&
|
r.existLocal &&
|
||||||
r.changeLocalMtimeUsingMapping &&
|
r.changeLocalMtimeUsingMapping &&
|
||||||
r.mtimeLocal > 0 &&
|
r.mtimeLocal! > 0 &&
|
||||||
r.mtimeLocal > deltimeLocal &&
|
r.mtimeLocal! > deltimeLocal &&
|
||||||
r.mtimeLocal > deltimeRemote
|
r.mtimeLocal! > deltimeRemote
|
||||||
) {
|
) {
|
||||||
keptFolder.add(getParentFolder(r.key));
|
keptFolder.add(getParentFolder(r.key));
|
||||||
if (r.existLocal && r.existRemote) {
|
if (r.existLocal && r.existRemote) {
|
||||||
@@ -1018,8 +1027,8 @@ export const getSyncPlan = async (
|
|||||||
remoteStates: FileOrFolderMixedState[],
|
remoteStates: FileOrFolderMixedState[],
|
||||||
local: TAbstractFile[],
|
local: TAbstractFile[],
|
||||||
localConfigDirContents: ObsConfigDirFileType[] | undefined,
|
localConfigDirContents: ObsConfigDirFileType[] | undefined,
|
||||||
remoteDeleteHistory: DeletionOnRemote[],
|
remoteDeleteHistory: DeletionOnRemote[] | undefined,
|
||||||
localFileHistory: FileFolderHistoryRecord[],
|
localFileHistory: FileFolderHistoryRecord[] | undefined,
|
||||||
remoteType: SUPPORTED_SERVICES_TYPE,
|
remoteType: SUPPORTED_SERVICES_TYPE,
|
||||||
triggerSource: SyncTriggerSourceType,
|
triggerSource: SyncTriggerSourceType,
|
||||||
vault: Vault,
|
vault: Vault,
|
||||||
@@ -1071,30 +1080,30 @@ export const getSyncPlan = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SIZES_GO_WRONG_DECISIONS.has(val.decision)) {
|
if (SIZES_GO_WRONG_DECISIONS.has(val.decision!)) {
|
||||||
sizesGoWrong.push(val);
|
sizesGoWrong.push(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DELETION_DECISIONS.has(val.decision)) {
|
if (DELETION_DECISIONS.has(val.decision!)) {
|
||||||
if (val.decision === "uploadLocalDelHistToRemote") {
|
if (val.decision === "uploadLocalDelHistToRemote") {
|
||||||
deletions.push({
|
deletions.push({
|
||||||
key: key,
|
key: key,
|
||||||
actionWhen: val.deltimeLocal,
|
actionWhen: val.deltimeLocal!,
|
||||||
});
|
});
|
||||||
} else if (val.decision === "keepRemoteDelHist") {
|
} else if (val.decision === "keepRemoteDelHist") {
|
||||||
deletions.push({
|
deletions.push({
|
||||||
key: key,
|
key: key,
|
||||||
actionWhen: val.deltimeRemote,
|
actionWhen: val.deltimeRemote!,
|
||||||
});
|
});
|
||||||
} else if (val.decision === "uploadLocalDelHistToRemoteFolder") {
|
} else if (val.decision === "uploadLocalDelHistToRemoteFolder") {
|
||||||
deletions.push({
|
deletions.push({
|
||||||
key: key,
|
key: key,
|
||||||
actionWhen: val.deltimeLocal,
|
actionWhen: val.deltimeLocal!,
|
||||||
});
|
});
|
||||||
} else if (val.decision === "keepRemoteDelHistFolder") {
|
} else if (val.decision === "keepRemoteDelHistFolder") {
|
||||||
deletions.push({
|
deletions.push({
|
||||||
key: key,
|
key: key,
|
||||||
actionWhen: val.deltimeRemote,
|
actionWhen: val.deltimeRemote!,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw Error(`do not know how to delete for decision ${val.decision}`);
|
throw Error(`do not know how to delete for decision ${val.decision}`);
|
||||||
@@ -1131,7 +1140,7 @@ const uploadExtraMeta = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const key = DEFAULT_FILE_NAME_FOR_METADATAONREMOTE;
|
const key = DEFAULT_FILE_NAME_FOR_METADATAONREMOTE;
|
||||||
let remoteEncryptedKey = key;
|
let remoteEncryptedKey: string | undefined = key;
|
||||||
|
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
if (metadataFile === undefined) {
|
if (metadataFile === undefined) {
|
||||||
@@ -1180,7 +1189,7 @@ const dispatchOperationToActual = async (
|
|||||||
localDeleteFunc: any,
|
localDeleteFunc: any,
|
||||||
password: string = ""
|
password: string = ""
|
||||||
) => {
|
) => {
|
||||||
let remoteEncryptedKey = key;
|
let remoteEncryptedKey: string | undefined = key;
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
remoteEncryptedKey = r.remoteEncryptedKey;
|
remoteEncryptedKey = r.remoteEncryptedKey;
|
||||||
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
||||||
@@ -1233,12 +1242,12 @@ const dispatchOperationToActual = async (
|
|||||||
client.serviceType,
|
client.serviceType,
|
||||||
db,
|
db,
|
||||||
r.key,
|
r.key,
|
||||||
r.mtimeLocal,
|
r.mtimeLocal!,
|
||||||
r.sizeLocal,
|
r.sizeLocal!,
|
||||||
r.key,
|
r.key,
|
||||||
remoteObjMeta.lastModified,
|
remoteObjMeta.lastModified ?? Date.now(),
|
||||||
remoteObjMeta.size,
|
remoteObjMeta.size,
|
||||||
remoteObjMeta.etag,
|
remoteObjMeta.etag ?? "",
|
||||||
vaultRandomID
|
vaultRandomID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1248,7 +1257,7 @@ const dispatchOperationToActual = async (
|
|||||||
await client.downloadFromRemote(
|
await client.downloadFromRemote(
|
||||||
r.key,
|
r.key,
|
||||||
vault,
|
vault,
|
||||||
r.mtimeRemote,
|
r.mtimeRemote!,
|
||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
@@ -1269,12 +1278,12 @@ const dispatchOperationToActual = async (
|
|||||||
client.serviceType,
|
client.serviceType,
|
||||||
db,
|
db,
|
||||||
r.key,
|
r.key,
|
||||||
r.mtimeLocal,
|
r.mtimeLocal!,
|
||||||
r.sizeLocal,
|
r.sizeLocal!,
|
||||||
r.key,
|
r.key,
|
||||||
remoteObjMeta.lastModified,
|
remoteObjMeta.lastModified ?? Date.now(),
|
||||||
remoteObjMeta.size,
|
remoteObjMeta.size,
|
||||||
remoteObjMeta.etag,
|
remoteObjMeta.etag ?? "",
|
||||||
vaultRandomID
|
vaultRandomID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1388,7 +1397,7 @@ export const doActualSync = async (
|
|||||||
vault: Vault,
|
vault: Vault,
|
||||||
syncPlan: SyncPlanType,
|
syncPlan: SyncPlanType,
|
||||||
sortedKeys: string[],
|
sortedKeys: string[],
|
||||||
metadataFile: FileOrFolderMixedState,
|
metadataFile: FileOrFolderMixedState | undefined,
|
||||||
origMetadata: MetadataOnRemote,
|
origMetadata: MetadataOnRemote,
|
||||||
sizesGoWrong: FileOrFolderMixedState[],
|
sizesGoWrong: FileOrFolderMixedState[],
|
||||||
deletions: DeletionOnRemote[],
|
deletions: DeletionOnRemote[],
|
||||||
@@ -1418,34 +1427,6 @@ export const doActualSync = async (
|
|||||||
log.debug(`finish syncing extra data firstly`);
|
log.debug(`finish syncing extra data firstly`);
|
||||||
|
|
||||||
log.debug(`concurrency === ${concurrency}`);
|
log.debug(`concurrency === ${concurrency}`);
|
||||||
if (concurrency === 1) {
|
|
||||||
// run everything in sequence
|
|
||||||
// good old way
|
|
||||||
for (let i = 0; i < sortedKeys.length; ++i) {
|
|
||||||
const key = sortedKeys[i];
|
|
||||||
const val = mixedStates[key];
|
|
||||||
|
|
||||||
log.debug(`start syncing "${key}" with plan ${JSON.stringify(val)}`);
|
|
||||||
|
|
||||||
if (callbackSyncProcess !== undefined) {
|
|
||||||
await callbackSyncProcess(i, totalCount, key, val.decision);
|
|
||||||
}
|
|
||||||
|
|
||||||
await dispatchOperationToActual(
|
|
||||||
key,
|
|
||||||
vaultRandomID,
|
|
||||||
val,
|
|
||||||
client,
|
|
||||||
db,
|
|
||||||
vault,
|
|
||||||
localDeleteFunc,
|
|
||||||
password
|
|
||||||
);
|
|
||||||
log.debug(`finished ${key}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return; // shortcut return, avoid too many nests below
|
|
||||||
}
|
|
||||||
|
|
||||||
const { folderCreationOps, deletionOps, uploadDownloads, realTotalCount } =
|
const { folderCreationOps, deletionOps, uploadDownloads, realTotalCount } =
|
||||||
splitThreeSteps(syncPlan, sortedKeys);
|
splitThreeSteps(syncPlan, sortedKeys);
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ describe("Metadata operations tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should treat undefined correctly", async () => {
|
it("should treat undefined correctly", async () => {
|
||||||
const a: MetadataOnRemote = undefined;
|
const a: MetadataOnRemote | undefined = undefined;
|
||||||
let b: MetadataOnRemote = {
|
let b: MetadataOnRemote | undefined = {
|
||||||
deletions: [
|
deletions: [
|
||||||
{ key: "xxx", actionWhen: 1 },
|
{ key: "xxx", actionWhen: 1 },
|
||||||
{ key: "yyy", actionWhen: 2 },
|
{ key: "yyy", actionWhen: 2 },
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import { expect } from "chai";
|
|
||||||
import type { WebdavConfig } from "../src/baseTypes";
|
|
||||||
import { applyWebdavPresetRulesInplace } from "../src/presetRules";
|
|
||||||
|
|
||||||
describe("Preset rules tests", () => {
|
|
||||||
it("should check undefined correctly", () => {
|
|
||||||
let x: Partial<WebdavConfig> | undefined = undefined;
|
|
||||||
const y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(y.webdav === undefined);
|
|
||||||
expect(!y.changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should check empty object", () => {
|
|
||||||
let x: Partial<WebdavConfig> | undefined = {};
|
|
||||||
const y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(y.webdav).deep.equals({});
|
|
||||||
expect(!y.changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should modify depths correctly", () => {
|
|
||||||
let x: Partial<WebdavConfig> = {
|
|
||||||
address: "https://example.teracloud.jp/dav/",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
let y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_1");
|
|
||||||
expect(y.changed);
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://example.teracloud.jp/dav/example",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_1");
|
|
||||||
expect(y.changed);
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://dav.jianguoyun.com/dav/",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_1");
|
|
||||||
expect(y.changed);
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://dav.jianguoyun.com/dav/",
|
|
||||||
depth: "auto_infinity",
|
|
||||||
};
|
|
||||||
y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_1");
|
|
||||||
expect(y.changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not modify depths if depths is set automatically correctly", () => {
|
|
||||||
let x: Partial<WebdavConfig> = {
|
|
||||||
address: "https://dav.jianguoyun.com/dav/",
|
|
||||||
depth: "auto_1",
|
|
||||||
};
|
|
||||||
let y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_1");
|
|
||||||
expect(!y.changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not modify depths if depths have been set manually", () => {
|
|
||||||
let x: Partial<WebdavConfig> = {
|
|
||||||
address: "https://example.teracloud.jp/dav/",
|
|
||||||
depth: "manual_infinity",
|
|
||||||
};
|
|
||||||
let y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "manual_infinity");
|
|
||||||
expect(!y.changed);
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://example.teracloud.jp/dav/example",
|
|
||||||
depth: "manual_1",
|
|
||||||
};
|
|
||||||
y = applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "manual_1");
|
|
||||||
expect(!y.changed);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not modify depths when urls are not in preset rules", () => {
|
|
||||||
let x: Partial<WebdavConfig> = {
|
|
||||||
address: "https://teracloud.jp/dav/",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_unknown");
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://dav.jianguoyun.com/dav_example",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_unknown");
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "",
|
|
||||||
depth: "auto_unknown",
|
|
||||||
};
|
|
||||||
applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === "auto_unknown");
|
|
||||||
|
|
||||||
x = {
|
|
||||||
address: "https://dav.jianguoyun.com/dav/",
|
|
||||||
depth: "what" as any,
|
|
||||||
};
|
|
||||||
applyWebdavPresetRulesInplace(x);
|
|
||||||
expect(x.depth === ("what" as any));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
|
"strict": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|||||||
Reference in New Issue
Block a user