Compare commits

..
15 Commits
Author SHA1 Message Date
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
15 changed files with 198 additions and 42 deletions
+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.11",
"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.11",
"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;
} }
}; };
+27 -2
View File
@@ -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.11": "0.13.21"
} }