This commit is contained in:
@@ -30,7 +30,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const root = resolveLibraryRoot(library)
|
const root = resolveLibraryRoot(library)
|
||||||
const recursive = request.nextUrl.searchParams.get('recursive') === 'true'
|
const recursive = request.nextUrl.searchParams.get('recursive') === 'true'
|
||||||
const listing = recursive
|
const listing = recursive
|
||||||
? scanDirectoryRecursive(root, libraryId, subpath)
|
? await scanDirectoryRecursive(root, libraryId, subpath)
|
||||||
: scanDirectory(root, libraryId, subpath)
|
: scanDirectory(root, libraryId, subpath)
|
||||||
|
|
||||||
// Annotate image files with hasExtractedText, and directories if any descendant has extracted text
|
// 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
|
* Recursively walks every subdirectory under `subpath` and returns a flat list
|
||||||
* of all files. Directory entries are omitted. Each FileEntry.name is the full
|
* 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).
|
* 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,
|
libraryRoot: string,
|
||||||
libraryId: string,
|
libraryId: string,
|
||||||
subpath: string
|
subpath: string
|
||||||
): DirectoryListing {
|
): Promise<DirectoryListing> {
|
||||||
let rootAbsPath: string
|
let rootAbsPath: string
|
||||||
try {
|
try {
|
||||||
rootAbsPath = subpath ? resolveAndJail(libraryRoot, subpath) : libraryRoot
|
rootAbsPath = subpath ? resolveAndJail(libraryRoot, subpath) : libraryRoot
|
||||||
@@ -89,35 +93,37 @@ export function scanDirectoryRecursive(
|
|||||||
|
|
||||||
const entries: FileEntry[] = []
|
const entries: FileEntry[] = []
|
||||||
|
|
||||||
function walk(absDir: string, relDir: string): void {
|
async function walk(absDir: string, relDir: string): Promise<void> {
|
||||||
let dirents: fs.Dirent[]
|
let dirents: fs.Dirent[]
|
||||||
try {
|
try {
|
||||||
dirents = fs.readdirSync(absDir, { withFileTypes: true })
|
dirents = await fs.promises.readdir(absDir, { withFileTypes: true })
|
||||||
} catch {
|
} catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const d of dirents) {
|
await Promise.all(
|
||||||
if (HIDDEN_FILES.test(d.name)) continue
|
dirents.map(async (d) => {
|
||||||
const relPath = relDir ? path.join(relDir, d.name) : d.name
|
if (HIDDEN_FILES.test(d.name)) return
|
||||||
if (d.isDirectory()) {
|
const relPath = relDir ? path.join(relDir, d.name) : d.name
|
||||||
walk(path.join(absDir, d.name), relPath)
|
if (d.isDirectory()) {
|
||||||
} else {
|
await walk(path.join(absDir, d.name), relPath)
|
||||||
const mediaType = getMediaType(d.name)
|
} else {
|
||||||
const hasThumbnail = mediaType === 'image' || mediaType === 'video'
|
const mediaType = getMediaType(d.name)
|
||||||
// name = full relative path from library root so media keys match
|
const hasThumbnail = mediaType === 'image' || mediaType === 'video'
|
||||||
const fullRelPath = subpath ? path.join(subpath, relPath) : relPath
|
// name = full relative path from library root so media keys match
|
||||||
entries.push({
|
const fullRelPath = subpath ? path.join(subpath, relPath) : relPath
|
||||||
name: fullRelPath,
|
entries.push({
|
||||||
type: 'file',
|
name: fullRelPath,
|
||||||
mediaType,
|
type: 'file',
|
||||||
url: fileApiUrl(libraryId, fullRelPath),
|
mediaType,
|
||||||
thumbnailUrl: hasThumbnail ? thumbnailApiUrl(libraryId, fullRelPath) : null,
|
url: fileApiUrl(libraryId, fullRelPath),
|
||||||
})
|
thumbnailUrl: hasThumbnail ? thumbnailApiUrl(libraryId, fullRelPath) : null,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
walk(rootAbsPath, '')
|
await walk(rootAbsPath, '')
|
||||||
entries.sort((a, b) => a.name.localeCompare(b.name))
|
entries.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
return { path: subpath, entries }
|
return { path: subpath, entries }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user