search-fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 56s

This commit is contained in:
Garret Patti
2026-04-21 14:55:28 -04:00
parent da3ad97d51
commit 2cf8bc6d7d
2 changed files with 31 additions and 25 deletions

View File

@@ -30,7 +30,7 @@ export async function GET(request: NextRequest) {
const root = resolveLibraryRoot(library)
const recursive = request.nextUrl.searchParams.get('recursive') === 'true'
const listing = recursive
? scanDirectoryRecursive(root, libraryId, subpath)
? await scanDirectoryRecursive(root, libraryId, subpath)
: scanDirectory(root, libraryId, subpath)
// Annotate image files with hasExtractedText, and directories if any descendant has extracted text

View File

@@ -74,12 +74,16 @@ export function scanDirectory(
* Recursively walks every subdirectory under `subpath` and returns a flat list
* of all files. Directory entries are omitted. Each FileEntry.name is the full
* relative path from the library root (e.g. FolderA/SubFolder/video.mp4).
*
* Uses async I/O so the Node.js event loop is not blocked during large
* directory trees (blocking stalls streaming responses and causes
* "ReadableStream is already closed" errors on concurrent requests).
*/
export function scanDirectoryRecursive(
export async function scanDirectoryRecursive(
libraryRoot: string,
libraryId: string,
subpath: string
): DirectoryListing {
): Promise<DirectoryListing> {
let rootAbsPath: string
try {
rootAbsPath = subpath ? resolveAndJail(libraryRoot, subpath) : libraryRoot
@@ -89,18 +93,19 @@ export function scanDirectoryRecursive(
const entries: FileEntry[] = []
function walk(absDir: string, relDir: string): void {
async function walk(absDir: string, relDir: string): Promise<void> {
let dirents: fs.Dirent[]
try {
dirents = fs.readdirSync(absDir, { withFileTypes: true })
dirents = await fs.promises.readdir(absDir, { withFileTypes: true })
} catch {
return
}
for (const d of dirents) {
if (HIDDEN_FILES.test(d.name)) continue
await Promise.all(
dirents.map(async (d) => {
if (HIDDEN_FILES.test(d.name)) return
const relPath = relDir ? path.join(relDir, d.name) : d.name
if (d.isDirectory()) {
walk(path.join(absDir, d.name), relPath)
await walk(path.join(absDir, d.name), relPath)
} else {
const mediaType = getMediaType(d.name)
const hasThumbnail = mediaType === 'image' || mediaType === 'video'
@@ -114,10 +119,11 @@ export function scanDirectoryRecursive(
thumbnailUrl: hasThumbnail ? thumbnailApiUrl(libraryId, fullRelPath) : null,
})
}
}
})
)
}
walk(rootAbsPath, '')
await walk(rootAbsPath, '')
entries.sort((a, b) => a.name.localeCompare(b.name))
return { path: subpath, entries }
}