mirror of
https://github.com/remotely-save/remotely-save.git
synced 2024-06-07 21:10:45 +00:00
add protection!
This commit is contained in:
parent
c828a341ba
commit
7ec0102c7a
@ -107,6 +107,8 @@ export interface RemotelySavePluginSettings {
|
||||
conflictAction?: ConflictActionType;
|
||||
howToCleanEmptyFolder?: EmptyFolderCleanType;
|
||||
|
||||
protectModifyPercentage?: number;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
@ -23,6 +23,7 @@
|
||||
"syncrun_shortstep2skip": "2/2 Remotely Save real sync is skipped in dry run mode.",
|
||||
"syncrun_shortstep2": "2/2 Remotely Save finish!",
|
||||
"syncrun_abort": "{{manifestID}}-{{theDate}}: abort sync, triggerSource={{triggerSource}}, error while {{syncStatus}}",
|
||||
"syncrun_abort_protectmodifypercentage": "Abort! you set changing files >= {{protectModifyPercentage}}% is not allowed but {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% is going to be modified or deleted! If you are sure you want this sync, please adjust the allowed ratio in the settings.",
|
||||
"protocol_saveqr": "New not-oauth2 settings for {{manifestName}} saved. Reopen the plugin Settings to the effect.",
|
||||
"protocol_callbacknotsupported": "Your uri call a callback that's not supported yet: {{params}}",
|
||||
"protocol_dropbox_connecting": "Connecting to Dropbox...\nPlease DO NOT close this modal.",
|
||||
@ -253,6 +254,11 @@
|
||||
"settings_cleanemptyfolder_desc": "The sync algorithm majorly deals with files, so you need to specify how to deal with empty folders.",
|
||||
"settings_cleanemptyfolder_skip": "leave them as is (default)",
|
||||
"settings_cleanemptyfolder_clean_both": "delete local and remote",
|
||||
"settings_protectmodifypercentage": "Abort Sync If Modification Above Percentage",
|
||||
"settings_protectmodifypercentage_desc": "Abort the sync if more than n% of the files are going to be deleted / modified. Useful to protect users' files from unexpected modifications. You can set to 100 to disable the protection, or set to 0 to always block the sync.",
|
||||
"settings_protectmodifypercentage_000_desc": "0 (always block)",
|
||||
"settings_protectmodifypercentage_050_desc": "50 (default)",
|
||||
"settings_protectmodifypercentage_100_desc": "100 (disable the protection)",
|
||||
"settings_importexport": "Import and Export Partial Settings",
|
||||
"settings_export": "Export",
|
||||
"settings_export_desc": "Export not-oauth2 settings by generating a qrcode.",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"syncrun_shortstep2skip": "2/2 Remotely Save 在空跑模式,跳过实际数据交换步骤。",
|
||||
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
||||
"syncrun_abort": "{{manifestID}}-{{theDate}}:中断同步,同步来源={{triggerSource}},出错阶段={{syncStatus}}",
|
||||
"syncrun_abort_protectmodifypercentage": "中断同步!您设置了不允许 >= {{protectModifyPercentage}}% 的变更,但是现在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的文件会被修改或删除!如果您确认这次同步是您想要的,那么请在设置里修改允许比例。",
|
||||
"protocol_saveqr": " {{manifestName}} 新的非 oauth2 设置保存完成。请重启插件设置页使之生效。",
|
||||
"protocol_callbacknotsupported": "您的 uri callback 暂不支持: {{params}}",
|
||||
"protocol_dropbox_connecting": "正在连接 Dropbox……\n请不要关闭此弹窗。",
|
||||
@ -253,6 +254,11 @@
|
||||
"settings_cleanemptyfolder_desc": "同步算法主要是针对文件处理的,您要要手动指定空文件夹如何处理。",
|
||||
"settings_cleanemptyfolder_skip": "跳过处理空文件夹(默认)",
|
||||
"settings_cleanemptyfolder_clean_both": "删除本地和服务器的空文件夹",
|
||||
"settings_protectmodifypercentage": "如果修改超过百分比则中止同步",
|
||||
"settings_protectmodifypercentage_desc": "如果算法检测到超过 n% 的文件会被修改或删除,则中止同步。从而可以保护用户的文件免受预料之外的修改。您可以设置为 100 而去除此保护,也可以设置为 0 总是强制中止所有同步。",
|
||||
"settings_protectmodifypercentage_000_desc": "0(总是强制中止)",
|
||||
"settings_protectmodifypercentage_050_desc": "50(默认值)",
|
||||
"settings_protectmodifypercentage_100_desc": "100(去除此保护)",
|
||||
"settings_importexport": "导入导出部分设置",
|
||||
"settings_export": "导出",
|
||||
"settings_export_desc": "用 QR 码导出非 oauth2 的设置信息。",
|
||||
|
@ -23,6 +23,7 @@
|
||||
"syncrun_shortstep2skip": "2/2 Remotely Save 在空跑模式,跳過實際資料交換步驟。",
|
||||
"syncrun_shortstep2": "2/2 Remotely Save 已完成同步!",
|
||||
"syncrun_abort": "{{manifestID}}-{{theDate}}:中斷同步,同步來源={{triggerSource}},出錯階段={{syncStatus}}",
|
||||
"syncrun_abort_protectmodifypercentage": "中斷同步!您設定了不允許 >= {{protectModifyPercentage}}% 的變更,但是現在 {{realModifyDeleteCount}}/{{allFilesCount}}={{percent}}% 的檔案會被修改或刪除!如果您確認這次同步是您想要的,那麼請在設定裡修改允許比例。",
|
||||
"protocol_saveqr": " {{manifestName}} 新的非 oauth2 設定儲存完成。請重啟外掛設定頁使之生效。",
|
||||
"protocol_callbacknotsupported": "您的 uri callback 暫不支援: {{params}}",
|
||||
"protocol_dropbox_connecting": "正在連線 Dropbox……\n請不要關閉此彈窗。",
|
||||
@ -253,6 +254,11 @@
|
||||
"settings_cleanemptyfolder_desc": "同步演算法主要是針對檔案處理的,您需要手動指定空資料夾如何處理。",
|
||||
"settings_cleanemptyfolder_skip": "跳過處理空資料夾(預設)",
|
||||
"settings_cleanemptyfolder_clean_both": "刪除本地和伺服器的空資料夾",
|
||||
"settings_protectmodifypercentage": "如果修改超過百分比則中止同步",
|
||||
"settings_protectmodifypercentage_desc": "如果演算法檢測到超過 n% 的檔案會被修改或刪除,則中止同步。從而可以保護使用者的檔案免受預料之外的修改。您可以設定為 100 而去除此保護,也可以設定為 0 總是強制中止所有同步。",
|
||||
"settings_protectmodifypercentage_000_desc": "0(總是強制中止)",
|
||||
"settings_protectmodifypercentage_050_desc": "50(預設值)",
|
||||
"settings_protectmodifypercentage_100_desc": "100(去除此保護)",
|
||||
"settings_importexport": "匯入匯出部分設定",
|
||||
"settings_export": "匯出",
|
||||
"settings_export_desc": "用 QR 碼匯出非 oauth2 的設定資訊。",
|
||||
|
22
src/main.ts
22
src/main.ts
@ -93,6 +93,7 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||
agreeToUseSyncV3: false,
|
||||
conflictAction: "keep_newer",
|
||||
howToCleanEmptyFolder: "skip",
|
||||
protectModifyPercentage: 50,
|
||||
};
|
||||
|
||||
interface OAuth2Info {
|
||||
@ -338,6 +339,24 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
this.settings.password,
|
||||
this.settings.concurrency ?? 5,
|
||||
(key: string) => self.trash(key),
|
||||
this.settings.protectModifyPercentage ?? 50,
|
||||
(
|
||||
protectModifyPercentage: number,
|
||||
realModifyDeleteCount: number,
|
||||
allFilesCount: number
|
||||
) => {
|
||||
const percent = (
|
||||
(100 * realModifyDeleteCount) /
|
||||
allFilesCount
|
||||
).toFixed(1);
|
||||
const res = t("syncrun_abort_protectmodifypercentage", {
|
||||
protectModifyPercentage,
|
||||
realModifyDeleteCount,
|
||||
allFilesCount,
|
||||
percent,
|
||||
});
|
||||
return res;
|
||||
},
|
||||
(
|
||||
realCounter: number,
|
||||
realTotalCount: number,
|
||||
@ -866,6 +885,9 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
if (this.settings.howToCleanEmptyFolder === undefined) {
|
||||
this.settings.howToCleanEmptyFolder = "skip";
|
||||
}
|
||||
if (this.settings.protectModifyPercentage === undefined) {
|
||||
this.settings.protectModifyPercentage = 50;
|
||||
}
|
||||
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
@ -1968,6 +1968,29 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(advDiv)
|
||||
.setName(t("settings_protectmodifypercentage"))
|
||||
.setDesc(t("settings_protectmodifypercentage_desc"))
|
||||
.addDropdown((dropdown) => {
|
||||
for (const i of Array.from({ length: 11 }, (x, i) => i * 10)) {
|
||||
let desc = `${i}`;
|
||||
if (i === 0) {
|
||||
desc = t("settings_protectmodifypercentage_000_desc");
|
||||
} else if (i === 50) {
|
||||
desc = t("settings_protectmodifypercentage_050_desc");
|
||||
} else if (i === 100) {
|
||||
desc = t("settings_protectmodifypercentage_100_desc");
|
||||
}
|
||||
dropdown.addOption(`${i}`, desc);
|
||||
}
|
||||
dropdown
|
||||
.setValue(`${this.plugin.settings.protectModifyPercentage ?? 50}`)
|
||||
.onChange(async (val) => {
|
||||
this.plugin.settings.protectModifyPercentage = parseInt(val);
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// below for import and export functions
|
||||
//////////////////////////////////////////////////
|
||||
|
58
src/sync.ts
58
src/sync.ts
@ -788,12 +788,18 @@ const splitThreeStepsOnEntityMappings = (
|
||||
(k1, k2) => k2.length - k1.length
|
||||
);
|
||||
|
||||
let realTotalCount = 0;
|
||||
let allFilesCount = 0; // how many files in entities
|
||||
let realModifyDeleteCount = 0; // how many files to be modified / deleted
|
||||
let realTotalCount = 0; // how many files to be delt with
|
||||
|
||||
for (let i = 0; i < sortedKeys.length; ++i) {
|
||||
const key = sortedKeys[i];
|
||||
const val = mixedEntityMappings[key];
|
||||
|
||||
if (!key.endsWith("/")) {
|
||||
allFilesCount += 1;
|
||||
}
|
||||
|
||||
if (
|
||||
val.decision === "equal" ||
|
||||
val.decision === "folder_existed_both" ||
|
||||
@ -829,6 +835,10 @@ const splitThreeStepsOnEntityMappings = (
|
||||
k.push(val);
|
||||
}
|
||||
realTotalCount += 1;
|
||||
|
||||
if (val.decision.startsWith("deleted")) {
|
||||
realModifyDeleteCount += 1;
|
||||
}
|
||||
} else if (
|
||||
val.decision === "modified_local" ||
|
||||
val.decision === "modified_remote" ||
|
||||
@ -851,6 +861,13 @@ const splitThreeStepsOnEntityMappings = (
|
||||
uploadDownloads[0].push(val); // only one level is needed here
|
||||
}
|
||||
realTotalCount += 1;
|
||||
|
||||
if (
|
||||
val.decision.startsWith("modified") ||
|
||||
val.decision.startsWith("conflict")
|
||||
) {
|
||||
realModifyDeleteCount += 1;
|
||||
}
|
||||
} else {
|
||||
throw Error(`unknown decision ${val.decision} for ${key}`);
|
||||
}
|
||||
@ -865,6 +882,8 @@ const splitThreeStepsOnEntityMappings = (
|
||||
folderCreationOps: folderCreationOps,
|
||||
deletionOps: deletionOps,
|
||||
uploadDownloads: uploadDownloads,
|
||||
allFilesCount: allFilesCount,
|
||||
realModifyDeleteCount: realModifyDeleteCount,
|
||||
realTotalCount: realTotalCount,
|
||||
};
|
||||
};
|
||||
@ -1014,16 +1033,47 @@ export const doActualSync = async (
|
||||
password: string,
|
||||
concurrency: number,
|
||||
localDeleteFunc: any,
|
||||
protectModifyPercentage: number,
|
||||
getProtectModifyPercentageErrorStrFunc: any,
|
||||
callbackSyncProcess: any,
|
||||
db: InternalDBs
|
||||
) => {
|
||||
console.debug(`concurrency === ${concurrency}`);
|
||||
const { folderCreationOps, deletionOps, uploadDownloads, realTotalCount } =
|
||||
splitThreeStepsOnEntityMappings(mixedEntityMappings);
|
||||
const {
|
||||
folderCreationOps,
|
||||
deletionOps,
|
||||
uploadDownloads,
|
||||
allFilesCount,
|
||||
realModifyDeleteCount,
|
||||
realTotalCount,
|
||||
} = splitThreeStepsOnEntityMappings(mixedEntityMappings);
|
||||
// console.debug(`folderCreationOps: ${JSON.stringify(folderCreationOps)}`);
|
||||
// console.debug(`deletionOps: ${JSON.stringify(deletionOps)}`);
|
||||
// console.debug(`uploadDownloads: ${JSON.stringify(uploadDownloads)}`);
|
||||
// console.debug(`realTotalCount: ${JSON.stringify(realTotalCount)}`);
|
||||
console.debug(`allFilesCount: ${allFilesCount}`);
|
||||
console.debug(`realModifyDeleteCount: ${realModifyDeleteCount}`);
|
||||
console.debug(`realTotalCount: ${realTotalCount}`);
|
||||
|
||||
console.debug(`protectModifyPercentage: ${protectModifyPercentage}`);
|
||||
|
||||
if (
|
||||
protectModifyPercentage >= 0 &&
|
||||
realModifyDeleteCount >= 0 &&
|
||||
allFilesCount > 0
|
||||
) {
|
||||
if (
|
||||
realModifyDeleteCount * 100 >=
|
||||
allFilesCount * protectModifyPercentage
|
||||
) {
|
||||
const errorStr: string = getProtectModifyPercentageErrorStrFunc(
|
||||
protectModifyPercentage,
|
||||
realModifyDeleteCount,
|
||||
allFilesCount
|
||||
);
|
||||
|
||||
throw Error(errorStr);
|
||||
}
|
||||
}
|
||||
|
||||
const nested = [folderCreationOps, deletionOps, uploadDownloads];
|
||||
const logTexts = [
|
||||
|
Loading…
Reference in New Issue
Block a user