This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user