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;