Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c37bf6aedd | ||
|
|
bed28d9f0b | ||
|
|
02e03681f7 | ||
|
|
62452341a3 | ||
|
|
bff2f6a642 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.4.10",
|
"version": "0.4.11",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "remotely-save",
|
"id": "remotely-save",
|
||||||
"name": "Remotely Save",
|
"name": "Remotely Save",
|
||||||
"version": "0.4.10",
|
"version": "0.4.11",
|
||||||
"minAppVersion": "0.13.21",
|
"minAppVersion": "0.13.21",
|
||||||
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
"description": "Yet another unofficial plugin allowing users to synchronize notes between local device and the cloud service.",
|
||||||
"author": "fyears",
|
"author": "fyears",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "remotely-save",
|
"name": "remotely-save",
|
||||||
"version": "0.4.10",
|
"version": "0.4.11",
|
||||||
"description": "This is yet another sync plugin for Obsidian app.",
|
"description": "This is yet another sync plugin for Obsidian app.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev2": "node esbuild.config.mjs --watch",
|
"dev2": "node esbuild.config.mjs --watch",
|
||||||
|
|||||||
+5
-1
@@ -165,6 +165,8 @@ export type DecisionTypeForMixedEntity =
|
|||||||
| "remote_is_modified_then_pull"
|
| "remote_is_modified_then_pull"
|
||||||
| "local_is_created_then_push"
|
| "local_is_created_then_push"
|
||||||
| "remote_is_created_then_pull"
|
| "remote_is_created_then_pull"
|
||||||
|
| "local_is_created_too_large_then_do_nothing"
|
||||||
|
| "remote_is_created_too_large_then_do_nothing"
|
||||||
| "local_is_deleted_thus_also_delete_remote"
|
| "local_is_deleted_thus_also_delete_remote"
|
||||||
| "remote_is_deleted_thus_also_delete_local"
|
| "remote_is_deleted_thus_also_delete_local"
|
||||||
| "conflict_created_then_keep_local"
|
| "conflict_created_then_keep_local"
|
||||||
@@ -179,7 +181,9 @@ export type DecisionTypeForMixedEntity =
|
|||||||
| "folder_existed_remote_then_also_create_local"
|
| "folder_existed_remote_then_also_create_local"
|
||||||
| "folder_to_be_created"
|
| "folder_to_be_created"
|
||||||
| "folder_to_skip"
|
| "folder_to_skip"
|
||||||
| "folder_to_be_deleted";
|
| "folder_to_be_deleted_on_both"
|
||||||
|
| "folder_to_be_deleted_on_remote"
|
||||||
|
| "folder_to_be_deleted_on_local";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uniform representation
|
* uniform representation
|
||||||
|
|||||||
+78
-11
@@ -42,9 +42,18 @@ export class Cipher {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
if (this.method === "openssl-base64") {
|
if (this.method === "openssl-base64") {
|
||||||
return await openssl.encryptArrayBuffer(content, this.password);
|
const res = await openssl.encryptArrayBuffer(content, this.password);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot encrypt content`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else if (this.method === "rclone-base64") {
|
} else if (this.method === "rclone-base64") {
|
||||||
return await this.cipherRClone!.encryptContentByCallingWorker(content);
|
const res =
|
||||||
|
await this.cipherRClone!.encryptContentByCallingWorker(content);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot encrypt content`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported encrypt method=${this.method}`);
|
throw Error(`not supported encrypt method=${this.method}`);
|
||||||
}
|
}
|
||||||
@@ -56,9 +65,18 @@ export class Cipher {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
if (this.method === "openssl-base64") {
|
if (this.method === "openssl-base64") {
|
||||||
return await openssl.decryptArrayBuffer(content, this.password);
|
const res = await openssl.decryptArrayBuffer(content, this.password);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot decrypt content`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else if (this.method === "rclone-base64") {
|
} else if (this.method === "rclone-base64") {
|
||||||
return await this.cipherRClone!.decryptContentByCallingWorker(content);
|
const res =
|
||||||
|
await this.cipherRClone!.decryptContentByCallingWorker(content);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot decrypt content`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported decrypt method=${this.method}`);
|
throw Error(`not supported decrypt method=${this.method}`);
|
||||||
}
|
}
|
||||||
@@ -70,15 +88,23 @@ export class Cipher {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
if (this.method === "openssl-base64") {
|
if (this.method === "openssl-base64") {
|
||||||
return await openssl.encryptStringToBase64url(name, this.password);
|
const res = await openssl.encryptStringToBase64url(name, this.password);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot encrypt name=${name}`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else if (this.method === "rclone-base64") {
|
} else if (this.method === "rclone-base64") {
|
||||||
return await this.cipherRClone!.encryptNameByCallingWorker(name);
|
const res = await this.cipherRClone!.encryptNameByCallingWorker(name);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot encrypt name=${name}`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported encrypt method=${this.method}`);
|
throw Error(`not supported encrypt method=${this.method}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptName(name: string) {
|
async decryptName(name: string): Promise<string> {
|
||||||
// console.debug("start decryptName");
|
// console.debug("start decryptName");
|
||||||
if (this.password === "") {
|
if (this.password === "") {
|
||||||
return name;
|
return name;
|
||||||
@@ -88,7 +114,7 @@ export class Cipher {
|
|||||||
// backward compitable with the openssl-base32
|
// backward compitable with the openssl-base32
|
||||||
try {
|
try {
|
||||||
const res = await openssl.decryptBase32ToString(name, this.password);
|
const res = await openssl.decryptBase32ToString(name, this.password);
|
||||||
if (isVaildText(res)) {
|
if (res !== undefined && isVaildText(res)) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`cannot decrypt name=${name}`);
|
throw Error(`cannot decrypt name=${name}`);
|
||||||
@@ -102,7 +128,7 @@ export class Cipher {
|
|||||||
name,
|
name,
|
||||||
this.password
|
this.password
|
||||||
);
|
);
|
||||||
if (isVaildText(res)) {
|
if (res !== undefined && isVaildText(res)) {
|
||||||
return res;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`cannot decrypt name=${name}`);
|
throw Error(`cannot decrypt name=${name}`);
|
||||||
@@ -110,9 +136,17 @@ export class Cipher {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw Error(`cannot decrypt name=${name}`);
|
throw Error(`cannot decrypt name=${name}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`method=${this.method} but the name=${name}, likely mismatch`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (this.method === "rclone-base64") {
|
} else if (this.method === "rclone-base64") {
|
||||||
return await this.cipherRClone!.decryptNameByCallingWorker(name);
|
const res = await this.cipherRClone!.decryptNameByCallingWorker(name);
|
||||||
|
if (res === undefined) {
|
||||||
|
throw Error(`cannot decrypt name=${name}`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported decrypt method=${this.method}`);
|
throw Error(`not supported decrypt method=${this.method}`);
|
||||||
}
|
}
|
||||||
@@ -136,7 +170,7 @@ export class Cipher {
|
|||||||
* @param name
|
* @param name
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static isLikelyEncryptedName(name: string): boolean {
|
static isLikelyOpenSSLEncryptedName(name: string): boolean {
|
||||||
if (
|
if (
|
||||||
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE32) ||
|
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE32) ||
|
||||||
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE64URL)
|
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE64URL)
|
||||||
@@ -145,4 +179,37 @@ export class Cipher {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* quick guess, no actual decryption here
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static isLikelyEncryptedName(name: string): boolean {
|
||||||
|
return Cipher.isLikelyOpenSSLEncryptedName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* quick guess, no actual decryption here, only openssl can be guessed here
|
||||||
|
* @param name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static isLikelyEncryptedNameNotMatchMethod(
|
||||||
|
name: string,
|
||||||
|
method: CipherMethodType
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
Cipher.isLikelyOpenSSLEncryptedName(name) &&
|
||||||
|
method !== "openssl-base64"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!Cipher.isLikelyOpenSSLEncryptedName(name) &&
|
||||||
|
method === "openssl-base64"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,9 @@ export const base64ToBase64url = (a: string, pad: boolean = false) => {
|
|||||||
* @param a
|
* @param a
|
||||||
*/
|
*/
|
||||||
export const isVaildText = (a: string) => {
|
export const isVaildText = (a: string) => {
|
||||||
|
if (a === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// If the regex matches, the string is invalid.
|
// If the regex matches, the string is invalid.
|
||||||
return !XRegExp("\\p{Cc}|\\p{Cf}|\\p{Co}|\\p{Cn}|\\p{Zl}|\\p{Zp}", "A").test(
|
return !XRegExp("\\p{Cc}|\\p{Cf}|\\p{Co}|\\p{Cn}|\\p{Zl}|\\p{Zp}", "A").test(
|
||||||
a
|
a
|
||||||
|
|||||||
+9
-33
@@ -125,15 +125,9 @@ class PasswordModal extends Modal {
|
|||||||
|
|
||||||
class EncryptionMethodModal extends Modal {
|
class EncryptionMethodModal extends Modal {
|
||||||
plugin: RemotelySavePlugin;
|
plugin: RemotelySavePlugin;
|
||||||
newEncryptionMethod: CipherMethodType;
|
constructor(app: App, plugin: RemotelySavePlugin) {
|
||||||
constructor(
|
|
||||||
app: App,
|
|
||||||
plugin: RemotelySavePlugin,
|
|
||||||
newEncryptionMethod: CipherMethodType
|
|
||||||
) {
|
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.newEncryptionMethod = newEncryptionMethod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpen() {
|
onOpen() {
|
||||||
@@ -153,21 +147,12 @@ class EncryptionMethodModal extends Modal {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
new Setting(contentEl)
|
new Setting(contentEl).addButton((button) => {
|
||||||
.addButton((button) => {
|
|
||||||
button.setButtonText(t("confirm"));
|
button.setButtonText(t("confirm"));
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
this.plugin.settings.encryptionMethod = this.newEncryptionMethod;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
this.close();
|
this.close();
|
||||||
});
|
});
|
||||||
button.setClass("encryptionmethod-second-confirm");
|
button.setClass("encryptionmethod-second-confirm");
|
||||||
})
|
|
||||||
.addButton((button) => {
|
|
||||||
button.setButtonText(t("goback"));
|
|
||||||
button.onClick(() => {
|
|
||||||
this.close();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1693,24 +1678,15 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setName(t("settings_encryptionmethod"))
|
.setName(t("settings_encryptionmethod"))
|
||||||
.setDesc(stringToFragment(t("settings_encryptionmethod_desc")))
|
.setDesc(stringToFragment(t("settings_encryptionmethod_desc")))
|
||||||
.addDropdown((dropdown) => {
|
.addDropdown((dropdown) => {
|
||||||
dropdown.addOption(
|
dropdown
|
||||||
"rclone-base64",
|
.addOption("rclone-base64", t("settings_encryptionmethod_rclone"))
|
||||||
t("settings_encryptionmethod_rclone")
|
.addOption("openssl-base64", t("settings_encryptionmethod_openssl"))
|
||||||
);
|
.setValue(this.plugin.settings.encryptionMethod ?? "rclone-base64")
|
||||||
dropdown.addOption(
|
.onChange(async (val: string) => {
|
||||||
"openssl-base64",
|
|
||||||
t("settings_encryptionmethod_openssl")
|
|
||||||
);
|
|
||||||
dropdown.onChange(async (val: string) => {
|
|
||||||
if (this.plugin.settings.password === "") {
|
|
||||||
this.plugin.settings.encryptionMethod = val as CipherMethodType;
|
this.plugin.settings.encryptionMethod = val as CipherMethodType;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
} else {
|
if (this.plugin.settings.password !== "") {
|
||||||
new EncryptionMethodModal(
|
new EncryptionMethodModal(this.app, this.plugin).open();
|
||||||
this.app,
|
|
||||||
this.plugin,
|
|
||||||
val as CipherMethodType
|
|
||||||
).open();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+91
-21
@@ -53,8 +53,9 @@ export interface PasswordCheckType {
|
|||||||
| "unknown_encryption_method"
|
| "unknown_encryption_method"
|
||||||
| "remote_encrypted_local_no_password"
|
| "remote_encrypted_local_no_password"
|
||||||
| "password_matched"
|
| "password_matched"
|
||||||
| "password_not_matched_or_remote_not_encrypted"
|
| "password_or_method_not_matched_or_remote_not_encrypted"
|
||||||
| "likely_no_password_both_sides";
|
| "likely_no_password_both_sides"
|
||||||
|
| "encryption_method_not_matched";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isPasswordOk = async (
|
export const isPasswordOk = async (
|
||||||
@@ -91,8 +92,19 @@ export const isPasswordOk = async (
|
|||||||
reason: "unknown_encryption_method",
|
reason: "unknown_encryption_method",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
Cipher.isLikelyEncryptedNameNotMatchMethod(santyCheckKey, cipher.method)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
reason: "encryption_method_not_matched",
|
||||||
|
};
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await cipher.decryptName(santyCheckKey);
|
const k = await cipher.decryptName(santyCheckKey);
|
||||||
|
if (k === undefined) {
|
||||||
|
throw Error(`decryption failed`);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
reason: "password_matched",
|
reason: "password_matched",
|
||||||
@@ -100,7 +112,7 @@ export const isPasswordOk = async (
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
reason: "password_not_matched_or_remote_not_encrypted",
|
reason: "password_or_method_not_matched_or_remote_not_encrypted",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -494,9 +506,41 @@ export const getSyncPlanInplace = async (
|
|||||||
mixedEntry.decisionBranch = 105;
|
mixedEntry.decisionBranch = 105;
|
||||||
mixedEntry.decision = "folder_to_skip";
|
mixedEntry.decision = "folder_to_skip";
|
||||||
} else if (howToCleanEmptyFolder === "clean_both") {
|
} else if (howToCleanEmptyFolder === "clean_both") {
|
||||||
|
if (local !== undefined && remote !== undefined) {
|
||||||
|
if (syncDirection === "bidirectional") {
|
||||||
mixedEntry.decisionBranch = 106;
|
mixedEntry.decisionBranch = 106;
|
||||||
mixedEntry.decision = "folder_to_be_deleted";
|
mixedEntry.decision = "folder_to_be_deleted_on_both";
|
||||||
// TODO: what to do in different sync direction?
|
} else {
|
||||||
|
// right now it does nothing because of "incremental"
|
||||||
|
// TODO: should we delete??
|
||||||
|
mixedEntry.decisionBranch = 109;
|
||||||
|
mixedEntry.decision = "folder_to_skip";
|
||||||
|
}
|
||||||
|
} else if (local !== undefined && remote === undefined) {
|
||||||
|
if (syncDirection === "bidirectional") {
|
||||||
|
mixedEntry.decisionBranch = 110;
|
||||||
|
mixedEntry.decision = "folder_to_be_deleted_on_local";
|
||||||
|
} else {
|
||||||
|
// right now it does nothing because of "incremental"
|
||||||
|
// TODO: should we delete??
|
||||||
|
mixedEntry.decisionBranch = 111;
|
||||||
|
mixedEntry.decision = "folder_to_skip";
|
||||||
|
}
|
||||||
|
} else if (local === undefined && remote !== undefined) {
|
||||||
|
if (syncDirection === "bidirectional") {
|
||||||
|
mixedEntry.decisionBranch = 112;
|
||||||
|
mixedEntry.decision = "folder_to_be_deleted_on_remote";
|
||||||
|
} else {
|
||||||
|
// right now it does nothing because of "incremental"
|
||||||
|
// TODO: should we delete??
|
||||||
|
mixedEntry.decisionBranch = 113;
|
||||||
|
mixedEntry.decision = "folder_to_skip";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no folder to delete, do nothing
|
||||||
|
mixedEntry.decisionBranch = 114;
|
||||||
|
mixedEntry.decision = "folder_to_skip";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Error(
|
throw Error(
|
||||||
`do not know how to deal with empty folder ${mixedEntry.key}`
|
`do not know how to deal with empty folder ${mixedEntry.key}`
|
||||||
@@ -694,11 +738,9 @@ export const getSyncPlanInplace = async (
|
|||||||
keptFolder.add(getParentFolder(key));
|
keptFolder.add(getParentFolder(key));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Error(
|
mixedEntry.decisionBranch = 36;
|
||||||
`remote is created (branch 3) but size larger than ${skipSizeLargerThan}, don't know what to do: ${JSON.stringify(
|
mixedEntry.decision = "remote_is_created_too_large_then_do_nothing";
|
||||||
mixedEntry
|
keptFolder.add(getParentFolder(key));
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
(prevSync.mtimeSvr === remote.mtimeCli ||
|
(prevSync.mtimeSvr === remote.mtimeCli ||
|
||||||
@@ -757,11 +799,9 @@ export const getSyncPlanInplace = async (
|
|||||||
keptFolder.add(getParentFolder(key));
|
keptFolder.add(getParentFolder(key));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw Error(
|
mixedEntry.decisionBranch = 37;
|
||||||
`local is created (branch 6) but size larger than ${skipSizeLargerThan}, don't know what to do: ${JSON.stringify(
|
mixedEntry.decision = "local_is_created_too_large_then_do_nothing";
|
||||||
mixedEntry
|
keptFolder.add(getParentFolder(key));
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
(prevSync.mtimeSvr === local.mtimeCli ||
|
(prevSync.mtimeSvr === local.mtimeCli ||
|
||||||
@@ -855,6 +895,8 @@ const splitThreeStepsOnEntityMappings = (
|
|||||||
val.decision === "equal" ||
|
val.decision === "equal" ||
|
||||||
val.decision === "conflict_created_then_do_nothing" ||
|
val.decision === "conflict_created_then_do_nothing" ||
|
||||||
val.decision === "folder_existed_both_then_do_nothing" ||
|
val.decision === "folder_existed_both_then_do_nothing" ||
|
||||||
|
val.decision === "local_is_created_too_large_then_do_nothing" ||
|
||||||
|
val.decision === "remote_is_created_too_large_then_do_nothing" ||
|
||||||
val.decision === "folder_to_skip"
|
val.decision === "folder_to_skip"
|
||||||
) {
|
) {
|
||||||
// pass
|
// pass
|
||||||
@@ -877,7 +919,9 @@ const splitThreeStepsOnEntityMappings = (
|
|||||||
val.decision === "only_history" ||
|
val.decision === "only_history" ||
|
||||||
val.decision === "local_is_deleted_thus_also_delete_remote" ||
|
val.decision === "local_is_deleted_thus_also_delete_remote" ||
|
||||||
val.decision === "remote_is_deleted_thus_also_delete_local" ||
|
val.decision === "remote_is_deleted_thus_also_delete_local" ||
|
||||||
val.decision === "folder_to_be_deleted"
|
val.decision === "folder_to_be_deleted_on_both" ||
|
||||||
|
val.decision === "folder_to_be_deleted_on_local" ||
|
||||||
|
val.decision === "folder_to_be_deleted_on_remote"
|
||||||
) {
|
) {
|
||||||
const level = atWhichLevel(key);
|
const level = atWhichLevel(key);
|
||||||
const k = deletionOps[level - 1];
|
const k = deletionOps[level - 1];
|
||||||
@@ -888,7 +932,11 @@ const splitThreeStepsOnEntityMappings = (
|
|||||||
}
|
}
|
||||||
realTotalCount += 1;
|
realTotalCount += 1;
|
||||||
|
|
||||||
if (val.decision.startsWith("deleted")) {
|
if (
|
||||||
|
val.decision.includes("deleted") &&
|
||||||
|
!val.decision.includes("folder")
|
||||||
|
) {
|
||||||
|
// only count files here, skip folder
|
||||||
realModifyDeleteCount += 1;
|
realModifyDeleteCount += 1;
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
@@ -915,8 +963,8 @@ const splitThreeStepsOnEntityMappings = (
|
|||||||
realTotalCount += 1;
|
realTotalCount += 1;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
val.decision.startsWith("modified") ||
|
val.decision.includes("modified") ||
|
||||||
val.decision.startsWith("conflict")
|
val.decision.includes("conflict")
|
||||||
) {
|
) {
|
||||||
realModifyDeleteCount += 1;
|
realModifyDeleteCount += 1;
|
||||||
}
|
}
|
||||||
@@ -963,6 +1011,8 @@ const dispatchOperationToActualV3 = async (
|
|||||||
} else if (
|
} else if (
|
||||||
r.decision === "equal" ||
|
r.decision === "equal" ||
|
||||||
r.decision === "conflict_created_then_do_nothing" ||
|
r.decision === "conflict_created_then_do_nothing" ||
|
||||||
|
r.decision === "local_is_created_too_large_then_do_nothing" ||
|
||||||
|
r.decision === "remote_is_created_too_large_then_do_nothing" ||
|
||||||
r.decision === "folder_to_skip" ||
|
r.decision === "folder_to_skip" ||
|
||||||
r.decision === "folder_existed_both_then_do_nothing"
|
r.decision === "folder_existed_both_then_do_nothing"
|
||||||
) {
|
) {
|
||||||
@@ -1063,9 +1113,23 @@ const dispatchOperationToActualV3 = async (
|
|||||||
profileID,
|
profileID,
|
||||||
entity
|
entity
|
||||||
);
|
);
|
||||||
} else if (r.decision === "folder_to_be_deleted") {
|
} else if (
|
||||||
|
r.decision === "folder_to_be_deleted_on_both" ||
|
||||||
|
r.decision === "folder_to_be_deleted_on_local" ||
|
||||||
|
r.decision === "folder_to_be_deleted_on_remote"
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
r.decision === "folder_to_be_deleted_on_both" ||
|
||||||
|
r.decision === "folder_to_be_deleted_on_local"
|
||||||
|
) {
|
||||||
await localDeleteFunc(r.key);
|
await localDeleteFunc(r.key);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
r.decision === "folder_to_be_deleted_on_both" ||
|
||||||
|
r.decision === "folder_to_be_deleted_on_remote"
|
||||||
|
) {
|
||||||
await client.deleteFromRemote(r.key, cipher, r.remote!.keyEnc);
|
await client.deleteFromRemote(r.key, cipher, r.remote!.keyEnc);
|
||||||
|
}
|
||||||
await clearPrevSyncRecordByVaultAndProfile(
|
await clearPrevSyncRecordByVaultAndProfile(
|
||||||
db,
|
db,
|
||||||
vaultRandomID,
|
vaultRandomID,
|
||||||
@@ -1115,6 +1179,12 @@ export const doActualSync = async (
|
|||||||
allFilesCount > 0
|
allFilesCount > 0
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
|
protectModifyPercentage === 100 &&
|
||||||
|
realModifyDeleteCount === allFilesCount
|
||||||
|
) {
|
||||||
|
// special treatment for 100%
|
||||||
|
// let it pass, we do nothing here
|
||||||
|
} else if (
|
||||||
realModifyDeleteCount * 100 >=
|
realModifyDeleteCount * 100 >=
|
||||||
allFilesCount * protectModifyPercentage
|
allFilesCount * protectModifyPercentage
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user