diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 07b4ee67f..ee8e15d77 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -184,6 +184,11 @@ class LibraryItemController { if (audioMimeType) { res.setHeader('Content-Type', audioMimeType) } + // Set Content-Length so clients can show accurate progress bars. + const fileStat = await fs.stat(libraryItemPath).catch(() => null) + if (fileStat) { + res.setHeader('Content-Length', fileStat.size) + } await new Promise((resolve, reject) => res.download(libraryItemPath, req.libraryItem.relPath, (error) => (error ? reject(error) : resolve()))) } else { const filename = `${itemTitle}.zip` @@ -1097,6 +1102,12 @@ class LibraryItemController { } try { + // Set Content-Length so clients (including the Android app) can show accurate + // download progress and avoid chunked-transfer encoding when the file size is known. + const fileStat = await fs.stat(libraryFile.metadata.path).catch(() => null) + if (fileStat) { + res.setHeader('Content-Length', fileStat.size) + } await new Promise((resolve, reject) => res.download(libraryFile.metadata.path, libraryFile.metadata.filename, (error) => (error ? reject(error) : resolve()))) Logger.info(`[LibraryItemController] Downloaded file "${libraryFile.metadata.path}"`) } catch (error) { diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 9a349bd54..6164efd0e 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -318,8 +318,9 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => { const totalSize = parseInt(response.headers['content-length'], 10) let downloadedSize = 0 - // Write to filepath - const writer = fs.createWriteStream(filepath) + // Use a 512 KiB write buffer. Node's default 16 KiB highWaterMark causes + // many small write syscalls for large audio files and limits throughput. + const writer = fs.createWriteStream(filepath, { highWaterMark: 524288 }) response.data.pipe(writer) let lastProgress = 0 diff --git a/server/utils/zipHelpers.js b/server/utils/zipHelpers.js index 421ca73cd..10fab4d1a 100644 --- a/server/utils/zipHelpers.js +++ b/server/utils/zipHelpers.js @@ -9,7 +9,11 @@ module.exports.zipDirectoryPipe = (path, filename, res) => { res.attachment(filename) const archive = archiver('zip', { - zlib: { level: 0 } // Sets the compression level. + zlib: { level: 0 }, // Sets the compression level (0 = store, no CPU overhead). + // Increase the internal pipeline highWaterMark from 16 KiB to 512 KiB. + // This allows archiver to buffer more data before applying backpressure, + // keeping the read-from-disk and write-to-network pipelines busy. + highWaterMark: 524288 }) // listen for all archive data to be written @@ -67,7 +71,9 @@ module.exports.zipDirectoriesPipe = (pathObjects, filename, res) => { res.attachment(filename) const archive = archiver('zip', { - zlib: { level: 0 } // Sets the compression level. + zlib: { level: 0 }, // Sets the compression level (0 = store, no CPU overhead). + // Increase the internal pipeline highWaterMark from 16 KiB to 512 KiB. + highWaterMark: 524288 }) // listen for all archive data to be written