better code: split file

Massimo Melina committed Dec 22, 2021 at 23:24 UTC bc5690478bfed74f3cf69585ab7579b8682755c2
2 files changed +88 -85
frontend/src/BrowseFiles.ts
+3 -85
@@ -1,13 +1,13 @@
1 import { Link, useLocation } from 'react-router-dom'
2 -import { apiCall, apiEvents } from './api'
3 -import { createContext, createElement as h, Fragment, useContext, useEffect, useRef, useState } from 'react'
2 +import { createContext, createElement as h, Fragment, useContext } from 'react'
3 import { formatBytes, hError, hIcon } from './misc'
4 import { Spinner } from './components'
5 import { Head } from './Head'
6 import { state, useSnapState } from './state'
7 import _ from 'lodash'
8 +import useFetchList from './useFetchList'
9
10 -function usePath() {
10 +export function usePath() {
11 return decodeURI(useLocation().pathname)
12 }
13
@@ -28,88 +28,6 @@ export function BrowseFiles() {
28 h(FilesList))
29 }
30
31 -function useFetchList() {
32 - const snap = useSnapState()
33 - const desiredPath = usePath()
34 - const search = snap.remoteSearch || undefined
35 - const [list, setList] = useState<DirList>([])
36 - const [unfinished, setUnfinished] = useState(true)
37 - const [error, setError] = useState<Error>()
38 - const lastPath = useRef('')
39 - useEffect(()=>{
40 - const loc = window.location
41 - if (!desiredPath.endsWith('/')) { // useful only in dev, while accessing the frontend directly without passing by the main server
42 - loc.href = loc.href + '/'
43 - return
44 - }
45 - const previous = lastPath.current
46 - lastPath.current = desiredPath
47 - if (previous !== desiredPath && search) {
48 - state.remoteSearch = ''
49 - state.stopSearch?.()
50 - return
51 - }
52 -
53 - ;(async ()=>{
54 - const API = 'file_list'
55 - const sse = search
56 - const baseParams = { path:desiredPath, search, sse, omit:'c' }
57 - let list: DirList = []
58 - setUnfinished(true)
59 - setList(list)
60 -
61 - if (sse) { // buffering entries is necessary against burst of events that will hang the browser
62 - const buffer:DirList = []
63 - const flush = () => {
64 - const chunk = buffer.splice(0, Infinity)
65 - if (chunk.length)
66 - setList(list = [...list, ...chunk])
67 - }
68 - const timer = setInterval(flush, 1000)
69 - const src = apiEvents(API, baseParams, (type, data) => {
70 - switch (type) {
71 - case 'error':
72 - return setError(Error(JSON.stringify(data)))
73 - case 'closed':
74 - clearInterval(timer)
75 - flush()
76 - return setUnfinished(false)
77 - case 'msg':
78 - if (src?.readyState === src?.CLOSED)
79 - return state.stopSearch?.()
80 - let { entry } = data
81 - console.log(entry.n)
82 - buffer.push(entry)
83 - }
84 - })
85 - state.stopSearch = ()=>{
86 - buffer.length = 0
87 - clearInterval(timer)
88 - state.stopSearch = undefined
89 - src.close()
90 - }
91 - return
92 - }
93 -
94 - let offset = 0
95 - while (1) {
96 - const limit = list.length ? 1000 : 100
97 - const res = await apiCall(API, { ...baseParams, offset, limit })
98 - || Error()
99 - if (res instanceof Error)
100 - return setError(res)
101 - const chunk = res.list
102 - setList(list = [ ...list, ...chunk ])
103 - if (chunk.length < limit)
104 - break
105 - offset = list.length
106 - }
107 - setUnfinished(false)
108 - })()
109 - }, [desiredPath, search])
110 - return { list, unfinished, error }
111 -}
112 -
31 function FilesList() {
32 const { list, unfinished } = useContext(ListContext)
33 const snap = useSnapState()
frontend/src/useFetchList.ts new
+85
@@ -0,0 +1,85 @@
1 +import { state, useSnapState } from './state'
2 +import { useEffect, useRef, useState } from 'react'
3 +import { apiCall, apiEvents } from './api'
4 +import { DirList, usePath } from './BrowseFiles'
5 +
6 +export default function useFetchList() {
7 + const snap = useSnapState()
8 + const desiredPath = usePath()
9 + const search = snap.remoteSearch || undefined
10 + const [list, setList] = useState<DirList>([])
11 + const [unfinished, setUnfinished] = useState(true)
12 + const [error, setError] = useState<Error>()
13 + const lastPath = useRef('')
14 + useEffect(()=>{
15 + const loc = window.location
16 + if (!desiredPath.endsWith('/')) { // useful only in dev, while accessing the frontend directly without passing by the main server
17 + loc.href = loc.href + '/'
18 + return
19 + }
20 + const previous = lastPath.current
21 + lastPath.current = desiredPath
22 + if (previous !== desiredPath && search) {
23 + state.remoteSearch = ''
24 + state.stopSearch?.()
25 + return
26 + }
27 +
28 + ;(async ()=>{
29 + const API = 'file_list'
30 + const sse = search
31 + const baseParams = { path:desiredPath, search, sse, omit:'c' }
32 + let list: DirList = []
33 + setUnfinished(true)
34 + setList(list)
35 +
36 + if (sse) { // buffering entries is necessary against burst of events that will hang the browser
37 + const buffer:DirList = []
38 + const flush = () => {
39 + const chunk = buffer.splice(0, Infinity)
40 + if (chunk.length)
41 + setList(list = [...list, ...chunk])
42 + }
43 + const timer = setInterval(flush, 1000)
44 + const src = apiEvents(API, baseParams, (type, data) => {
45 + switch (type) {
46 + case 'error':
47 + return setError(Error(JSON.stringify(data)))
48 + case 'closed':
49 + flush()
50 + state.stopSearch?.()
51 + return setUnfinished(false)
52 + case 'msg':
53 + if (src?.readyState === src?.CLOSED)
54 + return state.stopSearch?.()
55 + buffer.push(data.entry)
56 + }
57 + })
58 + state.stopSearch = ()=>{
59 + buffer.length = 0
60 + clearInterval(timer)
61 + state.stopSearch = undefined
62 + src.close()
63 + }
64 + return
65 + }
66 +
67 + let offset = 0
68 + while (1) {
69 + const limit = list.length ? 1000 : 100
70 + const res = await apiCall(API, { ...baseParams, offset, limit })
71 + || Error()
72 + if (res instanceof Error)
73 + return setError(res)
74 + const chunk = res.list
75 + setList(list = [ ...list, ...chunk ])
76 + if (chunk.length < limit)
77 + break
78 + offset = list.length
79 + }
80 + setUnfinished(false)
81 + })()
82 + }, [desiredPath, search])
83 + return { list, unfinished, error }
84 +}
85 +