plugin/download-counter: Count files in zip/archives (optional)

Massimo Melina committed Aug 2, 2023 at 17:43 UTC 9c71218ec96184197426126c9d89ab8490d04048
4 files changed +28 -7
plugins/download-counter/plugin.js
+11 -6
@@ -1,13 +1,14 @@
1 // other plugins can use ctx.state.download_counter_ignore to mark downloads that shouldn't be counted
2
3 exports.description = "Counts downloads for each file, and displays the total in the list or file menu"
4 -exports.version = 4.1 // fix: different cases and encodings with urls weren't properly counted
4 +exports.version = 5 // count files in archive
5 exports.apiRequired = 8.3
6
7 exports.config = {
8 where: { frontend: true, type: 'select', defaultValue: 'menu',
9 options: ['list', { value: 'menu', label: "file menu" }],
10 - }
10 + },
11 + archives: { defaultValue: true, type: 'boolean', label: "Count files in zip/archives" },
12 }
13 exports.configDialog = {
14 sx: { maxWidth: '20em' },
@@ -45,11 +46,15 @@ exports.init = async api => {
46 unload: () => save.flush(), // we may have pending savings
47 middleware: (ctx) =>
48 () => { // execute after other middlewares are done
48 - if (ctx.status >= 300 || !ctx.vfsNode || ctx.state.download_counter_ignore) return
49 - if (ctx.state.includesLastByte === false) return
49 + if (ctx.status >= 300 || ctx.state.download_counter_ignore || ctx.state.includesLastByte === false) return
50 + if (!(ctx.vfsNode || api.getConfig('archives') && ctx.state.archive)) return
51 ctx.state.completed.then(() => {
51 - const k = uri2key(ctx.path)
52 - counters[k] = counters[k] + 1 || 1
52 + const key = uri2key(ctx.path)
53 + const entries = ctx.vfsNode ? [key]
54 + : ctx.state.originalStream?.getArchiveEntries?.().filter(x => x.at(-1) !== '/').map(x => key + uri2key(x))
55 + if (!entries) return
56 + for (const k of entries)
57 + counters[k] = counters[k] + 1 || 1
58 save()
59 })
60 },
plugins/vhosting/plugin.js
+2
@@ -44,6 +44,8 @@ exports.init = api => {
44 let { root='' } = row
45 if (root.endsWith('/'))
46 root = root.slice(0, -1)
47 + if (root && root[0] !== '/') // normalize
48 + root = '/' + root
49 if (!root) return
50 if (!params)
51 ctx.path = root + ctx.path
src/QuickZipStream.ts
+14 -1
@@ -24,10 +24,19 @@ interface ZipSource {
24 ts?: Date
25 mode?: number
26 }
27 +interface QuickZipEntry {
28 + size: number,
29 + crc: number,
30 + ts: Date,
31 + pathAsBuffer: Buffer,
32 + offset: number,
33 + version: number,
34 + extAttr: number,
35 +}
36 export class QuickZipStream extends Readable {
37 private workingFile: Readable | undefined
38 private finished = false
30 - private readonly entries: ({ size:number, crc:number, ts:Date, pathAsBuffer:Buffer, offset:number, version:number, extAttr: number })[] = []
39 + private readonly entries: QuickZipEntry[] = []
40 private dataWritten = 0
41 private consumedCalculating: ZipSource[] = []
42 private skip: number = 0
@@ -38,6 +47,10 @@ export class QuickZipStream extends Readable {
47 super({})
48 }
49
50 + getArchiveEntries() {
51 + return this.entries.map(x => String(x.pathAsBuffer))
52 + }
53 +
54 earlyClose() {
55 this.finished = true
56 this.push(null)
src/throttler.ts
+1
@@ -76,6 +76,7 @@ export const throttler: Koa.Middleware = async (ctx, next) => {
76 })
77
78 const downloadTotal: number = ctx.response.length
79 + ctx.state.originalStream = ctx.body
80 ctx.body = ctx.body.pipe(ts)
81
82 if (downloadTotal) // preserve this info