Compare commits

...
6 Commits
Author SHA1 Message Date
fyears 2930fe5350 0.1.9 2021-12-01 01:50:00 +08:00
fyears a9abf6d834 webpack is slow but works 2021-12-01 01:49:29 +08:00
fyears bdf09e24c3 remove assert 2021-12-01 01:49:07 +08:00
fyears 8dc4b835c9 0.1.8 2021-12-01 00:34:02 +08:00
fyears 2e800af4f8 dropbox should use sub folder 2021-12-01 00:33:24 +08:00
fyears e9e9e2b6eb fix typo 2021-11-30 00:44:26 +08:00
8 changed files with 154 additions and 29 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ As of November 2021, the plugin is considered in BETA stage. **DO NOT USE IT for
- **This plugin's function for Dropbox is not as mature as functions for S3.**
- **This plugin is NOT an official Dropbox product.** The plugin just uses Dropbox's public API.
- After the authorization, the plugin can read your name and email (which cannot be unselected on Dropbox api), and read and write files in your Dropbox's `/App/obsidian-remotely-save` folder.
- After the authorization, the plugin can read your name and email (which cannot be unselected on Dropbox api), and read and write files in your Dropbox's `/Apps/obsidian-remotely-save` folder.
- 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.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "obsidian-remotely-save",
"name": "Remotely Save",
"version": "0.1.7",
"version": "0.1.9",
"minAppVersion": "0.12.15",
"description": "Yet another unofficial plugin allowing users to sync notes between local device and the cloud service.",
"author": "fyears",
+6 -3
View File
@@ -1,10 +1,11 @@
{
"name": "obsidian-remotely-save",
"version": "0.1.7",
"version": "0.1.9",
"description": "This is yet another sync plugin for Obsidian app.",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"build2": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"build": "webpack --mode production",
"format": "npx prettier --write .",
"clean": "npx rimraf main.js",
"test": "cross-env TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\"} mocha -r ts-node/register 'tests/**/*.ts'"
@@ -39,7 +40,9 @@
"ts-node": "^10.4.0",
"tslib": "^2.2.0",
"typescript": "^4.4.4",
"webdav-server": "^2.6.2"
"webdav-server": "^2.6.2",
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.37.0",
+4
View File
@@ -110,6 +110,7 @@ export default class RemotelySavePlugin extends Plugin {
this.settings.s3,
this.settings.webdav,
this.settings.dropbox,
this.app.vault.getName(),
() => self.saveSettings()
);
const remoteRsp = await client.listFromRemote();
@@ -349,6 +350,7 @@ export class DropboxAuthModal extends Modal {
undefined,
undefined,
this.plugin.settings.dropbox,
this.app.vault.getName(),
() => self.plugin.saveSettings()
);
const username = await client.getUser();
@@ -626,6 +628,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
undefined,
undefined,
this.plugin.settings.dropbox,
this.app.vault.getName(),
() => self.plugin.saveSettings()
);
await client.revokeAuth();
@@ -687,6 +690,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
undefined,
undefined,
this.plugin.settings.dropbox,
this.app.vault.getName(),
() => self.plugin.saveSettings()
);
+6 -2
View File
@@ -19,6 +19,7 @@ export class RemoteClient {
s3Config?: s3.S3Config,
webdavConfig?: webdav.WebdavConfig,
dropboxConfig?: dropbox.DropboxConfig,
vaultName?: string,
saveUpdatedConfigFunc?: () => Promise<any>
) {
this.serviceType = serviceType;
@@ -31,12 +32,15 @@ export class RemoteClient {
this.webdavConfig = webdavConfig;
this.webdavClient = webdav.getWebdavClient(this.webdavConfig);
} else if (serviceType === "dropbox") {
if (saveUpdatedConfigFunc === undefined) {
throw Error("remember to provide callback while init dropbox client");
if (vaultName === undefined || saveUpdatedConfigFunc === undefined) {
throw Error(
"remember to provide vault name and callback while init dropbox client"
);
}
this.dropboxConfig = dropboxConfig;
this.dropboxClient = dropbox.getDropboxClient(
this.dropboxConfig,
vaultName,
saveUpdatedConfigFunc
);
} else {
+67 -21
View File
@@ -15,7 +15,6 @@ import {
setToString,
} from "./misc";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
import { strict as assert } from "assert";
export interface DropboxConfig {
accessToken: string;
@@ -37,10 +36,11 @@ export const DEFAULT_DROPBOX_CONFIG = {
username: "",
};
export const getDropboxPath = (fileOrFolderPath: string) => {
export const getDropboxPath = (fileOrFolderPath: string, vaultName: string) => {
let key = fileOrFolderPath;
if (!fileOrFolderPath.startsWith("/")) {
key = `/${fileOrFolderPath}`;
// then this is original path in Obsidian
key = `/${vaultName}/${fileOrFolderPath}`;
}
if (key.endsWith("/")) {
key = key.slice(0, key.length - 1);
@@ -48,20 +48,26 @@ export const getDropboxPath = (fileOrFolderPath: string) => {
return key;
};
const getNormPath = (fileOrFolderPath: string) => {
if (fileOrFolderPath.startsWith("/")) {
return fileOrFolderPath.slice(1);
const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
if (
!(
fileOrFolderPath === `/${vaultName}` ||
fileOrFolderPath.startsWith(`/${vaultName}/`)
)
) {
throw Error(`"${fileOrFolderPath}" doesn't starts with "/${vaultName}/"`);
}
return fileOrFolderPath;
return fileOrFolderPath.slice(`/${vaultName}/`.length);
};
const fromDropboxItemToRemoteItem = (
x:
| files.FileMetadataReference
| files.FolderMetadataReference
| files.DeletedMetadataReference
| files.DeletedMetadataReference,
vaultName: string
): RemoteItem => {
let key = getNormPath(x.path_display);
let key = getNormPath(x.path_display, vaultName);
if (x[".tag"] === "folder" && !key.endsWith("/")) {
key = `${key}/`;
}
@@ -139,7 +145,6 @@ const fixLastModifiedTimeInplace = (allFilesFolders: RemoteItem[]) => {
if (item.lastModified !== undefined) {
continue; // don't need to deal with it
}
assert(!(item.key in potentialMTime));
const parent = `${path.posix.dirname(item.key)}/`;
if (parent in potentialMTime) {
item.lastModified = potentialMTime[parent];
@@ -261,17 +266,23 @@ export const setConfigBySuccessfullAuthInplace = async (
export class WrappedDropboxClient {
dropboxConfig: DropboxConfig;
vaultName: string;
saveUpdatedConfigFunc: () => Promise<any>;
dropbox: Dropbox;
vaultFolderExists: boolean;
constructor(
dropboxConfig: DropboxConfig,
vaultName: string,
saveUpdatedConfigFunc: () => Promise<any>
) {
this.dropboxConfig = dropboxConfig;
this.vaultName = vaultName;
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
this.vaultFolderExists = false;
}
init = async () => {
// check token
if (
this.dropboxConfig.accessToken === "" ||
this.dropboxConfig.refreshToken === ""
@@ -303,6 +314,33 @@ export class WrappedDropboxClient {
accessToken: this.dropboxConfig.accessToken,
});
}
// check vault folder
// console.log(`checking remote has folder /${this.vaultName}`);
if (this.vaultFolderExists) {
// console.log(`already checked, /${this.vaultName} exist before`)
} else {
const res = await this.dropbox.filesListFolder({
path: "",
recursive: false,
});
for (const item of res.result.entries) {
if (item.path_display === `/${this.vaultName}`) {
this.vaultFolderExists = true;
break;
}
}
if (!this.vaultFolderExists) {
console.log(`remote does not have folder /${this.vaultName}`);
await this.dropbox.filesCreateFolderV2({
path: `/${this.vaultName}`,
});
console.log(`remote folder /${this.vaultName} created`);
} else {
// console.log(`remote folder /${this.vaultName} exists`);
}
}
return this.dropbox;
};
}
@@ -313,9 +351,14 @@ export class WrappedDropboxClient {
*/
export const getDropboxClient = (
dropboxConfig: DropboxConfig,
vaultName: string,
saveUpdatedConfigFunc: () => Promise<any>
) => {
return new WrappedDropboxClient(dropboxConfig, saveUpdatedConfigFunc);
return new WrappedDropboxClient(
dropboxConfig,
vaultName,
saveUpdatedConfigFunc
);
};
export const getRemoteMeta = async (
@@ -328,7 +371,7 @@ export const getRemoteMeta = async (
// we instead try to list files
// if no error occurs, we ensemble a fake result.
const rsp = await client.dropbox.filesListFolder({
path: "",
path: `/${client.vaultName}`,
recursive: false, // don't need to recursive here
});
if (rsp.status !== 200) {
@@ -343,7 +386,7 @@ export const getRemoteMeta = async (
} as RemoteItem;
}
const key = getDropboxPath(fileOrFolderPath);
const key = getDropboxPath(fileOrFolderPath, client.vaultName);
const rsp = await client.dropbox.filesGetMetadata({
path: key,
@@ -351,7 +394,7 @@ export const getRemoteMeta = async (
if (rsp.status !== 200) {
throw Error(JSON.stringify(rsp));
}
return fromDropboxItemToRemoteItem(rsp.result);
return fromDropboxItemToRemoteItem(rsp.result, client.vaultName);
};
export const uploadToRemote = async (
@@ -369,7 +412,7 @@ export const uploadToRemote = async (
if (password !== "") {
uploadFile = remoteEncryptedKey;
}
uploadFile = getDropboxPath(uploadFile);
uploadFile = getDropboxPath(uploadFile, client.vaultName);
const isFolder = fileOrFolderPath.endsWith("/");
@@ -425,7 +468,9 @@ export const uploadToRemote = async (
});
// we want to mark that parent folders are created
if (foldersCreatedBefore !== undefined) {
const dirs = getFolderLevels(uploadFile).map(getDropboxPath);
const dirs = getFolderLevels(uploadFile).map((x) =>
getDropboxPath(x, client.vaultName)
);
for (const dir of dirs) {
foldersCreatedBefore?.add(dir);
}
@@ -443,7 +488,7 @@ export const listFromRemote = async (
}
await client.init();
const res = await client.dropbox.filesListFolder({
path: "",
path: `/${client.vaultName}`,
recursive: true,
});
if (res.status !== 200) {
@@ -453,7 +498,8 @@ export const listFromRemote = async (
const contents = res.result.entries;
const unifiedContents = contents
.filter((x) => x[".tag"] !== "deleted")
.map(fromDropboxItemToRemoteItem);
.filter((x) => x.path_display !== `/${client.vaultName}`)
.map((x) => fromDropboxItemToRemoteItem(x, client.vaultName));
fixLastModifiedTimeInplace(unifiedContents);
return {
Contents: unifiedContents,
@@ -465,7 +511,7 @@ const downloadFromRemoteRaw = async (
fileOrFolderPath: string
) => {
await client.init();
const key = getDropboxPath(fileOrFolderPath);
const key = getDropboxPath(fileOrFolderPath, client.vaultName);
const rsp = await client.dropbox.filesDownload({
path: key,
});
@@ -505,7 +551,7 @@ export const downloadFromRemote = async (
if (password !== "") {
downloadFile = remoteEncryptedKey;
}
downloadFile = getDropboxPath(downloadFile);
downloadFile = getDropboxPath(downloadFile, client.vaultName);
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
let localContent = remoteContent;
if (password !== "") {
@@ -530,7 +576,7 @@ export const deleteFromRemote = async (
if (password !== "") {
remoteFileName = remoteEncryptedKey;
}
remoteFileName = getDropboxPath(remoteFileName);
remoteFileName = getDropboxPath(remoteFileName, client.vaultName);
await client.init();
try {
+1 -1
View File
@@ -1,3 +1,3 @@
{
"0.1.7": "0.12.15"
"0.1.9": "0.12.15"
}
+68
View File
@@ -0,0 +1,68 @@
const path = require("path");
const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
entry: "./src/main.ts",
target: "web",
output: {
filename: "main.js",
path: __dirname,
libraryTarget: "commonjs",
},
plugins: [
// Work around for Buffer is undefined:
// https://github.com/webpack/changelog-v5/issues/10
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
}),
new webpack.ProvidePlugin({
process: "process/browser",
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js"],
mainFields: ["browser", "module", "main"],
fallback: {
// assert: require.resolve("assert"),
// buffer: require.resolve("buffer/"),
// console: require.resolve("console-browserify"),
// constants: require.resolve("constants-browserify"),
crypto: require.resolve("crypto-browserify"),
// domain: require.resolve("domain-browser"),
// events: require.resolve("events"),
// http: require.resolve("stream-http"),
// https: require.resolve("https-browserify"),
// os: require.resolve("os-browserify/browser"),
path: require.resolve("path-browserify"),
// punycode: require.resolve("punycode"),
process: require.resolve("process/browser"),
// querystring: require.resolve("querystring-es3"),
stream: require.resolve("stream-browserify"),
// string_decoder: require.resolve("string_decoder"),
// sys: require.resolve("util"),
// timers: require.resolve("timers-browserify"),
// tty: require.resolve("tty-browserify"),
// url: require.resolve("url"),
// util: require.resolve("util"),
// vm: require.resolve("vm-browserify"),
// zlib: require.resolve("browserify-zlib"),
},
},
externals: {
obsidian: "commonjs2 obsidian",
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin({ extractComments: false })],
},
};