Compare commits

...
15 Commits
Author SHA1 Message Date
fyears 07896f92a3 0.2.9 2022-01-05 10:05:24 +08:00
fyears 9eddfb02d6 encode config 2022-01-05 10:04:58 +08:00
fyears f0dd3bb5ef add button to copy url of dropbox 2022-01-05 09:30:07 +08:00
fyears b91c0e694c 0.2.8 2022-01-05 00:59:59 +08:00
fyears 84b03d2c2b add button to copy url 2022-01-05 00:58:59 +08:00
fyears 313eff6a14 log debug every sync file 2022-01-05 00:24:21 +08:00
fyears 8671ba4660 use loglevel everywhere 2022-01-05 00:23:49 +08:00
fyears 3b195f33c8 change some desc 2022-01-04 23:25:45 +08:00
fyears 65e18aa4b7 special folder for logo 2022-01-03 23:39:09 +08:00
fyears 2cedef8168 add diff sizes' logos 2022-01-03 23:37:43 +08:00
fyears 22bc74f4d7 statement that only support onedrive for personal currently 2022-01-03 20:35:19 +08:00
fyears 277793ccf4 download stats for 0.2.7 2022-01-03 01:07:41 +08:00
fyears 9a1b11b811 0.2.7 2022-01-03 01:04:45 +08:00
fyears 3d6746ed99 quick fix for onedrive path 2022-01-03 01:03:56 +08:00
fyears 2a797f8c40 force refresh version download 2022-01-01 23:58:25 +08:00
31 changed files with 420 additions and 92 deletions
+6 -3
View File
@@ -2,7 +2,9 @@
This is yet another unofficial sync plugin for Obsidian. If you like it or find it useful, please consider give it a [star ![GitHub Repo stars](https://img.shields.io/github/stars/fyears/remotely-save?style=social)](https://github.com/fyears/remotely-save) on Github. This is yet another unofficial sync plugin for Obsidian. If you like it or find it useful, please consider give it a [star ![GitHub Repo stars](https://img.shields.io/github/stars/fyears/remotely-save?style=social)](https://github.com/fyears/remotely-save) on Github.
[![BuildCI](https://github.com/fyears/remotely-save/actions/workflows/auto-build.yml/badge.svg)](https://github.com/fyears/remotely-save/actions/workflows/auto-build.yml) [![Latest release: 0.2.6)](https://img.shields.io/github/downloads/fyears/remotely-save/0.2.6/main.js)](https://github.com/fyears/remotely-save/releases) [![Previous release: 0.2.5)](https://img.shields.io/github/downloads/fyears/remotely-save/0.2.5/main.js)](https://github.com/fyears/remotely-save/releases) [![BuildCI](https://github.com/fyears/remotely-save/actions/workflows/auto-build.yml/badge.svg)](https://github.com/fyears/remotely-save/actions/workflows/auto-build.yml)
[![Latest release: 0.2.7)](https://img.shields.io/github/downloads/fyears/remotely-save/0.2.7/main.js?sort=semver)](https://github.com/fyears/remotely-save/releases) [![Old release: 0.2.6)](https://img.shields.io/github/downloads/fyears/remotely-save/0.2.6/main.js?sort=semver)](https://github.com/fyears/remotely-save/releases) [![Old release: 0.2.5)](https://img.shields.io/github/downloads/fyears/remotely-save/0.2.5/main.js)](https://github.com/fyears/remotely-save/releases)
## Disclaimer ## Disclaimer
@@ -17,7 +19,7 @@ As of Jan 2022, the plugin is considered in BETA stage. **DO NOT USE IT for any
- Supports: - Supports:
- Amazon S3 or S3-compatible - Amazon S3 or S3-compatible
- Dropbox - Dropbox
- OneDrive - OneDrive for personal
- Webdav - Webdav
- **Obsidiain Mobile supported.** Vaults can be synced across mobile and desktop devices with the cloud service as the "broker". - **Obsidiain Mobile supported.** Vaults can be synced across mobile and desktop devices with the cloud service as the "broker".
- **[End-to-end encryption](./docs/encryption.md) supported.** Files would be encrypted using openssl format before being sent to the cloud **if** user specify a password. - **[End-to-end encryption](./docs/encryption.md) supported.** Files would be encrypted using openssl format before being sent to the cloud **if** user specify a password.
@@ -70,10 +72,11 @@ Additionally, the plugin author may occasionally visit Obsidian official forum a
- If you decide to authorize this plugin to connect to Dropbox, please go to plugin's settings, and choose Dropbox then follow the instructions. - If you decide to authorize this plugin to connect to Dropbox, please go to plugin's settings, and choose Dropbox then follow the instructions.
- 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**.
### OneDrive ### OneDrive for personal
- **This plugin's function for OneDrive is not as mature as functions for S3.** - **This plugin's function for OneDrive is not as mature as functions for S3.**
- **This plugin is NOT an official Microsoft / OneDrive product.** The plugin just uses Microsoft's [OneDrive's public API](https://docs.microsoft.com/en-us/onedrive/developer/rest-api). - **This plugin is NOT an official Microsoft / OneDrive product.** The plugin just uses Microsoft's [OneDrive's public API](https://docs.microsoft.com/en-us/onedrive/developer/rest-api).
- This plugin only works for "OneDrive for personal", and not works for "OneDrive for Business" (yet). See [#11](https://github.com/fyears/remotely-save/issues/11) to further details.
- After the authorization, the plugin can read your name and email, and read and write files in your OneDrive's `/Apps/remotely-save` folder. - After the authorization, the plugin can read your name and email, and read and write files in your OneDrive's `/Apps/remotely-save` folder.
- If you decide to authorize this plugin to connect to OneDrive, please go to plugin's settings, and choose OneDrive then follow the instructions. - If you decide to authorize this plugin to connect to OneDrive, please go to plugin's settings, and choose OneDrive then follow the instructions.
- 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**.
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9bf1cc2403c3a32e7a6f5d5829bb065573922dc050ae2fdb8b3d34b4c87ecf5
size 8296
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6990a598379c457442345e50c3f056c4598a121523bc10980835c08fc7447a54
size 10119
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:432198e63fa9676541a41436917e025d1ea6af3772139c1dea450fcc0155cfd0
size 13054
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fe3b5b8bf9c32e57c0af366c455b11ae68eb9fb8a32f371c9c445b3e7e89cc02
size 1120
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f68df7bae94eb4c4cddfa6af47d7819d01a6482d4f6afe6ef3236f72f9adf15f
size 1523
+1
View File
@@ -0,0 +1 @@
This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/
+75
View File
@@ -0,0 +1,75 @@
# the logo
Some different sizes' logos are needed.
## license
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
## the process of creation
No professional designers are here. Thus the following steps involve many programer styles. :-)
1. use excalidraw and export png and svg.
```bash
# results
logo.excalidraw
logo.png
logo.svg
```
2. manually edit the `logo.svg` and make background transparent.
```bash
# results
logo-transparent.svg
```
3. use python library [`svgutils`](https://github.com/btel/svg_utils) to make a strictly square figure. The [doc](https://svgutils.readthedocs.io/en/latest/tutorials/composing_multipanel_figures.html) is very useful.
```python
from svgutils.compose import *
def get_standard_300x300(file_name):
fig = Figure(300, 300,
Panel(
SVG(file_name),
).move(-3, 12),
)
return fig
get_standard_300x300('logo-transparent.svg').save('300x300.svg')
# def get_other_size_from_standard(file_name, px):
# fig = Figure(px, px,
# Panel(
# SVG(file_name).scale(px/300.0),
# ).move(-3*px/300.0, 12*px/300.0),
# )
# return fig
# get_other_size_from_standard('logo.svg',256).save('256x256.svg')
```
```bash
# results
300x300.svg
```
4. use `inkscape` command line to get different sizes' `.png` files.
```bash
inkscape 300x300.svg -o 300x300.png
inkscape 300x300.svg -o 50x50.png -w 50 -h 50
inkscape 300x300.svg -o 64x64.png -w 64 -h 64
inkscape 300x300.svg -o 256x256.png -w 256 -h 256
```
```bash
# results
50x50.png
64x64.png
256x256.png
```
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:daedd262170200cb5e8cc288898b40892771b3962cf2bc17dc443f1c82a4aa94
size 12886
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"id": "remotely-save", "id": "remotely-save",
"name": "Remotely Save", "name": "Remotely Save",
"version": "0.2.6", "version": "0.2.9",
"minAppVersion": "0.12.15", "minAppVersion": "0.12.15",
"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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "remotely-save", "name": "remotely-save",
"version": "0.2.6", "version": "0.2.9",
"description": "This is yet another sync plugin for Obsidian app.", "description": "This is yet another sync plugin for Obsidian app.",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
@@ -62,6 +62,7 @@
"dropbox": "^10.22.0", "dropbox": "^10.22.0",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"loglevel": "^1.8.0",
"mime-types": "^2.1.33", "mime-types": "^2.1.33",
"obsidian": "^0.12.0", "obsidian": "^0.12.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
+1
View File
@@ -52,6 +52,7 @@ export interface RemotelySavePluginSettings {
onedrive: OnedriveConfig; onedrive: OnedriveConfig;
password: string; password: string;
serviceType: SUPPORTED_SERVICES_TYPE; serviceType: SUPPORTED_SERVICES_TYPE;
currLogLevel?: string;
} }
export interface RemoteItem { export interface RemoteItem {
+60
View File
@@ -0,0 +1,60 @@
import { base64, base64url } from "rfc4648";
import { reverseString } from "./misc";
import type { RemotelySavePluginSettings } from "./baseTypes";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
const DEFAULT_README: string =
"Do NOT modify this manually. It's generated automatically.";
interface MessyConfigType {
readme: string;
d: string;
}
/**
* this should accept the result after loadData();
*/
export const messyConfigToNormal = (
x: MessyConfigType | RemotelySavePluginSettings
): RemotelySavePluginSettings => {
log.debug("loading, original config on disk:");
log.debug(x);
if ("readme" in x && "d" in x) {
// we should decode
const y = JSON.parse(
(
base64url.parse(reverseString(x["d"]), {
out: Buffer.allocUnsafe as any,
loose: true,
}) as Buffer
).toString("utf-8")
);
log.debug("loading, parsed config is:");
log.debug(y);
return y;
} else {
// return as is
log.debug("loading, parsed config is the same");
return x;
}
};
/**
* this should accept the result of original config
*/
export const normalConfigToMessy = (x: RemotelySavePluginSettings) => {
const y = {
readme: DEFAULT_README,
d: reverseString(
base64url.stringify(Buffer.from(JSON.stringify(x), "utf-8"), {
pad: false,
})
),
};
log.debug("encoding, encoded config is:");
log.debug(y);
return y;
};
+5 -2
View File
@@ -5,11 +5,14 @@ import { readAllSyncPlanRecordTexts } from "./localdb";
import type { InternalDBs } from "./localdb"; import type { InternalDBs } from "./localdb";
import { mkdirpInVault } from "./misc"; import { mkdirpInVault } from "./misc";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
const DEFAULT_DEBUG_FOLDER = "_debug_remotely_save/"; const DEFAULT_DEBUG_FOLDER = "_debug_remotely_save/";
const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX = "sync_plans_hist_exported_on_"; const DEFAULT_SYNC_PLANS_HISTORY_FILE_PREFIX = "sync_plans_hist_exported_on_";
export const exportSyncPlansToFiles = async (db: InternalDBs, vault: Vault) => { export const exportSyncPlansToFiles = async (db: InternalDBs, vault: Vault) => {
console.log("exporting"); log.info("exporting");
await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault); await mkdirpInVault(DEFAULT_DEBUG_FOLDER, vault);
const records = await readAllSyncPlanRecordTexts(db); const records = await readAllSyncPlanRecordTexts(db);
let md = ""; let md = "";
@@ -25,5 +28,5 @@ export const exportSyncPlansToFiles = async (db: InternalDBs, vault: Vault) => {
await vault.create(filePath, md, { await vault.create(filePath, md, {
mtime: ts, mtime: ts,
}); });
console.log("finish exporting"); log.info("finish exporting");
}; };
+3
View File
@@ -1,6 +1,9 @@
import { base32, base64url } from "rfc4648"; import { base32, base64url } from "rfc4648";
import { bufferToArrayBuffer, hexStringToTypedArray } from "./misc"; import { bufferToArrayBuffer, hexStringToTypedArray } from "./misc";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
const DEFAULT_ITER = 20000; const DEFAULT_ITER = 20000;
// base32.stringify(Buffer.from('Salted__')) // base32.stringify(Buffer.from('Salted__'))
+4 -1
View File
@@ -7,6 +7,9 @@ import {
RemotelySavePluginSettings, RemotelySavePluginSettings,
} from "./baseTypes"; } from "./baseTypes";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export const exportQrCodeUri = async ( export const exportQrCodeUri = async (
settings: RemotelySavePluginSettings, settings: RemotelySavePluginSettings,
currentVaultName: string, currentVaultName: string,
@@ -19,7 +22,7 @@ export const exportQrCodeUri = async (
const vault = encodeURIComponent(currentVaultName); const vault = encodeURIComponent(currentVaultName);
const version = encodeURIComponent(pluginVersion); const version = encodeURIComponent(pluginVersion);
const rawUri = `obsidian://${COMMAND_URI}?func=settings&version=${version}&vault=${vault}&data=${data}`; const rawUri = `obsidian://${COMMAND_URI}?func=settings&version=${version}&vault=${vault}&data=${data}`;
// console.log(uri) // log.info(uri)
const imgUri = await QRCode.toDataURL(rawUri); const imgUri = await QRCode.toDataURL(rawUri);
return { return {
rawUri, rawUri,
+8 -5
View File
@@ -6,6 +6,9 @@ import type { SyncPlanType } from "./sync";
export type LocalForage = typeof localforage; export type LocalForage = typeof localforage;
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export const DEFAULT_DB_VERSION_NUMBER: number = 20211114; export const DEFAULT_DB_VERSION_NUMBER: number = 20211114;
export const DEFAULT_DB_NAME = "remotelysavedb"; export const DEFAULT_DB_NAME = "remotelysavedb";
export const DEFAULT_TBL_VERSION = "schemaversion"; export const DEFAULT_TBL_VERSION = "schemaversion";
@@ -78,7 +81,7 @@ export const prepareDBs = async () => {
await migrateDBs(db, originalVersion, DEFAULT_DB_VERSION_NUMBER); await migrateDBs(db, originalVersion, DEFAULT_DB_VERSION_NUMBER);
} }
console.log("db connected"); log.info("db connected");
return db; return db;
}; };
@@ -86,10 +89,10 @@ export const destroyDBs = async () => {
// await localforage.dropInstance({ // await localforage.dropInstance({
// name: DEFAULT_DB_NAME, // name: DEFAULT_DB_NAME,
// }); // });
// console.log("db deleted"); // log.info("db deleted");
const req = indexedDB.deleteDatabase(DEFAULT_DB_NAME); const req = indexedDB.deleteDatabase(DEFAULT_DB_NAME);
req.onsuccess = (event) => { req.onsuccess = (event) => {
console.log("db deleted"); log.info("db deleted");
}; };
req.onblocked = (event) => { req.onblocked = (event) => {
console.warn("trying to delete db but it was blocked"); console.warn("trying to delete db but it was blocked");
@@ -128,7 +131,7 @@ export const insertDeleteRecord = async (
db: InternalDBs, db: InternalDBs,
fileOrFolder: TAbstractFile fileOrFolder: TAbstractFile
) => { ) => {
// console.log(fileOrFolder); // log.info(fileOrFolder);
let k: FileFolderHistoryRecord; let k: FileFolderHistoryRecord;
if (fileOrFolder instanceof TFile) { if (fileOrFolder instanceof TFile) {
k = { k = {
@@ -165,7 +168,7 @@ export const insertRenameRecord = async (
fileOrFolder: TAbstractFile, fileOrFolder: TAbstractFile,
oldPath: string oldPath: string
) => { ) => {
// console.log(fileOrFolder); // log.info(fileOrFolder);
let k: FileFolderHistoryRecord; let k: FileFolderHistoryRecord;
if (fileOrFolder instanceof TFile) { if (fileOrFolder instanceof TFile) {
k = { k = {
+24 -13
View File
@@ -34,6 +34,10 @@ import { DEFAULT_WEBDAV_CONFIG } from "./remoteForWebdav";
import { RemotelySaveSettingTab } from "./settings"; import { RemotelySaveSettingTab } from "./settings";
import type { SyncStatusType } from "./sync"; import type { SyncStatusType } from "./sync";
import { doActualSync, getSyncPlan, isPasswordOk } from "./sync"; import { doActualSync, getSyncPlan, isPasswordOk } from "./sync";
import { messyConfigToNormal, normalConfigToMessy } from "./configPersist";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
const DEFAULT_SETTINGS: RemotelySavePluginSettings = { const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
s3: DEFAULT_S3_CONFIG, s3: DEFAULT_S3_CONFIG,
@@ -42,6 +46,7 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
onedrive: DEFAULT_ONEDRIVE_CONFIG, onedrive: DEFAULT_ONEDRIVE_CONFIG,
password: "", password: "",
serviceType: "s3", serviceType: "s3",
currLogLevel: "info",
}; };
interface OAuth2Info { interface OAuth2Info {
@@ -58,9 +63,10 @@ export default class RemotelySavePlugin extends Plugin {
db: InternalDBs; db: InternalDBs;
syncStatus: SyncStatusType; syncStatus: SyncStatusType;
oauth2Info: OAuth2Info; oauth2Info: OAuth2Info;
currLogLevel: string;
async onload() { async onload() {
console.log(`loading plugin ${this.manifest.id}`); log.info(`loading plugin ${this.manifest.id}`);
this.oauth2Info = { this.oauth2Info = {
verifier: "", verifier: "",
@@ -71,6 +77,11 @@ export default class RemotelySavePlugin extends Plugin {
}; // init }; // init
await this.loadSettings(); await this.loadSettings();
if (this.settings.currLogLevel !== undefined) {
log.setLevel(this.settings.currLogLevel as any);
}
await this.checkIfOauthExpires(); await this.checkIfOauthExpires();
await this.prepareDB(); await this.prepareDB();
@@ -275,7 +286,7 @@ export default class RemotelySavePlugin extends Plugin {
} }
try { try {
//console.log(`huh ${this.settings.password}`) //log.info(`huh ${this.settings.password}`)
new Notice( new Notice(
`1/7 Remotely Save Sync Preparing (${this.settings.serviceType})` `1/7 Remotely Save Sync Preparing (${this.settings.serviceType})`
); );
@@ -294,14 +305,14 @@ export default class RemotelySavePlugin extends Plugin {
() => self.saveSettings() () => self.saveSettings()
); );
const remoteRsp = await client.listFromRemote(); const remoteRsp = await client.listFromRemote();
// console.log(remoteRsp); // log.info(remoteRsp);
new Notice("3/7 Starting to fetch local meta data."); new Notice("3/7 Starting to fetch local meta data.");
this.syncStatus = "getting_local_meta"; this.syncStatus = "getting_local_meta";
const local = this.app.vault.getAllLoadedFiles(); const local = this.app.vault.getAllLoadedFiles();
const localHistory = await loadDeleteRenameHistoryTable(this.db); const localHistory = await loadDeleteRenameHistoryTable(this.db);
// console.log(local); // log.info(local);
// console.log(localHistory); // log.info(localHistory);
new Notice("4/7 Checking password correct or not."); new Notice("4/7 Checking password correct or not.");
this.syncStatus = "checking_password"; this.syncStatus = "checking_password";
@@ -324,7 +335,7 @@ export default class RemotelySavePlugin extends Plugin {
client.serviceType, client.serviceType,
this.settings.password this.settings.password
); );
console.log(syncPlan.mixedStates); // for debugging log.info(syncPlan.mixedStates); // for debugging
await insertSyncPlanRecord(this.db, syncPlan); await insertSyncPlanRecord(this.db, syncPlan);
// The operations above are read only and kind of safe. // The operations above are read only and kind of safe.
@@ -346,8 +357,8 @@ export default class RemotelySavePlugin extends Plugin {
this.syncStatus = "idle"; this.syncStatus = "idle";
} catch (error) { } catch (error) {
const msg = `Remotely Save error while ${this.syncStatus}`; const msg = `Remotely Save error while ${this.syncStatus}`;
console.log(msg); log.info(msg);
console.log(error); log.info(error);
new Notice(msg); new Notice(msg);
new Notice(error.message); new Notice(error.message);
this.syncStatus = "idle"; this.syncStatus = "idle";
@@ -357,16 +368,16 @@ export default class RemotelySavePlugin extends Plugin {
this.addSettingTab(new RemotelySaveSettingTab(this.app, this)); this.addSettingTab(new RemotelySaveSettingTab(this.app, this));
// this.registerDomEvent(document, "click", (evt: MouseEvent) => { // this.registerDomEvent(document, "click", (evt: MouseEvent) => {
// console.log("click", evt); // log.info("click", evt);
// }); // });
// this.registerInterval( // this.registerInterval(
// window.setInterval(() => console.log("setInterval"), 5 * 60 * 1000) // window.setInterval(() => log.info("setInterval"), 5 * 60 * 1000)
// ); // );
} }
onunload() { onunload() {
console.log(`unloading plugin ${this.manifest.id}`); log.info(`unloading plugin ${this.manifest.id}`);
this.destroyDBs(); this.destroyDBs();
} }
@@ -374,7 +385,7 @@ export default class RemotelySavePlugin extends Plugin {
this.settings = Object.assign( this.settings = Object.assign(
{}, {},
cloneDeep(DEFAULT_SETTINGS), cloneDeep(DEFAULT_SETTINGS),
await this.loadData() messyConfigToNormal(await this.loadData())
); );
if (this.settings.dropbox.clientID === "") { if (this.settings.dropbox.clientID === "") {
this.settings.dropbox.clientID = DEFAULT_SETTINGS.dropbox.clientID; this.settings.dropbox.clientID = DEFAULT_SETTINGS.dropbox.clientID;
@@ -388,7 +399,7 @@ export default class RemotelySavePlugin extends Plugin {
} }
async saveSettings() { async saveSettings() {
await this.saveData(this.settings); await this.saveData(normalConfigToMessy(this.settings));
} }
async checkIfOauthExpires() { async checkIfOauthExpires() {
+17 -5
View File
@@ -4,6 +4,9 @@ import * as path from "path";
import { base32, base64url } from "rfc4648"; import { base32, base64url } from "rfc4648";
import XRegExp from "xregexp"; import XRegExp from "xregexp";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
/** /**
* If any part of the file starts with '.' or '_' then it's a hidden file. * If any part of the file starts with '.' or '_' then it's a hidden file.
* @param item * @param item
@@ -13,7 +16,7 @@ import XRegExp from "xregexp";
export const isHiddenPath = (item: string, loose: boolean = true) => { export const isHiddenPath = (item: string, loose: boolean = true) => {
const k = path.posix.normalize(item); // TODO: only unix path now const k = path.posix.normalize(item); // TODO: only unix path now
const k2 = k.split("/"); // TODO: only unix path now const k2 = k.split("/"); // TODO: only unix path now
// console.log(k2) // log.info(k2)
for (const singlePart of k2) { for (const singlePart of k2) {
if (singlePart === "." || singlePart === ".." || singlePart === "") { if (singlePart === "." || singlePart === ".." || singlePart === "") {
continue; continue;
@@ -54,14 +57,14 @@ export const getFolderLevels = (x: string) => {
}; };
export const mkdirpInVault = async (thePath: string, vault: Vault) => { export const mkdirpInVault = async (thePath: string, vault: Vault) => {
// console.log(thePath); // log.info(thePath);
const foldersToBuild = getFolderLevels(thePath); const foldersToBuild = getFolderLevels(thePath);
// console.log(foldersToBuild); // log.info(foldersToBuild);
for (const folder of foldersToBuild) { for (const folder of foldersToBuild) {
const r = await vault.adapter.exists(folder); const r = await vault.adapter.exists(folder);
// console.log(r); // log.info(r);
if (!r) { if (!r) {
console.log(`mkdir ${folder}`); log.info(`mkdir ${folder}`);
await vault.adapter.mkdir(folder); await vault.adapter.mkdir(folder);
} }
} }
@@ -196,3 +199,12 @@ export const getRandomArrayBuffer = (byteLength: number) => {
const k = window.crypto.getRandomValues(new Uint8Array(byteLength)); const k = window.crypto.getRandomValues(new Uint8Array(byteLength));
return bufferToArrayBuffer(k); return bufferToArrayBuffer(k);
}; };
/**
* https://stackoverflow.com/questions/958908
* @param x
* @returns
*/
export const reverseString = (x: string) => {
return [...x].reverse().join("");
};
+3
View File
@@ -11,6 +11,9 @@ import * as onedrive from "./remoteForOnedrive";
import * as s3 from "./remoteForS3"; import * as s3 from "./remoteForS3";
import * as webdav from "./remoteForWebdav"; import * as webdav from "./remoteForWebdav";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export class RemoteClient { export class RemoteClient {
readonly serviceType: SUPPORTED_SERVICES_TYPE; readonly serviceType: SUPPORTED_SERVICES_TYPE;
readonly s3Client?: s3.S3Client; readonly s3Client?: s3.S3Client;
+13 -10
View File
@@ -12,6 +12,9 @@ import { bufferToArrayBuffer, getFolderLevels, mkdirpInVault } from "./misc";
export { Dropbox } from "dropbox"; export { Dropbox } from "dropbox";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
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,
@@ -208,7 +211,7 @@ export const sendRefreshTokenReq = async (
appKey: string, appKey: string,
refreshToken: string refreshToken: string
) => { ) => {
console.log("start auto getting refreshed Dropbox access token."); log.info("start auto getting refreshed Dropbox access token.");
const resp1 = await fetch("https://api.dropboxapi.com/oauth2/token", { const resp1 = await fetch("https://api.dropboxapi.com/oauth2/token", {
method: "POST", method: "POST",
body: new URLSearchParams({ body: new URLSearchParams({
@@ -218,7 +221,7 @@ export const sendRefreshTokenReq = async (
}), }),
}); });
const resp2 = (await resp1.json()) as DropboxSuccessAuthRes; const resp2 = (await resp1.json()) as DropboxSuccessAuthRes;
console.log("finish auto getting refreshed Dropbox access token."); log.info("finish auto getting refreshed Dropbox access token.");
return resp2; return resp2;
}; };
@@ -227,7 +230,7 @@ export const setConfigBySuccessfullAuthInplace = async (
authRes: DropboxSuccessAuthRes, authRes: DropboxSuccessAuthRes,
saveUpdatedConfigFunc: () => Promise<any> | undefined saveUpdatedConfigFunc: () => Promise<any> | undefined
) => { ) => {
console.log("start updating local info of Dropbox token"); log.info("start updating local info of Dropbox token");
config.accessToken = authRes.access_token; config.accessToken = authRes.access_token;
config.accessTokenExpiresInSeconds = parseInt(authRes.expires_in); config.accessTokenExpiresInSeconds = parseInt(authRes.expires_in);
@@ -249,7 +252,7 @@ export const setConfigBySuccessfullAuthInplace = async (
await saveUpdatedConfigFunc(); await saveUpdatedConfigFunc();
} }
console.log("finish updating local info of Dropbox token"); log.info("finish updating local info of Dropbox token");
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -308,9 +311,9 @@ export class WrappedDropboxClient {
} }
// check vault folder // check vault folder
// console.log(`checking remote has folder /${this.vaultName}`); // log.info(`checking remote has folder /${this.vaultName}`);
if (this.vaultFolderExists) { if (this.vaultFolderExists) {
// console.log(`already checked, /${this.vaultName} exist before`) // log.info(`already checked, /${this.vaultName} exist before`)
} else { } else {
const res = await this.dropbox.filesListFolder({ const res = await this.dropbox.filesListFolder({
path: "", path: "",
@@ -323,14 +326,14 @@ export class WrappedDropboxClient {
} }
} }
if (!this.vaultFolderExists) { if (!this.vaultFolderExists) {
console.log(`remote does not have folder /${this.vaultName}`); log.info(`remote does not have folder /${this.vaultName}`);
await this.dropbox.filesCreateFolderV2({ await this.dropbox.filesCreateFolderV2({
path: `/${this.vaultName}`, path: `/${this.vaultName}`,
}); });
console.log(`remote folder /${this.vaultName} created`); log.info(`remote folder /${this.vaultName} created`);
this.vaultFolderExists = true; this.vaultFolderExists = true;
} else { } else {
// console.log(`remote folder /${this.vaultName} exists`); // log.info(`remote folder /${this.vaultName} exists`);
} }
} }
@@ -489,7 +492,7 @@ export const listFromRemote = async (
if (res.status !== 200) { if (res.status !== 200) {
throw Error(JSON.stringify(res)); throw Error(JSON.stringify(res));
} }
// console.log(res); // log.info(res);
const contents = res.result.entries; const contents = res.result.entries;
const unifiedContents = contents const unifiedContents = contents
+54 -31
View File
@@ -28,6 +28,9 @@ import {
mkdirpInVault, mkdirpInVault,
} from "./misc"; } from "./misc";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
const SCOPES = ["User.Read", "Files.ReadWrite.AppFolder", "offline_access"]; const SCOPES = ["User.Read", "Files.ReadWrite.AppFolder", "offline_access"];
const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`; const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`;
@@ -117,8 +120,8 @@ export const sendAuthReq = async (
// code: authCode, // code: authCode,
// codeVerifier: verifier, // PKCE Code Verifier // codeVerifier: verifier, // PKCE Code Verifier
// }); // });
// console.log('authResponse') // log.info('authResponse')
// console.log(authResponse) // log.info(authResponse)
// return authResponse; // return authResponse;
// Because of the CORS problem, // Because of the CORS problem,
@@ -142,7 +145,7 @@ export const sendAuthReq = async (
}); });
const rsp2 = JSON.parse(rsp1); const rsp2 = JSON.parse(rsp1);
// console.log(rsp2); // log.info(rsp2);
if (rsp2.error !== undefined) { if (rsp2.error !== undefined) {
return rsp2 as AccessCodeResponseFailedType; return rsp2 as AccessCodeResponseFailedType;
@@ -171,7 +174,7 @@ export const sendRefreshTokenReq = async (
}); });
const rsp2 = JSON.parse(rsp1); const rsp2 = JSON.parse(rsp1);
// console.log(rsp2); // log.info(rsp2);
if (rsp2.error !== undefined) { if (rsp2.error !== undefined) {
return rsp2 as AccessCodeResponseFailedType; return rsp2 as AccessCodeResponseFailedType;
@@ -185,7 +188,7 @@ export const setConfigBySuccessfullAuthInplace = async (
authRes: AccessCodeResponseSuccessfulType, authRes: AccessCodeResponseSuccessfulType,
saveUpdatedConfigFunc: () => Promise<any> | undefined saveUpdatedConfigFunc: () => Promise<any> | undefined
) => { ) => {
console.log("start updating local info of OneDrive token"); log.info("start updating local info of OneDrive token");
config.accessToken = authRes.access_token; config.accessToken = authRes.access_token;
config.accessTokenExpiresAtTime = config.accessTokenExpiresAtTime =
Date.now() + authRes.expires_in - 5 * 60 * 1000; Date.now() + authRes.expires_in - 5 * 60 * 1000;
@@ -200,7 +203,7 @@ export const setConfigBySuccessfullAuthInplace = async (
await saveUpdatedConfigFunc(); await saveUpdatedConfigFunc();
} }
console.log("finish updating local info of Onedrive token"); log.info("finish updating local info of Onedrive token");
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@@ -245,19 +248,33 @@ const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
return fileOrFolderPath.slice(`${prefix}/`.length); return fileOrFolderPath.slice(`${prefix}/`.length);
}; };
const constructFromDriveItemToRemoteItemError = (x: DriveItem) => {
return `parentPath="${x.parentReference.path}", selfName="${x.name}"`;
};
const fromDriveItemToRemoteItem = ( const fromDriveItemToRemoteItem = (
x: DriveItem, x: DriveItem,
vaultName: string vaultName: string
): RemoteItem => { ): RemoteItem => {
let key = ""; let key = "";
const COMMON_PREFIX = `/drive/root:/Apps/remotely-save/${vaultName}`; // possible prefix:
const COMMON_PREFIX_OTHERS = `/drive/items/`; // pure english: /drive/root:/Apps/remotely-save/${vaultName}
if (`${x.parentReference.path}/${x.name}`.startsWith(COMMON_PREFIX)) { // or localized, e.g.: /drive/root:/应用/remotely-save/${vaultName}
key = `${x.parentReference.path}/${x.name}`.substring( const FIRST_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/remotely-save\//g;
COMMON_PREFIX.length + 1
); // another possibile prefix
} else if (x.parentReference.path.startsWith(COMMON_PREFIX_OTHERS)) { const SECOND_COMMON_PREFIX_RAW = `/drive/items/`;
const fullPathOriginal = `${x.parentReference.path}/${x.name}`;
const matchFirstPrefixRes = fullPathOriginal.match(FIRST_COMMON_PREFIX_REGEX);
if (
matchFirstPrefixRes !== null &&
fullPathOriginal.startsWith(`${matchFirstPrefixRes[0]}${vaultName}`)
) {
const foundPrefix = `${matchFirstPrefixRes[0]}${vaultName}`;
key = fullPathOriginal.substring(foundPrefix.length + 1);
} else if (x.parentReference.path.startsWith(SECOND_COMMON_PREFIX_RAW)) {
// it's something like // it's something like
// /drive/items/<some_id>!<another_id>:/${vaultName}/<subfolder> // /drive/items/<some_id>!<another_id>:/${vaultName}/<subfolder>
// with uri encoded! // with uri encoded!
@@ -270,14 +287,14 @@ const fromDriveItemToRemoteItem = (
key = x.name; key = x.name;
} else { } else {
throw Error( throw Error(
`we meet file/folder and do not know how to deal with it:\n${JSON.stringify( `we meet file/folder and do not know how to deal with it:\n${constructFromDriveItemToRemoteItemError(
x x
)}` )}`
); );
} }
} else { } else {
throw Error( throw Error(
`we meet file/folder and do not know how to deal with it:\n${JSON.stringify( `we meet file/folder and do not know how to deal with it:\n${constructFromDriveItemToRemoteItemError(
x x
)}` )}`
); );
@@ -338,7 +355,7 @@ class MyAuthProvider implements AuthenticationProvider {
this.onedriveConfig.accessTokenExpiresAtTime = this.onedriveConfig.accessTokenExpiresAtTime =
currentTs + r2.expires_in * 1000 - 60 * 2 * 1000; currentTs + r2.expires_in * 1000 - 60 * 2 * 1000;
await this.saveUpdatedConfigFunc(); await this.saveUpdatedConfigFunc();
console.log("Onedrive accessToken updated"); log.info("Onedrive accessToken updated");
return this.onedriveConfig.accessToken; return this.onedriveConfig.accessToken;
} }
}; };
@@ -374,26 +391,26 @@ export class WrappedOnedriveClient {
} }
// check vault folder // check vault folder
// console.log(`checking remote has folder /${this.vaultName}`); // log.info(`checking remote has folder /${this.vaultName}`);
if (this.vaultFolderExists) { if (this.vaultFolderExists) {
// console.log(`already checked, /${this.vaultName} exist before`) // log.info(`already checked, /${this.vaultName} exist before`)
} else { } else {
const k = await this.client.api("/drive/special/approot/children").get(); const k = await this.client.api("/drive/special/approot/children").get();
// console.log(k); // log.info(k);
this.vaultFolderExists = this.vaultFolderExists =
(k.value as DriveItem[]).filter((x) => x.name === this.vaultName) (k.value as DriveItem[]).filter((x) => x.name === this.vaultName)
.length > 0; .length > 0;
if (!this.vaultFolderExists) { if (!this.vaultFolderExists) {
console.log(`remote does not have folder /${this.vaultName}`); log.info(`remote does not have folder /${this.vaultName}`);
await this.client.api("/drive/special/approot/children").post({ await this.client.api("/drive/special/approot/children").post({
name: `${this.vaultName}`, name: `${this.vaultName}`,
folder: {}, folder: {},
"@microsoft.graph.conflictBehavior": "replace", "@microsoft.graph.conflictBehavior": "replace",
}); });
console.log(`remote folder /${this.vaultName} created`); log.info(`remote folder /${this.vaultName} created`);
this.vaultFolderExists = true; this.vaultFolderExists = true;
} else { } else {
// console.log(`remote folder /${this.vaultName} exists`); // log.info(`remote folder /${this.vaultName} exists`);
} }
} }
}; };
@@ -431,7 +448,7 @@ export const listFromRemote = async (
let res = await client.client let res = await client.client
.api(`/drive/special/approot:/${client.vaultName}:/delta`) .api(`/drive/special/approot:/${client.vaultName}:/delta`)
.get(); .get();
const driveItems = res.value as DriveItem[]; let driveItems = res.value as DriveItem[];
while (NEXT_LINK_KEY in res) { while (NEXT_LINK_KEY in res) {
res = await client.client.api(res[NEXT_LINK_KEY]).get(); res = await client.client.api(res[NEXT_LINK_KEY]).get();
@@ -444,6 +461,12 @@ export const listFromRemote = async (
await client.saveUpdatedConfigFunc(); await client.saveUpdatedConfigFunc();
} }
driveItems = driveItems.map((x) => {
const y = cloneDeep(x);
y.parentReference.path = y.parentReference.path.replace("/Apps", "/应用");
return y;
});
// unify everything to RemoteItem // unify everything to RemoteItem
const unifiedContents = driveItems const unifiedContents = driveItems
.map((x) => fromDriveItemToRemoteItem(x, client.vaultName)) .map((x) => fromDriveItemToRemoteItem(x, client.vaultName))
@@ -459,15 +482,15 @@ export const getRemoteMeta = async (
) => { ) => {
await client.init(); await client.init();
const remotePath = getOnedrivePath(fileOrFolderPath, client.vaultName); const remotePath = getOnedrivePath(fileOrFolderPath, client.vaultName);
// console.log(`remotePath=${remotePath}`); // log.info(`remotePath=${remotePath}`);
const rsp = await client.client const rsp = await client.client
.api(remotePath) .api(remotePath)
.select("cTag,eTag,fileSystemInfo,folder,file,name,parentReference,size") .select("cTag,eTag,fileSystemInfo,folder,file,name,parentReference,size")
.get(); .get();
// console.log(rsp); // log.info(rsp);
const driveItem = rsp as DriveItem; const driveItem = rsp as DriveItem;
const res = fromDriveItemToRemoteItem(driveItem, client.vaultName); const res = fromDriveItemToRemoteItem(driveItem, client.vaultName);
// console.log(res); // log.info(res);
return res; return res;
}; };
@@ -487,7 +510,7 @@ export const uploadToRemote = async (
uploadFile = remoteEncryptedKey; uploadFile = remoteEncryptedKey;
} }
uploadFile = getOnedrivePath(uploadFile, client.vaultName); uploadFile = getOnedrivePath(uploadFile, client.vaultName);
// console.log(`uploadFile=${uploadFile}`); // log.info(`uploadFile=${uploadFile}`);
const isFolder = fileOrFolderPath.endsWith("/"); const isFolder = fileOrFolderPath.endsWith("/");
@@ -547,7 +570,7 @@ export const uploadToRemote = async (
uploadEventHandlers: { uploadEventHandlers: {
progress: (range?: Range) => { progress: (range?: Range) => {
// Handle progress event // Handle progress event
// console.log( // log.info(
// `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}` // `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}`
// ); // );
}, },
@@ -555,7 +578,7 @@ export const uploadToRemote = async (
} as LargeFileUploadTaskOptions } as LargeFileUploadTaskOptions
); );
const uploadResult: UploadResult = await task.upload(); const uploadResult: UploadResult = await task.upload();
// console.log(uploadResult) // log.info(uploadResult)
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return res;
} }
@@ -574,7 +597,7 @@ export const uploadToRemote = async (
// so use LargeFileUploadTask instead of OneDriveLargeFileUploadTask // so use LargeFileUploadTask instead of OneDriveLargeFileUploadTask
const progress = (range?: Range) => { const progress = (range?: Range) => {
// Handle progress event // Handle progress event
// console.log( // log.info(
// `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}` // `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}`
// ); // );
}; };
@@ -611,7 +634,7 @@ export const uploadToRemote = async (
options options
); );
const uploadResult: UploadResult = await task.upload(); const uploadResult: UploadResult = await task.upload();
// console.log(uploadResult) // log.info(uploadResult)
const res = await getRemoteMeta(client, uploadFile); const res = await getRemoteMeta(client, uploadFile);
return res; return res;
} }
+4 -1
View File
@@ -25,6 +25,9 @@ import {
export { S3Client } from "@aws-sdk/client-s3"; export { S3Client } from "@aws-sdk/client-s3";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export const DEFAULT_S3_CONFIG = { export const DEFAULT_S3_CONFIG = {
s3Endpoint: "", s3Endpoint: "",
s3Region: "", s3Region: "",
@@ -150,7 +153,7 @@ export const uploadToRemote = async (
}, },
}); });
upload.on("httpUploadProgress", (progress) => { upload.on("httpUploadProgress", (progress) => {
// console.log(progress); // log.info(progress);
}); });
await upload.done(); await upload.done();
+12 -9
View File
@@ -7,6 +7,9 @@ import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
import { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc"; import { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc";
export type { WebDAVClient } from "webdav/web"; export type { WebDAVClient } from "webdav/web";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export const DEFAULT_WEBDAV_CONFIG = { export const DEFAULT_WEBDAV_CONFIG = {
address: "", address: "",
username: "", username: "",
@@ -82,7 +85,7 @@ export class WrappedWebdavClient {
: AuthType.Password, : AuthType.Password,
}); });
} else { } else {
console.log("no password"); log.info("no password");
this.client = createClient(this.webdavConfig.address); this.client = createClient(this.webdavConfig.address);
} }
} }
@@ -93,12 +96,12 @@ export class WrappedWebdavClient {
} else { } else {
const res = await this.client.exists(`/${this.vaultName}`); const res = await this.client.exists(`/${this.vaultName}`);
if (res) { if (res) {
// console.log("remote vault folder exits!"); // log.info("remote vault folder exits!");
this.vaultFolderExists = true; this.vaultFolderExists = true;
} else { } else {
console.log("remote vault folder not exists, creating"); log.info("remote vault folder not exists, creating");
await this.client.createDirectory(`/${this.vaultName}`); await this.client.createDirectory(`/${this.vaultName}`);
console.log("remote vault folder created!"); log.info("remote vault folder created!");
this.vaultFolderExists = true; this.vaultFolderExists = true;
} }
} }
@@ -118,7 +121,7 @@ export const getRemoteMeta = async (
) => { ) => {
await client.init(); await client.init();
const remotePath = getWebdavPath(fileOrFolderPath, client.vaultName); const remotePath = getWebdavPath(fileOrFolderPath, client.vaultName);
// console.log(`remotePath = ${remotePath}`); // log.info(`remotePath = ${remotePath}`);
const res = (await client.client.stat(remotePath, { const res = (await client.client.stat(remotePath, {
details: false, details: false,
})) as FileStat; })) as FileStat;
@@ -158,7 +161,7 @@ export const uploadToRemote = async (
await client.client.putFileContents(uploadFile, "", { await client.client.putFileContents(uploadFile, "", {
overwrite: true, overwrite: true,
onUploadProgress: (progress) => { onUploadProgress: (progress) => {
// console.log(`Uploaded ${progress.loaded} bytes of ${progress.total}`); // log.info(`Uploaded ${progress.loaded} bytes of ${progress.total}`);
}, },
}); });
@@ -180,7 +183,7 @@ export const uploadToRemote = async (
await client.client.putFileContents(uploadFile, remoteContent, { await client.client.putFileContents(uploadFile, remoteContent, {
overwrite: true, overwrite: true,
onUploadProgress: (progress) => { onUploadProgress: (progress) => {
console.log(`Uploaded ${progress.loaded} bytes of ${progress.total}`); log.info(`Uploaded ${progress.loaded} bytes of ${progress.total}`);
}, },
}); });
@@ -282,10 +285,10 @@ export const deleteFromRemote = async (
await client.init(); await client.init();
try { try {
await client.client.deleteFile(remoteFileName); await client.client.deleteFile(remoteFileName);
// console.log(`delete ${remoteFileName} succeeded`); // log.info(`delete ${remoteFileName} succeeded`);
} catch (err) { } catch (err) {
console.error("some error while deleting"); console.error("some error while deleting");
console.log(err); log.info(err);
} }
}; };
+66 -6
View File
@@ -20,6 +20,9 @@ import {
getAuthUrlAndVerifier as getAuthUrlAndVerifierOnedrive, getAuthUrlAndVerifier as getAuthUrlAndVerifierOnedrive,
} from "./remoteForOnedrive"; } from "./remoteForOnedrive";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
class PasswordModal extends Modal { class PasswordModal extends Modal {
plugin: RemotelySavePlugin; plugin: RemotelySavePlugin;
newPassword: string; newPassword: string;
@@ -117,6 +120,21 @@ class DropboxAuthModal extends Modal {
contentEl.createEl("p", { contentEl.createEl("p", {
text: "Finally you should be redirected to Obsidian.", text: "Finally you should be redirected to Obsidian.",
}); });
const div2 = contentEl.createDiv();
div2.createEl(
"button",
{
text: "Click to copy the auth url",
},
(el) => {
el.onclick = async () => {
await navigator.clipboard.writeText(authUrl);
new Notice("the auth url copied to clipboard!");
};
}
);
contentEl.createEl("p").createEl("a", { contentEl.createEl("p").createEl("a", {
href: authUrl, href: authUrl,
text: authUrl, text: authUrl,
@@ -157,12 +175,31 @@ export class OnedriveAuthModal extends Modal {
); );
this.plugin.oauth2Info.verifier = verifier; this.plugin.oauth2Info.verifier = verifier;
contentEl.createEl("p", {
text: "Currently only OneDrive for personal is supported. OneDrive for Business is NOT supported (yet).",
});
contentEl.createEl("p", { contentEl.createEl("p", {
text: "Visit the address in a browser, and follow the steps.", text: "Visit the address in a browser, and follow the steps.",
}); });
contentEl.createEl("p", { contentEl.createEl("p", {
text: "Finally you should be redirected to Obsidian.", text: "Finally you should be redirected to Obsidian.",
}); });
const div2 = contentEl.createDiv();
div2.createEl(
"button",
{
text: "Click to copy the auth url",
},
(el) => {
el.onclick = async () => {
await navigator.clipboard.writeText(authUrl);
new Notice("the auth url copied to clipboard!");
};
}
);
contentEl.createEl("p").createEl("a", { contentEl.createEl("p").createEl("a", {
href: authUrl, href: authUrl,
text: authUrl, text: authUrl,
@@ -365,7 +402,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
}); });
s3Div.createEl("p", { s3Div.createEl("p", {
text: "Disclaimer: The information is stored in PLAIN TEXT locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your bucket, please immediately delete the access key on your AWS (or other S3-service provider) settings.", text: "Disclaimer: The information is stored in locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your bucket, please immediately delete the access key on your AWS (or other S3-service provider) settings.",
cls: "s3-disclaimer", cls: "s3-disclaimer",
}); });
@@ -491,7 +528,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
cls: "dropbox-disclaimer", cls: "dropbox-disclaimer",
}); });
dropboxDiv.createEl("p", { dropboxDiv.createEl("p", {
text: "Disclaimer: The information is stored in PLAIN TEXT locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Dropbox, please immediately disconnect this app on https://www.dropbox.com/account/connected_apps .", text: "Disclaimer: The information is stored in locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Dropbox, please immediately disconnect this app on https://www.dropbox.com/account/connected_apps .",
cls: "dropbox-disclaimer", cls: "dropbox-disclaimer",
}); });
dropboxDiv.createEl("p", { dropboxDiv.createEl("p", {
@@ -614,13 +651,13 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
"onedrive-hide", "onedrive-hide",
this.plugin.settings.serviceType !== "onedrive" this.plugin.settings.serviceType !== "onedrive"
); );
onedriveDiv.createEl("h2", { text: "Remote For Onedrive" }); onedriveDiv.createEl("h2", { text: "Remote For Onedrive (for personal)" });
onedriveDiv.createEl("p", { onedriveDiv.createEl("p", {
text: "Disclaimer: This app is NOT an official Microsoft / Onedrive product.", text: "Disclaimer: This app is NOT an official Microsoft / Onedrive product.",
cls: "onedrive-disclaimer", cls: "onedrive-disclaimer",
}); });
onedriveDiv.createEl("p", { onedriveDiv.createEl("p", {
text: "Disclaimer: The information is stored in PLAIN TEXT locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Onedrive, please immediately disconnect this app on https://microsoft.com/consent .", text: "Disclaimer: The information is stored locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Onedrive, please immediately disconnect this app on https://microsoft.com/consent .",
cls: "onedrive-disclaimer", cls: "onedrive-disclaimer",
}); });
onedriveDiv.createEl("p", { onedriveDiv.createEl("p", {
@@ -629,6 +666,10 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
}/${this.app.vault.getName()} on your Onedrive.`, }/${this.app.vault.getName()} on your Onedrive.`,
}); });
onedriveDiv.createEl("p", {
text: "Currently only OneDrive for personal is supported. OneDrive for Business is NOT supported (yet).",
});
const onedriveSelectAuthDiv = onedriveDiv.createDiv(); const onedriveSelectAuthDiv = onedriveDiv.createDiv();
const onedriveAuthDiv = onedriveSelectAuthDiv.createDiv({ const onedriveAuthDiv = onedriveSelectAuthDiv.createDiv({
cls: "onedrive-auth-button-hide", cls: "onedrive-auth-button-hide",
@@ -724,7 +765,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
webdavDiv.createEl("h2", { text: "Remote For Webdav" }); webdavDiv.createEl("h2", { text: "Remote For Webdav" });
webdavDiv.createEl("p", { webdavDiv.createEl("p", {
text: "Disclaimer: The information is stored in PLAIN TEXT locally. Other malicious/harmful/faulty plugins may read the info. If you see any unintentional access to your webdav server, please immediately change the username and password.", text: "Disclaimer: The information is stored in locally. Other malicious/harmful/faulty plugins may read the info. If you see any unintentional access to your webdav server, please immediately change the username and password.",
cls: "webdav-disclaimer", cls: "webdav-disclaimer",
}); });
@@ -829,7 +870,7 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
dropdown.addOption("s3", "S3 or compatible"); dropdown.addOption("s3", "S3 or compatible");
dropdown.addOption("dropbox", "Dropbox"); dropdown.addOption("dropbox", "Dropbox");
dropdown.addOption("webdav", "Webdav (beta)"); dropdown.addOption("webdav", "Webdav (beta)");
dropdown.addOption("onedrive", "OneDrive (alpha)"); dropdown.addOption("onedrive", "OneDrive for personal (alpha)");
dropdown dropdown
.setValue(this.plugin.settings.serviceType) .setValue(this.plugin.settings.serviceType)
.onChange(async (val: SUPPORTED_SERVICES_TYPE) => { .onChange(async (val: SUPPORTED_SERVICES_TYPE) => {
@@ -886,6 +927,25 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
const debugDiv = containerEl.createEl("div"); const debugDiv = containerEl.createEl("div");
debugDiv.createEl("h2", { text: "Debug" }); debugDiv.createEl("h2", { text: "Debug" });
const setConsoleLogLevelDiv = debugDiv.createDiv("div");
new Setting(setConsoleLogLevelDiv)
.setName("alter console log level")
.setDesc(
'By default the log level is "info". You can change to "debug" to get verbose infomation in console.'
)
.addDropdown(async (dropdown) => {
dropdown.addOption("info", "info");
dropdown.addOption("debug", "debug");
dropdown
.setValue(this.plugin.settings.currLogLevel)
.onChange(async (val: string) => {
this.plugin.settings.currLogLevel = val;
log.setLevel(val as any);
await this.plugin.saveSettings();
log.info(`the log level is changed to ${val}`);
});
});
const syncPlanDiv = debugDiv.createEl("div"); const syncPlanDiv = debugDiv.createEl("div");
new Setting(syncPlanDiv) new Setting(syncPlanDiv)
.setName("export sync plans") .setName("export sync plans")
+7 -3
View File
@@ -16,6 +16,9 @@ import {
import { isHiddenPath, isVaildText, mkdirpInVault } from "./misc"; import { isHiddenPath, isVaildText, mkdirpInVault } from "./misc";
import { RemoteClient } from "./remote"; import { RemoteClient } from "./remote";
import * as origLog from "loglevel";
const log = origLog.getLogger("rs-default");
export type SyncStatusType = export type SyncStatusType =
| "idle" | "idle"
| "preparing" | "preparing"
@@ -573,16 +576,17 @@ export const doActualSync = async (
)) { )) {
const k2 = k as string; const k2 = k as string;
const v2 = v as FileOrFolderMixedState; const v2 = v as FileOrFolderMixedState;
log.debug(`start syncing "${k2}" with plan ${JSON.stringify(v2)}`);
await dispatchOperationToActual( await dispatchOperationToActual(
k as string, k2,
v as FileOrFolderMixedState, v2,
client, client,
db, db,
vault, vault,
password, password,
foldersCreatedBefore foldersCreatedBefore
); );
// console.log(`finished ${k}, with ${setToString(foldersCreatedBefore)}`); // log.info(`finished ${k}, with ${setToString(foldersCreatedBefore)}`);
} }
// await Promise.all( // await Promise.all(
// Object.entries(keyStates) // Object.entries(keyStates)
+35
View File
@@ -0,0 +1,35 @@
import * as chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { RemotelySavePluginSettings } from "../src/baseTypes";
import { messyConfigToNormal, normalConfigToMessy } from "../src/configPersist";
chai.use(chaiAsPromised);
const expect = chai.expect;
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
s3: {
s3AccessKeyID: "acc",
} as any,
webdav: {
address: "addr",
} as any,
dropbox: {
username: "测试中文",
} as any,
onedrive: {
username: "test 🍎 emoji",
} as any,
password: "password",
serviceType: "s3",
currLogLevel: "info",
};
describe("Config Persist tests", () => {
it("should encrypt go back and forth conrrectly", async () => {
const k = DEFAULT_SETTINGS;
const k2 = normalConfigToMessy(k);
const k3 = messyConfigToNormal(k2);
expect(k3).to.deep.equal(k);
});
});
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"0.2.6": "0.12.15" "0.2.9": "0.12.15"
} }