admin/config: delete_unfinished_uploads_after
Massimo Melina committed
Jan 22, 2023 at 10:00 UTC
cab88fecd2edeb0270b1b8a3b78b680c2dfc3b63
4 files changed
+32
-7
README.md
+1
@@ -157,6 +157,7 @@ an *env* called `HFS_CONFIG`. Any relative path provided is relative to the *cwd
157
- `custom_header` provide HTML code to be put at the top of your Frontend. Default is none.
158
- `localhost_admin` should Admin be accessed without credentials when on localhost. Default is true.
159
- `proxies` number of proxies between server and clients to be trusted about providing clients' IP addresses. Default is 0.
160
+- `keep_unfinished_uploads` should unfinished uploads be deleted immediately when interrupted. Default is true.
161
162
#### Virtual File System (VFS)
163
admin/src/ConfigPage.ts
+4
-2
@@ -110,9 +110,11 @@ export default function ConfigPage() {
110
},
111
{ k: 'allowed_referer', placeholder: "any",
112
helperText: values.allowed_referer ? "Leave empty to allow any" : "Use this to avoid direct links from other websites", },
113
- { k: 'zip_calculate_size_for_seconds', comp: NumberField, sm: 6, label: "Calculate ZIP size for seconds",
113
+ { k: 'zip_calculate_size_for_seconds', comp: NumberField, sm: 6, md: 3, label: "Calculate ZIP size for", unit: "seconds",
114
helperText: "If time is not enough, the browser will not show download percentage" },
115
- { k: 'custom_header', multiline: true, sx: { '& textarea': { fontFamily: 'monospace' } },
115
+ { k: 'delete_unfinished_uploads_after', comp: NumberField, sm: 6, md: 3, min : 0, unit: "seconds",
116
+ helperText: "Leave empty to never delete" },
117
+ { k: 'custom_header', multiline: true, sm: 12, md: 6, sx: { '& textarea': { fontFamily: 'monospace' } },
118
helperText: "Any HTML code here will be used as header for the Frontend"
119
},
120
{ k: 'mime', comp: StringStringField,
mui-grid-form/misc-fields.ts
+9
-1
@@ -11,6 +11,7 @@ import {
11
FormGroup,
12
FormHelperText,
13
FormLabel,
14
+ InputAdornment,
15
Switch
16
} from '@mui/material'
17
@@ -20,7 +21,7 @@ export function DisplayField({ value, empty='-', ...props }: any) {
21
return h(StringField, { ...props, value, disabled: true })
22
}
23
23
-export function NumberField({ value, onChange, getApi, required, min, max, step, ...props }: FieldProps<number | null>) {
24
+export function NumberField({ value, onChange, getApi, required, min, max, step, unit, ...props }: FieldProps<number | null>) {
25
getApi?.({
26
getError() {
27
return value == null ? (required ? "required" : false)
@@ -37,6 +38,13 @@ export function NumberField({ value, onChange, getApi, required, min, max, step,
38
})
39
},
40
inputProps: { min, max, step, },
41
+ InputProps: unit && {
42
+ sx: { pr: '6px', '& input': { pl: '.2em', textAlign: 'right' } },
43
+ endAdornment: h(InputAdornment, {
44
+ position: 'end',
45
+ sx: { mt: '1.2em', ml: '2px', '& p': { fontSize: '80%' } }
46
+ }, unit),
47
+ },
48
...props,
49
})
50
}
src/middlewares.ts
+18
-4
@@ -27,10 +27,11 @@ import basicAuth from 'basic-auth'
27
import { SRPClientSession, SRPParameters, SRPRoutines } from 'tssrp6a'
28
import { srpStep1 } from './api.auth'
29
import { basename, dirname, join } from 'path'
30
-import { createWriteStream, mkdirSync, rename } from 'fs'
30
+import { createWriteStream, mkdirSync, rename, rm } from 'fs'
31
import { pipeline } from 'stream/promises'
32
import formidable from 'formidable'
33
import { notifyClient } from './frontEndApis'
34
+import { defineConfig } from './config'
35
36
export const gzipper = compress({
37
threshold: 2048,
@@ -144,16 +145,29 @@ export const serveGuiAndSharedFiles: Koa.Middleware = async (ctx, next) => {
145
mkdirSync(dir, { recursive: true })
146
const tempName = join(dir, 'hfs$uploading-' + basename(fullPath).slice(-20))
147
const ret = createWriteStream(tempName)
148
+ clearTimeout(waitingToBeDeleted[tempName])
149
+ delete waitingToBeDeleted[tempName]
150
ret.on('close', () => {
148
- if (ctx.req.aborted) return
149
- rename(tempName, fullPath, err =>
150
- err && console.error("couldn't rename temp to", fullPath, String(err)))
151
+ if (!ctx.req.aborted)
152
+ return rename(tempName, fullPath, err =>
153
+ err && console.error("couldn't rename temp to", fullPath, String(err)))
154
+ const sec = deleteUnfinishedUploadsAfter.get()
155
+ if (typeof sec !== 'number') return
156
+ waitingToBeDeleted[tempName] = setTimeout(deleteNow, sec * 1000)
157
+
158
+ function deleteNow() {
159
+ delete waitingToBeDeleted[tempName]
160
+ rm(tempName, () => {})
161
+ }
162
})
163
return ret
164
}
165
166
}
167
168
+const deleteUnfinishedUploadsAfter = defineConfig('delete_unfinished_uploads_after')
169
+const waitingToBeDeleted: Record<string, ReturnType<typeof setTimeout>> = {}
170
+
171
let proxyDetected = false
172
export const someSecurity: Koa.Middleware = async (ctx, next) => {
173
ctx.request.ip = normalizeIp(ctx.ip)