Compare commits

...
18 Commits
Author SHA1 Message Date
fyears 4edd8caaea bump to 0.3.12 2022-03-23 10:02:35 +08:00
fyears 005b698862 fix folder 2022-03-23 10:02:02 +08:00
fyears e76a5e4c12 add version desc 2022-03-23 10:01:08 +08:00
fyears ea3dcc6533 bump to 0.3.11 2022-03-22 00:56:29 +08:00
fyears c8bd10a855 fix typo 2022-03-22 00:55:44 +08:00
fyears 996074e431 bump to 0.3.10 2022-03-22 00:36:09 +08:00
fyears 275224cf73 full outout for error file 2022-03-22 00:35:18 +08:00
fyears 575a27db26 err msg stays longer 2022-03-22 00:31:17 +08:00
fyears 76127dd131 fix auto depth 2022-03-21 23:34:18 +08:00
fyears e2e6f6a12c a little help for webdav addr 2022-03-21 23:26:09 +08:00
fyears 4f3e8803da more verbose reason for conn fail 2022-03-21 23:04:56 +08:00
fyears 63a66d69f9 bump to 0.3.9 2022-03-20 23:43:48 +08:00
fyears 79fb1d90d0 Merge branch 'Victrid-master' 2022-03-20 23:37:45 +08:00
fyears ac4eff376d Merge branch 'master' of https://github.com/Victrid/remotely-save into Victrid-master 2022-03-20 23:37:26 +08:00
fyears 5512978dee add enc size compute 2022-03-20 23:01:32 +08:00
fyears 2bac0c32dc update lang link 2022-03-20 20:53:04 +08:00
fyears 1cd02913bb reduce size by using lucide not feather-icons 2022-03-20 20:35:09 +08:00
Victrid 5add7ad645 Add path style configurations for S3. 2022-03-04 23:24:48 +08:00
19 changed files with 241 additions and 85 deletions
+4 -4
View File
@@ -61,8 +61,8 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
- 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.
- About CORS: - About CORS:
- If you are using Obsidian >= 0.13.25, you can skip this CORS part. - If you are using Obsidian desktop >= 0.13.25 or mobile >= 1.1.1, you can skip this CORS part.
- If you are using Obsidian < 0.13.25, you need to configure (enable) [CORS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html) for requests from `app://obsidian.md` and `capacitor://localhost` and `http://localhost`, and add at least `ETag` into exposed headers. Full example is [here](./docs/s3_cors_configure.md). It's unfortunately required, because the plugin sends requests from a browser-like envirement. And those addresses are tested and found on desktop and ios and android. - If you are using Obsidian desktop < 0.13.25 or mobile < 1.1.1, you need to configure (enable) [CORS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html) for requests from `app://obsidian.md` and `capacitor://localhost` and `http://localhost`, and add at least `ETag` into exposed headers. Full example is [here](./docs/s3_cors_configure.md). It's unfortunately required, because the plugin sends requests from a browser-like envirement. And those addresses are tested and found on desktop and ios and android.
- Download and enable this plugin. - Download and enable this plugin.
- Enter your information to the settings of this plugin. - Enter your information to the settings of this plugin.
- If you want to enable end-to-end encryption, also set a password in settings. If you do not specify a password, the files and folders are synced in plain, original content to the cloud. - If you want to enable end-to-end encryption, also set a password in settings. If you do not specify a password, the files and folders are synced in plain, original content to the cloud.
@@ -87,8 +87,8 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
### webdav ### webdav
- About CORS: - About CORS:
- If you are using Obsidian >= 0.13.25, you can skip this CORS part. - If you are using Obsidian desktop >= 0.13.25 or mobile >= 1.1.1, you can skip this CORS part.
- If you are using Obsidian < 0.13.25: - If you are using Obsidian desktop < 0.13.25 or mobile < 1.1.1:
- The webdav server has to be enabled CORS for requests from `app://obsidian.md` and `capacitor://localhost` and `http://localhost`, **AND** all webdav HTTP methods, **AND** all webdav headers. These are required, because Obsidian mobile works like a browser and mobile plugins are limited by CORS policies unless under a upgraded Obsidian version. - The webdav server has to be enabled CORS for requests from `app://obsidian.md` and `capacitor://localhost` and `http://localhost`, **AND** all webdav HTTP methods, **AND** all webdav headers. These are required, because Obsidian mobile works like a browser and mobile plugins are limited by CORS policies unless under a upgraded Obsidian version.
- Popular software NextCloud, OwnCloud, `rclone serve webdav` do **NOT** enable CORS by default. If you are using any of them, you should evaluate the risk, and find a way to enable CORS, before using this plugin, or use a upgraded Obsidian version. - Popular software NextCloud, OwnCloud, `rclone serve webdav` do **NOT** enable CORS by default. If you are using any of them, you should evaluate the risk, and find a way to enable CORS, before using this plugin, or use a upgraded Obsidian version.
- The plugin is tested successfully under python package [`wsgidav` (version 4.0)](https://github.com/mar10/wsgidav). See [this issue](https://github.com/mar10/wsgidav/issues/239) for some details. - The plugin is tested successfully under python package [`wsgidav` (version 4.0)](https://github.com/mar10/wsgidav). See [this issue](https://github.com/mar10/wsgidav/issues/239) for some details.
+2 -2
View File
@@ -4,6 +4,6 @@ The plugin is developed for the browser environment. The "fake" browser behind t
[MDN has a doc about CORS.](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) [MDN has a doc about CORS.](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
From Obsidian version >= insider 0.13.25, Obsidian [provides a new API `requiestUrl`](https://forum.obsidian.md/t/obsidian-release-v0-13-25-insider-build/32701), that allows the plugin to fully bypass the CORS issue. As of Mar 2022, the latest public-released Obsidian desktop has supported this API, but the Obsidian mobile still stays in insider. From Obsidian desktop >= 0.13.25 or mobile >= 1.1.1, Obsidian [provides a new API `requiestUrl`](https://forum.obsidian.md/t/obsidian-release-v0-13-25-insider-build/32701), that allows the plugin to fully bypass the CORS issue. As of Mar 2022, the latest public-released Obsidian desktop has supported this API, but the Obsidian mobile still stays in insider.
For using this plugin in Obsidian version < 0.13.25, we need to configure the server side to return the header `Access-Control-Allow-Origin` allowing the origins `app://obsidian.md` and `capacitor://localhost` and `http://localhost`. Here is an example [configuration for Amazon S3](./s3_cors_configure.md). For using this plugin in Obsidian desktop < 0.13.25 or mobile < 1.1.1, we need to configure the server side to return the header `Access-Control-Allow-Origin` allowing the origins `app://obsidian.md` and `capacitor://localhost` and `http://localhost`. Here is an example [configuration for Amazon S3](./s3_cors_configure.md).
+1 -1
View File
@@ -1,6 +1,6 @@
# How To Configure S3 CORS Rules # How To Configure S3 CORS Rules
If you are using the latest version of this plugin AND Obsidian >= 0.13.25, you do not need to configure it any more. If you are using Obsidian < 0.13.25, you are required to configure the rules as following. If you are using the latest version of this plugin AND Obsidian desktop >= 0.13.25, mobile >= 1.1.1, you do not need to configure it any more. If you are using Obsidian desktop < 0.13.25, moble < 1.1.1, you are required to configure the rules as following.
Thanks to [@NAL100 in the Discussion](https://github.com/fyears/remotely-save/discussions/28). Thanks to [@NAL100 in the Discussion](https://github.com/fyears/remotely-save/discussions/28).
+35 -35
View File
@@ -2,41 +2,41 @@
Here is an overview of the connectability ("connectable" or "not connectable" or "in the plan" or "never") to some services by this plugin. Here is an overview of the connectability ("connectable" or "not connectable" or "in the plan" or "never") to some services by this plugin.
The plugin works under the browser environment in Obsidian, so CORS is an issue. Obsidian starts to provide a rich API `requestUrl` for version >= 0.13.25 to bypass the CORS issue. But if the users are still using an older version of Obsidian, they need to configure CORS on server. The plugin works under the browser environment in Obsidian, so CORS is an issue. Obsidian starts to provide a rich API `requestUrl` for desktop version >= 0.13.25, mobile >= 1.1.1 to bypass the CORS issue. But if the users are still using an older version of Obsidian, they need to configure CORS on server.
The list is for information purposes only. The list is for information purposes only.
| Service | Connectable | by S3 | by WebDAV | by other protocol | can bypass CORS issue in Obsidian>=0.13.25 | need CORS config in Obsidian<0.13.25 | | Service | Connectable | by S3 | by WebDAV | by other protocol | can bypass CORS issue in latest Obsidian | need CORS config in old Obsidian |
| ------------------------------------------------------------------------------------- | ----------- | ----- | --------- | ---------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------ | | ------------------------------------------------------------------------------------- | ----------- | ----- | --------- | ---------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------ |
| Amazon S3 | Yes | Yes | | | Yes | [CORS config needed.](./s3_cors_configure.md) | | Amazon S3 | Yes | Yes | | | Yes | [CORS config needed.](./s3_cors_configure.md) |
| Tencent Cloud - Cloud Object Storage (COS) 腾讯云对象存储 | Yes | Yes | | | Yes | CORS config needed. | | Tencent Cloud - Cloud Object Storage (COS) 腾讯云对象存储 | Yes | Yes | | | Yes | CORS config needed. |
| Alibaba Cloud - Object Storage Service 阿里云对象存储 | Yes | Yes | | | Yes | CORS config needed. | | Alibaba Cloud - Object Storage Service 阿里云对象存储 | Yes | Yes | | | Yes | CORS config needed. |
| Backblaze B2 Cloud Storage | Yes | Yes | | | Yes | Its CORS rules doesn't allow no-http(s) origins. | | Backblaze B2 Cloud Storage | Yes | Yes | | | Yes | Its CORS rules doesn't allow no-http(s) origins. |
| [Wasabi](https://wasabi.com) | ? | ? | | | | | | [Wasabi](https://wasabi.com) | ? | ? | | | | |
| [filebase](https://filebase.com/) | Yes | Yes | | | Yes | CORS config needed. | | [filebase](https://filebase.com/) | Yes | Yes | | | Yes | CORS config needed. |
| QingStor 青云 | ? | ? | | | | | | QingStor 青云 | ? | ? | | | | |
| [MinIO](https://min.io/) | ? | ? | | | | | | [MinIO](https://min.io/) | ? | ? | | | | |
| [WsgiDAV](https://github.com/mar10/wsgidav) | Yes | | Yes | | Yes | CORS rules can be set. | | [WsgiDAV](https://github.com/mar10/wsgidav) | Yes | | Yes | | Yes | CORS rules can be set. |
| [Nginx `ngx_http_dav_module`](http://nginx.org/en/docs/http/ngx_http_dav_module.html) | Yes? | | Yes? | | Yes? | ? | | [Nginx `ngx_http_dav_module`](http://nginx.org/en/docs/http/ngx_http_dav_module.html) | Yes? | | Yes? | | Yes? | ? |
| NextCloud | Yes? | | Yes? | | Yes? | No CORS config by default. | | NextCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
| OwnCloud | Yes? | | Yes? | | Yes? | No CORS config by default. | | OwnCloud | Yes? | | Yes? | | Yes? | No CORS config by default. |
| Seafile | Yes? | | Yes? | | Yes? | | | Seafile | Yes? | | Yes? | | Yes? | |
| `rclone serve webdav` | Yes | | Yes | | Yes | No CORS support. | | `rclone serve webdav` | Yes | | Yes | | Yes | No CORS support. |
| [Nutstore 坚果云](https://www.jianguoyun.com/) | Yes | | Yes | | Yes | No CORS support. | | [Nutstore 坚果云](https://www.jianguoyun.com/) | Yes | | Yes | | Yes | No CORS support. |
| [TeraCLOUD](https://teracloud.jp/en/) | Yes | | Yes | | Yes | No CORS support. | | [TeraCLOUD](https://teracloud.jp/en/) | Yes | | Yes | | Yes | No CORS support. |
| Dropbox | Yes | | | Yes | | | | Dropbox | Yes | | | Yes | | |
| OneDrive for personal | Yes | | | Yes | | | | OneDrive for personal | Yes | | | Yes | | |
| OneDrive for Business | In the plan | | | ? | | | | OneDrive for Business | In the plan | | | ? | | |
| Google Drive | In the plan | | | ? | | | | Google Drive | In the plan | | | ? | | |
| [Box](https://www.box.com/) | ? | | | May be possible but needs further development. | | | | [Box](https://www.box.com/) | ? | | | May be possible but needs further development. | | |
| Google Cloud Storage | ? | | | May be possible but needs further development. | | | | Google Cloud Storage | ? | | | May be possible but needs further development. | | |
| Microsoft Azure Blob Storage | ? | | | May be possible but needs further development. | | | | Microsoft Azure Blob Storage | ? | | | May be possible but needs further development. | | |
| [OpenStack Storage (Swift)](https://github.com/openstack/swift) | ? | | | May be possible but needs further development. | | | | [OpenStack Storage (Swift)](https://github.com/openstack/swift) | ? | | | May be possible but needs further development. | | |
| https://put.io/ | ? | | | ? | | | | https://put.io/ | ? | | | ? | | |
| Yandex Disk | ? | | | ? | | | | Yandex Disk | ? | | | ? | | |
| FTP / FTPS | Never | | | Technically never possible to be implemented. | | | | FTP / FTPS | Never | | | Technically never possible to be implemented. | | |
| SFTP | Never | | | Technically never possible to be implemented. | | | | SFTP | Never | | | Technically never possible to be implemented. | | |
| Jottacloud | No | | | No. It seems that no open api is available. | | | | Jottacloud | No | | | No. It seems that no open api is available. | | |
| Mega | Never | | | No. No js api is available. | | | | Mega | Never | | | No. No js api is available. | | |
| Git | Never | | | No. Technically very hard, if not impossible, to be implemented. | | | | Git | Never | | | No. Technically very hard, if not impossible, to be implemented. | | |
| | | | | | | | | | | | | | | |
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "remotely-save", "id": "remotely-save",
"name": "Remotely Save", "name": "Remotely Save",
"version": "0.3.8", "version": "0.3.12",
"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",
+2 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "remotely-save", "name": "remotely-save",
"version": "0.3.8", "version": "0.3.12",
"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", "dev2": "node esbuild.config.mjs",
@@ -26,7 +26,6 @@
"@microsoft/microsoft-graph-types": "^2.11.0", "@microsoft/microsoft-graph-types": "^2.11.0",
"@types/chai": "^4.2.22", "@types/chai": "^4.2.22",
"@types/chai-as-promised": "^7.1.4", "@types/chai-as-promised": "^7.1.4",
"@types/feather-icons": "^4.7.0",
"@types/jsdom": "^16.2.13", "@types/jsdom": "^16.2.13",
"@types/lodash": "^4.14.178", "@types/lodash": "^4.14.178",
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
@@ -69,11 +68,11 @@
"codemirror": "^5.63.1", "codemirror": "^5.63.1",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"dropbox": "^10.22.0", "dropbox": "^10.22.0",
"feather-icons": "^4.28.0",
"http-status-codes": "^2.2.0", "http-status-codes": "^2.2.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"loglevel": "^1.8.0", "loglevel": "^1.8.0",
"lucide": "^0.17.12",
"mime-types": "^2.1.33", "mime-types": "^2.1.33",
"mustache": "^4.2.0", "mustache": "^4.2.0",
"nanoid": "^3.1.30", "nanoid": "^3.1.30",
+1
View File
@@ -15,6 +15,7 @@ export interface S3Config {
s3BucketName: string; s3BucketName: string;
bypassCorsLocally?: boolean; bypassCorsLocally?: boolean;
partsConcurrency?: number; partsConcurrency?: number;
forcePathStyle?: boolean;
} }
export interface DropboxConfig { export interface DropboxConfig {
+20
View File
@@ -165,3 +165,23 @@ export const decryptBase64urlToString = async (
) )
); );
}; };
export const getSizeFromOrigToEnc = (x: number) => {
if (x < 0 || !Number.isInteger(x)) {
throw Error(`x=${x} is not a valid size`);
}
return (Math.floor(x / 16) + 1) * 16 + 16;
};
export const getSizeFromEncToOrig = (x: number) => {
if (x < 32 || !Number.isInteger(x)) {
throw Error(`${x} is not a valid size`);
}
if (x % 16 !== 0) {
throw Error(`${x} is not a valid encrypted file size`);
}
return {
minSize: ((x - 16) / 16 - 1) * 16,
maxSize: ((x - 16) / 16 - 1) * 16 + 15,
};
};
+17 -15
View File
@@ -1,7 +1,7 @@
import { Modal, Notice, Plugin, Setting, addIcon, setIcon } from "obsidian"; import { Modal, Notice, Plugin, Setting, addIcon, setIcon } from "obsidian";
import cloneDeep from "lodash/cloneDeep"; import cloneDeep from "lodash/cloneDeep";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import feather from "feather-icons"; import { createElement, RotateCcw, RefreshCcw } from "lucide";
import type { RemotelySavePluginSettings } from "./baseTypes"; import type { RemotelySavePluginSettings } from "./baseTypes";
import { import {
COMMAND_CALLBACK, COMMAND_CALLBACK,
@@ -76,14 +76,13 @@ type SyncTriggerSourceType = "manual" | "auto" | "dry" | "autoOnceInit";
const iconNameSyncWait = `remotely-save-sync-wait`; const iconNameSyncWait = `remotely-save-sync-wait`;
const iconNameSyncRunning = `remotely-save-sync-running`; const iconNameSyncRunning = `remotely-save-sync-running`;
const iconSvgSyncWait = feather.icons["rotate-ccw"].toSvg({
width: 100, const iconSvgSyncWait = createElement(RotateCcw);
height: 100, iconSvgSyncWait.setAttribute("width", "100");
}); iconSvgSyncWait.setAttribute("height", "100");
const iconSvgSyncRunning = feather.icons["refresh-ccw"].toSvg({ const iconSvgSyncRunning = createElement(RefreshCcw);
width: 100, iconSvgSyncRunning.setAttribute("width", "100");
height: 100, iconSvgSyncRunning.setAttribute("height", "100");
});
export default class RemotelySavePlugin extends Plugin { export default class RemotelySavePlugin extends Plugin {
settings: RemotelySavePluginSettings; settings: RemotelySavePluginSettings;
@@ -102,11 +101,11 @@ export default class RemotelySavePlugin extends Plugin {
return this.i18n.t(x, vars); return this.i18n.t(x, vars);
}; };
const getNotice = (x: string) => { const getNotice = (x: string, timeout?: number) => {
// only show notices in manual mode // only show notices in manual mode
// no notice in auto mode // no notice in auto mode
if (triggerSource === "manual" || triggerSource === "dry") { if (triggerSource === "manual" || triggerSource === "dry") {
new Notice(x); new Notice(x, timeout);
} }
}; };
if (this.syncStatus !== "idle") { if (this.syncStatus !== "idle") {
@@ -332,8 +331,8 @@ export default class RemotelySavePlugin extends Plugin {
}); });
log.info(msg); log.info(msg);
log.info(error); log.info(error);
getNotice(msg); getNotice(msg, 10 * 1000);
getNotice(error.message); getNotice(error.message, 10 * 1000);
this.syncStatus = "idle"; this.syncStatus = "idle";
if (this.syncRibbon !== undefined) { if (this.syncRibbon !== undefined) {
setIcon(this.syncRibbon, iconNameSyncWait); setIcon(this.syncRibbon, iconNameSyncWait);
@@ -345,8 +344,8 @@ export default class RemotelySavePlugin extends Plugin {
async onload() { async onload() {
log.info(`loading plugin ${this.manifest.id}`); log.info(`loading plugin ${this.manifest.id}`);
addIcon(iconNameSyncWait, iconSvgSyncWait); addIcon(iconNameSyncWait, iconSvgSyncWait.outerHTML);
addIcon(iconNameSyncRunning, iconSvgSyncRunning); addIcon(iconNameSyncRunning, iconSvgSyncRunning.outerHTML);
this.oauth2Info = { this.oauth2Info = {
verifier: "", verifier: "",
@@ -659,6 +658,9 @@ export default class RemotelySavePlugin extends Plugin {
if (this.settings.s3.partsConcurrency === undefined) { if (this.settings.s3.partsConcurrency === undefined) {
this.settings.s3.partsConcurrency = 20; this.settings.s3.partsConcurrency = 20;
} }
if (this.settings.s3.forcePathStyle === undefined) {
this.settings.s3.forcePathStyle = false;
}
} }
async saveSettings() { async saveSettings() {
+12 -5
View File
@@ -271,15 +271,22 @@ export class RemoteClient {
} }
}; };
checkConnectivity = async () => { checkConnectivity = async (callbackFunc?: any) => {
if (this.serviceType === "s3") { if (this.serviceType === "s3") {
return await s3.checkConnectivity(this.s3Client, this.s3Config); return await s3.checkConnectivity(
this.s3Client,
this.s3Config,
callbackFunc
);
} else if (this.serviceType === "webdav") { } else if (this.serviceType === "webdav") {
return await webdav.checkConnectivity(this.webdavClient); return await webdav.checkConnectivity(this.webdavClient, callbackFunc);
} else if (this.serviceType === "dropbox") { } else if (this.serviceType === "dropbox") {
return await dropbox.checkConnectivity(this.dropboxClient); return await dropbox.checkConnectivity(this.dropboxClient, callbackFunc);
} else if (this.serviceType === "onedrive") { } else if (this.serviceType === "onedrive") {
return await onedrive.checkConnectivity(this.onedriveClient); return await onedrive.checkConnectivity(
this.onedriveClient,
callbackFunc
);
} else { } else {
throw Error(`not supported service type ${this.serviceType}`); throw Error(`not supported service type ${this.serviceType}`);
} }
+8 -3
View File
@@ -636,7 +636,10 @@ export const deleteFromRemote = async (
} }
}; };
export const checkConnectivity = async (client: WrappedDropboxClient) => { export const checkConnectivity = async (
client: WrappedDropboxClient,
callbackFunc?: any
) => {
try { try {
const results = await getRemoteMeta(client, "/"); const results = await getRemoteMeta(client, "/");
if (results === undefined) { if (results === undefined) {
@@ -644,8 +647,10 @@ export const checkConnectivity = async (client: WrappedDropboxClient) => {
} }
return true; return true;
} catch (err) { } catch (err) {
console.error("dropbox connectivity error:"); log.debug(err);
console.error(err); if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
}; };
+8 -1
View File
@@ -866,11 +866,18 @@ export const deleteFromRemote = async (
await client.deleteJson(remoteFileName); await client.deleteJson(remoteFileName);
}; };
export const checkConnectivity = async (client: WrappedOnedriveClient) => { export const checkConnectivity = async (
client: WrappedOnedriveClient,
callbackFunc?: any
) => {
try { try {
const k = await getUserDisplayName(client); const k = await getUserDisplayName(client);
return k !== "<unknown display name>"; return k !== "<unknown display name>";
} catch (err) { } catch (err) {
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
}; };
+14 -1
View File
@@ -155,6 +155,7 @@ export const DEFAULT_S3_CONFIG = {
s3BucketName: "", s3BucketName: "",
bypassCorsLocally: true, bypassCorsLocally: true,
partsConcurrency: 20, partsConcurrency: 20,
forcePathStyle: false,
}; };
export type S3ObjectType = _Object; export type S3ObjectType = _Object;
@@ -192,6 +193,7 @@ export const getS3Client = (s3Config: S3Config) => {
const s3Client = new S3Client({ const s3Client = new S3Client({
region: s3Config.s3Region, region: s3Config.s3Region,
endpoint: endpoint, endpoint: endpoint,
forcePathStyle: s3Config.forcePathStyle,
credentials: { credentials: {
accessKeyId: s3Config.s3AccessKeyID, accessKeyId: s3Config.s3AccessKeyID,
secretAccessKey: s3Config.s3SecretAccessKey, secretAccessKey: s3Config.s3SecretAccessKey,
@@ -203,6 +205,7 @@ export const getS3Client = (s3Config: S3Config) => {
const s3Client = new S3Client({ const s3Client = new S3Client({
region: s3Config.s3Region, region: s3Config.s3Region,
endpoint: endpoint, endpoint: endpoint,
forcePathStyle: s3Config.forcePathStyle,
credentials: { credentials: {
accessKeyId: s3Config.s3AccessKeyID, accessKeyId: s3Config.s3AccessKeyID,
secretAccessKey: s3Config.s3SecretAccessKey, secretAccessKey: s3Config.s3SecretAccessKey,
@@ -495,7 +498,8 @@ export const deleteFromRemote = async (
*/ */
export const checkConnectivity = async ( export const checkConnectivity = async (
s3Client: S3Client, s3Client: S3Client,
s3Config: S3Config s3Config: S3Config,
callbackFunc?: any
) => { ) => {
try { try {
const results = await s3Client.send( const results = await s3Client.send(
@@ -506,10 +510,19 @@ export const checkConnectivity = async (
results.$metadata === undefined || results.$metadata === undefined ||
results.$metadata.httpStatusCode === undefined results.$metadata.httpStatusCode === undefined
) { ) {
const err = "results or $metadata or httStatusCode is undefined";
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
return results.$metadata.httpStatusCode === 200; return results.$metadata.httpStatusCode === 200;
} catch (err) { } catch (err) {
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
}; };
+28 -3
View File
@@ -204,7 +204,7 @@ export class WrappedWebdavClient {
if (this.vaultFolderExists) { if (this.vaultFolderExists) {
// pass // pass
} else { } else {
const res = await this.client.exists(`/${this.vaultName}`); const res = await this.client.exists(`/${this.vaultName}/`);
if (res) { if (res) {
// log.info("remote vault folder exits!"); // log.info("remote vault folder exits!");
this.vaultFolderExists = true; this.vaultFolderExists = true;
@@ -223,7 +223,7 @@ export class WrappedWebdavClient {
const res = await this.client.customRequest(`/${this.vaultName}`, { const res = await this.client.customRequest(`/${this.vaultName}`, {
method: "PROPFIND", method: "PROPFIND",
headers: { headers: {
Depth: "Infinity", Depth: "infinity",
}, },
responseType: "text", responseType: "text",
}); });
@@ -523,15 +523,40 @@ export const deleteFromRemote = async (
} }
}; };
export const checkConnectivity = async (client: WrappedWebdavClient) => { export const checkConnectivity = async (
client: WrappedWebdavClient,
callbackFunc?: any
) => {
if (
!(
client.webdavConfig.address.startsWith("http://") ||
client.webdavConfig.address.startsWith("https://")
)
) {
const err = "Error: the url should start with http(s):// but it does not!";
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false;
}
try { try {
await client.init(); await client.init();
const results = await getRemoteMeta(client, "/"); const results = await getRemoteMeta(client, "/");
if (results === undefined) { if (results === undefined) {
const err = "results is undefined";
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
return true; return true;
} catch (err) { } catch (err) {
log.debug(err);
if (callbackFunc !== undefined) {
callbackFunc(err);
}
return false; return false;
} }
}; };
+41 -4
View File
@@ -745,6 +745,27 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
}) })
); );
new Setting(s3Div)
.setName(t("settings_s3_urlstyle"))
.setDesc(t("settings_s3_urlstyle_desc"))
.addDropdown((dropdown) => {
dropdown.addOption(
"virtualHostedStyle",
"Virtual Hosted-Style (default)"
);
dropdown.addOption("pathStyle", "Path-Style");
dropdown
.setValue(
this.plugin.settings.s3.forcePathStyle
? "pathStyle"
: "virtualHostedStyle"
)
.onChange(async (val: string) => {
this.plugin.settings.s3.forcePathStyle = val === "pathStyle";
await this.plugin.saveSettings();
});
});
if (requireApiVersion(API_VER_REQURL)) { if (requireApiVersion(API_VER_REQURL)) {
new Setting(s3Div) new Setting(s3Div)
.setName(t("settings_s3_bypasscorslocally")) .setName(t("settings_s3_bypasscorslocally"))
@@ -805,11 +826,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
button.onClick(async () => { button.onClick(async () => {
new Notice(t("settings_checkonnectivity_checking")); new Notice(t("settings_checkonnectivity_checking"));
const client = new RemoteClient("s3", this.plugin.settings.s3); const client = new RemoteClient("s3", this.plugin.settings.s3);
const res = await client.checkConnectivity(); const errors = { msg: "" };
const res = await client.checkConnectivity((err: any) => {
errors.msg = err;
});
if (res) { if (res) {
new Notice(t("settings_s3_connect_succ")); new Notice(t("settings_s3_connect_succ"));
} else { } else {
new Notice(t("settings_s3_connect_fail")); new Notice(t("settings_s3_connect_fail"));
new Notice(errors.msg);
} }
}); });
}); });
@@ -938,11 +963,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
() => self.plugin.saveSettings() () => self.plugin.saveSettings()
); );
const res = await client.checkConnectivity(); const errors = { msg: "" };
const res = await client.checkConnectivity((err: any) => {
errors.msg = `${err}`;
});
if (res) { if (res) {
new Notice(t("settings_dropbox_connect_succ")); new Notice(t("settings_dropbox_connect_succ"));
} else { } else {
new Notice(t("settings_dropbox_connect_fail")); new Notice(t("settings_dropbox_connect_fail"));
new Notice(errors.msg);
} }
}); });
}); });
@@ -1053,11 +1082,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
() => self.plugin.saveSettings() () => self.plugin.saveSettings()
); );
const res = await client.checkConnectivity(); const errors = { msg: "" };
const res = await client.checkConnectivity((err: any) => {
errors.msg = `${err}`;
});
if (res) { if (res) {
new Notice(t("settings_onedrive_connect_succ")); new Notice(t("settings_onedrive_connect_succ"));
} else { } else {
new Notice(t("settings_onedrive_connect_fail")); new Notice(t("settings_onedrive_connect_fail"));
new Notice(errors.msg);
} }
}); });
}); });
@@ -1209,7 +1242,10 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
this.app.vault.getName(), this.app.vault.getName(),
() => self.plugin.saveSettings() () => self.plugin.saveSettings()
); );
const res = await client.checkConnectivity(); const errors = { msg: "" };
const res = await client.checkConnectivity((err: any) => {
errors.msg = `${err}`;
});
if (res) { if (res) {
new Notice(t("settings_webdav_connect_succ")); new Notice(t("settings_webdav_connect_succ"));
} else { } else {
@@ -1223,6 +1259,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
corsErrMsg: corsErrMsg, corsErrMsg: corsErrMsg,
}) })
); );
new Notice(errors.msg);
} }
}); });
}); });
+13 -5
View File
@@ -323,7 +323,7 @@ const ensembleMixedStates = async (
r = { r = {
key: entry.path, key: entry.path,
existLocal: true, existLocal: true,
mtimeLocal: Math.max(entry.stat.mtime, entry.stat.ctime), mtimeLocal: Math.max(entry.stat.mtime || 0, entry.stat.ctime || 0),
sizeLocal: entry.stat.size, sizeLocal: entry.stat.size,
}; };
} else if (entry instanceof TFolder) { } else if (entry instanceof TFolder) {
@@ -450,22 +450,30 @@ const assignOperationToFileInplace = (
// 0. find anything inconsistent // 0. find anything inconsistent
if (r.existLocal && (r.mtimeLocal === undefined || r.mtimeLocal <= 0)) { if (r.existLocal && (r.mtimeLocal === undefined || r.mtimeLocal <= 0)) {
throw Error( throw Error(
`Error: File ${r.key} has a last modified time <=0 or undefined in the local file system. It's abnormal and the plugin stops.` `Error: Abnormal last modified time locally: ${JSON.stringify(
r,
null,
2
)}`
); );
} }
if (r.existRemote && (r.mtimeRemote === undefined || r.mtimeRemote <= 0)) { if (r.existRemote && (r.mtimeRemote === undefined || r.mtimeRemote <= 0)) {
throw Error( throw Error(
`Error: File ${r.key} has a last modified time <=0 or undefined on the remote service. It's abnormal and the plugin stops.` `Error: Abnormal last modified time remotely: ${JSON.stringify(
r,
null,
2
)}`
); );
} }
if (r.deltimeLocal !== undefined && r.deltimeLocal <= 0) { if (r.deltimeLocal !== undefined && r.deltimeLocal <= 0) {
throw Error( throw Error(
`Error: File ${r.key} has a local deletion time <=0. It's abnormal and the plugin stops.` `Error: Abnormal deletion time locally: ${JSON.stringify(r, null, 2)}`
); );
} }
if (r.deltimeRemote !== undefined && r.deltimeRemote <= 0) { if (r.deltimeRemote !== undefined && r.deltimeRemote <= 0) {
throw Error( throw Error(
`Error: File ${r.key} has a remote deletion time <=0. It's abnormal and the plugin stops.` `Error: Abnormal deletion time remotely: ${JSON.stringify(r, null, 2)}`
); );
} }
+32
View File
@@ -8,6 +8,8 @@ import {
encryptArrayBuffer, encryptArrayBuffer,
encryptStringToBase32, encryptStringToBase32,
encryptStringToBase64url, encryptStringToBase64url,
getSizeFromEncToOrig,
getSizeFromOrigToEnc,
} from "../src/encrypt"; } from "../src/encrypt";
import { base64ToBase64url, bufferToArrayBuffer } from "../src/misc"; import { base64ToBase64url, bufferToArrayBuffer } from "../src/misc";
@@ -110,4 +112,34 @@ describe("Encryption tests", () => {
expect(Buffer.from(dec).equals(Buffer.from(opensslArrBuf))).to.be.true; expect(Buffer.from(dec).equals(Buffer.from(opensslArrBuf))).to.be.true;
}); });
it("should get size from origin to encrypted correctly", () => {
expect(() => getSizeFromOrigToEnc(-1)).to.throw();
expect(() => getSizeFromOrigToEnc(0.5)).to.throw();
expect(getSizeFromOrigToEnc(0)).equals(32);
expect(getSizeFromOrigToEnc(15)).equals(32);
expect(getSizeFromOrigToEnc(16)).equals(48);
expect(getSizeFromOrigToEnc(31)).equals(48);
expect(getSizeFromOrigToEnc(32)).equals(64);
expect(getSizeFromOrigToEnc(14787203)).equals(14787232);
});
it("should get size from encrypted to origin correctly", () => {
expect(() => getSizeFromEncToOrig(-1)).to.throw();
expect(() => getSizeFromEncToOrig(30)).to.throw();
expect(getSizeFromEncToOrig(32)).to.deep.equal({
minSize: 0,
maxSize: 15,
});
expect(getSizeFromEncToOrig(48)).to.deep.equal({
minSize: 16,
maxSize: 31,
});
expect(() => getSizeFromEncToOrig(14787231)).to.throw();
let { minSize, maxSize } = getSizeFromEncToOrig(14787232);
expect(minSize <= 14787203 && 14787203 <= maxSize).to.be.true;
});
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"0.3.2": "0.12.15", "0.3.2": "0.12.15",
"0.3.8": "0.13.21" "0.3.12": "0.13.21"
} }