Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aa1bd821e | ||
|
|
a967d6ffd9 | ||
|
|
a2ce95441a | ||
|
|
e74e49538e | ||
|
|
11c66d7030 | ||
|
|
56bd4c000e | ||
|
|
a7cc4e2d7e | ||
|
|
f306445e0d | ||
|
|
9f4de49495 | ||
|
|
54c25a70ed |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.5.11",
|
"version": "0.5.12",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.5.11",
|
"version": "0.5.12",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.5.11",
|
"version": "0.5.12",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev2": "node esbuild.config.mjs --watch",
|
"dev2": "node esbuild.config.mjs --watch",
|
||||||
|
|||||||
+150
-30
@@ -200,22 +200,141 @@ export function getFileRename(key: string) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function arraysAreEqual(arr1: ArrayBuffer, arr2: ArrayBuffer) {
|
||||||
|
if (arr1.byteLength !== arr2.byteLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u1 = new Uint8Array(arr1);
|
||||||
|
const u2 = new Uint8Array(arr2);
|
||||||
|
|
||||||
|
for (let i = 0; i < u1.byteLength; ++i) {
|
||||||
|
if (u1[i] !== u2[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. download remote
|
||||||
|
* 2. compare
|
||||||
|
* 3. if the same, update local but not upload
|
||||||
|
* 4. if not the same, rename local and save remote
|
||||||
|
*/
|
||||||
|
async function tryDuplicateFileForSameSizes(
|
||||||
|
key: string,
|
||||||
|
key2: string,
|
||||||
|
fsLocal: FakeFs,
|
||||||
|
fsRemote: FakeFs,
|
||||||
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
|
) {
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key}`);
|
||||||
|
|
||||||
|
// 1. download
|
||||||
|
const remoteContent = await fsRemote.readFile(key);
|
||||||
|
|
||||||
|
// 2. compare
|
||||||
|
const localContent = await fsLocal.readFile(key);
|
||||||
|
const eq = arraysAreEqual(localContent, remoteContent);
|
||||||
|
|
||||||
|
if (eq) {
|
||||||
|
// 3. if the same, update local but not upload
|
||||||
|
// read meta of remote, as if we have downloaded the file
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key} content equal`);
|
||||||
|
const entityRemote = await fsRemote.stat(key);
|
||||||
|
|
||||||
|
// write
|
||||||
|
const downloadResultEntity = await fsLocal.writeFile(
|
||||||
|
key,
|
||||||
|
remoteContent,
|
||||||
|
entityRemote.mtimeCli ?? Date.now(),
|
||||||
|
entityRemote.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await downloadCallback(downloadResultEntity);
|
||||||
|
|
||||||
|
// no uploadCallback here
|
||||||
|
} else {
|
||||||
|
// 4. if not the same, rename local and save remote
|
||||||
|
console.debug(`tryDuplicateFileForSameSizes: ${key} content not equal`);
|
||||||
|
|
||||||
|
await fsLocal.rename(key, key2);
|
||||||
|
|
||||||
|
const entityRemote = await fsRemote.stat(key);
|
||||||
|
const downloadResultEntity = await fsLocal.writeFile(
|
||||||
|
key,
|
||||||
|
remoteContent,
|
||||||
|
entityRemote.mtimeCli ?? Date.now(),
|
||||||
|
entityRemote.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await downloadCallback(downloadResultEntity);
|
||||||
|
|
||||||
|
const entityLocal = await fsLocal.stat(key2); // key2 here!
|
||||||
|
const uploadResultEntity = await fsRemote.writeFile(
|
||||||
|
key2, // key2 here!
|
||||||
|
localContent,
|
||||||
|
entityLocal.mtimeCli ?? Date.now(),
|
||||||
|
entityLocal.mtimeCli ?? Date.now()
|
||||||
|
);
|
||||||
|
await uploadCallback(uploadResultEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* local: x.md -> x.dup.md -> upload to remote
|
* local: x.md -> x.dup.md -> upload to remote
|
||||||
* remote: x.md -> download to local -> using original name x.md
|
* remote: x.md -> download to local -> using original name x.md
|
||||||
*/
|
*/
|
||||||
export async function duplicateFile(
|
async function tryDuplicateFileForDiffSizes(
|
||||||
key: string,
|
key: string,
|
||||||
left: FakeFs,
|
key2: string,
|
||||||
right: FakeFs,
|
fsLocal: FakeFs,
|
||||||
uploadCallback: (entity: Entity) => Promise<any>,
|
fsRemote: FakeFs,
|
||||||
downloadCallback: (entity: Entity) => Promise<any>
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
|
) {
|
||||||
|
console.debug(`tryDuplicateFileForDiffSizes: ${key}`);
|
||||||
|
|
||||||
|
await fsLocal.rename(key, key2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x.dup.md -> upload to remote
|
||||||
|
*/
|
||||||
|
async function f1() {
|
||||||
|
const k = await copyFile(key2, fsLocal, fsRemote);
|
||||||
|
await uploadCallback(k.entity);
|
||||||
|
return k.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x.md -> download to local
|
||||||
|
*/
|
||||||
|
async function f2() {
|
||||||
|
const k = await copyFile(key, fsRemote, fsLocal);
|
||||||
|
await downloadCallback(k.entity);
|
||||||
|
return k.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [resUpload, resDownload] = await Promise.all([f1(), f2()]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
upload: resUpload,
|
||||||
|
download: resDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tryDuplicateFile(
|
||||||
|
key: string,
|
||||||
|
fsLocal: FakeFs,
|
||||||
|
fsRemote: FakeFs,
|
||||||
|
uploadCallback: (entity: Entity | undefined) => Promise<any>,
|
||||||
|
downloadCallback: (entity: Entity | undefined) => Promise<any>
|
||||||
) {
|
) {
|
||||||
let key2 = getFileRename(key);
|
let key2 = getFileRename(key);
|
||||||
let usable = false;
|
let usable = false;
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
const s = await left.stat(key2);
|
const s = await fsLocal.stat(key2);
|
||||||
if (s === null || s === undefined) {
|
if (s === null || s === undefined) {
|
||||||
throw Error(`not exist $${key2}`);
|
throw Error(`not exist $${key2}`);
|
||||||
}
|
}
|
||||||
@@ -228,30 +347,31 @@ export async function duplicateFile(
|
|||||||
usable = true;
|
usable = true;
|
||||||
}
|
}
|
||||||
} while (!usable);
|
} while (!usable);
|
||||||
await left.rename(key, key2);
|
|
||||||
|
|
||||||
/**
|
const localSize = await fsLocal.stat(key);
|
||||||
* x.dup.md -> upload to remote
|
const remoteSize = await fsRemote.stat(key);
|
||||||
*/
|
|
||||||
async function f1() {
|
if (
|
||||||
const k = await copyFile(key2, left, right);
|
localSize !== undefined &&
|
||||||
await uploadCallback(k.entity);
|
remoteSize !== undefined &&
|
||||||
return k.entity;
|
localSize.sizeRaw === remoteSize.sizeRaw
|
||||||
|
) {
|
||||||
|
return await tryDuplicateFileForSameSizes(
|
||||||
|
key,
|
||||||
|
key2,
|
||||||
|
fsLocal,
|
||||||
|
fsRemote,
|
||||||
|
uploadCallback,
|
||||||
|
downloadCallback
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await tryDuplicateFileForDiffSizes(
|
||||||
|
key,
|
||||||
|
key2,
|
||||||
|
fsLocal,
|
||||||
|
fsRemote,
|
||||||
|
uploadCallback,
|
||||||
|
downloadCallback
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* x.md -> download to local
|
|
||||||
*/
|
|
||||||
async function f2() {
|
|
||||||
const k = await copyFile(key, right, left);
|
|
||||||
await downloadCallback(k.entity);
|
|
||||||
return k.entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [resUpload, resDownload] = await Promise.all([f1(), f2()]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
upload: resUpload,
|
|
||||||
download: resDownload,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"modal_proauth_maualinput_conn_fail": "连接失败",
|
"modal_proauth_maualinput_conn_fail": "连接失败",
|
||||||
|
|
||||||
"settings_onedrivefull": "Onedrive(个人版)(Full)设置",
|
"settings_onedrivefull": "Onedrive(个人版)(Full)设置",
|
||||||
"settings_chooseservice_onedrivefull": "OneDrive(个人版)(Full)",
|
"settings_chooseservice_onedrivefull": "OneDrive(个人版)(Full)(PRO)",
|
||||||
"settings_onedrivefull_disclaimer1": "声明:此插件不是微软或 OneDrive 的官方产品。",
|
"settings_onedrivefull_disclaimer1": "声明:此插件不是微软或 OneDrive 的官方产品。",
|
||||||
"settings_onedrivefull_disclaimer2": "声明:您所输入的信息存储于本地。其它有害的或者出错的插件,是有可能读取到这些信息的。如果您发现了 OneDrive 有不符合预期的访问,请立刻从 https://microsoft.com/consent 删除记录于此插件的连接鉴权。",
|
"settings_onedrivefull_disclaimer2": "声明:您所输入的信息存储于本地。其它有害的或者出错的插件,是有可能读取到这些信息的。如果您发现了 OneDrive 有不符合预期的访问,请立刻从 https://microsoft.com/consent 删除记录于此插件的连接鉴权。",
|
||||||
"settings_onedrivefull_folder": "我们会在您的 OneDrive 上创建此文件夹并在里面同步:/{{remoteBaseDir}}。",
|
"settings_onedrivefull_folder": "我们会在您的 OneDrive 上创建此文件夹并在里面同步:/{{remoteBaseDir}}。",
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"modal_proauth_maualinput_conn_fail": "連線失敗",
|
"modal_proauth_maualinput_conn_fail": "連線失敗",
|
||||||
|
|
||||||
"settings_onedrivefull": "Onedrive(個人版)(Full)設定",
|
"settings_onedrivefull": "Onedrive(個人版)(Full)設定",
|
||||||
"settings_chooseservice_onedrivefull": "OneDrive(個人版)(Full)",
|
"settings_chooseservice_onedrivefull": "OneDrive(個人版)(Full)(PRO)",
|
||||||
"settings_onedrivefull_disclaimer1": "宣告:此外掛不是微軟或 OneDrive 的官方產品。",
|
"settings_onedrivefull_disclaimer1": "宣告:此外掛不是微軟或 OneDrive 的官方產品。",
|
||||||
"settings_onedrivefull_disclaimer2": "宣告:您所輸入的資訊儲存於本地。其它有害的或者出錯的外掛,是有可能讀取到這些資訊的。如果您發現了 OneDrive 有不符合預期的訪問,請立刻從 https://microsoft.com/consent 刪除記錄於此外掛的連線鑑權。",
|
"settings_onedrivefull_disclaimer2": "宣告:您所輸入的資訊儲存於本地。其它有害的或者出錯的外掛,是有可能讀取到這些資訊的。如果您發現了 OneDrive 有不符合預期的訪問,請立刻從 https://microsoft.com/consent 刪除記錄於此外掛的連線鑑權。",
|
||||||
"settings_onedrivefull_folder": "我們會在您的 OneDrive 上建立此資料夾並在裡面同步:/{{remoteBaseDir}}。",
|
"settings_onedrivefull_folder": "我們會在您的 OneDrive 上建立此資料夾並在裡面同步:/{{remoteBaseDir}}。",
|
||||||
|
|||||||
+34
-20
@@ -36,7 +36,7 @@ import {
|
|||||||
} from "../../src/misc";
|
} from "../../src/misc";
|
||||||
import type { Profiler } from "../../src/profiler";
|
import type { Profiler } from "../../src/profiler";
|
||||||
import { checkProRunnableAndFixInplace } from "./account";
|
import { checkProRunnableAndFixInplace } from "./account";
|
||||||
import { duplicateFile, isMergable, mergeFile } from "./conflictLogic";
|
import { isMergable, mergeFile, tryDuplicateFile } from "./conflictLogic";
|
||||||
import {
|
import {
|
||||||
clearFileContentHistoryByVaultAndProfile,
|
clearFileContentHistoryByVaultAndProfile,
|
||||||
getFileContentHistoryByVaultAndProfile,
|
getFileContentHistoryByVaultAndProfile,
|
||||||
@@ -296,7 +296,8 @@ const getSyncPlanInplace = async (
|
|||||||
syncDirection: SyncDirectionType,
|
syncDirection: SyncDirectionType,
|
||||||
profiler: Profiler | undefined,
|
profiler: Profiler | undefined,
|
||||||
settings: RemotelySavePluginSettings,
|
settings: RemotelySavePluginSettings,
|
||||||
triggerSource: SyncTriggerSourceType
|
triggerSource: SyncTriggerSourceType,
|
||||||
|
configDir: string
|
||||||
) => {
|
) => {
|
||||||
profiler?.addIndent();
|
profiler?.addIndent();
|
||||||
profiler?.insert("getSyncPlanInplace: enter");
|
profiler?.insert("getSyncPlanInplace: enter");
|
||||||
@@ -582,7 +583,11 @@ const getSyncPlanInplace = async (
|
|||||||
if (prevSync === undefined) {
|
if (prevSync === undefined) {
|
||||||
// Didn't exist means both are new
|
// Didn't exist means both are new
|
||||||
if (syncDirection === "bidirectional") {
|
if (syncDirection === "bidirectional") {
|
||||||
if (conflictAction === "keep_newer") {
|
if (
|
||||||
|
conflictAction === "keep_newer" ||
|
||||||
|
(conflictAction === "smart_conflict" &&
|
||||||
|
key.startsWith(`${configDir}/`))
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
(local.mtimeCli ?? local.mtimeSvr ?? 0) >=
|
(local.mtimeCli ?? local.mtimeSvr ?? 0) >=
|
||||||
(remote.mtimeCli ?? remote.mtimeSvr ?? 0)
|
(remote.mtimeCli ?? remote.mtimeSvr ?? 0)
|
||||||
@@ -640,7 +645,11 @@ const getSyncPlanInplace = async (
|
|||||||
} else {
|
} else {
|
||||||
// Both exist but don't compare means both are modified
|
// Both exist but don't compare means both are modified
|
||||||
if (syncDirection === "bidirectional") {
|
if (syncDirection === "bidirectional") {
|
||||||
if (conflictAction === "keep_newer") {
|
if (
|
||||||
|
conflictAction === "keep_newer" ||
|
||||||
|
(conflictAction === "smart_conflict" &&
|
||||||
|
key.startsWith(`${configDir}/`))
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
(local.mtimeCli ?? local.mtimeSvr ?? 0) >=
|
(local.mtimeCli ?? local.mtimeSvr ?? 0) >=
|
||||||
(remote.mtimeCli ?? remote.mtimeSvr ?? 0)
|
(remote.mtimeCli ?? remote.mtimeSvr ?? 0)
|
||||||
@@ -1342,27 +1351,31 @@ const dispatchOperationToActualV3 = async (
|
|||||||
r.key
|
r.key
|
||||||
);
|
);
|
||||||
const mtimeCli = (await fsLocal.stat(r.key)).mtimeCli!;
|
const mtimeCli = (await fsLocal.stat(r.key)).mtimeCli!;
|
||||||
const { upload, download } = await duplicateFile(
|
await tryDuplicateFile(
|
||||||
r.key,
|
r.key,
|
||||||
fsLocal,
|
fsLocal,
|
||||||
fsEncrypt,
|
fsEncrypt,
|
||||||
async (upload) => {
|
async (upload) => {
|
||||||
// TODO: abstract away the dirty hack
|
if (upload !== undefined) {
|
||||||
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
// TODO: abstract away the dirty hack
|
||||||
await upsertPrevSyncRecordByVaultAndProfile(
|
fullfillMTimeOfRemoteEntityInplace(upload, mtimeCli);
|
||||||
db,
|
await upsertPrevSyncRecordByVaultAndProfile(
|
||||||
vaultRandomID,
|
db,
|
||||||
profileID,
|
vaultRandomID,
|
||||||
upload
|
profileID,
|
||||||
);
|
upload
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async (download) => {
|
async (download) => {
|
||||||
await upsertPrevSyncRecordByVaultAndProfile(
|
if (download !== undefined) {
|
||||||
db,
|
await upsertPrevSyncRecordByVaultAndProfile(
|
||||||
vaultRandomID,
|
db,
|
||||||
profileID,
|
vaultRandomID,
|
||||||
download
|
profileID,
|
||||||
);
|
download
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1712,7 +1725,8 @@ export async function syncer(
|
|||||||
settings.syncDirection ?? "bidirectional",
|
settings.syncDirection ?? "bidirectional",
|
||||||
profiler,
|
profiler,
|
||||||
settings,
|
settings,
|
||||||
triggerSource
|
triggerSource,
|
||||||
|
configDir
|
||||||
);
|
);
|
||||||
console.debug(`mixedEntityMappings:`);
|
console.debug(`mixedEntityMappings:`);
|
||||||
console.debug(mixedEntityMappings); // for debugging
|
console.debug(mixedEntityMappings); // for debugging
|
||||||
|
|||||||
+4
-1
@@ -91,7 +91,8 @@ export class FakeFsEncrypt extends FakeFs {
|
|||||||
this.password !== "" ? method : "no password"
|
this.password !== "" ? method : "no password"
|
||||||
})`;
|
})`;
|
||||||
|
|
||||||
if (method === "rclone-base64") {
|
if (this.password !== "" && method === "rclone-base64") {
|
||||||
|
// no need to init if no password or not rclone
|
||||||
this.cipherRClone = new rclone.CipherRclone(password, 5);
|
this.cipherRClone = new rclone.CipherRclone(password, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +138,8 @@ export class FakeFsEncrypt extends FakeFs {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// the config has a password
|
||||||
|
|
||||||
if (this.method === "unknown") {
|
if (this.method === "unknown") {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|||||||
+86
-13
@@ -264,6 +264,9 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
// another possibile prefix
|
// another possibile prefix
|
||||||
const FOURTH_COMMON_PREFIX_RAW = `/drive/items/`;
|
const FOURTH_COMMON_PREFIX_RAW = `/drive/items/`;
|
||||||
|
|
||||||
|
// when to use decode?
|
||||||
|
const remoteBaseDirEncoded = encodeURIComponent(remoteBaseDir);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
x.parentReference === undefined ||
|
x.parentReference === undefined ||
|
||||||
x.parentReference === null ||
|
x.parentReference === null ||
|
||||||
@@ -279,6 +282,8 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
SECOND_COMMON_PREFIX_REGEX
|
SECOND_COMMON_PREFIX_REGEX
|
||||||
);
|
);
|
||||||
const matchThirdPrefixRes = fullPathOriginal.match(THIRD_COMMON_PREFIX_REGEX);
|
const matchThirdPrefixRes = fullPathOriginal.match(THIRD_COMMON_PREFIX_REGEX);
|
||||||
|
|
||||||
|
// first
|
||||||
if (
|
if (
|
||||||
matchFirstPrefixRes !== null &&
|
matchFirstPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchFirstPrefixRes[0]}${remoteBaseDir}`)
|
fullPathOriginal.startsWith(`${matchFirstPrefixRes[0]}${remoteBaseDir}`)
|
||||||
@@ -286,24 +291,68 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDir}`;
|
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (
|
} else if (
|
||||||
|
matchFirstPrefixRes !== null &&
|
||||||
|
fullPathOriginal.startsWith(
|
||||||
|
`${matchFirstPrefixRes[0]}${remoteBaseDirEncoded}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const foundPrefix = `${matchFirstPrefixRes[0]}${remoteBaseDirEncoded}`;
|
||||||
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fifth
|
||||||
|
else if (
|
||||||
matchFifthPrefixRes !== null &&
|
matchFifthPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchFifthPrefixRes[0]}${remoteBaseDir}`)
|
fullPathOriginal.startsWith(`${matchFifthPrefixRes[0]}${remoteBaseDir}`)
|
||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchFifthPrefixRes[0]}${remoteBaseDir}`;
|
const foundPrefix = `${matchFifthPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (
|
} else if (
|
||||||
|
matchFifthPrefixRes !== null &&
|
||||||
|
fullPathOriginal.startsWith(
|
||||||
|
`${matchFifthPrefixRes[0]}${remoteBaseDirEncoded}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const foundPrefix = `${matchFifthPrefixRes[0]}${remoteBaseDirEncoded}`;
|
||||||
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// second
|
||||||
|
else if (
|
||||||
matchSecondPrefixRes !== null &&
|
matchSecondPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${remoteBaseDir}`)
|
fullPathOriginal.startsWith(`${matchSecondPrefixRes[0]}${remoteBaseDir}`)
|
||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchSecondPrefixRes[0]}${remoteBaseDir}`;
|
const foundPrefix = `${matchSecondPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (
|
} else if (
|
||||||
|
matchSecondPrefixRes !== null &&
|
||||||
|
fullPathOriginal.startsWith(
|
||||||
|
`${matchSecondPrefixRes[0]}${remoteBaseDirEncoded}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const foundPrefix = `${matchSecondPrefixRes[0]}${remoteBaseDirEncoded}`;
|
||||||
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// third
|
||||||
|
else if (
|
||||||
matchThirdPrefixRes !== null &&
|
matchThirdPrefixRes !== null &&
|
||||||
fullPathOriginal.startsWith(`${matchThirdPrefixRes[0]}${remoteBaseDir}`)
|
fullPathOriginal.startsWith(`${matchThirdPrefixRes[0]}${remoteBaseDir}`)
|
||||||
) {
|
) {
|
||||||
const foundPrefix = `${matchThirdPrefixRes[0]}${remoteBaseDir}`;
|
const foundPrefix = `${matchThirdPrefixRes[0]}${remoteBaseDir}`;
|
||||||
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
} else if (x.parentReference.path.startsWith(FOURTH_COMMON_PREFIX_RAW)) {
|
} else if (
|
||||||
|
matchThirdPrefixRes !== null &&
|
||||||
|
fullPathOriginal.startsWith(
|
||||||
|
`${matchThirdPrefixRes[0]}${remoteBaseDirEncoded}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const foundPrefix = `${matchThirdPrefixRes[0]}${remoteBaseDirEncoded}`;
|
||||||
|
key = fullPathOriginal.substring(foundPrefix.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fourth
|
||||||
|
else if (x.parentReference.path.startsWith(FOURTH_COMMON_PREFIX_RAW)) {
|
||||||
// it's something like
|
// it's something like
|
||||||
// /drive/items/<some_id>!<another_id>:/${remoteBaseDir}/<subfolder>
|
// /drive/items/<some_id>!<another_id>:/${remoteBaseDir}/<subfolder>
|
||||||
// with uri encoded!
|
// with uri encoded!
|
||||||
@@ -321,16 +370,27 @@ const fromDriveItemToEntity = (x: DriveItem, remoteBaseDir: string): Entity => {
|
|||||||
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${constructFromDriveItemToEntityError(
|
`file/folder with /drive/items/, no idea how to deal with it:
|
||||||
x
|
fullPathOriginal=${fullPathOriginal}
|
||||||
)}`
|
matchFirstPrefixRes=${matchFirstPrefixRes}
|
||||||
|
matchFifthPrefixRes=${matchFifthPrefixRes}
|
||||||
|
matchSecondPrefixRes=${matchSecondPrefixRes}
|
||||||
|
matchThirdPrefixRes=${matchThirdPrefixRes}
|
||||||
|
${constructFromDriveItemToEntityError(x)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// others
|
||||||
|
else {
|
||||||
throw Error(
|
throw Error(
|
||||||
`we meet file/folder and do not know how to deal with it:\n${constructFromDriveItemToEntityError(
|
`file/folder, no idea how to deal with it without known prefix:
|
||||||
x
|
fullPathOriginal=${fullPathOriginal}
|
||||||
)}`
|
matchFirstPrefixRes=${matchFirstPrefixRes}
|
||||||
|
matchFifthPrefixRes=${matchFifthPrefixRes}
|
||||||
|
matchSecondPrefixRes=${matchSecondPrefixRes}
|
||||||
|
matchThirdPrefixRes=${matchThirdPrefixRes}
|
||||||
|
${constructFromDriveItemToEntityError(x)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,11 +986,24 @@ export class FakeFsOnedrive extends FakeFs {
|
|||||||
).arrayBuffer;
|
).arrayBuffer;
|
||||||
return content;
|
return content;
|
||||||
} else {
|
} else {
|
||||||
// cannot set no-cache here, will have cors error
|
// so strange, sometimes (!!!)
|
||||||
const content = await (
|
// we cannot download the file because of CORS
|
||||||
await fetch(downloadUrl, { cache: "no-store" })
|
try {
|
||||||
).arrayBuffer();
|
// cannot set no-cache here, will have cors error
|
||||||
return content;
|
const content = await (
|
||||||
|
await fetch(downloadUrl, { cache: "no-store" })
|
||||||
|
).arrayBuffer();
|
||||||
|
return content;
|
||||||
|
} catch (e) {
|
||||||
|
// let's try again to bypass the CORS
|
||||||
|
const content = (
|
||||||
|
await requestUrl({
|
||||||
|
url: downloadUrl,
|
||||||
|
headers: { "Cache-Control": "no-cache" },
|
||||||
|
})
|
||||||
|
).arrayBuffer;
|
||||||
|
return content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -269,7 +269,7 @@
|
|||||||
"settings_chooseservice_s3": "S3 or compatible",
|
"settings_chooseservice_s3": "S3 or compatible",
|
||||||
"settings_chooseservice_dropbox": "Dropbox",
|
"settings_chooseservice_dropbox": "Dropbox",
|
||||||
"settings_chooseservice_webdav": "Webdav",
|
"settings_chooseservice_webdav": "Webdav",
|
||||||
"settings_chooseservice_onedrive": "OneDrive for personal (App Folder)",
|
"settings_chooseservice_onedrive": "OneDrive for personal",
|
||||||
"settings_chooseservice_webdis": "Webdis (HTTP for Redis®)",
|
"settings_chooseservice_webdis": "Webdis (HTTP for Redis®)",
|
||||||
"settings_adv": "Advanced Settings",
|
"settings_adv": "Advanced Settings",
|
||||||
"settings_concurrency": "Concurrency",
|
"settings_concurrency": "Concurrency",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
"statusbar_time_days": "{{time}} 天前",
|
"statusbar_time_days": "{{time}} 天前",
|
||||||
"statusbar_time_hours": "{{time}} 小时前",
|
"statusbar_time_hours": "{{time}} 小时前",
|
||||||
"statusbar_time_minutes": "{{time}} 分钟前",
|
"statusbar_time_minutes": "{{time}} 分钟前",
|
||||||
"statusbar_time_lessminute": "1 分钟之内",
|
"statusbar_time_lessminute": "1 分钟内",
|
||||||
"statusbar_time_now": "刚刚",
|
"statusbar_time_now": "刚刚",
|
||||||
"statusbar_syncing": "正在同步",
|
"statusbar_syncing": "正在同步",
|
||||||
"statusbar_lastsync_label": "日期:{{date}}",
|
"statusbar_lastsync_label": "日期:{{date}}",
|
||||||
@@ -268,7 +268,7 @@
|
|||||||
"settings_chooseservice_s3": "S3 或兼容 S3 的服务",
|
"settings_chooseservice_s3": "S3 或兼容 S3 的服务",
|
||||||
"settings_chooseservice_dropbox": "Dropbox",
|
"settings_chooseservice_dropbox": "Dropbox",
|
||||||
"settings_chooseservice_webdav": "Webdav",
|
"settings_chooseservice_webdav": "Webdav",
|
||||||
"settings_chooseservice_onedrive": "OneDrive(个人版)(App Folder)",
|
"settings_chooseservice_onedrive": "OneDrive(个人版)",
|
||||||
"settings_chooseservice_webdis": "Webdis (an HTTP interface for Redis)",
|
"settings_chooseservice_webdis": "Webdis (an HTTP interface for Redis)",
|
||||||
"settings_adv": "进阶设置",
|
"settings_adv": "进阶设置",
|
||||||
"settings_concurrency": "并行度",
|
"settings_concurrency": "并行度",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"statusbar_time_days": "{{time}} 天前",
|
"statusbar_time_days": "{{time}} 天前",
|
||||||
"statusbar_time_hours": "{{time}} 小時前",
|
"statusbar_time_hours": "{{time}} 小時前",
|
||||||
"statusbar_time_minutes": "{{time}} 分鐘前",
|
"statusbar_time_minutes": "{{time}} 分鐘前",
|
||||||
"statusbar_time_lessminute": "1 分鐘之內",
|
"statusbar_time_lessminute": "1 分鐘內",
|
||||||
"statusbar_time_now": "剛剛",
|
"statusbar_time_now": "剛剛",
|
||||||
"statusbar_syncing": "正在同步",
|
"statusbar_syncing": "正在同步",
|
||||||
"statusbar_lastsync_label": "日期:{{date}}",
|
"statusbar_lastsync_label": "日期:{{date}}",
|
||||||
@@ -267,7 +267,7 @@
|
|||||||
"settings_chooseservice_s3": "S3 或相容 S3 的服務",
|
"settings_chooseservice_s3": "S3 或相容 S3 的服務",
|
||||||
"settings_chooseservice_dropbox": "Dropbox",
|
"settings_chooseservice_dropbox": "Dropbox",
|
||||||
"settings_chooseservice_webdav": "Webdav",
|
"settings_chooseservice_webdav": "Webdav",
|
||||||
"settings_chooseservice_onedrive": "OneDrive(個人版)(App Folder)",
|
"settings_chooseservice_onedrive": "OneDrive(個人版)",
|
||||||
"settings_chooseservice_webdis": "Webdis (an HTTP interface for Redis®)",
|
"settings_chooseservice_webdis": "Webdis (an HTTP interface for Redis®)",
|
||||||
"settings_adv": "進階設定",
|
"settings_adv": "進階設定",
|
||||||
"settings_concurrency": "並行度",
|
"settings_concurrency": "並行度",
|
||||||
|
|||||||
+11
-4
@@ -1921,15 +1921,22 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
dropdown.addOption("dropbox", t("settings_chooseservice_dropbox"));
|
dropdown.addOption("dropbox", t("settings_chooseservice_dropbox"));
|
||||||
dropdown.addOption("webdav", t("settings_chooseservice_webdav"));
|
dropdown.addOption("webdav", t("settings_chooseservice_webdav"));
|
||||||
dropdown.addOption("onedrive", t("settings_chooseservice_onedrive"));
|
dropdown.addOption("onedrive", t("settings_chooseservice_onedrive"));
|
||||||
dropdown.addOption(
|
|
||||||
"onedrivefull",
|
|
||||||
t("settings_chooseservice_onedrivefull")
|
|
||||||
);
|
|
||||||
dropdown.addOption("webdis", t("settings_chooseservice_webdis"));
|
dropdown.addOption("webdis", t("settings_chooseservice_webdis"));
|
||||||
|
|
||||||
|
dropdown.addOption("separator line", "-----");
|
||||||
|
(dropdown.selectEl.lastChild as HTMLElement).setAttribute(
|
||||||
|
"disabled",
|
||||||
|
"disabled"
|
||||||
|
);
|
||||||
|
|
||||||
dropdown.addOption(
|
dropdown.addOption(
|
||||||
"googledrive",
|
"googledrive",
|
||||||
t("settings_chooseservice_googledrive")
|
t("settings_chooseservice_googledrive")
|
||||||
);
|
);
|
||||||
|
dropdown.addOption(
|
||||||
|
"onedrivefull",
|
||||||
|
t("settings_chooseservice_onedrivefull")
|
||||||
|
);
|
||||||
dropdown.addOption("box", t("settings_chooseservice_box"));
|
dropdown.addOption("box", t("settings_chooseservice_box"));
|
||||||
dropdown.addOption("pcloud", t("settings_chooseservice_pcloud"));
|
dropdown.addOption("pcloud", t("settings_chooseservice_pcloud"));
|
||||||
dropdown.addOption(
|
dropdown.addOption(
|
||||||
|
|||||||
Reference in New Issue
Block a user