@samitouri / QOSami-HFS / commits / e2a28d8a

upload: "Skip existing files" option

Massimo Melina committed May 10, 2023 at 00:25 UTC e2a28d8a4d91492aa218f6baf72d35b72c051323
4 files changed +40 -24
frontend/src/upload.ts
+33 -22
@@ -1,7 +1,7 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { createElement as h, Fragment, useMemo, useState } from 'react'
4 -import { Flex, FlexV } from './components'
4 +import { Checkbox, Flex, FlexV } from './components'
5 import {
6 closeDialog,
7 DialogCloser,
@@ -33,6 +33,7 @@ export const uploadState = proxy<{
33 partial: number // relative to uploading file. This is how much we have done of the current queue.
34 speed: number
35 eta: number
36 + skipExisting: boolean
37 }>({
38 eta: 0,
39 speed: 0,
@@ -43,6 +44,7 @@ export const uploadState = proxy<{
44 errors: 0,
45 doneByte: 0,
46 done: 0,
47 + skipExisting: false,
48 })
49
50 // keep track of speed
@@ -94,7 +96,7 @@ export function showUpload() {
96
97 function Content(){
98 const [files, setFiles] = useState([] as File[])
97 - const { qs, paused, eta } = useSnapshot(uploadState)
99 + const { qs, paused, eta, skipExisting } = useSnapshot(uploadState)
100 const { can_upload, accept } = useSnapState()
101 const etaStr = useMemo(() => !eta ? '' : formatTime(eta*1000, 0, 2), [eta])
102 const size = formatBytes(files.reduce((a, f) => a + f.size, 0))
@@ -102,24 +104,29 @@ export function showUpload() {
104 return h(FlexV, { gap: 0, props: acceptDropFiles(x => setFiles([ ...files, ...x ])) },
105 h(FlexV, { props: { className: 'upload-toolbar' } },
106 !can_upload ? t('no_upload_here', "No upload permission for the current folder")
105 - : h(Flex, { justifyContent: 'center', flexWrap: 'wrap', margin: '1em 0' },
106 - h('button', {
107 - className: 'upload-files',
108 - onClick: () => pickFiles({ accept: normalizeAccept(accept) })
109 - }, t`Pick files`),
110 - !isMobile() && h('button', {
111 - className: 'upload-folder',
112 - onClick: () => pickFiles({ folder: true })
113 - }, t`Pick folder`),
114 - files.length > 0 && h('button', {
115 - className: 'upload-send',
116 - onClick() {
117 - enqueue(files)
118 - setFiles([])
119 - }
120 - }, t('send_files', { n: files.length, size }, "Send {n,plural,one{# file} other{# files}}, {size}")),
121 - files.length > 1 && h('button', { onClick() { setFiles([]) } }, t`Clear`),
122 - h('button', { className: 'create-folder', onClick: createFolder }, t`Create folder`),
107 + : h(FlexV, { margin: '0 0 1em' },
108 + h(Flex, { justifyContent: 'center', flexWrap: 'wrap', alignItems: 'center' },
109 + h('button', {
110 + className: 'upload-files',
111 + onClick: () => pickFiles({ accept: normalizeAccept(accept) })
112 + }, t`Pick files`),
113 + !isMobile() && h('button', {
114 + className: 'upload-folder',
115 + onClick: () => pickFiles({ folder: true })
116 + }, t`Pick folder`),
117 + h('button', { className: 'create-folder', onClick: createFolder }, t`Create folder`),
118 + h(Checkbox, { value: skipExisting, onChange: v => uploadState.skipExisting = v }, t`Skip existing files`),
119 + ),
120 + files.length > 0 && h(Flex, { justifyContent: 'center', flexWrap: 'wrap' },
121 + h('button', {
122 + className: 'upload-send',
123 + onClick() {
124 + enqueue(files)
125 + setFiles([])
126 + }
127 + }, t('send_files', { n: files.length, size }, "Send {n,plural,one{# file} other{# files}}, {size}")),
128 + h('button', { onClick() { setFiles([]) } }, t`Clear`),
129 + )
130 ),
131 ),
132 h(FilesList, {
@@ -269,7 +276,7 @@ async function startUpload(f: File, to: string, resume=0) {
276 if (req?.readyState !== 4) return
277 const status = overrideStatus || req.status
278 closeLast?.()
272 - if (status) // 0 = user-aborted
279 + if (status && status !== 409) // 0 = user-aborted, 409 = skipped because existing
280 if (status >= 400)
281 error(status)
282 else
@@ -285,7 +292,11 @@ async function startUpload(f: File, to: string, resume=0) {
292 bytesSent += e.loaded - lastProgress
293 lastProgress = e.loaded
294 }
288 - req.open('POST', to + '?' + new URLSearchParams({ notificationChannel, resume: String(resume) }), true)
295 + req.open('POST', to + '?' + new URLSearchParams({
296 + notificationChannel,
297 + resume: String(resume),
298 + ...uploadState.skipExisting && { skipExisting: '1' },
299 + }), true)
300 const form = new FormData()
301 form.append('file', f.slice(resume), path(f))
302 req.send(form)
src/langs/hfs-lang-en.json
+2 -1
@@ -111,6 +111,7 @@
111 "Download": "Download",
112 "Missing permission": "Missing permission",
113 "Reload": "Reload",
114 - "Get list": "Get list"
114 + "Get list": "Get list",
115 + "Skip existing files": "Skip existing files"
116 }
117 }
src/langs/hfs-lang-it.json
+2 -1
@@ -105,6 +105,7 @@
105 "Download": "Download",
106 "Missing permission": "Permesso mancante",
107 "Reload": "Ricarica",
108 - "Get list": "Lista"
108 + "Get list": "Lista",
109 + "Skip existing files": "Salta file esistenti"
110 }
111 }
src/upload.ts
+3
@@ -1,6 +1,7 @@
1 import { statusCodeForMissingPerm, VfsNode } from './vfs'
2 import Koa from 'koa'
3 import {
4 + HTTP_CONFLICT,
5 HTTP_PAYLOAD_TOO_LARGE,
6 HTTP_RANGE_NOT_SATISFIABLE,
7 HTTP_SERVER_ERROR,
@@ -39,6 +40,8 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
40 catch(e: any) {
41 console.warn("can't check disk size:", e.message || String(e))
42 }
43 + if (ctx.query.skipExisting && fs.existsSync(fullPath))
44 + return fail(HTTP_CONFLICT)
45 fs.mkdirSync(dir, { recursive: true })
46 const keepName = basename(fullPath).slice(-200)
47 let tempName = join(dir, 'hfs$upload-' + keepName)