admin/monitoring: track progress for zip-s
Massimo Melina committed
Dec 30, 2023 at 23:37 UTC
7ff0d2033bafa305147324b48632aa929379b07c
4 files changed
+27
-23
admin/src/MonitorPage.ts
+7
-13
@@ -4,9 +4,9 @@ import _ from "lodash"
4
import { createElement as h, useMemo, Fragment, useState } from "react"
5
import { apiCall, useApiEvents, useApiEx, useApiList } from "./api"
6
import { LinkOff, Lock, Block, FolderZip, Upload, Download } from '@mui/icons-material'
7
-import { Box, Chip, ChipProps, Tooltip } from '@mui/material'
7
+import { Box, Chip, ChipProps } from '@mui/material'
8
import { DataTable } from './DataTable'
9
-import { formatBytes, ipForUrl, manipulateConfig, CFG, formatSpeed } from "./misc"
9
+import { formatBytes, ipForUrl, manipulateConfig, CFG, formatSpeed, with_ } from "./misc"
10
import { IconBtn, IconProgress, iconTooltip, usePauseButton, useBreakpoint, Country } from './mui'
11
import { Field, SelectField } from '@hfs/mui-grid-form'
12
import { StandardCSSProperties } from '@mui/system/styleFunctionSx/StandardCssProperties'
@@ -141,26 +141,20 @@ function Connections() {
141
flex: 1.5,
142
renderCell({ value, row }) {
143
if (!value) return
144
- if (row.archive)
145
- return h(Fragment, {},
146
- h(FolderZip, { sx: { mr: 1 } }),
147
- row.archive,
148
- h(Box, { ml: 2, color: 'text.secondary' }, value)
149
- )
144
if (!row.op)
145
return h(Box, {}, value, h(Box, { fontSize: 'x-small' }, "browsing"))
152
- const i = value?.lastIndexOf('/')
146
return h(Fragment, {},
147
h(IconProgress, {
155
- icon: row.op === 'upload' ? Upload : Download,
148
+ icon: row.archive ? FolderZip : row.op === 'upload' ? Upload : Download,
149
progress: row.opProgress ?? row.opOffset,
150
offset: row.opOffset,
151
addTitle: row.opTotal && h('div', {}, "Total: " + formatBytes(row.opTotal)),
152
sx: { mr: 1 }
153
}),
161
- h(Box, {}, value.slice(i + 1),
162
- i > 0 && h(Box, { ml: 2, fontSize: 'x-small', color: 'text.secondary' }, value.slice(0, i))
163
- ),
154
+ row.archive ? h(Box, {}, value, h(Box, { fontSize: 'x-small', color: 'text.secondary' }, row.archive))
155
+ : with_(value?.lastIndexOf('/'), i => h(Box, {}, value.slice(i + 1),
156
+ i > 0 && h(Box, { fontSize: 'x-small', color: 'text.secondary' }, value.slice(0, i))
157
+ )),
158
)
159
}
160
},
admin/src/mui.ts
+1
-1
@@ -42,7 +42,7 @@ export function IconProgress({ icon, progress, offset, addTitle, sx }: IconProgr
42
sx: { position: 'absolute' },
43
}),
44
h(Tooltip, {
45
- title: h(Fragment, {}, formatPerc(progress), addTitle),
45
+ title: h(Fragment, {}, _.isNumber(progress) ? formatPerc(progress) : "Size unknown", addTitle),
46
children: h(CircularProgress, {
47
color: 'success',
48
value: (offset || 1e-7) * 100,
src/serveFile.ts
+17
-8
@@ -14,6 +14,7 @@ import { promisify } from 'util'
14
import { updateConnection } from './connections'
15
import { getCurrentUsername } from './auth'
16
import { sendErrorPage } from './errorPages'
17
+import { Readable } from 'stream'
18
19
const allowedReferer = defineConfig('allowed_referer', '')
20
const limitDownloads = downloadLimiter(defineConfig(CFG.max_downloads, 0), () => true)
@@ -80,21 +81,29 @@ export async function serveFile(ctx: Koa.Context, source:string, mime?:string, c
81
return ctx.body = content
82
const { size } = stats
83
const range = getRange(ctx, size)
83
- ctx.body = createReadStream(source, range).on('end', () =>
84
- updateConnection(ctx.state.connection, { opProgress: 1 }) )
84
+ ctx.body = createReadStream(source, range)
85
if (ctx.vfsNode)
86
- updateConnection(ctx.state.connection, {
87
- ctx, // this will cause 'path' to be sent as well
88
- op: 'download',
89
- opTotal: stats.size,
90
- opOffset: range && (range.start / size),
91
- })
86
+ monitorAsDownload(ctx, size, range?.start)
87
}
88
catch (e: any) {
89
return ctx.status = HTTP_NOT_FOUND
90
}
91
}
92
93
+export function monitorAsDownload(ctx: Koa.Context, size?: number, offset?: number) {
94
+ if (!(ctx.body instanceof Readable))
95
+ throw 'incompatible body'
96
+ const { connection } = ctx.state
97
+ ctx.body.on('end', () =>
98
+ updateConnection(connection, { opProgress: 1 }) )
99
+ updateConnection(connection, {
100
+ ctx, // this will cause 'path' to be sent as well
101
+ op: 'download',
102
+ opTotal: size,
103
+ opOffset: size && offset && (offset / size),
104
+ })
105
+}
106
+
107
export function getRange(ctx: Koa.Context, totalSize: number) {
108
ctx.set('Accept-Ranges', 'bytes')
109
const { range } = ctx.request.header
src/zip.ts
+2
-1
@@ -8,7 +8,7 @@ import { createReadStream } from 'fs'
8
import fs from 'fs/promises'
9
import { defineConfig } from './config'
10
import { basename, dirname } from 'path'
11
-import { getRange } from './serveFile'
11
+import { getRange, monitorAsDownload } from './serveFile'
12
import { HTTP_OK } from './const'
13
14
// expects 'node' to have had permissions checked by caller
@@ -76,6 +76,7 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
76
ctx.body = zip
77
ctx.req.on('close', ()=> zip.destroy())
78
ctx.state.archive = 'zip'
79
+ monitorAsDownload(ctx, size, range?.start)
80
}
81
82
const zipSeconds = defineConfig('zip_calculate_size_for_seconds', 1)