@samitouri / QOSami-HFS / commits / 2d92329b

fix: upload-dialog was heavy on the gpu

Massimo Melina committed May 5, 2025 at 00:48 UTC 2d92329b96e45f3e3b630e9ec753a17d25f70a18
3 files changed +16 -12
frontend/src/menu.ts
+2 -2
@@ -25,7 +25,7 @@ const { t, useI18N } = i18n
25 export function MenuPanel() {
26 const { showFilter, remoteSearch, stopSearch, searchManuallyInterrupted, selected, props } = useSnapState()
27 const { can_upload, can_delete, can_archive } = props ? { ...defaultPerms, ...props } : {} as VfsPerms
28 - const { uploading, qs } = useSnapshot(uploadState)
28 + const { uploading, qs, uploadDialogIsOpen } = useSnapshot(uploadState)
29 useEffect(() => {
30 if (!showFilter)
31 state.selected = {}
@@ -77,7 +77,7 @@ export function MenuPanel() {
77 label: t`Upload`,
78 disabled: !changingButton,
79 tabIndex: changingButton ? undefined : -1,
80 - className: changingButton ? 'show-sliding ' + (uploading ? 'ani-working' : '') : 'before-sliding',
80 + className: changingButton ? 'show-sliding ' + (uploading && !uploadDialogIsOpen ? 'ani-working' : '') : 'before-sliding',
81 onClick: showUpload,
82 }),
83 h(Btn, showFilter && can_delete ? {
frontend/src/upload.ts
+3 -3
@@ -166,21 +166,21 @@ function FileList({ entries, actions }: { entries: ToUpload[], actions: { [icon:
166 useEffect(() => setAll(false), [entries.length])
167 const MAX = all ? Infinity : _.round(_.clamp(100 * cpuSpeedIndex, 10, 100))
168 const rest = Math.max(0, snapEntries.length - MAX)
169 + const title = formatPerc(progress)
170 return !snapEntries.length ? null : h('table', { className: 'upload-list', width: '100%' },
171 h('tbody', {},
172 snapEntries.slice(0, MAX).map((e, i) => {
173 const working = e.file === uploading?.file // e is a proxy, so we check 'file' as it's a ref
173 - const title = formatPerc(progress)
174 return h(Fragment, { key: i },
175 h('tr', {},
176 h('td', { className: 'nowrap '}, ..._.map(actions, (cb, icon) =>
177 cb && iconBtn(icon, () => cb(entries[i]), { className: `action-${icon}` })) ),
178 h('td', {}, formatBytes(e.file.size)),
179 h('td', {},
180 - h('span', { className: working ? 'ani-working' : undefined }, e.name || getFilePath(entries[i].file)),
180 + h('span', {}, e.name || getFilePath(entries[i].file)),
181 working && h('span', { className: 'upload-progress', title }, formatBytes(partial)),
182 working && hashing && h('span', { className: 'upload-hashing' }, t`Considering resume`, ' (', formatPerc(hashing), ')'),
183 - working && h('progress', { className: 'upload-progress-bar', title, value: progress, max: 1 }),
183 + working && h('progress', { className: 'upload-progress-bar', title, max: 1, value: _.round(progress, 3) }), // round for fewer dom updates
184 ),
185 ),
186 e.comment && h('tr', {}, h('td', { colSpan: 3 }, h('div', { className: 'entry-comment' }, e.comment)) )
frontend/src/uploadQueue.ts
+11 -7
@@ -59,7 +59,6 @@ let bytesSent = 0
59 setInterval(() => {
60 const now = Date.now()
61 const passed = (now - bytesSentTimestamp) / 1000
62 - if (passed < 3 && uploadState.speed) return
62 uploadState.speed = bytesSent / passed
63 if (now - stuckSince >= 10_000) { // this will normally cause the upload to be retried after 10+10 seconds of no progress
64 overrideStatus = RETRY_UPLOAD // try again
@@ -72,9 +71,9 @@ setInterval(() => {
71 const qBytes = _.sumBy(uploadState.qs, q => _.sumBy(q.entries, x => x.file.size))
72 const left = (qBytes - uploadState.partial)
73 uploadState.eta = uploadState.speed && Math.round(left / uploadState.speed)
75 -}, 5_000)
74 +}, 2_000)
75
77 -let req: XMLHttpRequest | undefined
76 +let currentReq: XMLHttpRequest | undefined
77 let overrideStatus = 0
78 let notificationChannel = ''
79 let notificationSource: EventSource | undefined
@@ -100,11 +99,14 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
99 let offset = resume
100 let stopLooping = false
101 do { // at least one iteration, even for empty files
103 - req = new XMLHttpRequest()
102 + const req = currentReq = new XMLHttpRequest()
103 + req.timeout = 10_000
104 const finished = pendingPromise()
105 + let aborted = false
106 + req.onabort = () => aborted = true
107 req.onloadend = () => {
108 finished.resolve()
107 - if (req?.readyState !== 4) return
109 + currentReq = undefined
110 if (overrideStatus === RETRY_UPLOAD) {
111 overrideStatus = 0
112 stopLooping = true
@@ -119,10 +121,12 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
121 stopLooping = true
122 return
123 }
122 - if (!status || status === HTTP_CONFLICT) // 0 = user-aborted, HTTP_CONFLICT = skipped because existing
124 + if (aborted || status === HTTP_CONFLICT) // 0 = user-aborted, HTTP_CONFLICT = skipped because existing
125 uploadState.skipped.push(toUpload)
126 else if (status >= 400)
127 error(status)
128 + else if (!status) // since no aborted, the request failed at a network level, so try again
129 + return
130 else {
131 if (splitSize) {
132 offset += splitSize
@@ -255,7 +259,7 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
259 }
260
261 export function abortCurrentUpload() {
258 - req?.abort()
262 + currentReq?.abort()
263 }
264 subscribe(uploadState, () => {
265 const [cur] = uploadState.qs