From 4363ed3271841f617a9afab79b20b6b416dfa041 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Thu, 31 Oct 2024 15:52:49 +0000 Subject: [PATCH] fix: uid check on upload/download state changes (#1517) Signed-off-by: Pedro Lamas --- .../widgets/filesystem/FileSystem.vue | 8 +- .../filesystem/FileSystemUploadDialog.vue | 6 +- src/mixins/files.ts | 86 ++++++++++++------- src/store/files/mutations.ts | 23 +++-- src/store/files/types.ts | 5 +- 5 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index 086d916cbb..a270b72b14 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -142,7 +142,7 @@ diff --git a/src/mixins/files.ts b/src/mixins/files.ts index 6b9ed5c2b6..f36e3c9604 100644 --- a/src/mixins/files.ts +++ b/src/mixins/files.ts @@ -1,10 +1,11 @@ -import type { AppFile, FilesUpload, AppFileThumbnail, KlipperFileMeta } from '@/store/files/types' +import type { AppFile, FileUpload, AppFileThumbnail, KlipperFileMeta, FileDownload } from '@/store/files/types' import Vue from 'vue' import { Component } from 'vue-property-decorator' import type { AxiosRequestConfig, AxiosProgressEvent } from 'axios' import { httpClientActions } from '@/api/httpClientActions' import type { FileWithPath } from '@/types' import consola from 'consola' +import { v4 as uuidv4 } from 'uuid' @Component export default class FilesMixin extends Vue { @@ -71,14 +72,26 @@ export default class FilesMixin extends Vue { * @param path The path to the file */ async getFile (filename: string, path: string, size = 0, options?: AxiosRequestConfig) { + const currentDownload: FileDownload | null = this.$store.state.files.download + + if (currentDownload) { + currentDownload.abortController.abort() + + this.$store.dispatch('files/removeFileDownload', currentDownload.uid) + } + // Sort out the filepath - const filepath = path ? `${path}/${filename}` : filename + const filepath = path + ? `${path}/${filename}` + : filename + const uid = uuidv4() try { const abortController = new AbortController() // Add an entry to vuex indicating we're downloading a file. this.$store.dispatch('files/updateFileDownload', { + uid, filepath, size, loaded: 0, @@ -102,7 +115,7 @@ export default class FilesMixin extends Vue { ) const payload: any = { - filepath, + uid, loaded: event.loaded, percent: Math.round(progress * 100), speed: event.rate ?? 0 @@ -120,7 +133,7 @@ export default class FilesMixin extends Vue { return response } finally { - this.$store.dispatch('files/removeFileDownload') + this.$store.dispatch('files/removeFileDownload', uid) } } @@ -176,23 +189,26 @@ export default class FilesMixin extends Vue { * @param andPrint If we should attempt to print this file or not. * @param options Axios request options */ - async uploadFile (file: File, path: string, root: string, andPrint: boolean, options?: AxiosRequestConfig) { + async uploadFile (file: File, path: string, root: string, andPrint: boolean, uid?: string, options?: AxiosRequestConfig) { const filepath = path ? `${path}/${file.name}` : file.name + uid = uid || uuidv4() try { const abortController = new AbortController() this.$store.dispatch('files/updateFileUpload', { + uid, filepath, size: file.size, loaded: 0, percent: 0, speed: 0, cancelled: false, + complete: false, abortController - }) + } satisfies FileUpload) const response = await httpClientActions.serverFilesUploadPost(file, path, root, andPrint, { ...options, @@ -203,7 +219,7 @@ export default class FilesMixin extends Vue { } this.$store.dispatch('files/updateFileUpload', { - filepath, + uid, loaded: event.loaded, percent: event.progress ? Math.round(event.progress * 100) : 0, speed: event.rate ?? 0 @@ -215,7 +231,7 @@ export default class FilesMixin extends Vue { return response } finally { - this.$store.dispatch('files/removeFileUpload', filepath) + this.$store.dispatch('files/removeFileUpload', uid) } } @@ -238,45 +254,49 @@ export default class FilesMixin extends Vue { // Upload some files. async uploadFiles (files: FileList | File[] | FileWithPath[], path: string, root: string, andPrint: boolean) { // For each file, adds the associated state. - for (const file of files) { - const [fullPath, fileObject] = this.getFullPathAndFile(path, file) - - const filepath = fullPath - ? `${fullPath}/${fileObject.name}` - : fileObject.name + const fileUploads = [...files] + .map(file => { + const uid = uuidv4() + const [fullPath, fileObject] = this.getFullPathAndFile(path, file) + + const filepath = fullPath + ? `${fullPath}/${fileObject.name}` + : fileObject.name + + this.$store.dispatch('files/updateFileUpload', { + uid, + filepath, + size: fileObject.size, + loaded: 0, + percent: 0, + speed: 0, + cancelled: false, + complete: false + }) - this.$store.dispatch('files/updateFileUpload', { - filepath, - size: fileObject.size, - loaded: 0, - percent: 0, - speed: 0, - unit: 'kB', - cancelled: false + return { + uid, + file + } }) - } // Async uploads cause issues in moonraker / klipper. // So instead, upload sequentially waiting for moonraker to finish // processing of each file. - if (files.length > 1) andPrint = false - for (const file of files) { - const [fullPath, fileObject] = this.getFullPathAndFile(path, file) - - const filepath = fullPath - ? `${fullPath}/${fileObject.name}` - : fileObject.name + if (fileUploads.length > 1) andPrint = false + for (const fileUpload of fileUploads) { + const [fullPath, fileObject] = this.getFullPathAndFile(path, fileUpload.file) - const fileState = this.$store.state.files.uploads.find((u: FilesUpload) => u.filepath === filepath) + const fileState = this.$store.state.files.uploads.find((u: FileUpload) => u.uid === fileUpload.uid) if (fileState && !fileState?.cancelled) { try { - await this.uploadFile(fileObject, fullPath, root, andPrint) + await this.uploadFile(fileObject, fullPath, root, andPrint, fileUpload.uid) } catch (error: unknown) { consola.error('[FileUpload] file', error) } } else { - this.$store.dispatch('files/removeFileUpload', filepath) + this.$store.dispatch('files/removeFileUpload', fileUpload.uid) } } } diff --git a/src/store/files/mutations.ts b/src/store/files/mutations.ts index 91d4d7d0e9..2a56cf47fd 100644 --- a/src/store/files/mutations.ts +++ b/src/store/files/mutations.ts @@ -96,7 +96,7 @@ export const mutations: MutationTree = { }, setUpdateFileUpload (state, payload) { - const i = state.uploads.findIndex((u) => u.filepath === payload.filepath) + const i = state.uploads.findIndex((u) => u.uid === payload.uid) if (i >= 0) { Vue.set(state.uploads, i, { ...state.uploads[i], ...payload }) } else { @@ -104,22 +104,29 @@ export const mutations: MutationTree = { } }, - setRemoveFileUpload (state, payload) { - const i = state.uploads.findIndex((u) => u.filepath === payload) + setRemoveFileUpload (state, payload: string) { + const i = state.uploads.findIndex((u) => u.uid === payload) if (i >= 0) { state.uploads.splice(i, 1) } }, setUpdateFileDownload (state, payload) { - state.download = { - ...state.download, - ...payload + if ( + state.download == null || + state.download.uid === payload.uid + ) { + state.download = { + ...state.download, + ...payload + } } }, - setRemoveFileDownload (state) { - state.download = null + setRemoveFileDownload (state, payload: string) { + if (state.download?.uid === payload) { + state.download = null + } }, setCurrentPath (state, payload) { diff --git a/src/store/files/types.ts b/src/store/files/types.ts index 74478ff49d..da0ad133ae 100644 --- a/src/store/files/types.ts +++ b/src/store/files/types.ts @@ -4,7 +4,7 @@ import type { HistoryItem } from '@/store/history/types' export type { KlipperFileMeta, KlipperFileMetaThumbnail } export interface FilesState { - uploads: FilesUpload[]; + uploads: FileUpload[]; download: FileDownload | null; currentPaths: Record; disk_usage: DiskUsage; @@ -97,6 +97,7 @@ export interface FileUpdate { } export interface FileDownload { + uid: string; filepath: string; size: number; loaded: number; @@ -105,7 +106,7 @@ export interface FileDownload { abortController: AbortController; } -export interface FilesUpload extends FileDownload { +export interface FileUpload extends FileDownload { complete: boolean; // indicates moonraker is finished with the file. cancelled: boolean; // in a cancelled state, don't show - nor try to upload. }