import type Sync from "./sync"; import { type SyncMode } from "../types"; import { type LocalTree, type LocalTreeError, type LocalTreeIgnored } from "./filesystems/local"; import { type RemoteTree } from "./filesystems/remote"; export type Delta = { path: string; } & ({ type: "uploadFile"; size: number; md5Hash?: string; } | { type: "createRemoteDirectory"; } | { type: "createLocalDirectory"; } | { type: "deleteLocalFile"; } | { type: "deleteRemoteFile"; } | { type: "deleteLocalDirectory"; } | { type: "deleteRemoteDirectory"; } | { type: "downloadFile"; size: number; } | { type: "renameLocalFile"; from: string; to: string; } | { type: "renameRemoteFile"; from: string; to: string; } | { type: "renameRemoteDirectory"; from: string; to: string; } | { type: "renameLocalDirectory"; from: string; to: string; }); export declare function collapseDeltas({ deltas, renamedLocalDirectories, renamedRemoteDirectories, deletedLocalDirectories, deletedRemoteDirectories }: { deltas: Delta[]; renamedLocalDirectories: Delta[]; renamedRemoteDirectories: Delta[]; deletedLocalDirectories: Delta[]; deletedRemoteDirectories: Delta[]; }): Delta[]; /** * Find directories slated for deletion that STILL hold live content on the deleting-resistant side — a * freshly-added child, or a base child kept alive by newer-modify-wins — and must therefore survive. * * When one side deletes a directory and the other adds/keeps something inside it in the same cycle, the * raw delta set contains both `deleteXDirectory ` and the child's own add. Left alone, collapseDeltas * subsumes the child's sibling DELETES under the dir-delete and the dir-delete then cascades over the * surviving child at execution time — wiping a brand-new file before it is ever propagated (data loss). * Newer content beats a delete (the same rule the per-file passes already apply), so the directory must * stay: drop its delete and let the surviving child's own add re-assert it. * * A child "survives under " iff it is present in the current tree (`tree`), is NOT itself slated for * deletion (same-direction `delete{File,Directory}`), and is NOT leaving via a rename — neither the child * itself nor any ancestor is the `from` of a same-direction `rename{File,Directory}` (a renamed directory * carries all its descendants out with it). Without the rename exclusion, a directory rename — which is * emitted as "rename the children to the new path + delete the now-empty old directory" — would look like * the old directory still has live children and never get deleted (its children are still at their old * paths in this pre-rebase tree). Returns the set of dir paths to KEEP. Pure: the caller applies the * pruning. Linear in tree size times depth; short-circuits when nothing is being deleted. */ export declare function directoriesWithSurvivingChildren(deltas: Delta[], dirDeleteType: "deleteLocalDirectory" | "deleteRemoteDirectory", fileDeleteType: "deleteLocalFile" | "deleteRemoteFile", renameDirType: "renameLocalDirectory" | "renameRemoteDirectory", renameFileType: "renameLocalFile" | "renameRemoteFile", tree: Record): Set; /** * Whether a directory rename `fromDir` → `toDir` is corroborated by a surviving child IDENTITY: some inode * that lived under `fromDir` in the base now lives under `toDir` in the current tree. Used only as a * fallback when the directory's birthtime does not match across the rename (a platform that rewrites a * directory's creation time on rename — observed on Windows), to tell a genuine move from an inode-reuse * coincidence: a reused directory inode belongs to a brand-new directory that shares NONE of the old * children, so it finds no corroborating child and is still rejected. O(inodes), short-circuits on the * first match, and only evaluated for a directory rename candidate whose birthtime already failed the * cheap equality/zero checks — so it never runs on the normal path. */ export declare function directoryRenameCorroboratedByChild(fromDir: string, toDir: string, currentTree: LocalTree, previousTree: LocalTree): boolean; /** * Remaps `path` across a set of propagated directory renames, returning its post-rename path (unchanged if * none applies). The most-specific (longest `from`) ancestor wins, and each rename's `to` already encodes * any outer renames (the rename pass records every directory's FINAL position), so one pass handles * independent AND nested directory renames correctly. */ export declare function rebasePathAcrossRenames(path: string, renames: { from: string; to: string; }[]): string; /** * Returns a COPY of `tree` with every DESCENDANT of a renamed directory moved to its post-rename path. The * input is never mutated (entries outside any renamed subtree are shared by reference). The renamed * directory nodes themselves are NOT moved here — they are handled via `pathsAdded` — only their children. * Used to realign the base + the not-yet-renamed side so the per-descendant passes attribute change at the * correct post-rename path (BUG-A / BUG-B). */ export declare function rebaseLocalTreeAcrossRenames(tree: LocalTree, renames: { from: string; to: string; }[]): LocalTree; export declare function rebaseRemoteTreeAcrossRenames(tree: RemoteTree, renames: { from: string; to: string; }[]): RemoteTree; /** * Deltas * @date 3/1/2024 - 11:11:32 PM * * @export * @class Deltas * @typedef {Deltas} */ export declare class Deltas { private readonly sync; /** * Creates an instance of Deltas. * * @constructor * @public * @param {Sync} sync */ constructor(sync: Sync); /** * Process the directory trees and return all sync deltas. * * @public * @async * @param {{ * currentLocalTree: LocalTree * currentRemoteTree: RemoteTree * previousLocalTree: LocalTree * previousRemoteTree: RemoteTree * currentLocalTreeErrors: LocalTreeError[] * }} param0 * @param {LocalTree} param0.currentLocalTree * @param {RemoteTree} param0.currentRemoteTree * @param {LocalTree} param0.previousLocalTree * @param {RemoteTree} param0.previousRemoteTree * @param {{}} param0.currentLocalTreeErrors * @returns {Promise<{ * deltas: Delta[], * deleteLocalDirectoryCountRaw: number * deleteLocalFileCountRaw: number * deleteRemoteDirectoryCountRaw: number * deleteRemoteFileCountRaw: number * }>} */ process({ currentLocalTree, currentRemoteTree, previousLocalTree, previousRemoteTree, currentLocalTreeErrors, currentLocalTreeIgnored }: { currentLocalTree: LocalTree; currentRemoteTree: RemoteTree; previousLocalTree: LocalTree; previousRemoteTree: RemoteTree; currentLocalTreeErrors: LocalTreeError[]; currentLocalTreeIgnored: LocalTreeIgnored[]; }): Promise<{ deltas: Delta[]; deleteLocalDirectoryCountRaw: number; deleteLocalFileCountRaw: number; deleteRemoteDirectoryCountRaw: number; deleteRemoteFileCountRaw: number; mode: SyncMode; }>; } export default Deltas;