fix: folder-size didn't work on some entries

Massimo Melina committed Jan 19, 2025 at 18:06 UTC 7341aead49ffbbaef5582c49b317409ea77bd568
3 files changed +24 -23
frontend/src/fileMenu.ts
+13 -12
@@ -1,6 +1,6 @@
1 import {
2 dontBotherWithKeys, formatBytes, getHFS, hfsEvent, hIcon, newDialog, prefix, with_, working,
3 - pathEncode, closeDialog, anyDialogOpen, Falsy, operationSuccessful, randomId
3 + pathEncode, closeDialog, anyDialogOpen, Falsy, operationSuccessful, randomId, err2msg
4 } from './misc'
5 import { createElement as h, Fragment, isValidElement, MouseEvent, ReactNode, useState } from 'react'
6 import { Btn, Bytes, Spinner } from './components'
@@ -86,7 +86,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (Falsy
86 onClick: () => closeDialog(null, true)
87 }, folder.replaceAll('/', ' / '))
88 },
89 - isFolder && { id: 'folderSize', label: t`Size`, value: h(FolderSize) },
89 + isFolder && !entry.cantOpen && { id: 'folderSize', label: t`Size`, value: h(FolderSize) },
90 ].filter(Boolean)
91 const res = hfsEvent('fileMenu', { entry, menu, props })
92 menu.push(...res.flat()) // flat because each plugin may return an array of entries
@@ -149,19 +149,20 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (Falsy
149 function FolderSize() {
150 const [go, setGo] = useState(false)
151 const [id] = useState(() => randomId())
152 - const { data } = useApi(go && 'get_folder_size', { uri: entry.uri, id }, { timeout: false })
153 - const partial = useApi(go && !data && 'get_folder_size_partial', { id })
152 + const { data, error, loading } = useApi(go && 'get_folder_size', { uri: entry.uri, id }, { timeout: false })
153 + const partial = useApi(loading && 'get_folder_size_partial', { id })
154 useInterval(partial.reload, 1000)
155 - return data ? showRes(data)
156 - : !go ? h(Btn, { asText: true, label: t`Calculate`, onClick() { setGo(true) } })
157 - : h('span', {},
158 - showRes(partial.data),
159 - ' ',
160 - h(Btn, { asText: true, label: t`Cancel`, icon: h(Spinner), onClick() { setGo(false) } })
161 - )
155 + return showRes(data || error)
156 + || (!loading ? h(Btn, { asText: true, label: t`Calculate`, onClick() { setGo(true) } })
157 + : h('span', {}, showRes(partial.data), ' ',
158 + h(Btn, { asText: true, label: t`Cancel`, icon: h(Spinner), onClick() { setGo(false) } }) )
159 + )
160
161 function showRes(data: any) {
164 - return data && h('span', {}, h(Bytes, _.pick(data,'bytes')), ' / ', t('n_files', { n: data.files.toLocaleString() }, '{n,plural,one{# file} other{# files}}') )
162 + return data && (
163 + data.code ? err2msg(data)
164 + : h('span', {}, h(Bytes, _.pick(data,'bytes')), ' / ', t('n_files', { n: data.files.toLocaleString() }, '{n,plural,one{# file} other{# files}}') )
165 + )
166 }
167 }
168 }
shared/api.ts
+10 -10
@@ -84,8 +84,8 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
84 const [data, setData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
85 const [error, setError] = useStateMounted<Error | undefined>(undefined)
86 const [forcer, setForcer] = useStateMounted(0)
87 - const loadingRef = useRef<ReturnType<typeof apiCall>>()
88 - const reloadingRef = useRef<any>()
87 + const [loading, setLoading, getLoading] = useStateMounted<undefined | ReturnType<typeof apiCall>>(undefined)
88 + const reloadPromise = useRef<any>()
89 const dataRef = useRef<any>()
90 useEffect(() => {
91 setError(undefined)
@@ -98,25 +98,25 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
98 setError(err)
99 setData(dataRef.current = undefined)
100 })
101 - .finally(() => loadingRef.current = reloadingRef.current = undefined)
102 - loadingRef.current = Object.assign(wholePromise, {
101 + .finally(() => setLoading(reloadPromise.current = undefined))
102 + if (cmd && !aborted) setLoading(Object.assign(wholePromise, {
103 abort() {
104 aborted = true
105 req?.abort()
106 }
107 - })
108 - reloadingRef.current?.resolve(wholePromise)
109 - return () => loadingRef.current?.abort()
107 + }))
108 + reloadPromise.current?.resolve(wholePromise)
109 + return () => getLoading()?.abort()
110 }, [cmd, JSON.stringify(params), forcer]) //eslint-disable-line -- json-ize to detect deep changes
111 const reload = useCallback(() => {
112 - if (loadingRef.current) return
112 + if (getLoading()) return
113 setForcer(v => v + 1)
114 - reloadingRef.current = pendingPromise()
114 + reloadPromise.current = pendingPromise()
115 }, [setForcer])
116 const ee = useMemo(() => new BetterEventEmitter, [])
117 const sub = useCallback((cb: Callback) => ee.on('data', cb), [ee])
118 useEffect(() => { ee.emit('data') }, [data])
119 - return { data, setData, error, reload, sub, loading: loadingRef.current || reloadingRef.current, getData: () => dataRef.current }
119 + return { data, setData, error, reload, sub, loading, getData: () => dataRef.current }
120 }
121
122 type EventHandler = (type:string, data?:any) => void
src/frontEndApis.ts
+1 -1
@@ -194,7 +194,7 @@ export const frontEndApis: ApiHandlers = {
194 files++
195 partialFolderSize[id] = { bytes, files }
196 }
197 - return popKey(partialFolderSize, id)
197 + return popKey(partialFolderSize, id) || { bytes, files }
198 },
199 }
200