@samitouri / QOSami-HFS / commits / a2091a94

fix: admin/monitoring: reused connections were displaying wrong info (moved logic to SendList because to be effective it must know what has actually been sent, and SendList is the only one)

fix: admin/monitoring: reused connections were displaying wrong info (moved logic to SendList because to be effective it must know what has actually been sent, and SendList is the only one)

Massimo Melina committed Jan 8, 2024 at 00:30 UTC a2091a946222db4d2c378ad4b248232d40b8f482
5 files changed +38 -27
admin/src/MonitorPage.ts
+1 -1
@@ -149,7 +149,7 @@ function Connections() {
149 icon: row.archive ? FolderZip : row.op === 'upload' ? Upload : Download,
150 progress: row.opProgress ?? row.opOffset,
151 offset: row.opOffset,
152 - addTitle: row.op === 'cache' ? "Cache hit" : (row.opTotal && ("Total: " + formatBytes(row.opTotal))),
152 + addTitle: row.opTotal && ("Total: " + formatBytes(row.opTotal)),
153 sx: { mr: 1 }
154 }),
155 row.archive ? h(Box, {}, value, h(Box, { fontSize: 'x-small', color: 'text.secondary' }, row.archive))
src/api.monitor.ts
+8 -21
@@ -2,14 +2,12 @@
2
3 import _ from 'lodash'
4 import { Connection, getConnections } from './connections'
5 -import { HTTP_NOT_MODIFIED, pendingPromise, shortenAgent, typedEntries, wait } from './misc'
5 +import { pendingPromise, shortenAgent, wait } from './misc'
6 import { ApiHandlers, SendListReadable } from './apiMiddleware'
7 import Koa from 'koa'
8 import { totalGot, totalInSpeed, totalOutSpeed, totalSent } from './throttler'
9 import { getCurrentUsername } from './auth'
10
11 -const sent = Symbol('sent')
12 -
11 export default {
12
13 async disconnect({ ip, port, wait }) {
@@ -28,38 +26,27 @@ export default {
26
27 get_connections({}, ctx) {
28 const list = new SendListReadable({
29 + diff: true,
30 addAtStart: getConnections().map(c =>
32 - !ignore(c) && (c[sent] = serializeConnection(c))).filter(Boolean),
33 - onEnd() {
34 - for (const c of getConnections())
35 - delete c[sent]
36 - }
31 + !ignore(c) && serializeConnection(c)).filter(Boolean),
32 })
33 type Change = Partial<Omit<Connection,'ip'>>
34 list.props({ you: ctx.ip })
35 return list.events(ctx, {
36 connection(conn: Connection) {
37 if (ignore(conn)) return
43 - list.add(conn[sent] = serializeConnection(conn))
38 + list.add(serializeConnection(conn))
39 },
40 connectionClosed(conn: Connection) {
41 if (ignore(conn)) return
42 list.remove(getConnAddress(conn))
48 - delete conn[sent]
43 },
44 connectionUpdated(conn: Connection, change: Change) {
51 - if (ignore(conn) || ignore(change as any) || !conn[sent]) return
45 + if (conn.socket.closed || ignore(conn) || ignore(change as any) || _.isEmpty(change)) return
46 if (change.ctx) {
47 Object.assign(change, fromCtx(change.ctx))
48 change.ctx = undefined
49 }
56 - // avoid sending non-changes
57 - const last = conn[sent]
58 - for (const [k, v] of typedEntries(change))
59 - if (v === last[k])
60 - delete change[k]
61 - if (_.isEmpty(change)) return
62 - Object.assign(last, change)
50 list.update(getConnAddress(conn), change)
51 },
52 })
@@ -88,10 +75,10 @@ export default {
75 ...s.browsing ? { op: 'browsing', path: decodeURIComponent(s.browsing) }
76 : s.uploadPath ? { op: 'upload',path: decodeURIComponent(s.uploadPath) }
77 : {
91 - op: s.op === 'download' && ctx.status === HTTP_NOT_MODIFIED ? 'cache' : s.op,
92 - path: decodeURIComponent(ctx.path)
78 + op: !s.considerAsGui && s.op || undefined,
79 + path: decodeURIComponent(ctx.originalUrl)
80 },
94 - opProgress: _.round(s.opProgress, 3),
81 + opProgress: _.isNumber(s.opProgress) ? _.round(s.opProgress, 3) : undefined,
82 opTotal: s.opTotal,
83 opOffset: s.opOffset,
84 }
src/apiMiddleware.ts
+26 -2
@@ -3,7 +3,7 @@
3 import Koa from 'koa'
4 import createSSE from './sse'
5 import { Readable } from 'stream'
6 -import { asyncGeneratorToReadable, LIST, onOff, removeStarting } from './misc'
6 +import { asyncGeneratorToReadable, LIST, onOff, removeStarting, wantArray } from './misc'
7 import events from './events'
8 import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND } from './const'
9 import _ from 'lodash'
@@ -86,11 +86,35 @@ export class SendListReadable<T> extends Readable {
86 protected lastError: string | number | undefined
87 protected buffer: any[] = []
88 protected processBuffer: _.DebouncedFunc<any>
89 - constructor({ addAtStart, doAtStart, bufferTime, onEnd }:{ bufferTime?: number, addAtStart?: T[], doAtStart?: SendListFunc<T>, onEnd?: SendListFunc<T> }={}) {
89 + protected sent: undefined | T[]
90 + constructor({ addAtStart, doAtStart, bufferTime, onEnd, diff }:
91 + { bufferTime?: number, addAtStart?: T[], doAtStart?: SendListFunc<T>, onEnd?: SendListFunc<T>, diff?: boolean }={}) {
92 super({ objectMode: true, read(){} })
93 if (!bufferTime)
94 bufferTime = 200
95 + if (diff)
96 + this.sent = []
97 this.processBuffer = _.debounce(() => {
98 + const {sent} = this
99 + if (sent)
100 + this.buffer = this.buffer.filter(([cmd, a, b]) => {
101 + if (cmd === LIST.add)
102 + return sent.push(...wantArray(a))
103 + if (cmd === LIST.remove)
104 + return _.remove(sent, a)
105 + if (cmd !== LIST.update)
106 + return true
107 + const found = _.find(sent, a) as any
108 + if (!found) return
109 + for (const k in b)
110 + if (b[k] === found[k])
111 + delete b[k]
112 + else {
113 + found[k] = b[k]
114 + b[k] ??= null // go and delete it, remotely
115 + }
116 + return !_.isEmpty(b)
117 + })
118 if (!this.buffer.length) return
119 this.push(this.buffer)
120 this.buffer = []
src/connections.ts
+1
@@ -61,6 +61,7 @@ export function updateConnectionForCtx(ctx: Context ) {
61 const conn = getConnection(ctx)
62 if (conn)
63 updateConnection(conn, { ctx })
64 + return conn
65 }
66
67 export function updateConnection(conn: Connection, change: Partial<Connection>, changeState?: true | Partial<Context['state']>) {
src/upload.ts
+2 -3
@@ -12,7 +12,7 @@ import { Callback, dirTraversal, escapeHTML, loadFileAttr, storeFileAttr, try_ }
12 import { notifyClient } from './frontEndApis'
13 import { defineConfig } from './config'
14 import { getFreeDiskSync } from './util-os'
15 -import { socket2connection, updateConnection, updateConnectionForCtx } from './connections'
15 +import { updateConnection, updateConnectionForCtx } from './connections'
16 import { roundSpeed } from './throttler'
17 import { getCurrentUsername } from './auth'
18 import { setCommentFor } from './comments'
@@ -131,9 +131,8 @@ export function uploadWriter(base: VfsNode, path: string, ctx: Koa.Context) {
131 let lastGotTime = 0
132 const opTotal = reqSize + resume
133 Object.assign(ctx.state, { op: 'upload', opTotal, opOffset: resume / opTotal, opProgress: 0 })
134 - const conn = socket2connection(ctx.socket)
134 + const conn = updateConnectionForCtx(ctx)
135 if (!conn) return
136 - updateConnectionForCtx(ctx)
136 const h = setInterval(() => {
137 const now = Date.now()
138 const got = ret.bytesWritten