From c2b4de70210c8b807e45ea213b29cbec7fdec58a Mon Sep 17 00:00:00 2001 From: martastn Date: Wed, 10 Dec 2025 20:35:43 +0100 Subject: [PATCH] Display estimated time remaining for simulation dataset download --- .../Simulation/Geant4DatasetDownload.tsx | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/src/WrapperApp/components/Simulation/Geant4DatasetDownload.tsx b/src/WrapperApp/components/Simulation/Geant4DatasetDownload.tsx index 51f10f585..eb30565b6 100644 --- a/src/WrapperApp/components/Simulation/Geant4DatasetDownload.tsx +++ b/src/WrapperApp/components/Simulation/Geant4DatasetDownload.tsx @@ -12,7 +12,7 @@ import { Typography, useTheme } from '@mui/material'; -import { useState } from 'react'; +import { useEffect,useState } from 'react'; import { DatasetDownloadStatus, @@ -28,6 +28,21 @@ export enum Geant4DatasetsType { FULL } +function formatTime(seconds: number): string { + if (seconds < 1) { + return '<1s'; + } + + if (seconds < 60) { + return `${Math.ceil(seconds)}s`; + } + + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.ceil(seconds % 60); + + return `${minutes}m ${remainingSeconds}s`; +} + export interface Geant4DatasetsProps { geant4DownloadManagerState: DownloadManagerStatus; geant4DatasetStates: DatasetStatus[]; @@ -36,14 +51,91 @@ export interface Geant4DatasetsProps { setGeant4DatasetType: (type: Geant4DatasetsType) => void; } +interface SpeedHistory { + lastDone: number; + lastTime: number; + currentSpeed: number; +} + +const SMOOTHING = 0.1; + function DatasetCurrentStatus(props: { status: DatasetStatus }) { const { status } = props; + const [speedHistory, setSpeedHistory] = useState({ + lastDone: status.done ?? 0, + lastTime: Date.now(), + currentSpeed: 0 + }); + + useEffect(() => { + const currentDone = status.done ?? 0; + const currentTime = Date.now(); + + if (status.status === DatasetDownloadStatus.DOWNLOADING) { + setSpeedHistory(prev => { + const timeDelta = (currentTime - prev.lastTime) / 1000; + const bytesDelta = currentDone - prev.lastDone; + + if (bytesDelta > 0 && timeDelta > 0) { + const instSpeed = bytesDelta / timeDelta; + + const newSpeed = + prev.currentSpeed === 0 + ? instSpeed + : prev.currentSpeed * (1 - SMOOTHING) + instSpeed * SMOOTHING; + + return { + lastDone: currentDone, + lastTime: currentTime, + currentSpeed: newSpeed + }; + } + + return { + ...prev, + lastTime: currentTime + }; + }); + } + + if ( + status.status === DatasetDownloadStatus.DONE || + status.status === DatasetDownloadStatus.IDLE + ) { + setSpeedHistory({ + lastDone: status.done ?? 0, + lastTime: Date.now(), + currentSpeed: 0 + }); + } + }, [status.done, status.status]); + + const remainingBytes = (status.total ?? 0) - (status.done ?? 0); + let estimatedTimeRemaining = ''; + + if ( + status.status === DatasetDownloadStatus.DOWNLOADING && + speedHistory.currentSpeed > 0 && + remainingBytes > 0 + ) { + const remainingSeconds = remainingBytes / speedHistory.currentSpeed; + estimatedTimeRemaining = ` (${formatTime(remainingSeconds)})`; + } + return ( {status.name} + {status.status === DatasetDownloadStatus.DONE && } + {status.status === DatasetDownloadStatus.DOWNLOADING && ( + + {estimatedTimeRemaining} + + )} @@ -57,6 +149,7 @@ function DatasetCurrentStatus(props: { status: DatasetStatus }) { {status.status === DatasetDownloadStatus.PROCESSING && ( )} @@ -148,7 +241,10 @@ export function Geant4Datasets(props: Geant4DatasetsProps) { }}> } - onClick={() => setOpen(!open)}> + onClick={e => { + e.stopPropagation(); + setOpen(!open); + }}>