archive for search results

Massimo Melina committed Jan 18, 2022 at 09:15 UTC 51f03ed57d535b951c9f4610f9a86a945db93cd6
5 files changed +15 -10
frontend/src/menu.ts
+2 -2
@@ -40,8 +40,8 @@ export function MenuPanel() {
40 h(MenuLink, {
41 icon: 'archive',
42 label: 'Archive',
43 - href: '?get=zip', // @ts-ignore
44 - confirm: 'Download whole folder as ZIP archive?',
43 + href: '?get=zip' + prefix('&search=', remoteSearch),
44 + confirm: remoteSearch ? 'Download results of this search as ZIP archive?' : 'Download whole folder as ZIP archive?',
45 })
46 ),
47 remoteSearch && h('div', { id: 'searched' }, (stopSearch ? 'Searching' : 'Searched') + ': ' + remoteSearch + prefix(' (', stoppedSearch && 'interrupted', ')')),
src/api.file_list.ts
+3 -5
@@ -1,10 +1,10 @@
1 import { vfs, VfsNode, walkNode } from './vfs'
2 -import _ from 'lodash'
2 import createSSE from './sse'
3 import { basename } from 'path'
4 import { ApiError, ApiHandler } from './apis'
5 import { stat } from 'fs/promises'
6 import { mapPlugins } from './plugins'
7 +import { pattern2filter } from './misc'
8
9 export const file_list:ApiHandler = async ({ path, offset, limit, search, omit, sse }, ctx) => {
10 let node = await vfs.urlToNode(path || '/', ctx)
@@ -16,8 +16,7 @@ export const file_list:ApiHandler = async ({ path, offset, limit, search, omit,
16 return { redirect: path }
17 offset = Number(offset)
18 limit = Number(limit)
19 - const re = new RegExp(_.escapeRegExp(search),'i')
20 - const match = (s?:string) => !s || !search || re.test(s)
19 + const filter = pattern2filter(search)
20 const walker = walkNode(node, ctx, search ? Infinity : 0)
21 const sseSrv = sse ? createSSE(ctx) : null
22 const onDirEntryHandlers = mapPlugins(plug => plug.onDirEntry)
@@ -28,8 +27,7 @@ export const file_list:ApiHandler = async ({ path, offset, limit, search, omit,
27 const list = []
28 for await (const sub of walker) {
29 if (sseSrv?.stopped || ctx.aborted) break
31 - const filename = basename(sub.name||'')
32 - if (!match(filename))
30 + if (!filter(sub.name))
31 continue
32 const entry = await nodeToDirEntry(sub)
33 if (!entry)
src/misc.ts
+7
@@ -2,6 +2,7 @@ import { EventEmitter } from 'events'
2 import fs from 'fs/promises'
3 import { basename, dirname } from 'path'
4 import { watch } from 'fs'
5 +import _ from 'lodash'
6
7 export function enforceFinal(sub:string, s:string) {
8 return s.endsWith(sub) ? s : s+sub
@@ -97,3 +98,9 @@ export function watchDir(dir: string, cb: ()=>void) {
98 try { watch(dir, cb) }
99 catch {}
100 }
101 +
102 +export function pattern2filter(pattern: string){
103 + const re = new RegExp(_.escapeRegExp(pattern), 'i')
104 + return (s?:string) =>
105 + !s || !pattern || re.test(basename(s))
106 +}
src/zip.ts
+3 -2
@@ -1,6 +1,6 @@
1 import { VfsNode, walkNode } from './vfs'
2 import Koa from 'koa'
3 -import { filterMapGenerator } from './misc'
3 +import { filterMapGenerator, pattern2filter } from './misc'
4 import { QuickZipStream } from './QuickZipStream'
5 import { createReadStream } from 'fs'
6 import fs from 'fs/promises'
@@ -11,8 +11,9 @@ export async function zipStreamFromFolder(node: VfsNode, ctx: Koa.Context) {
11 ctx.mime = 'zip'
12 const { name } = node
13 ctx.attachment((name || 'archive') + '.zip')
14 + const filter = pattern2filter(String(ctx.query.search||''))
15 const walker = filterMapGenerator(walkNode(node, ctx, Infinity), async (el:VfsNode) => {
15 - if (!el.source || ctx.req.aborted)
16 + if (!el.source || ctx.req.aborted || !filter(el.name))
17 return
18 try {
19 const st = await fs.stat(el.source)
todo.md
-1
@@ -15,7 +15,6 @@
15 - config: bans
16 - config: min disk space
17 - thumbnails support
18 -- archive for search results
18 - archive only selected files
19 - "bottom" button (only on long screens, only after you scroll a bit, only for a few seconds)
20 - webdav?