- shared/types + shared/rpc: full wire contract (Result envelope, no exceptions cross the bridge) - FileSystemProvider interface, LocalProvider, SessionRegistry with built-in 'local' - walk.ts: provider-generic recursive walk + removeRecursive - rpc-handlers wired into BrowserWindow; 10min request timeout for slow SFTP ops - Angular: RpcService (Electroview bridge + plain-browser guard), PanelsService with signals-based PanelState (sort/cursor/selection), FilePanel component with keyboard nav (arrows/Space/Enter/Backspace/Tab), path bar, toasts - verified: both panels listDir home over the bridge in the running app Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
import type { EntryType, FileEntry } from "../../shared/types";
|
|
import type { FileSystemProvider } from "../fs/provider";
|
|
|
|
export interface WalkedTree {
|
|
/** Relative dir paths as segment arrays, top-down. [] is the root itself. */
|
|
dirs: string[][];
|
|
files: { entry: FileEntry; dirSegments: string[] }[];
|
|
totalBytes: number;
|
|
skippedSymlinks: number;
|
|
}
|
|
|
|
/**
|
|
* Depth-first walk of a directory. Segment arrays (not joined strings) so the
|
|
* destination side can re-join them with its own separator. Symlinks are
|
|
* skipped — following them risks cycles and surprising copies.
|
|
*/
|
|
export async function walkTree(
|
|
fs: FileSystemProvider,
|
|
rootDir: string,
|
|
): Promise<WalkedTree> {
|
|
const tree: WalkedTree = { dirs: [[]], files: [], totalBytes: 0, skippedSymlinks: 0 };
|
|
|
|
async function visit(dir: string, segments: string[]): Promise<void> {
|
|
for (const entry of await fs.list(dir)) {
|
|
if (entry.type === "symlink") {
|
|
tree.skippedSymlinks++;
|
|
} else if (entry.type === "dir") {
|
|
const childSegments = [...segments, entry.name];
|
|
tree.dirs.push(childSegments);
|
|
await visit(entry.path, childSegments);
|
|
} else {
|
|
tree.files.push({ entry, dirSegments: segments });
|
|
tree.totalBytes += entry.size;
|
|
}
|
|
}
|
|
}
|
|
|
|
await visit(rootDir, []);
|
|
return tree;
|
|
}
|
|
|
|
/** Post-order recursive delete, provider-generic (works locally and over SFTP). */
|
|
export async function removeRecursive(
|
|
fs: FileSystemProvider,
|
|
path: string,
|
|
type: EntryType,
|
|
): Promise<void> {
|
|
if (type !== "dir") {
|
|
await fs.deleteFile(path);
|
|
return;
|
|
}
|
|
for (const entry of await fs.list(path)) {
|
|
await removeRecursive(fs, entry.path, entry.type);
|
|
}
|
|
await fs.deleteEmptyDir(path);
|
|
}
|