P1: typed RPC bridge + FileSystemProvider + dual-panel local browsing
- 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>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user