@samitouri / QOSami-HFS / commits / 41ff4f22

?onlyFiles and ?onlyFolders #686

Massimo Melina committed Nov 30, 2024 at 00:09 UTC 41ff4f22731c8acadd78035171f29b4ad4a7caa0
5 files changed +24 -11
README.md
+2 -1
@@ -126,7 +126,8 @@ If your language is missing, please consider [translating yourself](https://gith
126 - Right-click on toggle-all checkbox will invert each checkbox state
127 - Appending `?login=USER:PASSWORD` will automatically log in the browser
128 - Appending `?overwrite` on uploads, will override the dont_overwrite_uploading configuration, provided you also have delete permission
129 -- Appending `?search=PATTERN` will trigger search at start
129 +- Appending `?search=PATTERN` will trigger search at start
130 +- Appending `?onlyFiles` or `?onlyFolders` will limit type of results
131 - Appending `?autoplay=shuffle` will trigger show & play; `?autoplay` will not shuffle, but also will not start until the list is complete
132 - Right-click on "check for updates" will let you input a URL of a version to install
133 - Shift+click on a file will show & play
frontend/src/useFetchList.ts
+11 -4
@@ -7,7 +7,10 @@ import _ from 'lodash'
7 import { subscribeKey } from 'valtio/utils'
8 import { useIsMounted } from 'usehooks-ts'
9 import { alertDialog } from './dialog'
10 -import { hfsEvent, HTTP_MESSAGES, HTTP_METHOD_NOT_ALLOWED, HTTP_UNAUTHORIZED, LIST, urlParams, xlate } from './misc'
10 +import {
11 + hfsEvent, LIST, urlParams, xlate, objFromKeys,
12 + HTTP_MESSAGES, HTTP_METHOD_NOT_ALLOWED, HTTP_UNAUTHORIZED,
13 +} from './misc'
14 import { t } from './i18n'
15 import { useLocation, useNavigate } from 'react-router-dom'
16 import { closeLoginDialog } from './login'
@@ -18,8 +21,11 @@ export function usePath() {
21 }
22
23 // allow links with ?search
21 -setTimeout(() => // wait, urlParams is defined at top level
22 - state.remoteSearch = urlParams.search || '')
24 +let firstListRequest: any
25 +setTimeout(() => {// wait, urlParams is defined at top level
26 + state.remoteSearch = urlParams.search || ''
27 + firstListRequest = objFromKeys(['onlyFiles', 'onlyFolders'], x => x in urlParams || undefined)
28 +})
29
30 let autoPlayOnce: string | undefined = urlParams.autoplay // this will be consumed, so to only act once
31
@@ -47,7 +53,7 @@ export default function useFetchList() {
53 return
54 }
55
50 - const params = { uri, search, ...snap.searchOptions }
56 + const params = { uri, search, ...firstListRequest, ...snap.searchOptions }
57 params.wild = params.wild ? undefined : 'no'
58 if (snap.listReloader === lastReloader.current && _.isEqual(params, lastParams.current)) return
59 lastParams.current = params
@@ -83,6 +89,7 @@ export default function useFetchList() {
89 if (autoPlayOnce === '') play = true
90 if (autoPlayOnce === 'shuffle') playShuffle = true
91 autoPlayOnce = undefined
92 + firstListRequest = undefined
93 return
94 case 'error':
95 state.stopSearch?.()
src/api.get_file_list.ts
+2 -2
@@ -19,7 +19,7 @@ import { SendListReadable } from './SendList'
19
20 export interface DirEntry { n:string, s?:number, m?:Date, c?:Date, p?: string, comment?: string, web?: boolean, url?: string, target?: string, icon?: string | true }
21
22 -export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, search, wild, c, onlyFolders, admin }, ctx) => {
22 +export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, search, wild, c, onlyFolders, onlyFiles, admin }, ctx) => {
23 const node = await urlToNode(uri, ctx)
24 const list = ctx.get('accept') === 'text/event-stream' ? new SendListReadable() : undefined
25 if (!node)
@@ -37,7 +37,7 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, search
37 search = String(search || '').toLocaleLowerCase()
38 const filter = wild === 'no' ? (s: string) => s.includes(search)
39 : pattern2filter(search)
40 - const walker = walkNode(node, { ctx: admin ? undefined : ctx, onlyFolders, depth: search ? Infinity : 0 })
40 + const walker = walkNode(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth: search ? Infinity : 0 })
41 const onDirEntryHandlers = mapPlugins(plug => plug.onDirEntry)
42 const can_upload = admin || hasPermission(node, 'can_upload', ctx)
43 const fakeChild = await applyParentToChild({ source: 'dummy-file' }, node) // used to check permission; simple but but can produce false results
src/cross.ts
+4
@@ -128,6 +128,10 @@ export function objSameKeys<S extends object,VR=any>(src: S, newValue:(value:Tru
128 return Object.fromEntries(Object.entries(src).map(([k,v]) => [k, newValue(v,k as keyof S)])) as { [K in keyof S]:VR }
129 }
130
131 +export function objFromKeys<K extends string, VR=unknown>(src: K[], getValue: (value: K)=> VR) {
132 + return Object.fromEntries(src.map(k => [k, getValue(k)]))
133 +}
134 +
135 export function enforceFinal(sub:string, s:string, evenEmpty=false) {
136 return (s ? !s.endsWith(sub) : evenEmpty) ? s + sub : s
137 }
src/vfs.ts
+5 -4
@@ -268,15 +268,16 @@ export async function* walkNode(parent: VfsNode, {
268 depth = Infinity,
269 prefixPath = '',
270 requiredPerm,
271 - onlyFolders = false
272 -}: { ctx?: Koa.Context,depth?: number, prefixPath?: string, requiredPerm?: undefined | keyof VfsPerms, onlyFolders?: boolean } = {}): AsyncIterableIterator<VfsNode> {
271 + onlyFolders = false,
272 + onlyFiles = false,
273 +}: { ctx?: Koa.Context,depth?: number, prefixPath?: string, requiredPerm?: undefined | keyof VfsPerms, onlyFolders?: boolean, onlyFiles?: boolean } = {}): AsyncIterableIterator<VfsNode> {
274 const { children, source } = parent
275 const took = prefixPath ? undefined : new Set()
276 const maskApplier = parentMaskApplier(parent)
277 const parentsCache = new Map() // we use this only if depth > 0
278 if (children)
279 for (const child of children) {
279 - if (onlyFolders && !await nodeIsDirectory(child)) continue
280 + if (await nodeIsDirectory(child) ? onlyFiles : onlyFolders) continue
281 const nodeName = getNodeName(child)
282 const name = prefixPath + nodeName
283 took?.add(normalizeFilename(name))
@@ -302,7 +303,7 @@ export async function* walkNode(parent: VfsNode, {
303 try {
304 let lastDir = prefixPath.slice(0, -1) || '.'
305 parentsCache.set(lastDir, parent)
305 - for await (const entry of dirStream(source, { depth, onlyFolders, hidden: showHiddenFiles.get() })) {
306 + for await (const entry of dirStream(source, { depth, onlyFolders, onlyFiles, hidden: showHiddenFiles.get() })) {
307 if (ctx?.req.aborted)
308 return
309 const {path} = entry