add remoteBaseDir support
This commit is contained in:
parent
b13bd8708b
commit
1013daa9fd
|
@ -7,6 +7,11 @@ import type { LangType, LangTypeAndAuto } from "./i18n";
|
||||||
|
|
||||||
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox" | "onedrive";
|
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox" | "onedrive";
|
||||||
|
|
||||||
|
export type SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR =
|
||||||
|
| "webdav"
|
||||||
|
| "dropbox"
|
||||||
|
| "onedrive";
|
||||||
|
|
||||||
export interface S3Config {
|
export interface S3Config {
|
||||||
s3Endpoint: string;
|
s3Endpoint: string;
|
||||||
s3Region: string;
|
s3Region: string;
|
||||||
|
@ -27,6 +32,7 @@ export interface DropboxConfig {
|
||||||
accountID: string;
|
accountID: string;
|
||||||
username: string;
|
username: string;
|
||||||
credentialsShouldBeDeletedAtTime?: number;
|
credentialsShouldBeDeletedAtTime?: number;
|
||||||
|
remoteBaseDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebdavAuthType = "digest" | "basic";
|
export type WebdavAuthType = "digest" | "basic";
|
||||||
|
@ -44,6 +50,7 @@ export interface WebdavConfig {
|
||||||
authType: WebdavAuthType;
|
authType: WebdavAuthType;
|
||||||
manualRecursive: boolean; // deprecated in 0.3.6, use depth
|
manualRecursive: boolean; // deprecated in 0.3.6, use depth
|
||||||
depth?: WebdavDepthType;
|
depth?: WebdavDepthType;
|
||||||
|
remoteBaseDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnedriveConfig {
|
export interface OnedriveConfig {
|
||||||
|
@ -56,6 +63,7 @@ export interface OnedriveConfig {
|
||||||
deltaLink: string;
|
deltaLink: string;
|
||||||
username: string;
|
username: string;
|
||||||
credentialsShouldBeDeletedAtTime?: number;
|
credentialsShouldBeDeletedAtTime?: number;
|
||||||
|
remoteBaseDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemotelySavePluginSettings {
|
export interface RemotelySavePluginSettings {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 230fee440e72736f7582372cbf9dc4ff648457de
|
Subproject commit c729c117e810fd6e01c52fe6af4f7c4764f19e48
|
|
@ -677,18 +677,27 @@ export default class RemotelySavePlugin extends Plugin {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
if (this.settings.dropbox.remoteBaseDir === undefined) {
|
||||||
|
this.settings.dropbox.remoteBaseDir = "";
|
||||||
|
}
|
||||||
if (this.settings.onedrive.clientID === "") {
|
if (this.settings.onedrive.clientID === "") {
|
||||||
this.settings.onedrive.clientID = DEFAULT_SETTINGS.onedrive.clientID;
|
this.settings.onedrive.clientID = DEFAULT_SETTINGS.onedrive.clientID;
|
||||||
}
|
}
|
||||||
if (this.settings.onedrive.authority === "") {
|
if (this.settings.onedrive.authority === "") {
|
||||||
this.settings.onedrive.authority = DEFAULT_SETTINGS.onedrive.authority;
|
this.settings.onedrive.authority = DEFAULT_SETTINGS.onedrive.authority;
|
||||||
}
|
}
|
||||||
|
if (this.settings.onedrive.remoteBaseDir === undefined) {
|
||||||
|
this.settings.onedrive.remoteBaseDir = "";
|
||||||
|
}
|
||||||
if (this.settings.webdav.manualRecursive === undefined) {
|
if (this.settings.webdav.manualRecursive === undefined) {
|
||||||
this.settings.webdav.manualRecursive = false;
|
this.settings.webdav.manualRecursive = false;
|
||||||
}
|
}
|
||||||
if (this.settings.webdav.depth === undefined) {
|
if (this.settings.webdav.depth === undefined) {
|
||||||
this.settings.webdav.depth = "auto_unknown";
|
this.settings.webdav.depth = "auto_unknown";
|
||||||
}
|
}
|
||||||
|
if (this.settings.webdav.remoteBaseDir === undefined) {
|
||||||
|
this.settings.webdav.remoteBaseDir = "";
|
||||||
|
}
|
||||||
if (this.settings.s3.partsConcurrency === undefined) {
|
if (this.settings.s3.partsConcurrency === undefined) {
|
||||||
this.settings.s3.partsConcurrency = 20;
|
this.settings.s3.partsConcurrency = 20;
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,3 +299,7 @@ export const atWhichLevel = (x: string) => {
|
||||||
}
|
}
|
||||||
return y.split("/").length;
|
return y.split("/").length;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const checkHasSpecialCharForDir = (x: string) => {
|
||||||
|
return /[?/\\]/.test(x);
|
||||||
|
};
|
||||||
|
|
|
@ -46,10 +46,11 @@ export class RemoteClient {
|
||||||
"remember to provide vault name and callback while init webdav client"
|
"remember to provide vault name and callback while init webdav client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const remoteBaseDir = webdavConfig.remoteBaseDir || vaultName;
|
||||||
this.webdavConfig = webdavConfig;
|
this.webdavConfig = webdavConfig;
|
||||||
this.webdavClient = webdav.getWebdavClient(
|
this.webdavClient = webdav.getWebdavClient(
|
||||||
this.webdavConfig,
|
this.webdavConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
} else if (serviceType === "dropbox") {
|
} else if (serviceType === "dropbox") {
|
||||||
|
@ -58,10 +59,11 @@ export class RemoteClient {
|
||||||
"remember to provide vault name and callback while init dropbox client"
|
"remember to provide vault name and callback while init dropbox client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const remoteBaseDir = dropboxConfig.remoteBaseDir || vaultName;
|
||||||
this.dropboxConfig = dropboxConfig;
|
this.dropboxConfig = dropboxConfig;
|
||||||
this.dropboxClient = dropbox.getDropboxClient(
|
this.dropboxClient = dropbox.getDropboxClient(
|
||||||
this.dropboxConfig,
|
this.dropboxConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
} else if (serviceType === "onedrive") {
|
} else if (serviceType === "onedrive") {
|
||||||
|
@ -70,10 +72,11 @@ export class RemoteClient {
|
||||||
"remember to provide vault name and callback while init onedrive client"
|
"remember to provide vault name and callback while init onedrive client"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const remoteBaseDir = onedriveConfig.remoteBaseDir || vaultName;
|
||||||
this.onedriveConfig = onedriveConfig;
|
this.onedriveConfig = onedriveConfig;
|
||||||
this.onedriveClient = onedrive.getOnedriveClient(
|
this.onedriveClient = onedrive.getOnedriveClient(
|
||||||
this.onedriveConfig,
|
this.onedriveConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -26,15 +26,18 @@ export const DEFAULT_DROPBOX_CONFIG: DropboxConfig = {
|
||||||
credentialsShouldBeDeletedAtTime: 0,
|
credentialsShouldBeDeletedAtTime: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDropboxPath = (fileOrFolderPath: string, vaultName: string) => {
|
export const getDropboxPath = (
|
||||||
|
fileOrFolderPath: string,
|
||||||
|
remoteBaseDir: string
|
||||||
|
) => {
|
||||||
let key = fileOrFolderPath;
|
let key = fileOrFolderPath;
|
||||||
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
||||||
// special
|
// special
|
||||||
key = `/${vaultName}`;
|
key = `/${remoteBaseDir}`;
|
||||||
}
|
}
|
||||||
if (!fileOrFolderPath.startsWith("/")) {
|
if (!fileOrFolderPath.startsWith("/")) {
|
||||||
// then this is original path in Obsidian
|
// then this is original path in Obsidian
|
||||||
key = `/${vaultName}/${fileOrFolderPath}`;
|
key = `/${remoteBaseDir}/${fileOrFolderPath}`;
|
||||||
}
|
}
|
||||||
if (key.endsWith("/")) {
|
if (key.endsWith("/")) {
|
||||||
key = key.slice(0, key.length - 1);
|
key = key.slice(0, key.length - 1);
|
||||||
|
@ -42,16 +45,18 @@ export const getDropboxPath = (fileOrFolderPath: string, vaultName: string) => {
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
|
const getNormPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
fileOrFolderPath === `/${vaultName}` ||
|
fileOrFolderPath === `/${remoteBaseDir}` ||
|
||||||
fileOrFolderPath.startsWith(`/${vaultName}/`)
|
fileOrFolderPath.startsWith(`/${remoteBaseDir}/`)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw Error(`"${fileOrFolderPath}" doesn't starts with "/${vaultName}/"`);
|
throw Error(
|
||||||
|
`"${fileOrFolderPath}" doesn't starts with "/${remoteBaseDir}/"`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return fileOrFolderPath.slice(`/${vaultName}/`.length);
|
return fileOrFolderPath.slice(`/${remoteBaseDir}/`.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromDropboxItemToRemoteItem = (
|
const fromDropboxItemToRemoteItem = (
|
||||||
|
@ -59,9 +64,9 @@ const fromDropboxItemToRemoteItem = (
|
||||||
| files.FileMetadataReference
|
| files.FileMetadataReference
|
||||||
| files.FolderMetadataReference
|
| files.FolderMetadataReference
|
||||||
| files.DeletedMetadataReference,
|
| files.DeletedMetadataReference,
|
||||||
vaultName: string
|
remoteBaseDir: string
|
||||||
): RemoteItem => {
|
): RemoteItem => {
|
||||||
let key = getNormPath(x.path_display, vaultName);
|
let key = getNormPath(x.path_display, remoteBaseDir);
|
||||||
if (x[".tag"] === "folder" && !key.endsWith("/")) {
|
if (x[".tag"] === "folder" && !key.endsWith("/")) {
|
||||||
key = `${key}/`;
|
key = `${key}/`;
|
||||||
}
|
}
|
||||||
|
@ -268,17 +273,17 @@ export const setConfigBySuccessfullAuthInplace = async (
|
||||||
|
|
||||||
export class WrappedDropboxClient {
|
export class WrappedDropboxClient {
|
||||||
dropboxConfig: DropboxConfig;
|
dropboxConfig: DropboxConfig;
|
||||||
vaultName: string;
|
remoteBaseDir: string;
|
||||||
saveUpdatedConfigFunc: () => Promise<any>;
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
dropbox: Dropbox;
|
dropbox: Dropbox;
|
||||||
vaultFolderExists: boolean;
|
vaultFolderExists: boolean;
|
||||||
constructor(
|
constructor(
|
||||||
dropboxConfig: DropboxConfig,
|
dropboxConfig: DropboxConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) {
|
) {
|
||||||
this.dropboxConfig = dropboxConfig;
|
this.dropboxConfig = dropboxConfig;
|
||||||
this.vaultName = vaultName;
|
this.remoteBaseDir = remoteBaseDir;
|
||||||
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
this.vaultFolderExists = false;
|
this.vaultFolderExists = false;
|
||||||
}
|
}
|
||||||
|
@ -318,29 +323,29 @@ export class WrappedDropboxClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check vault folder
|
// check vault folder
|
||||||
// log.info(`checking remote has folder /${this.vaultName}`);
|
// log.info(`checking remote has folder /${this.remoteBaseDir}`);
|
||||||
if (this.vaultFolderExists) {
|
if (this.vaultFolderExists) {
|
||||||
// log.info(`already checked, /${this.vaultName} exist before`)
|
// log.info(`already checked, /${this.remoteBaseDir} exist before`)
|
||||||
} else {
|
} else {
|
||||||
const res = await this.dropbox.filesListFolder({
|
const res = await this.dropbox.filesListFolder({
|
||||||
path: "",
|
path: "",
|
||||||
recursive: false,
|
recursive: false,
|
||||||
});
|
});
|
||||||
for (const item of res.result.entries) {
|
for (const item of res.result.entries) {
|
||||||
if (item.path_display === `/${this.vaultName}`) {
|
if (item.path_display === `/${this.remoteBaseDir}`) {
|
||||||
this.vaultFolderExists = true;
|
this.vaultFolderExists = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.vaultFolderExists) {
|
if (!this.vaultFolderExists) {
|
||||||
log.info(`remote does not have folder /${this.vaultName}`);
|
log.info(`remote does not have folder /${this.remoteBaseDir}`);
|
||||||
await this.dropbox.filesCreateFolderV2({
|
await this.dropbox.filesCreateFolderV2({
|
||||||
path: `/${this.vaultName}`,
|
path: `/${this.remoteBaseDir}`,
|
||||||
});
|
});
|
||||||
log.info(`remote folder /${this.vaultName} created`);
|
log.info(`remote folder /${this.remoteBaseDir} created`);
|
||||||
this.vaultFolderExists = true;
|
this.vaultFolderExists = true;
|
||||||
} else {
|
} else {
|
||||||
// log.info(`remote folder /${this.vaultName} exists`);
|
// log.info(`remote folder /${this.remoteBaseDir} exists`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,12 +359,12 @@ export class WrappedDropboxClient {
|
||||||
*/
|
*/
|
||||||
export const getDropboxClient = (
|
export const getDropboxClient = (
|
||||||
dropboxConfig: DropboxConfig,
|
dropboxConfig: DropboxConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) => {
|
) => {
|
||||||
return new WrappedDropboxClient(
|
return new WrappedDropboxClient(
|
||||||
dropboxConfig,
|
dropboxConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -374,7 +379,7 @@ export const getRemoteMeta = async (
|
||||||
// we instead try to list files
|
// we instead try to list files
|
||||||
// if no error occurs, we ensemble a fake result.
|
// if no error occurs, we ensemble a fake result.
|
||||||
const rsp = await client.dropbox.filesListFolder({
|
const rsp = await client.dropbox.filesListFolder({
|
||||||
path: `/${client.vaultName}`,
|
path: `/${client.remoteBaseDir}`,
|
||||||
recursive: false, // don't need to recursive here
|
recursive: false, // don't need to recursive here
|
||||||
});
|
});
|
||||||
if (rsp.status !== 200) {
|
if (rsp.status !== 200) {
|
||||||
|
@ -389,7 +394,7 @@ export const getRemoteMeta = async (
|
||||||
} as RemoteItem;
|
} as RemoteItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = getDropboxPath(fileOrFolderPath, client.vaultName);
|
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
|
|
||||||
const rsp = await client.dropbox.filesGetMetadata({
|
const rsp = await client.dropbox.filesGetMetadata({
|
||||||
path: key,
|
path: key,
|
||||||
|
@ -397,7 +402,7 @@ export const getRemoteMeta = async (
|
||||||
if (rsp.status !== 200) {
|
if (rsp.status !== 200) {
|
||||||
throw Error(JSON.stringify(rsp));
|
throw Error(JSON.stringify(rsp));
|
||||||
}
|
}
|
||||||
return fromDropboxItemToRemoteItem(rsp.result, client.vaultName);
|
return fromDropboxItemToRemoteItem(rsp.result, client.remoteBaseDir);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadToRemote = async (
|
export const uploadToRemote = async (
|
||||||
|
@ -417,7 +422,7 @@ export const uploadToRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
uploadFile = remoteEncryptedKey;
|
uploadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
uploadFile = getDropboxPath(uploadFile, client.vaultName);
|
uploadFile = getDropboxPath(uploadFile, client.remoteBaseDir);
|
||||||
|
|
||||||
const isFolder = fileOrFolderPath.endsWith("/");
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
|
@ -486,7 +491,7 @@ export const uploadToRemote = async (
|
||||||
// we want to mark that parent folders are created
|
// we want to mark that parent folders are created
|
||||||
if (foldersCreatedBefore !== undefined) {
|
if (foldersCreatedBefore !== undefined) {
|
||||||
const dirs = getFolderLevels(uploadFile).map((x) =>
|
const dirs = getFolderLevels(uploadFile).map((x) =>
|
||||||
getDropboxPath(x, client.vaultName)
|
getDropboxPath(x, client.remoteBaseDir)
|
||||||
);
|
);
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
foldersCreatedBefore?.add(dir);
|
foldersCreatedBefore?.add(dir);
|
||||||
|
@ -505,7 +510,7 @@ export const listFromRemote = async (
|
||||||
}
|
}
|
||||||
await client.init();
|
await client.init();
|
||||||
let res = await client.dropbox.filesListFolder({
|
let res = await client.dropbox.filesListFolder({
|
||||||
path: `/${client.vaultName}`,
|
path: `/${client.remoteBaseDir}`,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
include_deleted: false,
|
include_deleted: false,
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
|
@ -518,8 +523,8 @@ export const listFromRemote = async (
|
||||||
const contents = res.result.entries;
|
const contents = res.result.entries;
|
||||||
const unifiedContents = contents
|
const unifiedContents = contents
|
||||||
.filter((x) => x[".tag"] !== "deleted")
|
.filter((x) => x[".tag"] !== "deleted")
|
||||||
.filter((x) => x.path_display !== `/${client.vaultName}`)
|
.filter((x) => x.path_display !== `/${client.remoteBaseDir}`)
|
||||||
.map((x) => fromDropboxItemToRemoteItem(x, client.vaultName));
|
.map((x) => fromDropboxItemToRemoteItem(x, client.remoteBaseDir));
|
||||||
|
|
||||||
while (res.result.has_more) {
|
while (res.result.has_more) {
|
||||||
res = await client.dropbox.filesListFolderContinue({
|
res = await client.dropbox.filesListFolderContinue({
|
||||||
|
@ -532,8 +537,8 @@ export const listFromRemote = async (
|
||||||
const contents2 = res.result.entries;
|
const contents2 = res.result.entries;
|
||||||
const unifiedContents2 = contents2
|
const unifiedContents2 = contents2
|
||||||
.filter((x) => x[".tag"] !== "deleted")
|
.filter((x) => x[".tag"] !== "deleted")
|
||||||
.filter((x) => x.path_display !== `/${client.vaultName}`)
|
.filter((x) => x.path_display !== `/${client.remoteBaseDir}`)
|
||||||
.map((x) => fromDropboxItemToRemoteItem(x, client.vaultName));
|
.map((x) => fromDropboxItemToRemoteItem(x, client.remoteBaseDir));
|
||||||
unifiedContents.push(...unifiedContents2);
|
unifiedContents.push(...unifiedContents2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +554,7 @@ const downloadFromRemoteRaw = async (
|
||||||
fileOrFolderPath: string
|
fileOrFolderPath: string
|
||||||
) => {
|
) => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const key = getDropboxPath(fileOrFolderPath, client.vaultName);
|
const key = getDropboxPath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
const rsp = await client.dropbox.filesDownload({
|
const rsp = await client.dropbox.filesDownload({
|
||||||
path: key,
|
path: key,
|
||||||
});
|
});
|
||||||
|
@ -595,7 +600,7 @@ export const downloadFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
downloadFile = remoteEncryptedKey;
|
downloadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
downloadFile = getDropboxPath(downloadFile, client.vaultName);
|
downloadFile = getDropboxPath(downloadFile, client.remoteBaseDir);
|
||||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||||
let localContent = remoteContent;
|
let localContent = remoteContent;
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
|
@ -623,7 +628,7 @@ export const deleteFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
remoteFileName = remoteEncryptedKey;
|
remoteFileName = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
remoteFileName = getDropboxPath(remoteFileName, client.vaultName);
|
remoteFileName = getDropboxPath(remoteFileName, client.remoteBaseDir);
|
||||||
|
|
||||||
await client.init();
|
await client.init();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -204,9 +204,9 @@ export const setConfigBySuccessfullAuthInplace = async (
|
||||||
// Other usual common methods
|
// Other usual common methods
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const getOnedrivePath = (fileOrFolderPath: string, vaultName: string) => {
|
const getOnedrivePath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||||
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/special-folders-appfolder?view=odsp-graph-online
|
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/special-folders-appfolder?view=odsp-graph-online
|
||||||
const prefix = `/drive/special/approot:/${vaultName}`;
|
const prefix = `/drive/special/approot:/${remoteBaseDir}`;
|
||||||
if (fileOrFolderPath.startsWith(prefix)) {
|
if (fileOrFolderPath.startsWith(prefix)) {
|
||||||
// already transformed, return as is
|
// already transformed, return as is
|
||||||
return fileOrFolderPath;
|
return fileOrFolderPath;
|
||||||
|
@ -225,8 +225,8 @@ const getOnedrivePath = (fileOrFolderPath: string, vaultName: string) => {
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
|
const getNormPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||||
const prefix = `/drive/special/approot:/${vaultName}`;
|
const prefix = `/drive/special/approot:/${remoteBaseDir}`;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!(fileOrFolderPath === prefix || fileOrFolderPath.startsWith(`${prefix}/`))
|
!(fileOrFolderPath === prefix || fileOrFolderPath.startsWith(`${prefix}/`))
|
||||||
|
@ -248,16 +248,16 @@ const constructFromDriveItemToRemoteItemError = (x: DriveItem) => {
|
||||||
|
|
||||||
const fromDriveItemToRemoteItem = (
|
const fromDriveItemToRemoteItem = (
|
||||||
x: DriveItem,
|
x: DriveItem,
|
||||||
vaultName: string
|
remoteBaseDir: string
|
||||||
): RemoteItem => {
|
): RemoteItem => {
|
||||||
let key = "";
|
let key = "";
|
||||||
|
|
||||||
// possible prefix:
|
// possible prefix:
|
||||||
// pure english: /drive/root:/Apps/remotely-save/${vaultName}
|
// pure english: /drive/root:/Apps/remotely-save/${remoteBaseDir}
|
||||||
// or localized, e.g.: /drive/root:/应用/remotely-save/${vaultName}
|
// or localized, e.g.: /drive/root:/应用/remotely-save/${remoteBaseDir}
|
||||||
const FIRST_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/remotely-save\//g;
|
const FIRST_COMMON_PREFIX_REGEX = /^\/drive\/root:\/[^\/]+\/remotely-save\//g;
|
||||||
// or the root is absolute path /Livefolders,
|
// or the root is absolute path /Livefolders,
|
||||||
// e.g.: /Livefolders/应用/remotely-save/${vaultName}
|
// e.g.: /Livefolders/应用/remotely-save/${remoteBaseDir}
|
||||||
const SECOND_COMMON_PREFIX_REGEX = /^\/Livefolders\/[^\/]+\/remotely-save\//g;
|
const SECOND_COMMON_PREFIX_REGEX = /^\/Livefolders\/[^\/]+\/remotely-save\//g;
|
||||||
|
|
||||||
// another possibile prefix
|
// another possibile prefix
|
||||||
|
@ -270,26 +270,26 @@ const fromDriveItemToRemoteItem = (
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
matchFirstPrefixRes !== null &&
|
matchFirstPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchFirstPrefixRes[0]}${vaultName}`)
|
fullPathOriginal.startsWith(`${matchFirstPrefixRes[0]}${remoteBaseDir}`)
|
||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchFirstPrefixRes[0]}${vaultName}`;
|
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (
|
} else if (
|
||||||
matchSecondPrefixRes !== null &&
|
matchSecondPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${vaultName}`)
|
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${remoteBaseDir}`)
|
||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchSecondPrefixRes[0]}${vaultName}`;
|
const foundPrefix = `${matchSecondPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (x.parentReference.path.startsWith(THIRD_COMMON_PREFIX_RAW)) {
|
} else if (x.parentReference.path.startsWith(THIRD_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>:/${remoteBaseDir}/<subfolder>
|
||||||
// with uri encoded!
|
// with uri encoded!
|
||||||
const parPath = decodeURIComponent(x.parentReference.path);
|
const parPath = decodeURIComponent(x.parentReference.path);
|
||||||
key = parPath.substring(parPath.indexOf(":") + 1);
|
key = parPath.substring(parPath.indexOf(":") + 1);
|
||||||
if (key.startsWith(`/${vaultName}/`)) {
|
if (key.startsWith(`/${remoteBaseDir}/`)) {
|
||||||
key = key.substring(`/${vaultName}/`.length);
|
key = key.substring(`/${remoteBaseDir}/`.length);
|
||||||
key = `${key}/${x.name}`;
|
key = `${key}/${x.name}`;
|
||||||
} else if (key === `/${vaultName}`) {
|
} else if (key === `/${remoteBaseDir}`) {
|
||||||
key = x.name;
|
key = x.name;
|
||||||
} else {
|
} else {
|
||||||
throw Error(
|
throw Error(
|
||||||
|
@ -369,17 +369,17 @@ class MyAuthProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
export class WrappedOnedriveClient {
|
export class WrappedOnedriveClient {
|
||||||
onedriveConfig: OnedriveConfig;
|
onedriveConfig: OnedriveConfig;
|
||||||
vaultName: string;
|
remoteBaseDir: string;
|
||||||
vaultFolderExists: boolean;
|
vaultFolderExists: boolean;
|
||||||
authGetter: MyAuthProvider;
|
authGetter: MyAuthProvider;
|
||||||
saveUpdatedConfigFunc: () => Promise<any>;
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
constructor(
|
constructor(
|
||||||
onedriveConfig: OnedriveConfig,
|
onedriveConfig: OnedriveConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) {
|
) {
|
||||||
this.onedriveConfig = onedriveConfig;
|
this.onedriveConfig = onedriveConfig;
|
||||||
this.vaultName = vaultName;
|
this.remoteBaseDir = remoteBaseDir;
|
||||||
this.vaultFolderExists = false;
|
this.vaultFolderExists = false;
|
||||||
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
this.authGetter = new MyAuthProvider(onedriveConfig, saveUpdatedConfigFunc);
|
this.authGetter = new MyAuthProvider(onedriveConfig, saveUpdatedConfigFunc);
|
||||||
|
@ -395,26 +395,26 @@ export class WrappedOnedriveClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check vault folder
|
// check vault folder
|
||||||
// log.info(`checking remote has folder /${this.vaultName}`);
|
// log.info(`checking remote has folder /${this.remoteBaseDir}`);
|
||||||
if (this.vaultFolderExists) {
|
if (this.vaultFolderExists) {
|
||||||
// log.info(`already checked, /${this.vaultName} exist before`)
|
// log.info(`already checked, /${this.remoteBaseDir} exist before`)
|
||||||
} else {
|
} else {
|
||||||
const k = await this.getJson("/drive/special/approot/children");
|
const k = await this.getJson("/drive/special/approot/children");
|
||||||
log.debug(k);
|
log.debug(k);
|
||||||
this.vaultFolderExists =
|
this.vaultFolderExists =
|
||||||
(k.value as DriveItem[]).filter((x) => x.name === this.vaultName)
|
(k.value as DriveItem[]).filter((x) => x.name === this.remoteBaseDir)
|
||||||
.length > 0;
|
.length > 0;
|
||||||
if (!this.vaultFolderExists) {
|
if (!this.vaultFolderExists) {
|
||||||
log.info(`remote does not have folder /${this.vaultName}`);
|
log.info(`remote does not have folder /${this.remoteBaseDir}`);
|
||||||
await this.postJson("/drive/special/approot/children", {
|
await this.postJson("/drive/special/approot/children", {
|
||||||
name: `${this.vaultName}`,
|
name: `${this.remoteBaseDir}`,
|
||||||
folder: {},
|
folder: {},
|
||||||
"@microsoft.graph.conflictBehavior": "replace",
|
"@microsoft.graph.conflictBehavior": "replace",
|
||||||
});
|
});
|
||||||
log.info(`remote folder /${this.vaultName} created`);
|
log.info(`remote folder /${this.remoteBaseDir} created`);
|
||||||
this.vaultFolderExists = true;
|
this.vaultFolderExists = true;
|
||||||
} else {
|
} else {
|
||||||
// log.info(`remote folder /${this.vaultName} exists`);
|
// log.info(`remote folder /${this.remoteBaseDir} exists`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -576,12 +576,12 @@ export class WrappedOnedriveClient {
|
||||||
|
|
||||||
export const getOnedriveClient = (
|
export const getOnedriveClient = (
|
||||||
onedriveConfig: OnedriveConfig,
|
onedriveConfig: OnedriveConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) => {
|
) => {
|
||||||
return new WrappedOnedriveClient(
|
return new WrappedOnedriveClient(
|
||||||
onedriveConfig,
|
onedriveConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -605,7 +605,7 @@ export const listFromRemote = async (
|
||||||
const DELTA_LINK_KEY = "@odata.deltaLink";
|
const DELTA_LINK_KEY = "@odata.deltaLink";
|
||||||
|
|
||||||
let res = await client.getJson(
|
let res = await client.getJson(
|
||||||
`/drive/special/approot:/${client.vaultName}:/delta`
|
`/drive/special/approot:/${client.remoteBaseDir}:/delta`
|
||||||
);
|
);
|
||||||
let driveItems = res.value as DriveItem[];
|
let driveItems = res.value as DriveItem[];
|
||||||
|
|
||||||
|
@ -622,7 +622,7 @@ export const listFromRemote = async (
|
||||||
|
|
||||||
// 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.remoteBaseDir))
|
||||||
.filter((x) => x.key !== "/");
|
.filter((x) => x.key !== "/");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -635,14 +635,14 @@ export const getRemoteMeta = async (
|
||||||
fileOrFolderPath: string
|
fileOrFolderPath: string
|
||||||
) => {
|
) => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const remotePath = getOnedrivePath(fileOrFolderPath, client.vaultName);
|
const remotePath = getOnedrivePath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
// log.info(`remotePath=${remotePath}`);
|
// log.info(`remotePath=${remotePath}`);
|
||||||
const rsp = await client.getJson(
|
const rsp = await client.getJson(
|
||||||
`${remotePath}?$select=cTag,eTag,fileSystemInfo,folder,file,name,parentReference,size`
|
`${remotePath}?$select=cTag,eTag,fileSystemInfo,folder,file,name,parentReference,size`
|
||||||
);
|
);
|
||||||
// log.info(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.remoteBaseDir);
|
||||||
// log.info(res);
|
// log.info(res);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
@ -664,7 +664,7 @@ export const uploadToRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
uploadFile = remoteEncryptedKey;
|
uploadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
uploadFile = getOnedrivePath(uploadFile, client.vaultName);
|
uploadFile = getOnedrivePath(uploadFile, client.remoteBaseDir);
|
||||||
log.debug(`uploadFile=${uploadFile}`);
|
log.debug(`uploadFile=${uploadFile}`);
|
||||||
|
|
||||||
const isFolder = fileOrFolderPath.endsWith("/");
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
@ -751,7 +751,7 @@ export const uploadToRemote = async (
|
||||||
// ref: https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online
|
// ref: https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession?view=odsp-graph-online
|
||||||
|
|
||||||
// 1. create uploadSession
|
// 1. create uploadSession
|
||||||
// uploadFile already starts with /drive/special/approot:/${vaultName}
|
// uploadFile already starts with /drive/special/approot:/${remoteBaseDir}
|
||||||
const s: UploadSession = await client.postJson(
|
const s: UploadSession = await client.postJson(
|
||||||
`${uploadFile}:/createUploadSession`,
|
`${uploadFile}:/createUploadSession`,
|
||||||
{
|
{
|
||||||
|
@ -792,7 +792,7 @@ const downloadFromRemoteRaw = async (
|
||||||
fileOrFolderPath: string
|
fileOrFolderPath: string
|
||||||
): Promise<ArrayBuffer> => {
|
): Promise<ArrayBuffer> => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const key = getOnedrivePath(fileOrFolderPath, client.vaultName);
|
const key = getOnedrivePath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
const rsp = await client.getJson(
|
const rsp = await client.getJson(
|
||||||
`${key}?$select=@microsoft.graph.downloadUrl`
|
`${key}?$select=@microsoft.graph.downloadUrl`
|
||||||
);
|
);
|
||||||
|
@ -832,7 +832,7 @@ export const downloadFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
downloadFile = remoteEncryptedKey;
|
downloadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
downloadFile = getOnedrivePath(downloadFile, client.vaultName);
|
downloadFile = getOnedrivePath(downloadFile, client.remoteBaseDir);
|
||||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||||
let localContent = remoteContent;
|
let localContent = remoteContent;
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
|
@ -860,7 +860,7 @@ export const deleteFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
remoteFileName = remoteEncryptedKey;
|
remoteFileName = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
remoteFileName = getOnedrivePath(remoteFileName, client.vaultName);
|
remoteFileName = getOnedrivePath(remoteFileName, client.remoteBaseDir);
|
||||||
|
|
||||||
await client.init();
|
await client.init();
|
||||||
await client.deleteJson(remoteFileName);
|
await client.deleteJson(remoteFileName);
|
||||||
|
|
|
@ -127,37 +127,40 @@ export const DEFAULT_WEBDAV_CONFIG = {
|
||||||
authType: "basic",
|
authType: "basic",
|
||||||
manualRecursive: false,
|
manualRecursive: false,
|
||||||
depth: "auto_unknown",
|
depth: "auto_unknown",
|
||||||
|
remoteBaseDir: "",
|
||||||
} as WebdavConfig;
|
} as WebdavConfig;
|
||||||
|
|
||||||
const getWebdavPath = (fileOrFolderPath: string, vaultName: string) => {
|
const getWebdavPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||||
let key = fileOrFolderPath;
|
let key = fileOrFolderPath;
|
||||||
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
||||||
// special
|
// special
|
||||||
key = `/${vaultName}/`;
|
key = `/${remoteBaseDir}/`;
|
||||||
}
|
}
|
||||||
if (!fileOrFolderPath.startsWith("/")) {
|
if (!fileOrFolderPath.startsWith("/")) {
|
||||||
key = `/${vaultName}/${fileOrFolderPath}`;
|
key = `/${remoteBaseDir}/${fileOrFolderPath}`;
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
|
const getNormPath = (fileOrFolderPath: string, remoteBaseDir: string) => {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
fileOrFolderPath === `/${vaultName}` ||
|
fileOrFolderPath === `/${remoteBaseDir}` ||
|
||||||
fileOrFolderPath.startsWith(`/${vaultName}/`)
|
fileOrFolderPath.startsWith(`/${remoteBaseDir}/`)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw Error(`"${fileOrFolderPath}" doesn't starts with "/${vaultName}/"`);
|
throw Error(
|
||||||
|
`"${fileOrFolderPath}" doesn't starts with "/${remoteBaseDir}/"`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// if (fileOrFolderPath.startsWith("/")) {
|
// if (fileOrFolderPath.startsWith("/")) {
|
||||||
// return fileOrFolderPath.slice(1);
|
// return fileOrFolderPath.slice(1);
|
||||||
// }
|
// }
|
||||||
return fileOrFolderPath.slice(`/${vaultName}/`.length);
|
return fileOrFolderPath.slice(`/${remoteBaseDir}/`.length);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromWebdavItemToRemoteItem = (x: FileStat, vaultName: string) => {
|
const fromWebdavItemToRemoteItem = (x: FileStat, remoteBaseDir: string) => {
|
||||||
let key = getNormPath(x.filename, vaultName);
|
let key = getNormPath(x.filename, remoteBaseDir);
|
||||||
if (x.type === "directory" && !key.endsWith("/")) {
|
if (x.type === "directory" && !key.endsWith("/")) {
|
||||||
key = `${key}/`;
|
key = `${key}/`;
|
||||||
}
|
}
|
||||||
|
@ -172,17 +175,17 @@ const fromWebdavItemToRemoteItem = (x: FileStat, vaultName: string) => {
|
||||||
|
|
||||||
export class WrappedWebdavClient {
|
export class WrappedWebdavClient {
|
||||||
webdavConfig: WebdavConfig;
|
webdavConfig: WebdavConfig;
|
||||||
vaultName: string;
|
remoteBaseDir: string;
|
||||||
client: WebDAVClient;
|
client: WebDAVClient;
|
||||||
vaultFolderExists: boolean;
|
vaultFolderExists: boolean;
|
||||||
saveUpdatedConfigFunc: () => Promise<any>;
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
constructor(
|
constructor(
|
||||||
webdavConfig: WebdavConfig,
|
webdavConfig: WebdavConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) {
|
) {
|
||||||
this.webdavConfig = webdavConfig;
|
this.webdavConfig = webdavConfig;
|
||||||
this.vaultName = vaultName;
|
this.remoteBaseDir = remoteBaseDir;
|
||||||
this.vaultFolderExists = false;
|
this.vaultFolderExists = false;
|
||||||
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
}
|
}
|
||||||
|
@ -212,13 +215,13 @@ 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.remoteBaseDir}/`);
|
||||||
if (res) {
|
if (res) {
|
||||||
// log.info("remote vault folder exits!");
|
// log.info("remote vault folder exits!");
|
||||||
this.vaultFolderExists = true;
|
this.vaultFolderExists = true;
|
||||||
} else {
|
} else {
|
||||||
log.info("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.remoteBaseDir}/`);
|
||||||
log.info("remote vault folder created!");
|
log.info("remote vault folder created!");
|
||||||
this.vaultFolderExists = true;
|
this.vaultFolderExists = true;
|
||||||
}
|
}
|
||||||
|
@ -228,7 +231,7 @@ export class WrappedWebdavClient {
|
||||||
if (this.webdavConfig.depth === "auto_unknown") {
|
if (this.webdavConfig.depth === "auto_unknown") {
|
||||||
let testPassed = false;
|
let testPassed = false;
|
||||||
try {
|
try {
|
||||||
const res = await this.client.customRequest(`/${this.vaultName}/`, {
|
const res = await this.client.customRequest(`/${this.remoteBaseDir}/`, {
|
||||||
method: "PROPFIND",
|
method: "PROPFIND",
|
||||||
headers: {
|
headers: {
|
||||||
Depth: "infinity",
|
Depth: "infinity",
|
||||||
|
@ -247,13 +250,16 @@ export class WrappedWebdavClient {
|
||||||
}
|
}
|
||||||
if (!testPassed) {
|
if (!testPassed) {
|
||||||
try {
|
try {
|
||||||
const res = await this.client.customRequest(`/${this.vaultName}/`, {
|
const res = await this.client.customRequest(
|
||||||
method: "PROPFIND",
|
`/${this.remoteBaseDir}/`,
|
||||||
headers: {
|
{
|
||||||
Depth: "1",
|
method: "PROPFIND",
|
||||||
},
|
headers: {
|
||||||
responseType: "text",
|
Depth: "1",
|
||||||
});
|
},
|
||||||
|
responseType: "text",
|
||||||
|
}
|
||||||
|
);
|
||||||
testPassed = true;
|
testPassed = true;
|
||||||
this.webdavConfig.depth = "auto_1";
|
this.webdavConfig.depth = "auto_1";
|
||||||
this.webdavConfig.manualRecursive = true;
|
this.webdavConfig.manualRecursive = true;
|
||||||
|
@ -277,12 +283,12 @@ export class WrappedWebdavClient {
|
||||||
|
|
||||||
export const getWebdavClient = (
|
export const getWebdavClient = (
|
||||||
webdavConfig: WebdavConfig,
|
webdavConfig: WebdavConfig,
|
||||||
vaultName: string,
|
remoteBaseDir: string,
|
||||||
saveUpdatedConfigFunc: () => Promise<any>
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
) => {
|
) => {
|
||||||
return new WrappedWebdavClient(
|
return new WrappedWebdavClient(
|
||||||
webdavConfig,
|
webdavConfig,
|
||||||
vaultName,
|
remoteBaseDir,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -292,12 +298,12 @@ export const getRemoteMeta = async (
|
||||||
fileOrFolderPath: string
|
fileOrFolderPath: string
|
||||||
) => {
|
) => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const remotePath = getWebdavPath(fileOrFolderPath, client.vaultName);
|
const remotePath = getWebdavPath(fileOrFolderPath, client.remoteBaseDir);
|
||||||
// log.info(`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;
|
||||||
return fromWebdavItemToRemoteItem(res, client.vaultName);
|
return fromWebdavItemToRemoteItem(res, client.remoteBaseDir);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uploadToRemote = async (
|
export const uploadToRemote = async (
|
||||||
|
@ -315,7 +321,7 @@ export const uploadToRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
uploadFile = remoteEncryptedKey;
|
uploadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
uploadFile = getWebdavPath(uploadFile, client.vaultName);
|
uploadFile = getWebdavPath(uploadFile, client.remoteBaseDir);
|
||||||
|
|
||||||
const isFolder = fileOrFolderPath.endsWith("/");
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
|
@ -394,7 +400,7 @@ export const listFromRemote = async (
|
||||||
) {
|
) {
|
||||||
// the remote doesn't support infinity propfind,
|
// the remote doesn't support infinity propfind,
|
||||||
// we need to do a bfs here
|
// we need to do a bfs here
|
||||||
const q = new Queue([`/${client.vaultName}`]);
|
const q = new Queue([`/${client.remoteBaseDir}`]);
|
||||||
const CHUNK_SIZE = 10;
|
const CHUNK_SIZE = 10;
|
||||||
while (q.length > 0) {
|
while (q.length > 0) {
|
||||||
const itemsToFetch = [];
|
const itemsToFetch = [];
|
||||||
|
@ -429,7 +435,7 @@ export const listFromRemote = async (
|
||||||
} else {
|
} else {
|
||||||
// the remote supports infinity propfind
|
// the remote supports infinity propfind
|
||||||
contents = (await client.client.getDirectoryContents(
|
contents = (await client.client.getDirectoryContents(
|
||||||
`/${client.vaultName}`,
|
`/${client.remoteBaseDir}`,
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
details: false /* no need for verbose details here */,
|
details: false /* no need for verbose details here */,
|
||||||
|
@ -442,7 +448,7 @@ export const listFromRemote = async (
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
Contents: contents.map((x) =>
|
Contents: contents.map((x) =>
|
||||||
fromWebdavItemToRemoteItem(x, client.vaultName)
|
fromWebdavItemToRemoteItem(x, client.remoteBaseDir)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -453,7 +459,7 @@ const downloadFromRemoteRaw = async (
|
||||||
) => {
|
) => {
|
||||||
await client.init();
|
await client.init();
|
||||||
const buff = (await client.client.getFileContents(
|
const buff = (await client.client.getFileContents(
|
||||||
getWebdavPath(fileOrFolderPath, client.vaultName)
|
getWebdavPath(fileOrFolderPath, client.remoteBaseDir)
|
||||||
)) as BufferLike;
|
)) as BufferLike;
|
||||||
if (buff instanceof ArrayBuffer) {
|
if (buff instanceof ArrayBuffer) {
|
||||||
return buff;
|
return buff;
|
||||||
|
@ -492,7 +498,7 @@ export const downloadFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
downloadFile = remoteEncryptedKey;
|
downloadFile = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
downloadFile = getWebdavPath(downloadFile, client.vaultName);
|
downloadFile = getWebdavPath(downloadFile, client.remoteBaseDir);
|
||||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||||
let localContent = remoteContent;
|
let localContent = remoteContent;
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
|
@ -520,7 +526,7 @@ export const deleteFromRemote = async (
|
||||||
if (password !== "") {
|
if (password !== "") {
|
||||||
remoteFileName = remoteEncryptedKey;
|
remoteFileName = remoteEncryptedKey;
|
||||||
}
|
}
|
||||||
remoteFileName = getWebdavPath(remoteFileName, client.vaultName);
|
remoteFileName = getWebdavPath(remoteFileName, client.remoteBaseDir);
|
||||||
|
|
||||||
await client.init();
|
await client.init();
|
||||||
try {
|
try {
|
||||||
|
|
182
src/settings.ts
182
src/settings.ts
|
@ -10,6 +10,7 @@ import {
|
||||||
import {
|
import {
|
||||||
API_VER_REQURL,
|
API_VER_REQURL,
|
||||||
SUPPORTED_SERVICES_TYPE,
|
SUPPORTED_SERVICES_TYPE,
|
||||||
|
SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR,
|
||||||
WebdavAuthType,
|
WebdavAuthType,
|
||||||
WebdavDepthType,
|
WebdavDepthType,
|
||||||
} from "./baseTypes";
|
} from "./baseTypes";
|
||||||
|
@ -36,6 +37,7 @@ import { messyConfigToNormal } from "./configPersist";
|
||||||
import type { TransItemType } from "./i18n";
|
import type { TransItemType } from "./i18n";
|
||||||
|
|
||||||
import * as origLog from "loglevel";
|
import * as origLog from "loglevel";
|
||||||
|
import { checkHasSpecialCharForDir } from "./misc";
|
||||||
const log = origLog.getLogger("rs-default");
|
const log = origLog.getLogger("rs-default");
|
||||||
|
|
||||||
class PasswordModal extends Modal {
|
class PasswordModal extends Modal {
|
||||||
|
@ -108,6 +110,100 @@ class PasswordModal extends Modal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ChangeRemoteBaseDirModal extends Modal {
|
||||||
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
readonly newRemoteBaseDir: string;
|
||||||
|
readonly service: SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR;
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
newRemoteBaseDir: string,
|
||||||
|
service: SUPPORTED_SERVICES_TYPE_WITH_REMOTE_BASE_DIR
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.newRemoteBaseDir = newRemoteBaseDir;
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
|
||||||
|
const t = (x: TransItemType, vars?: any) => {
|
||||||
|
return this.plugin.i18n.t(x, vars);
|
||||||
|
};
|
||||||
|
|
||||||
|
contentEl.createEl("h2", { text: t("modal_remotebasedir_title") });
|
||||||
|
t("modal_remotebasedir_shortdesc")
|
||||||
|
.split("\n")
|
||||||
|
.forEach((val, idx) => {
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: val,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.newRemoteBaseDir === "" ||
|
||||||
|
this.newRemoteBaseDir === this.app.vault.getName()
|
||||||
|
) {
|
||||||
|
new Setting(contentEl)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(
|
||||||
|
t("modal_remotebasedir_secondconfirm_vaultname")
|
||||||
|
);
|
||||||
|
button.onClick(async () => {
|
||||||
|
// in the settings, the value is reset to the special case ""
|
||||||
|
this.plugin.settings[this.service].remoteBaseDir = "";
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
new Notice(t("modal_remotebasedir_notice"));
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
button.setClass("remotebasedir-second-confirm");
|
||||||
|
})
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("goback"));
|
||||||
|
button.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (checkHasSpecialCharForDir(this.newRemoteBaseDir)) {
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: t("modal_remotebasedir_invaliddirhint"),
|
||||||
|
});
|
||||||
|
new Setting(contentEl).addButton((button) => {
|
||||||
|
button.setButtonText(t("goback"));
|
||||||
|
button.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new Setting(contentEl)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("modal_remotebasedir_secondconfirm_change"));
|
||||||
|
button.onClick(async () => {
|
||||||
|
this.plugin.settings[this.service].remoteBaseDir =
|
||||||
|
this.newRemoteBaseDir;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
new Notice(t("modal_remotebasedir_notice"));
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
button.setClass("remotebasedir-second-confirm");
|
||||||
|
})
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("goback"));
|
||||||
|
button.onClick(() => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DropboxAuthModal extends Modal {
|
class DropboxAuthModal extends Modal {
|
||||||
readonly plugin: RemotelySavePlugin;
|
readonly plugin: RemotelySavePlugin;
|
||||||
readonly authDiv: HTMLDivElement;
|
readonly authDiv: HTMLDivElement;
|
||||||
|
@ -861,7 +957,9 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
dropboxDiv.createEl("p", {
|
dropboxDiv.createEl("p", {
|
||||||
text: t("settings_dropbox_folder", {
|
text: t("settings_dropbox_folder", {
|
||||||
pluginID: this.plugin.manifest.id,
|
pluginID: this.plugin.manifest.id,
|
||||||
vaultName: this.app.vault.getName(),
|
remoteBaseDir:
|
||||||
|
this.plugin.settings.dropbox.remoteBaseDir ||
|
||||||
|
this.app.vault.getName(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -945,6 +1043,31 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
this.plugin.settings.dropbox.username === ""
|
this.plugin.settings.dropbox.username === ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let newDropboxRemoteBaseDir =
|
||||||
|
this.plugin.settings.dropbox.remoteBaseDir || "";
|
||||||
|
new Setting(dropboxDiv)
|
||||||
|
.setName(t("settings_remotebasedir"))
|
||||||
|
.setDesc(t("settings_remotebasedir_desc"))
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(this.app.vault.getName())
|
||||||
|
.setValue(newDropboxRemoteBaseDir)
|
||||||
|
.onChange((value) => {
|
||||||
|
newDropboxRemoteBaseDir = value.trim();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(() => {
|
||||||
|
new ChangeRemoteBaseDirModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
newDropboxRemoteBaseDir,
|
||||||
|
"dropbox"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(dropboxDiv)
|
new Setting(dropboxDiv)
|
||||||
.setName(t("settings_checkonnectivity"))
|
.setName(t("settings_checkonnectivity"))
|
||||||
.setDesc(t("settings_checkonnectivity_desc"))
|
.setDesc(t("settings_checkonnectivity_desc"))
|
||||||
|
@ -999,7 +1122,9 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
onedriveDiv.createEl("p", {
|
onedriveDiv.createEl("p", {
|
||||||
text: t("settings_onedrive_folder", {
|
text: t("settings_onedrive_folder", {
|
||||||
pluginID: this.plugin.manifest.id,
|
pluginID: this.plugin.manifest.id,
|
||||||
vaultName: this.app.vault.getName(),
|
remoteBaseDir:
|
||||||
|
this.plugin.settings.onedrive.remoteBaseDir ||
|
||||||
|
this.app.vault.getName(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1064,6 +1189,31 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
this.plugin.settings.onedrive.username === ""
|
this.plugin.settings.onedrive.username === ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let newOnedriveRemoteBaseDir =
|
||||||
|
this.plugin.settings.onedrive.remoteBaseDir || "";
|
||||||
|
new Setting(onedriveDiv)
|
||||||
|
.setName(t("settings_remotebasedir"))
|
||||||
|
.setDesc(t("settings_remotebasedir_desc"))
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(this.app.vault.getName())
|
||||||
|
.setValue(newOnedriveRemoteBaseDir)
|
||||||
|
.onChange((value) => {
|
||||||
|
newOnedriveRemoteBaseDir = value.trim();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(() => {
|
||||||
|
new ChangeRemoteBaseDirModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
newOnedriveRemoteBaseDir,
|
||||||
|
"onedrive"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(onedriveDiv)
|
new Setting(onedriveDiv)
|
||||||
.setName(t("settings_checkonnectivity"))
|
.setName(t("settings_checkonnectivity"))
|
||||||
.setDesc(t("settings_checkonnectivity_desc"))
|
.setDesc(t("settings_checkonnectivity_desc"))
|
||||||
|
@ -1133,7 +1283,8 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
|
|
||||||
webdavDiv.createEl("p", {
|
webdavDiv.createEl("p", {
|
||||||
text: t("settings_webdav_folder", {
|
text: t("settings_webdav_folder", {
|
||||||
vaultName: this.app.vault.getName(),
|
remoteBaseDir:
|
||||||
|
this.plugin.settings.webdav.remoteBaseDir || this.app.vault.getName(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1253,6 +1404,31 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let newWebdavRemoteBaseDir =
|
||||||
|
this.plugin.settings.webdav.remoteBaseDir || "";
|
||||||
|
new Setting(webdavDiv)
|
||||||
|
.setName(t("settings_remotebasedir"))
|
||||||
|
.setDesc(t("settings_remotebasedir_desc"))
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(this.app.vault.getName())
|
||||||
|
.setValue(newWebdavRemoteBaseDir)
|
||||||
|
.onChange((value) => {
|
||||||
|
newWebdavRemoteBaseDir = value.trim();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addButton((button) => {
|
||||||
|
button.setButtonText(t("confirm"));
|
||||||
|
button.onClick(() => {
|
||||||
|
new ChangeRemoteBaseDirModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
newWebdavRemoteBaseDir,
|
||||||
|
"webdav"
|
||||||
|
).open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(webdavDiv)
|
new Setting(webdavDiv)
|
||||||
.setName(t("settings_checkonnectivity"))
|
.setName(t("settings_checkonnectivity"))
|
||||||
.setDesc(t("settings_checkonnectivity_desc"))
|
.setDesc(t("settings_checkonnectivity_desc"))
|
||||||
|
|
|
@ -266,3 +266,22 @@ describe("Misc: at which level", () => {
|
||||||
expect(misc.atWhichLevel("x/y/z.md")).to.be.equal(3);
|
expect(misc.atWhichLevel("x/y/z.md")).to.be.equal(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Misc: special char for dir", () => {
|
||||||
|
it("should return false for normal string", () => {
|
||||||
|
expect(misc.checkHasSpecialCharForDir("")).to.be.false;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("xxx")).to.be.false;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("yyy_xxx")).to.be.false;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("yyy.xxx")).to.be.false;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("yyy?xxx")).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true for special cases", () => {
|
||||||
|
expect(misc.checkHasSpecialCharForDir("?")).to.be.true;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("/")).to.be.true;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("\\")).to.be.true;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("xxx/yyy")).to.be.true;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("xxx\\yyy")).to.be.true;
|
||||||
|
expect(misc.checkHasSpecialCharForDir("xxx?yyy")).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue