fix: upload: resume dialog and file were not disposed of after complete upload

Massimo Melina committed Jan 22, 2023 at 21:43 UTC 7320d51fea0fd0d37543c39584b3e55f18128080
4 files changed +28 -13
frontend/src/dialog.ts
+17 -9
@@ -2,7 +2,7 @@
2
3 import { createElement as h, ReactElement, ReactNode, useEffect, useRef, useState } from 'react'
4 import './dialog.css'
5 -import { newDialog, closeDialog, DialogOptions } from '@hfs/shared/dialogs'
5 +import { newDialog, closeDialog, DialogOptions, DialogCloser } from '@hfs/shared/dialogs'
6 import _ from 'lodash'
7 import { useInterval } from 'usehooks-ts'
8 export * from '@hfs/shared/dialogs'
@@ -73,16 +73,24 @@ export async function alertDialog(msg: ReactElement | string | Error, type:Alert
73 }
74 }
75
76 -export interface ConfirmOptions { href?: string, afterButtons?: ReactNode, timeout?: number, timeoutConfirm?: boolean }
77 -export async function confirmDialog(msg: ReactElement | string, { href, afterButtons, timeout, timeoutConfirm=false }: ConfirmOptions={}) : Promise<boolean> {
76 +export interface ConfirmOptions {
77 + href?: string
78 + afterButtons?: ReactNode
79 + timeout?: number
80 + timeoutConfirm?: boolean
81 + getClose?: (cb: DialogCloser) => unknown
82 +}
83 +export async function confirmDialog(msg: ReactElement | string, options: ConfirmOptions={}) : Promise<unknown> {
84 + const { href, afterButtons, timeout, timeoutConfirm=false, getClose=_.noop } = options
85 if (typeof msg === 'string')
86 msg = h('p', {}, msg)
80 - return new Promise(resolve => newDialog({
81 - className: 'dialog-confirm',
82 - icon: '?',
83 - onClose: resolve,
84 - Content
85 - }) )
87 + return new Promise(resolve =>
88 + getClose(newDialog({
89 + className: 'dialog-confirm',
90 + icon: '?',
91 + onClose: resolve,
92 + Content
93 + })) )
94
95 function Content() {
96 const [sec,setSec] = useState(Math.ceil(timeout||0))
frontend/src/upload.ts
+5 -2
@@ -2,7 +2,7 @@
2
3 import { createElement as h, useState } from 'react'
4 import { Flex, FlexV } from './components'
5 -import { formatBytes, hIcon, newDialog, prefix } from './misc'
5 +import { DialogCloser, formatBytes, hIcon, newDialog, prefix } from './misc'
6 import _ from 'lodash'
7 import { proxy, ref, subscribe, useSnapshot } from 'valtio'
8 import { alertDialog, confirmDialog } from './dialog'
@@ -176,6 +176,7 @@ let req: XMLHttpRequest | undefined
176 let overrideStatus = 0
177 let notificationChannel = ''
178 let notificationSource: EventSource
179 +let closeResumeDialog: DialogCloser | undefined
180
181 async function startUpload(f: File, to: string, resume=0) {
182 let resuming = false
@@ -213,7 +214,8 @@ async function startUpload(f: File, to: string, resume=0) {
214 const {expires} = data
215 const timeout = typeof expires !== 'number' ? 0
216 : (Number(new Date(expires)) - Date.now()) / 1000
216 - if (!await confirmDialog(`Resume upload? (${formatPerc(size/f.size)} = ${formatBytes(size)})`, { timeout })) return
217 + const msg = `Resume upload? (${formatPerc(size/f.size)} = ${formatBytes(size)})`
218 + if (!await confirmDialog(msg, { timeout, getClose: x => closeResumeDialog=x })) return
219 if (uploading !== uploadState.uploading) return // too late
220 resuming = true
221 abortCurrentUpload()
@@ -241,6 +243,7 @@ async function startUpload(f: File, to: string, resume=0) {
243 }
244
245 function next() {
246 + closeResumeDialog?.()
247 uploadState.uploading = undefined
248 const { qs } = uploadState
249 if (!qs.length) return
shared/dialogs.ts
+1
@@ -20,6 +20,7 @@ export interface DialogOptions {
20 }
21
22 const dialogs = proxy<DialogOptions[]>([])
23 +export type DialogCloser = ReturnType<typeof newDialog>
24
25 export const dialogsDefaults: Partial<DialogOptions> = {
26 closableContent: 'x',
src/middlewares.ts
+5 -2
@@ -168,8 +168,11 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
168 cancelDeletion(tempName)
169 ret.on('close', () => {
170 if (!ctx.req.aborted)
171 - return fs.rename(tempName, fullPath, err =>
172 - err && console.error("couldn't rename temp to", fullPath, String(err)))
171 + return fs.rename(tempName, fullPath, err => {
172 + err && console.error("couldn't rename temp to", fullPath, String(err))
173 + if (resumable)
174 + delayedDelete(resumable, 0)
175 + })
176 const sec = deleteUnfinishedUploadsAfter.get()
177 if (typeof sec !== 'number') return
178 delayedDelete(tempName, sec)