[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
170
components/terminal/hooks/useZmodemTransfer.ts
Normal file
170
components/terminal/hooks/useZmodemTransfer.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { netcattyBridge } from '../../../infrastructure/services/netcattyBridge';
|
||||
|
||||
export interface ZmodemTransferEvent {
|
||||
type: 'detect' | 'progress' | 'complete' | 'error';
|
||||
sessionId: string;
|
||||
transferType?: 'upload' | 'download';
|
||||
filename?: string;
|
||||
transferred?: number;
|
||||
total?: number;
|
||||
fileIndex?: number;
|
||||
fileCount?: number;
|
||||
finalizing?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ZmodemTransferState {
|
||||
active: boolean;
|
||||
transferType: 'upload' | 'download' | null;
|
||||
filename: string | null;
|
||||
transferred: number;
|
||||
total: number;
|
||||
fileIndex: number;
|
||||
fileCount: number;
|
||||
finalizing: boolean;
|
||||
completed: boolean;
|
||||
startedAt: number | null;
|
||||
updatedAt: number | null;
|
||||
bytesPerSecond: number | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: ZmodemTransferState = {
|
||||
active: false,
|
||||
transferType: null,
|
||||
filename: null,
|
||||
transferred: 0,
|
||||
total: 0,
|
||||
fileIndex: 0,
|
||||
fileCount: 0,
|
||||
finalizing: false,
|
||||
completed: false,
|
||||
startedAt: null,
|
||||
updatedAt: null,
|
||||
bytesPerSecond: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export function reduceZmodemTransferState(
|
||||
prev: ZmodemTransferState,
|
||||
event: ZmodemTransferEvent,
|
||||
now: number = Date.now(),
|
||||
): ZmodemTransferState {
|
||||
switch (event.type) {
|
||||
case 'detect':
|
||||
return {
|
||||
...initialState,
|
||||
active: true,
|
||||
transferType: event.transferType ?? null,
|
||||
startedAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
case 'progress': {
|
||||
const transferred = event.transferred ?? prev.transferred;
|
||||
const fileChanged = (
|
||||
prev.filename !== null
|
||||
&& (
|
||||
(typeof event.fileIndex === 'number' && event.fileIndex !== prev.fileIndex)
|
||||
|| (typeof event.filename === 'string' && event.filename !== prev.filename)
|
||||
)
|
||||
);
|
||||
const previousUpdatedAt = fileChanged ? now : (prev.updatedAt ?? now);
|
||||
const elapsedSeconds = Math.max((now - previousUpdatedAt) / 1000, 0);
|
||||
const deltaBytes = Math.max(transferred - prev.transferred, 0);
|
||||
const bytesPerSecond = elapsedSeconds > 0 && deltaBytes > 0
|
||||
? deltaBytes / elapsedSeconds
|
||||
: fileChanged
|
||||
? null
|
||||
: prev.bytesPerSecond;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
active: true,
|
||||
transferType: event.transferType ?? prev.transferType,
|
||||
filename: event.filename ?? prev.filename,
|
||||
transferred,
|
||||
total: event.total ?? prev.total,
|
||||
fileIndex: event.fileIndex ?? prev.fileIndex,
|
||||
fileCount: event.fileCount ?? prev.fileCount,
|
||||
finalizing: !!event.finalizing,
|
||||
completed: false,
|
||||
startedAt: prev.startedAt ?? now,
|
||||
updatedAt: now,
|
||||
bytesPerSecond,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
case 'complete':
|
||||
return {
|
||||
...prev,
|
||||
active: false,
|
||||
finalizing: false,
|
||||
completed: true,
|
||||
updatedAt: now,
|
||||
};
|
||||
case 'error':
|
||||
return {
|
||||
...prev,
|
||||
active: false,
|
||||
finalizing: false,
|
||||
completed: false,
|
||||
updatedAt: now,
|
||||
error: event.error ?? 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function useZmodemTransfer(sessionId: string | null) {
|
||||
const [state, setState] = useState<ZmodemTransferState>(initialState);
|
||||
const [overwriteRequest, setOverwriteRequest] = useState<{ requestId: string; filename: string } | null>(null);
|
||||
const disposeRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const disposeExitRef = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId) return;
|
||||
|
||||
const bridge = netcattyBridge.get();
|
||||
if (!bridge?.onZmodemEvent) return;
|
||||
|
||||
disposeRef.current = bridge.onZmodemEvent(sessionId, (event) => {
|
||||
setState((prev) => reduceZmodemTransferState(prev, event));
|
||||
});
|
||||
|
||||
const disposeOverwrite = bridge.onZmodemOverwriteRequest?.(sessionId, (payload) => {
|
||||
setOverwriteRequest({ requestId: payload.requestId, filename: payload.filename });
|
||||
});
|
||||
|
||||
// If the session exits mid-transfer (disconnect, shell exit, etc.),
|
||||
// reset state so the progress indicator doesn't stay stuck.
|
||||
disposeExitRef.current = bridge.onSessionExit(sessionId, () => {
|
||||
setState(initialState);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposeRef.current?.();
|
||||
disposeRef.current = null;
|
||||
disposeOverwrite?.();
|
||||
disposeExitRef.current?.();
|
||||
disposeExitRef.current = null;
|
||||
setState(initialState);
|
||||
setOverwriteRequest(null);
|
||||
};
|
||||
}, [sessionId]);
|
||||
|
||||
const cancel = useCallback(() => {
|
||||
if (!sessionId) return;
|
||||
const bridge = netcattyBridge.get();
|
||||
bridge?.cancelZmodem?.(sessionId);
|
||||
}, [sessionId]);
|
||||
|
||||
const respondOverwrite = useCallback((action: "overwrite" | "skip" | "cancel", applyToRest: boolean) => {
|
||||
setOverwriteRequest((req) => {
|
||||
if (req) netcattyBridge.get()?.respondZmodemOverwrite?.({ requestId: req.requestId, action, applyToRest });
|
||||
return null;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { ...state, cancel, overwriteRequest, respondOverwrite };
|
||||
}
|
||||
Reference in New Issue
Block a user