@samitouri / QOSami-HFS / commits / 2ea262e9

optimized list protocol for space

Massimo Melina committed Nov 11, 2023 at 11:49 UTC 2ea262e938b89456b778d969894dde4cdcb747d6
4 files changed +25 -24
admin/src/api.ts
+7 -7
@@ -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, useEffect, useMemo, useRef, useState } from 'react'
4 -import { Dict, err2msg, Falsy, IconBtn, spinner, useStateMounted, wantArray, xlate } from './misc'
4 +import { Dict, err2msg, Falsy, IconBtn, LIST, spinner, useStateMounted, wantArray, xlate } from './misc'
5 import { Alert } from '@mui/material'
6 import _ from 'lodash'
7 import { state } from './state'
@@ -75,34 +75,34 @@ export function useApiList<T=any, S=T>(cmd:string|Falsy, params: Dict={}, { map
75 if (!Array.isArray(msg))
76 return console.debug('illegal list packet', msg)
77 const [op, par] = msg
78 - if (op === 'ready') {
78 + if (op === LIST.ready) {
79 apply.flush()
80 setInitializing(false)
81 return
82 }
83 - if (op === 'error') {
83 + if (op === LIST.error) {
84 if (par === 401)
85 state.loginRequired = msg[2].possible !== false || 403
86 else
87 setError(err2msg(par))
88 return
89 }
90 - if (op === 'props')
90 + if (op === LIST.props)
91 return setProps(par)
92 - if (op === 'add') {
92 + if (op === LIST.add) {
93 const mappedPar = map?.(par) ?? par
94 mappedPar.id ??= idGenerator.current = Math.max(idGenerator.current, Date.now()) + .001
95 bufferAdd.push(mappedPar)
96 apply()
97 return
98 }
99 - if (op === 'remove') {
99 + if (op === LIST.remove) {
100 const match = _.matches(par)
101 if (_.isEmpty(_.remove(bufferAdd, match))) // first remove from the buffer
102 removeOnList.push(match)
103 return
104 }
105 - if (op === 'update') {
105 + if (op === LIST.update) {
106 const change = msg[2]
107 const found = _.find(bufferAdd, par)
108 if (found)
frontend/src/useFetchList.ts
+4 -4
@@ -7,7 +7,7 @@ import _ from 'lodash'
7 import { subscribeKey } from 'valtio/utils'
8 import { useIsMounted } from 'usehooks-ts'
9 import { alertDialog } from './dialog'
10 -import { HTTP_MESSAGES, xlate } from './misc'
10 +import { HTTP_MESSAGES, LIST, xlate } from './misc'
11 import { t } from './i18n'
12 import { useLocation, useNavigate } from 'react-router-dom'
13
@@ -76,7 +76,7 @@ export default function useFetchList() {
76 for (const entry of data) {
77 if (!Array.isArray(entry)) continue // unexpected
78 const [op, par] = entry
79 - const error = op === 'error' && par
79 + const error = op === LIST.error && par
80 if (error === 405) { // "method not allowed" happens when we try to directly access an unauthorized file, and we get a login prompt, and then get_file_list the file (because we didn't know it was file or folder)
81 state.messageOnly = t('upload_starting', "Your download should now start")
82 window.location.reload() // reload will start the download, because now we got authenticated
@@ -93,11 +93,11 @@ export default function useFetchList() {
93 }
94 if (uri && !uri.endsWith('/')) // now we know it was a folder for sure
95 return navigate(uri + '/')
96 - if (op === 'props') {
96 + if (op === LIST.props) {
97 state.props = par
98 continue
99 }
100 - if (op === 'add')
100 + if (op === LIST.add)
101 buffer.push(new DirEntry(par.n, par))
102 }
103 if (src?.readyState === src?.CLOSED)
src/apiMiddleware.ts
+13 -13
@@ -3,7 +3,7 @@
3 import Koa from 'koa'
4 import createSSE from './sse'
5 import { Readable } from 'stream'
6 -import { asyncGeneratorToReadable, onOff, removeStarting } from './misc'
6 +import { asyncGeneratorToReadable, LIST, onOff, removeStarting } from './misc'
7 import events from './events'
8 import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND } from './const'
9 import _ from 'lodash'
@@ -113,41 +113,41 @@ export class SendListReadable<T> extends Readable {
113 this.processBuffer()
114 }
115 add(rec: T | T[]) {
116 - this._push(['add', rec])
116 + this._push([LIST.add, rec])
117 }
118 remove(search: Partial<T>) {
119 const match = _.matches(search)
120 const idx = _.findIndex(this.buffer, x => match(x[1]))
121 const found = this.buffer[idx]
122 const op = found?.[0]
123 - if (op === 'remove') return
123 + if (op === LIST.remove) return
124 if (found) {
125 this.buffer.splice(idx, 1)
126 - if (op === 'add') return
126 + if (op === LIST.add) return
127 }
128 - this._push(['remove', search])
128 + this._push([LIST.remove, search])
129 }
130 update(search: Partial<T>, change: Partial<T>) {
131 if (_.isEmpty(change)) return
132 const match = _.matches(search)
133 const found = _.find(this.buffer, x => match(x[1]))
134 const op = found?.[0]
135 - if (op === 'remove') return
136 - if (op === 'add' || op === 'update')
137 - return Object.assign(found[op === 'add' ? 1 : 2], change)
138 - return this._push(['update', search, change])
135 + if (op === LIST.remove) return
136 + if (op === LIST.add || op === LIST.update)
137 + return Object.assign(found[op === LIST.add ? 1 : 2], change)
138 + return this._push([LIST.update, search, change])
139 }
140 ready() { // useful to indicate the end of an initial phase, but we leave open for updates
141 - this._push(['ready'])
141 + this._push([LIST.ready])
142 }
143 custom(name: string, data: any) {
144 - this._push([name, data])
144 + this._push(data === undefined ? [name] : [name, data])
145 }
146 props(props: object) {
147 - this._push(['props', props])
147 + this._push([LIST.props, props])
148 }
149 error(msg: NonNullable<typeof this.lastError>, close=false, props?: object) {
150 - this._push(['error', msg, props])
150 + this._push([LIST.error, msg, props])
151 this.lastError = msg
152 if (close)
153 this.close()
src/cross.ts
+1
@@ -22,6 +22,7 @@ export const FRONTEND_OPTIONS = {
22 export const SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time']
23 export const THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' }
24 export const CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown'])
25 +export const LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' }
26 export type Dict<T=any> = Record<string, T>
27 export type Falsy = false | null | undefined | '' | 0
28 type Truthy<T> = T extends false | '' | 0 | null | undefined ? never : T