import { NgZone } from '@angular/core'; import { SidebarPlusI18nService } from './i18n'; import { SidebarPlusNoticesService } from './notices.service'; import { SFTPFile, SFTPPanelComponent } from 'tabby-ssh'; import { SidebarPlusTempFilesService } from './tempFiles.service'; import { SftpTransfers } from './transfers'; /** The live SFTP transport, borrowed from the one member that publicly exposes its type. */ type SftpSession = SFTPPanelComponent['sftp']; /** What a directory holds, as far as the guard rail below needs to know. */ export interface DirectoryWeight { files: number; bytes: number; /** True when counting stopped at the threshold instead of reaching the end — the totals are lower bounds. */ truncated: boolean; } /** * Dragging a remote entry out of the panel and into the OS. * * `webContents.startDrag()` — the only API for this, Tabby has none of its own * — needs a file that already exists locally, so every drag is really "download * to a temp dir, then start a native drag on the copy". Two consequences shape * everything here: * * - The download cannot happen inside the `dragstart` handler, which is * synchronous. The first drag of an entry therefore *prepares* it and * usually ends without dragging anything; the copy is kept, so the next * drag starts instantly. `startDrag()` is still attempted as soon as the * download lands, which makes the single-gesture case work whenever the * file is small enough and the button is still held. * - Copies are removed on `dispose()`, never on `dragend`: Windows reads the * file when the drop lands, which can be after the drag has ended. * * Deliberately *not* built on `SftpRemoteEditor`: that one installs an * `fs.watch` and re-uploads on every local change. A copy dragged to the * desktop and edited there would silently overwrite the server's file. */ export declare class SftpDragOut { private notifications; private zone; private transfers; private temp; private i18n; /** Remote path → the copy taken for it, with the fingerprint it was taken from. */ private ready; /** Remote paths being downloaded right now — a second drag must not start a second download. */ private preparing; /** Every directory handed out for this panel, so `dispose()` can drop them all. */ private dirs; /** * Preparations still running, as promises rather than as a flag. * * `preparing` says *that* something is running; only a promise says *when* * it stops, which is what `dispose()` needs before it deletes the directory * being written into. */ private inFlight; constructor(notifications: SidebarPlusNoticesService, zone: NgZone, transfers: SftpTransfers, temp: SidebarPlusTempFilesService, i18n: SidebarPlusI18nService); /** * Every mutation of `preparing` goes through the Angular zone. * * The flag drives the row's only visible state, and it is flipped on both * sides of `await sftp.download(...)` — russh's promises are native * bindings zone.js never patched, so the resumption runs outside the zone * and the class would be set on an object nothing repaints (piège #41). * With the gesture already over by then, that state is all the user has to * know the drag is being prepared. */ private setPreparing; /** True while an entry is downloading — drives the row's "préparation" state. */ isPreparing(remotePath: string): boolean; isReady(remotePath: string): boolean; /** * Whether the copy taken for an entry no longer matches it. * * Compares against the row as displayed, which costs nothing — the * authoritative check is the one `prepare()` makes against the server, via * `readRemoteEntry()` and never `stat()` (piège #50). This one only avoids * handing over a copy already known to be out of date. */ private isStale; /** * Starts the native drag for an entry already downloaded and still current. * * Returns false when there is nothing to drag yet, when the copy is stale, * or when the remote module is unreachable — the caller then falls back to * preparing it. */ startDrag(item: SFTPFile): boolean; /** * Downloads an entry to the temp directory, then tries to start the drag * straight away — worth attempting because a small file lands while the * button is often still held, which turns the whole thing into one gesture. */ prepare(sftp: SftpSession, item: SFTPFile, isDirectory: boolean, isGestureHeld?: () => boolean): Promise; private runPrepare; /** * Hands the copy over — as a drag if the gesture is still alive, as a * message otherwise. * * Starting the native drag once the button is released produces something * nobody asked for: a drag attached to a cursor that is not pressing * anything. While transfers were slow this never happened — the download * always outlasted the gesture by far — so the case only became reachable * once they got fast. */ private handOver; /** * Checks a copy that has just been handed over, and says so if it was stale. * * `dragstart` is synchronous: asking the server before starting the drag is * impossible, so a copy that still looks current in the listing is used as * is. This runs right after and closes the gap — the entry is dropped so the * next gesture downloads afresh, and the user is *told*, because silently * handing over yesterday's file is precisely the failure this chantier * exists to fix. */ revalidate(sftp: SftpSession, item: SFTPFile): Promise; /** * The entry as the server sees it *now*. * * Falls back to the displayed row if the read fails: a copy that cannot * be checked is better handed over than a gesture that fails outright, and * the download that follows would fail on its own if the entry were really * gone. */ private remoteFingerprint; /** * Counts what a directory holds, stopping as soon as either threshold is * passed. * * The early exit is the point: the guard rail only needs to know whether the * directory is *big*, and walking a genuinely huge tree to the end just to * decide whether to ask a question would cost as much as the download it is * meant to guard. */ weigh(sftp: SftpSession, remotePath: string, maxFiles: number, maxBytes: number): Promise; /** * Called when the browser is torn down — removes every copy made for a drag. * * Waits for whatever is still downloading before deleting anything. * Removing a directory a transfer is writing into does not raise here: the * removal is fire-and-forget, and Windows refuses to unlink an open file * anyway — so the visible outcome was simply a temp directory left behind, * silently, exactly what this class exists to avoid. The panel is already * gone by then, so waiting costs nothing anyone can see. */ dispose(): void; private downloadFile; /** * Depth-first copy of a remote directory. * * The walk itself lives in `remoteTree.ts`: the marker route of the * drag-out server delivers the same trees to a folder of the user's * choosing, and two copies of the symlink handling would be two chances to * fix only one of them. */ private downloadDirectory; } export {};