import { type Stats } from "fs-extra"; import type Sync from "../sync"; import { type CloudItem } from "@filen/sdk"; import { Semaphore } from "../../semaphore"; /** * Upper bound on how many local entries are stat'd concurrently during a single tree scan. * * The scan fans a filesystem `lstat` (+ `access`) out over every glob entry. Mapping all entries to * promises up front would, on a tree with millions of entries, allocate millions of pending promises * and in-flight stat results at once — an O(n) peak-memory spike on top of the (unavoidable) result * tree, plus unbounded pressure on the libuv thread pool and open file descriptors. Walking the entries * in fixed-size batches caps peak concurrency — and therefore peak memory — without reducing throughput, * since the thread pool only services a handful of these operations in parallel regardless of how many * are queued. Batches run sequentially; case-collision winners are resolved up front (see computeLocalCaseWinners), * independent of batch order. */ export declare const LOCAL_SCAN_CONCURRENCY = 1000; export type LocalItem = { lastModified: number; type: "file" | "directory"; path: string; size: number; creation: number; inode: number; }; export type LocalDirectoryTree = Record; export type LocalDirectoryINodes = Record; /** * Decide the deterministic winner for each case-insensitive path collision among the raw fast-glob entries * (markDirectories form: a trailing "/" marks a directory), returning the SET of winning raw entries. Pure and * order-independent (exported for testing): feeding the same entries in any order yields the same winners, so the local * tree does not flip between scans and cause churn. The scan gate then keeps an entry iff the set contains it — matching * by the raw entry so the main loop needs no second lowercasing. See {@link caseCollisionIncumbentWins}. * * @param {readonly (string | null)[]} entries The raw fast-glob entries (with markDirectories trailing slash). * @returns {Set} the winning raw entries. */ export declare function computeLocalCaseWinners(entries: readonly (string | null)[]): Set; export type LocalTree = { tree: LocalDirectoryTree; inodes: LocalDirectoryINodes; size: number; }; export type LocalTreeError = { localPath: string; relativePath: string; error: Error; uuid: string; }; export type LocalTreeIgnoredReason = "dotFile" | "filenIgnore" | "defaultIgnore" | "empty" | "symlink" | "invalidType" | "duplicate" | "permissions" | "pathLength" | "invalidPath" | "nameLength"; export type LocalTreeIgnored = { localPath: string; relativePath: string; reason: LocalTreeIgnoredReason; }; /** * LocalFileSystem * @date 3/2/2024 - 12:38:22 PM * * @export * @class LocalFileSystem * @typedef {LocalFileSystem} */ export declare class LocalFileSystem { private readonly sync; lastDirectoryChangeTimestamp: number; getDirectoryTreeCache: { timestamp: number; tree: LocalDirectoryTree; inodes: LocalDirectoryINodes; ignored: LocalTreeIgnored[]; errors: LocalTreeError[]; size: number; }; watcherRunning: boolean; private watcherInstance; readonly itemsMutex: Semaphore; readonly mutex: Semaphore; readonly mkdirMutex: Semaphore; readonly watcherMutex: Semaphore; watcherInstanceFallbackInterval: ReturnType | undefined; ignoredCache: Map; /** * Creates an instance of LocalFileSystem. * * @constructor * @public * @param {Sync} sync */ constructor(sync: Sync); isPathIgnored(relativePath: string, absolutePath: string, type: "file" | "directory"): { ignored: true; reason: LocalTreeIgnoredReason; } | { ignored: false; }; getDirectoryTree(): Promise<{ result: LocalTree; errors: LocalTreeError[]; ignored: LocalTreeIgnored[]; changed: boolean; }>; /** * Start the local sync directory watcher. * @date 3/2/2024 - 12:38:00 PM * * @public * @async * @returns {Promise} */ startDirectoryWatcher(): Promise; /** * Stop the local sync directory watcher. * @date 3/2/2024 - 12:37:48 PM * * @public * @async * @returns {Promise} */ stopDirectoryWatcher(): Promise; /** * Wait for local directory updates to be done. * Sometimes the user might copy a lot of new files, folders etc. * We want to wait (or at least try) until all local operations are done until we start syncing. * This can save a lot of sync cycles. * @date 3/1/2024 - 10:40:14 PM * * @public * @async * @returns {Promise} */ waitForLocalDirectoryChanges(): Promise; /** * Creates a hash of a file using streams. * * @public * @async * @param {{ * relativePath: string * algorithm: "sha512" | "md5" | "sha256" * }} param0 * @param {string} param0.relativePath * @param {("sha512" | "md5" | "sha256")} param0.algorithm * @returns {Promise} */ createFileHash({ relativePath, algorithm }: { relativePath: string; algorithm: "sha512" | "md5" | "sha256"; }): Promise; /** * Create a directory inside the local sync path. Recursively creates intermediate directories if needed. * @date 3/2/2024 - 12:36:23 PM * * @public * @async * @param {{ relativePath: string }} param0 * @param {string} param0.relativePath * @returns {Promise} */ mkdir({ relativePath }: { relativePath: string; }): Promise; unlink({ relativePath, permanent }: { relativePath: string; permanent?: boolean; }): Promise; rename({ fromRelativePath, toRelativePath }: { fromRelativePath: string; toRelativePath: string; }): Promise; /** * Upload a local file. * * @public * @async * @param {{ relativePath: string, passedMD5Hash?: string }} param0 * @param {string} param0.relativePath * @param {string} param0.passedMD5Hash * @returns {Promise} */ upload({ relativePath, passedMD5Hash }: { relativePath: string; passedMD5Hash?: string | undefined; }): Promise; isPathWritable(path: string): Promise; isPathReadable(path: string): Promise; pathExists(path: string): Promise; } export default LocalFileSystem;