diff --git a/src/sync.ts b/pro/src/sync.ts similarity index 90% rename from src/sync.ts rename to pro/src/sync.ts index f5bb053..578343e 100644 --- a/src/sync.ts +++ b/pro/src/sync.ts @@ -2,13 +2,6 @@ import AggregateError from "aggregate-error"; import PQueue from "p-queue"; import XRegExp from "xregexp"; -import { checkProRunnableAndFixInplace } from "../pro/src/account"; -import { duplicateFile, isMergable, mergeFile } from "../pro/src/conflictLogic"; -import { - clearFileContentHistoryByVaultAndProfile, - getFileContentHistoryByVaultAndProfile, - upsertFileContentHistoryByVaultAndProfile, -} from "../pro/src/localdb"; import type { ConflictActionType, EmptyFolderCleanType, @@ -18,21 +11,21 @@ import type { SUPPORTED_SERVICES_TYPE, SyncDirectionType, SyncTriggerSourceType, -} from "./baseTypes"; -import { copyFile, copyFileOrFolder, copyFolder } from "./copyLogic"; -import type { FakeFs } from "./fsAll"; -import type { FakeFsEncrypt } from "./fsEncrypt"; +} from "../../src/baseTypes"; +import { copyFile, copyFileOrFolder, copyFolder } from "../../src/copyLogic"; +import type { FakeFs } from "../../src/fsAll"; +import type { FakeFsEncrypt } from "../../src/fsEncrypt"; import { type InternalDBs, clearPrevSyncRecordByVaultAndProfile, getAllPrevSyncRecordsByVaultAndProfile, insertSyncPlanRecordByVault, upsertPrevSyncRecordByVaultAndProfile, -} from "./localdb"; +} from "../../src/localdb"; import { DEFAULT_FILE_NAME_FOR_METADATAONREMOTE, DEFAULT_FILE_NAME_FOR_METADATAONREMOTE2, -} from "./metadataOnRemote"; +} from "../../src/metadataOnRemote"; import { atWhichLevel, getParentFolder, @@ -40,8 +33,15 @@ import { isSpecialFolderNameToSkip, roughSizeOfObject, unixTimeToStr, -} from "./misc"; -import type { Profiler } from "./profiler"; +} from "../../src/misc"; +import type { Profiler } from "../../src/profiler"; +import { checkProRunnableAndFixInplace } from "./account"; +import { duplicateFile, isMergable, mergeFile } from "./conflictLogic"; +import { + clearFileContentHistoryByVaultAndProfile, + getFileContentHistoryByVaultAndProfile, + upsertFileContentHistoryByVaultAndProfile, +} from "./localdb"; const copyEntityAndFixTimeFormat = ( src: Entity, @@ -291,7 +291,6 @@ const ensembleMixedEnties = async ( */ const getSyncPlanInplace = async ( mixedEntityMappings: Record, - howToCleanEmptyFolder: EmptyFolderCleanType, skipSizeLargerThan: number, conflictAction: ConflictActionType, syncDirection: SyncDirectionType, @@ -309,7 +308,6 @@ const getSyncPlanInplace = async ( profiler?.insertSize("sizeof sortedKeys", sortedKeys); const keptFolder = new Set(); - const mayDeleteFolder = new Set(); for (let i = 0; i < sortedKeys.length; ++i) { if (i % 100 === 0) { @@ -331,7 +329,6 @@ const getSyncPlanInplace = async ( // parent should also be kept // console.debug(`${key} in keptFolder`) keptFolder.add(getParentFolder(key)); - mayDeleteFolder.delete(getParentFolder(key)); // should fill the missing part if (local !== undefined && remote !== undefined) { mixedEntry.decisionBranch = 101; @@ -366,110 +363,102 @@ const getSyncPlanInplace = async ( mixedEntry.change = true; } keptFolder.delete(key); // no need to save it in the Set later - mayDeleteFolder.delete(key); // must ignore this } else { - if (howToCleanEmptyFolder === "skip") { - mixedEntry.decisionBranch = 105; - mixedEntry.decision = "folder_to_skip"; + if (local !== undefined && remote !== undefined) { + // both exist, do nothing + mixedEntry.decisionBranch = 121; + mixedEntry.decision = "folder_existed_both_then_do_nothing"; mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } else if (howToCleanEmptyFolder === "clean_both") { - if (local !== undefined && remote !== undefined) { - if (syncDirection === "bidirectional") { - if (mayDeleteFolder.has(key)) { - // from 0.5.6 and on, - // we only delete the folders caused by file deletion - mixedEntry.decisionBranch = 106; - mixedEntry.decision = "folder_to_be_deleted_on_both"; - mixedEntry.change = true; - mayDeleteFolder.add(getParentFolder(key)); - mayDeleteFolder.delete(key); // good to remove now - } else { - mixedEntry.decisionBranch = 115; - mixedEntry.decision = "folder_existed_both_then_do_nothing"; - mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } - } else { - // right now it does nothing because of "incremental" - // TODO: should we delete?? - mixedEntry.decisionBranch = 109; + keptFolder.add(getParentFolder(key)); + } else if (local !== undefined && remote === undefined) { + if (prevSync !== undefined) { + // then the folder is deleted on remote + if (syncDirection === "incremental_push_only") { + mixedEntry.decisionBranch = 122; + mixedEntry.decision = "folder_to_skip"; + keptFolder.add(getParentFolder(key)); + mixedEntry.change = false; + } else if (syncDirection === "incremental_pull_only") { + mixedEntry.decisionBranch = 123; mixedEntry.decision = "folder_to_skip"; mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } - } else if (local !== undefined && remote === undefined) { - if (syncDirection === "bidirectional") { - if (mayDeleteFolder.has(key)) { - // from 0.5.6 and on, - // we only delete the folders caused by file deletion - mixedEntry.decisionBranch = 110; - mixedEntry.decision = "folder_to_be_deleted_on_local"; - mixedEntry.change = true; - mayDeleteFolder.add(getParentFolder(key)); - mayDeleteFolder.delete(key); // good to remove now - } else { - // the folder might be created locally - // so we want to create it remotely as well. - mixedEntry.decisionBranch = 116; - mixedEntry.decision = - "folder_existed_local_then_also_create_remote"; - mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } + keptFolder.add(getParentFolder(key)); } else { - // right now it does nothing because of "incremental" - // TODO: should we delete?? - mixedEntry.decisionBranch = 111; - mixedEntry.decision = "folder_to_skip"; - mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } - } else if (local === undefined && remote !== undefined) { - if (syncDirection === "bidirectional") { - if (mayDeleteFolder.has(key)) { - // from 0.5.6 and on, - // we only delete the folders caused by file deletion - mixedEntry.decisionBranch = 112; - mixedEntry.decision = "folder_to_be_deleted_on_remote"; - mixedEntry.change = true; - mayDeleteFolder.add(getParentFolder(key)); - mayDeleteFolder.delete(key); // good to remove now - } else { - // the folder might be created remotely - // so we want to create it locally as well. - mixedEntry.decisionBranch = 117; - mixedEntry.decision = - "folder_existed_remote_then_also_create_local"; - mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! - } - } else { - // right now it does nothing because of "incremental" - // TODO: should we delete?? - mixedEntry.decisionBranch = 113; - mixedEntry.decision = "folder_to_skip"; - mixedEntry.change = false; - keptFolder.add(getParentFolder(key)); // we want to keep parent! - mayDeleteFolder.delete(getParentFolder(key)); // we don't want to delete parent! + // bidirectional + mixedEntry.decisionBranch = 124; + mixedEntry.decision = "folder_to_be_deleted_on_local"; + mixedEntry.change = true; } } else { - // local === undefined && remote === undefined - // no folder to delete, do nothing - mixedEntry.decisionBranch = 114; - mixedEntry.decision = "folder_to_skip"; - mixedEntry.change = false; + // then the folder is created on local + + if (syncDirection === "incremental_push_only") { + mixedEntry.decisionBranch = 125; + mixedEntry.decision = + "folder_existed_local_then_also_create_remote"; + mixedEntry.change = true; + keptFolder.add(getParentFolder(key)); + } else if (syncDirection === "incremental_pull_only") { + mixedEntry.decisionBranch = 126; + mixedEntry.decision = "folder_to_skip"; + mixedEntry.change = false; + keptFolder.add(getParentFolder(key)); + } else { + // bidirectional + mixedEntry.decisionBranch = 127; + mixedEntry.decision = + "folder_existed_local_then_also_create_remote"; + mixedEntry.change = true; + keptFolder.add(getParentFolder(key)); + } + } + } else if (local === undefined && remote !== undefined) { + if (prevSync !== undefined) { + // then the folder is deleted on local + if (syncDirection === "incremental_push_only") { + mixedEntry.decisionBranch = 128; + mixedEntry.decision = "folder_to_skip"; + mixedEntry.change = false; + keptFolder.add(getParentFolder(key)); + } else if (syncDirection === "incremental_pull_only") { + mixedEntry.decisionBranch = 129; + mixedEntry.decision = "folder_to_skip"; + mixedEntry.change = false; + keptFolder.add(getParentFolder(key)); + } else { + // bidirectional + mixedEntry.decisionBranch = 130; + mixedEntry.decision = "folder_to_be_deleted_on_remote"; + mixedEntry.change = true; + } + } else { + // then the folder is created on remote + if (syncDirection === "incremental_push_only") { + mixedEntry.decisionBranch = 131; + mixedEntry.decision = "folder_to_skip"; + mixedEntry.change = false; + keptFolder.add(getParentFolder(key)); + } else if (syncDirection === "incremental_pull_only") { + mixedEntry.decisionBranch = 132; + mixedEntry.decision = + "folder_existed_remote_then_also_create_local"; + mixedEntry.change = true; + keptFolder.add(getParentFolder(key)); + } else { + // bidirectional + mixedEntry.decisionBranch = 133; + mixedEntry.decision = + "folder_existed_remote_then_also_create_local"; + mixedEntry.change = true; + keptFolder.add(getParentFolder(key)); + } } } else { - throw Error( - `do not know how to deal with empty folder ${mixedEntry.key}` - ); + // local === undefined && remote === undefined + // no folder to delete or create, do nothing + mixedEntry.decisionBranch = 134; + mixedEntry.decision = "folder_to_skip"; + mixedEntry.change = false; } } } else { @@ -712,7 +701,6 @@ const getSyncPlanInplace = async ( mixedEntry.decisionBranch = 4; mixedEntry.decision = "local_is_deleted_thus_also_delete_remote"; mixedEntry.change = true; - mayDeleteFolder.add(getParentFolder(key)); } } else { // if B is in the previous list and MODIFIED, B has been deleted by A but modified by B @@ -780,7 +768,6 @@ const getSyncPlanInplace = async ( mixedEntry.decisionBranch = 7; mixedEntry.decision = "remote_is_deleted_thus_also_delete_local"; mixedEntry.change = true; - mayDeleteFolder.add(getParentFolder(key)); } } else { // if A is in the previous list and MODIFIED, A has been deleted by B but modified by A @@ -826,9 +813,6 @@ const getSyncPlanInplace = async ( keptFolder.delete("/"); keptFolder.delete(""); - mayDeleteFolder.delete("/"); - mayDeleteFolder.delete(""); - if (keptFolder.size > 0) { throw Error(`unexpectedly keptFolder no decisions: ${[...keptFolder]}`); } @@ -1643,7 +1627,6 @@ export async function syncer( mixedEntityMappings = await getSyncPlanInplace( mixedEntityMappings, - settings.howToCleanEmptyFolder ?? "clean_both", settings.skipSizeLargerThan ?? -1, settings.conflictAction ?? "keep_newer", settings.syncDirection ?? "bidirectional", diff --git a/src/baseTypes.ts b/src/baseTypes.ts index 1eb9d4c..c473e5e 100644 --- a/src/baseTypes.ts +++ b/src/baseTypes.ts @@ -165,7 +165,6 @@ export interface RemotelySavePluginSettings { enableStatusBarInfo?: boolean; deleteToWhere?: "system" | "obsidian"; conflictAction?: ConflictActionType; - howToCleanEmptyFolder?: EmptyFolderCleanType; protectModifyPercentage?: number; syncDirection?: SyncDirectionType; @@ -194,6 +193,11 @@ export interface RemotelySavePluginSettings { * @deprecated */ logToDB?: boolean; + + /** + * @deprecated + */ + howToCleanEmptyFolder?: EmptyFolderCleanType; } export const COMMAND_URI = "remotely-save"; diff --git a/src/debugMode.ts b/src/debugMode.ts index e18acc7..3724f0b 100644 --- a/src/debugMode.ts +++ b/src/debugMode.ts @@ -1,5 +1,6 @@ import type { Vault } from "obsidian"; +import type { SyncPlanType } from "../pro/src/sync"; import { DEFAULT_DEBUG_FOLDER, DEFAULT_PROFILER_RESULT_FILE_PREFIX, @@ -11,7 +12,6 @@ import { } from "./localdb"; import type { InternalDBs } from "./localdb"; import { mkdirpInVault } from "./misc"; -import type { SyncPlanType } from "./sync"; const getSubsetOfSyncPlan = (x: string, onlyChange: boolean) => { if (!onlyChange) { diff --git a/src/localdb.ts b/src/localdb.ts index 994319e..be2a990 100644 --- a/src/localdb.ts +++ b/src/localdb.ts @@ -6,9 +6,9 @@ ep2(localforage); export type LocalForage = typeof localforage; import { nanoid } from "nanoid"; +import type { SyncPlanType } from "../pro/src/sync"; import type { Entity, SUPPORTED_SERVICES_TYPE } from "./baseTypes"; import { unixTimeToStr } from "./misc"; -import type { SyncPlanType } from "./sync"; const DB_VERSION_NUMBER_IN_HISTORY = [20211114, 20220108, 20220326, 20240220]; export const DEFAULT_DB_VERSION_NUMBER: number = 20240220; diff --git a/src/main.ts b/src/main.ts index 2658c0c..1f8ae18 100644 --- a/src/main.ts +++ b/src/main.ts @@ -48,6 +48,7 @@ import { sendAuthReq as sendAuthReqYandexDisk, setConfigBySuccessfullAuthInplace as setConfigBySuccessfullAuthInplaceYandexDisk, } from "../pro/src/fsYandexDisk"; +import { syncer } from "../pro/src/sync"; import type { RemotelySavePluginSettings, SyncTriggerSourceType, @@ -93,7 +94,6 @@ import { import { changeMobileStatusBar } from "./misc"; import { DEFAULT_PROFILER_CONFIG, type Profiler } from "./profiler"; import { RemotelySaveSettingTab } from "./settings"; -import { syncer } from "./sync"; import { SyncAlgoV3Modal } from "./syncAlgoV3Notice"; const DEFAULT_SETTINGS: RemotelySavePluginSettings = { diff --git a/src/settings.ts b/src/settings.ts index cdf58cb..99ea459 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2250,24 +2250,6 @@ export class RemotelySaveSettingTab extends PluginSettingTab { }); }); - new Setting(advDiv) - .setName(t("settings_cleanemptyfolder")) - .setDesc(t("settings_cleanemptyfolder_desc")) - .addDropdown((dropdown) => { - dropdown.addOption("skip", t("settings_cleanemptyfolder_skip")); - dropdown.addOption( - "clean_both", - t("settings_cleanemptyfolder_clean_both") - ); - dropdown - .setValue(this.plugin.settings.howToCleanEmptyFolder ?? "clean_both") - .onChange(async (val) => { - this.plugin.settings.howToCleanEmptyFolder = - val as EmptyFolderCleanType; - await this.plugin.saveSettings(); - }); - }); - const percentage1 = new Setting(advDiv) .setName(t("settings_protectmodifypercentage")) .setDesc(t("settings_protectmodifypercentage_desc"));