From 551631d8e2b7d91a3da6641c487ade9501b337b4 Mon Sep 17 00:00:00 2001 From: Ramires Viana <59319979+ramiresviana@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:49:35 -0300 Subject: [PATCH] move eta logic to modal --- frontend/src/api/tus.ts | 108 +----------------- .../src/components/prompts/UploadFiles.vue | 51 ++++++++- frontend/src/stores/upload.ts | 22 +--- frontend/src/types/upload.d.ts | 14 --- 4 files changed, 56 insertions(+), 139 deletions(-) diff --git a/frontend/src/api/tus.ts b/frontend/src/api/tus.ts index 8bffa6a5..2609d258 100644 --- a/frontend/src/api/tus.ts +++ b/frontend/src/api/tus.ts @@ -1,17 +1,11 @@ import * as tus from "tus-js-client"; import { baseURL, tusEndpoint, tusSettings, origin } from "@/utils/constants"; import { useAuthStore } from "@/stores/auth"; -import { useUploadStore } from "@/stores/upload"; import { removePrefix } from "@/api/utils"; const RETRY_BASE_DELAY = 1000; const RETRY_MAX_DELAY = 20000; -const SPEED_UPDATE_INTERVAL = 1000; -const ALPHA = 0.2; -const ONE_MINUS_ALPHA = 1 - ALPHA; -const RECENT_SPEEDS_LIMIT = 5; -const MB_DIVISOR = 1024 * 1024; -const CURRENT_UPLOAD_LIST: CurrentUploadList = {}; +const CURRENT_UPLOAD_LIST: { [key: string]: tus.Upload } = {}; export async function upload( filePath: string, @@ -56,9 +50,6 @@ export async function upload( return true; }, onError: function (error: Error | tus.DetailedError) { - if (CURRENT_UPLOAD_LIST[filePath].interval) { - clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); - } delete CURRENT_UPLOAD_LIST[filePath]; const message = @@ -73,40 +64,16 @@ export async function upload( reject(new Error(message)); }, onProgress: function (bytesUploaded) { - const fileData = CURRENT_UPLOAD_LIST[filePath]; - fileData.currentBytesUploaded = bytesUploaded; - - if (!fileData.hasStarted) { - fileData.hasStarted = true; - fileData.lastProgressTimestamp = Date.now(); - - fileData.interval = window.setInterval(() => { - calcProgress(filePath); - }, SPEED_UPDATE_INTERVAL); - } if (typeof onupload === "function") { onupload({ loaded: bytesUploaded }); } }, onSuccess: function () { - if (CURRENT_UPLOAD_LIST[filePath].interval) { - clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); - } delete CURRENT_UPLOAD_LIST[filePath]; resolve(); }, }); - CURRENT_UPLOAD_LIST[filePath] = { - upload: upload, - recentSpeeds: [], - initialBytesUploaded: 0, - currentBytesUploaded: 0, - currentAverageSpeed: 0, - lastProgressTimestamp: null, - sumOfRecentSpeeds: 0, - hasStarted: false, - interval: undefined, - }; + CURRENT_UPLOAD_LIST[filePath] = upload; upload.start(); }); } @@ -138,76 +105,11 @@ function isTusSupported() { return tus.isSupported === true; } -function computeETA(speed?: number) { - const state = useUploadStore(); - if (state.speedMbyte === 0) { - return Infinity; - } - const totalSize = state.sizes.reduce( - (acc: number, size: number) => acc + size, - 0 - ); - const uploadedSize = state.progress.reduce((a, b) => a + b, 0); - const remainingSize = totalSize - uploadedSize; - const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024; - return remainingSize / speedBytesPerSecond; -} - -function computeGlobalSpeedAndETA() { - let totalSpeed = 0; - let totalCount = 0; - - for (const filePath in CURRENT_UPLOAD_LIST) { - totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed; - totalCount++; - } - - if (totalCount === 0) return { speed: 0, eta: Infinity }; - - const averageSpeed = totalSpeed / totalCount; - const averageETA = computeETA(averageSpeed); - - return { speed: averageSpeed, eta: averageETA }; -} - -function calcProgress(filePath: string) { - const uploadStore = useUploadStore(); - const fileData = CURRENT_UPLOAD_LIST[filePath]; - - const elapsedTime = - (Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000; - const bytesSinceLastUpdate = - fileData.currentBytesUploaded - fileData.initialBytesUploaded; - const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime; - - if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) { - fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0; - } - - fileData.recentSpeeds.push(currentSpeed); - fileData.sumOfRecentSpeeds += currentSpeed; - - const avgRecentSpeed = - fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length; - fileData.currentAverageSpeed = - ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed; - - const { speed, eta } = computeGlobalSpeedAndETA(); - uploadStore.setUploadSpeed(speed); - uploadStore.setETA(eta); - - fileData.initialBytesUploaded = fileData.currentBytesUploaded; - fileData.lastProgressTimestamp = Date.now(); -} - export function abortAllUploads() { for (const filePath in CURRENT_UPLOAD_LIST) { - if (CURRENT_UPLOAD_LIST[filePath].interval) { - clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); - } - if (CURRENT_UPLOAD_LIST[filePath].upload) { - CURRENT_UPLOAD_LIST[filePath].upload.abort(true); - CURRENT_UPLOAD_LIST[filePath].upload.options!.onError!( + if (CURRENT_UPLOAD_LIST[filePath]) { + CURRENT_UPLOAD_LIST[filePath].abort(true); + CURRENT_UPLOAD_LIST[filePath].options!.onError!( new Error("Upload aborted") ); } diff --git a/frontend/src/components/prompts/UploadFiles.vue b/frontend/src/components/prompts/UploadFiles.vue index 50bde8ab..48332135 100644 --- a/frontend/src/components/prompts/UploadFiles.vue +++ b/frontend/src/components/prompts/UploadFiles.vue @@ -8,7 +8,7 @@

{{ $t("prompts.uploadFiles", { files: filesInUploadCount }) }}

-
{{ uploadSpeed.toFixed(2) }} MB/s
+
{{ speed.toFixed(2) }} MB/s
{{ formattedETA }} remaining
{{ getProgressDecimal }}% Completed @@ -62,7 +62,7 @@ import { useFileStore } from "@/stores/file"; import { useUploadStore } from "@/stores/upload"; import { storeToRefs } from "pinia"; -import { computed, ref } from "vue"; +import { computed, ref, watch } from "vue"; import { abortAllUploads } from "@/api/tus"; import buttons from "@/utils/buttons"; import { useI18n } from "vue-i18n"; @@ -70,6 +70,8 @@ import { useI18n } from "vue-i18n"; const { t } = useI18n({}); const open = ref(false); +const speed = ref(0); +const eta = ref(Infinity); const fileStore = useFileStore(); const uploadStore = useUploadStore(); @@ -77,19 +79,56 @@ const uploadStore = useUploadStore(); const { filesInUpload, filesInUploadCount, - uploadSpeed, - getETA, getProgressDecimal, getTotalProgressBytes, + getTotalProgress, getTotalSize, + getTotalBytes, } = storeToRefs(uploadStore); +let lastSpeedUpdate: number = 0; +const recentSpeeds: number[] = []; + +const calculateSpeed = (progress: number, oldProgress: number) => { + const elapsedTime = (Date.now() - (lastSpeedUpdate ?? 0)) / 1000; + const bytesSinceLastUpdate = progress - oldProgress; + const currentSpeed = bytesSinceLastUpdate / (1024 * 1024) / elapsedTime; + + recentSpeeds.push(currentSpeed); + if (recentSpeeds.length > 5) { + recentSpeeds.shift(); + } + + const recentSpeedsAverage = + recentSpeeds.reduce((acc, curr) => acc + curr) / recentSpeeds.length; + + speed.value = recentSpeedsAverage * 0.2 + speed.value * 0.8; + lastSpeedUpdate = Date.now(); + + calculateEta(); +}; + +const calculateEta = () => { + if (speed.value === 0) { + eta.value = Infinity; + + return Infinity; + } + + const remainingSize = getTotalBytes.value - getTotalProgress.value; + const speedBytesPerSecond = speed.value * 1024 * 1024; + + eta.value = remainingSize / speedBytesPerSecond; +}; + +watch(getTotalProgress, calculateSpeed); + const formattedETA = computed(() => { - if (!getETA.value || getETA.value === Infinity) { + if (!eta.value || eta.value === Infinity) { return "--:--:--"; } - let totalSeconds = getETA.value; + let totalSeconds = eta.value; const hours = Math.floor(totalSeconds / 3600); totalSeconds %= 3600; const minutes = Math.floor(totalSeconds / 60); diff --git a/frontend/src/stores/upload.ts b/frontend/src/stores/upload.ts index 8429146e..46dc6347 100644 --- a/frontend/src/stores/upload.ts +++ b/frontend/src/stores/upload.ts @@ -33,8 +33,6 @@ export const useUploadStore = defineStore("upload", { progress: number[]; queue: UploadItem[]; uploads: Uploads; - speedMbyte: number; - eta: number; error: Error | null; } => ({ id: 0, @@ -42,8 +40,6 @@ export const useUploadStore = defineStore("upload", { progress: [], queue: [], uploads: {}, - speedMbyte: 0, - eta: 0, error: null, }), getters: { @@ -73,6 +69,9 @@ export const useUploadStore = defineStore("upload", { const sum = state.progress.reduce((a, b) => a + b, 0); return formatSize(sum); }, + getTotalProgress: (state) => { + return state.progress.reduce((a, b) => a + b, 0); + }, getTotalSize: (state) => { if (state.sizes.length === 0) { return "0 Bytes"; @@ -80,6 +79,9 @@ export const useUploadStore = defineStore("upload", { const totalSize = state.sizes.reduce((a, b) => a + b, 0); return formatSize(totalSize); }, + getTotalBytes: (state) => { + return state.sizes.reduce((a, b) => a + b, 0); + }, filesInUploadCount: (state) => { return Object.keys(state.uploads).length + state.queue.length; }, @@ -108,10 +110,6 @@ export const useUploadStore = defineStore("upload", { return files.sort((a, b) => a.progress - b.progress); }, - uploadSpeed: (state) => { - return state.speedMbyte; - }, - getETA: (state) => state.eta, }, actions: { // no context as first argument, use `this` instead @@ -127,8 +125,6 @@ export const useUploadStore = defineStore("upload", { this.progress = []; this.queue = []; this.uploads = {}; - this.speedMbyte = 0; - this.eta = 0; this.error = null; }, addJob(item: UploadItem) { @@ -206,12 +202,6 @@ export const useUploadStore = defineStore("upload", { this.finishUpload(item); } }, - setUploadSpeed(value: number) { - this.speedMbyte = value; - }, - setETA(value: number) { - this.eta = value; - }, // easily reset state using `$reset` clearUpload() { this.$reset(); diff --git a/frontend/src/types/upload.d.ts b/frontend/src/types/upload.d.ts index 131f4b2c..98b675e4 100644 --- a/frontend/src/types/upload.d.ts +++ b/frontend/src/types/upload.d.ts @@ -27,17 +27,3 @@ interface UploadEntry { } type UploadList = UploadEntry[]; - -type CurrentUploadList = { - [key: string]: { - upload: import("tus-js-client").Upload; - recentSpeeds: number[]; - initialBytesUploaded: number; - currentBytesUploaded: number; - currentAverageSpeed: number; - lastProgressTimestamp: number | null; - sumOfRecentSpeeds: number; - hasStarted: boolean; - interval: number | undefined; - }; -};