153 lines
5.8 KiB
TypeScript
153 lines
5.8 KiB
TypeScript
|
|
import { useCallback, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
||
|
|
import type { FileConflict, TransferStatus, TransferTask } from "../../../domain/models";
|
||
|
|
import { netcattyBridge } from "../../../infrastructure/services/netcattyBridge";
|
||
|
|
import { logger } from "../../../lib/logger";
|
||
|
|
import { globalSftpTransferScheduler } from "./globalTransferScheduler";
|
||
|
|
import type { TransferResult } from "./useSftpTransfers.types";
|
||
|
|
|
||
|
|
interface UseSftpTransferTaskOpsParams {
|
||
|
|
cancelledTasksRef: MutableRefObject<Set<string>>;
|
||
|
|
activeChildIdsRef: MutableRefObject<Map<string, Set<string>>>;
|
||
|
|
transfersRef: MutableRefObject<TransferTask[]>;
|
||
|
|
completionHandlersRef: MutableRefObject<Map<string, (result: TransferResult) => void | Promise<void>>>;
|
||
|
|
setConflicts: Dispatch<SetStateAction<FileConflict[]>>;
|
||
|
|
setTransfers: Dispatch<SetStateAction<TransferTask[]>>;
|
||
|
|
releasePausedTransfer?: (taskId: string) => void;
|
||
|
|
cleanupTaskArtifacts?: (task: TransferTask) => void | Promise<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useSftpTransferTaskOps({
|
||
|
|
cancelledTasksRef,
|
||
|
|
activeChildIdsRef,
|
||
|
|
transfersRef,
|
||
|
|
completionHandlersRef,
|
||
|
|
setConflicts,
|
||
|
|
setTransfers,
|
||
|
|
releasePausedTransfer,
|
||
|
|
cleanupTaskArtifacts,
|
||
|
|
}: UseSftpTransferTaskOpsParams) {
|
||
|
|
const completeCancelledTask = useCallback(
|
||
|
|
async (task: TransferTask) => {
|
||
|
|
const completionHandler = completionHandlersRef.current.get(task.id);
|
||
|
|
if (completionHandler) {
|
||
|
|
try {
|
||
|
|
await completionHandler({
|
||
|
|
id: task.id,
|
||
|
|
fileName: task.fileName,
|
||
|
|
originalFileName: task.originalFileName ?? task.fileName,
|
||
|
|
status: "cancelled",
|
||
|
|
});
|
||
|
|
} finally {
|
||
|
|
completionHandlersRef.current.delete(task.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[completionHandlersRef],
|
||
|
|
);
|
||
|
|
|
||
|
|
const cancelBackendTransfers = useCallback(async (transferIds: string[]) => {
|
||
|
|
const idsToCancel = new Set<string>();
|
||
|
|
const currentTransfers = transfersRef.current;
|
||
|
|
for (const transferId of transferIds) {
|
||
|
|
idsToCancel.add(transferId);
|
||
|
|
const trackedChildren = activeChildIdsRef.current.get(transferId);
|
||
|
|
if (trackedChildren) {
|
||
|
|
for (const childId of trackedChildren) {
|
||
|
|
idsToCancel.add(childId);
|
||
|
|
cancelledTasksRef.current.add(childId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const transfer of currentTransfers) {
|
||
|
|
if (
|
||
|
|
transfer.parentTaskId === transferId &&
|
||
|
|
(transfer.status === "transferring" || transfer.status === "pending")
|
||
|
|
) {
|
||
|
|
idsToCancel.add(transfer.id);
|
||
|
|
cancelledTasksRef.current.add(transfer.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const bridge = netcattyBridge.get();
|
||
|
|
const cancelTransferAtBackend = bridge?.cancelTransfer;
|
||
|
|
const cancelCompressedUpload = bridge?.cancelCompressedUpload;
|
||
|
|
if (!cancelTransferAtBackend && !cancelCompressedUpload) return;
|
||
|
|
|
||
|
|
await Promise.all(
|
||
|
|
Array.from(idsToCancel).map(async (id) => {
|
||
|
|
const candidate = currentTransfers.find((task) => task.id === id);
|
||
|
|
const compressed = candidate?.controlKind === "compressed-upload";
|
||
|
|
const operation = compressed
|
||
|
|
? cancelCompressedUpload?.(id)
|
||
|
|
: cancelTransferAtBackend?.(id);
|
||
|
|
const results = operation ? await Promise.allSettled([operation]) : [];
|
||
|
|
if (results.some((result) => result.status === "rejected")) {
|
||
|
|
logger.warn("Failed to cancel one or more transfer backends");
|
||
|
|
}
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
}, [activeChildIdsRef, cancelledTasksRef, transfersRef]);
|
||
|
|
|
||
|
|
const markBatchStopped = useCallback(
|
||
|
|
async (task: TransferTask) => {
|
||
|
|
const batchId = task.batchId;
|
||
|
|
// Stop the whole unfinished batch, including siblings already waiting on
|
||
|
|
// conflict resolution (attention), not only pending/transferring rows.
|
||
|
|
const isUnfinished = (status: TransferTask["status"]) =>
|
||
|
|
!["completed", "cancelled", "failed"].includes(status);
|
||
|
|
const affected = transfersRef.current.filter((candidate) =>
|
||
|
|
candidate.id === task.id ||
|
||
|
|
(!!batchId && candidate.batchId === batchId && isUnfinished(candidate.status)),
|
||
|
|
);
|
||
|
|
|
||
|
|
for (const candidate of affected) {
|
||
|
|
cancelledTasksRef.current.add(candidate.id);
|
||
|
|
globalSftpTransferScheduler.cancel(candidate.id);
|
||
|
|
releasePausedTransfer?.(candidate.id);
|
||
|
|
}
|
||
|
|
const affectedIds = new Set(affected.map((candidate) => candidate.id));
|
||
|
|
for (const candidate of transfersRef.current) {
|
||
|
|
if (candidate.parentTaskId && affectedIds.has(candidate.parentTaskId)) {
|
||
|
|
cancelledTasksRef.current.add(candidate.id);
|
||
|
|
globalSftpTransferScheduler.cancel(candidate.id);
|
||
|
|
releasePausedTransfer?.(candidate.id);
|
||
|
|
affectedIds.add(candidate.id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const nextTransfers = transfersRef.current
|
||
|
|
.filter((candidate) => !(candidate.parentTaskId && affectedIds.has(candidate.parentTaskId)))
|
||
|
|
.map((candidate) =>
|
||
|
|
affectedIds.has(candidate.id)
|
||
|
|
? { ...candidate, status: "cancelled" as TransferStatus, endTime: Date.now(), conflict: undefined }
|
||
|
|
: candidate,
|
||
|
|
);
|
||
|
|
transfersRef.current = nextTransfers;
|
||
|
|
setTransfers(nextTransfers);
|
||
|
|
setConflicts((prev) => prev.filter((conflict) => !affectedIds.has(conflict.transferId) && (!batchId || conflict.batchId !== batchId)));
|
||
|
|
await cancelBackendTransfers([...affectedIds]);
|
||
|
|
|
||
|
|
for (const candidate of affected) {
|
||
|
|
try {
|
||
|
|
await cleanupTaskArtifacts?.(candidate);
|
||
|
|
} catch {
|
||
|
|
// best-effort
|
||
|
|
}
|
||
|
|
await completeCancelledTask(candidate);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
[
|
||
|
|
cancelBackendTransfers,
|
||
|
|
cancelledTasksRef,
|
||
|
|
cleanupTaskArtifacts,
|
||
|
|
completeCancelledTask,
|
||
|
|
releasePausedTransfer,
|
||
|
|
setConflicts,
|
||
|
|
setTransfers,
|
||
|
|
transfersRef,
|
||
|
|
],
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
return { completeCancelledTask, cancelBackendTransfers, markBatchStopped };
|
||
|
|
}
|