2025-03-18 17:28:49 -05:00
|
|
|
const Path = require('path')
|
|
|
|
const { Response } = require('express')
|
2023-04-09 17:05:35 -05:00
|
|
|
const Logger = require('../Logger')
|
|
|
|
const archiver = require('../libs/archiver')
|
|
|
|
|
|
|
|
module.exports.zipDirectoryPipe = (path, filename, res) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// create a file to stream archive data to
|
|
|
|
res.attachment(filename)
|
|
|
|
|
|
|
|
const archive = archiver('zip', {
|
2024-12-07 16:52:31 -07:00
|
|
|
zlib: { level: 0 } // Sets the compression level.
|
2023-04-09 17:05:35 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
// listen for all archive data to be written
|
|
|
|
// 'close' event is fired only when a file descriptor is involved
|
|
|
|
res.on('close', () => {
|
|
|
|
Logger.info(archive.pointer() + ' total bytes')
|
|
|
|
Logger.debug('archiver has been finalized and the output file descriptor has closed.')
|
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
|
|
|
|
// This event is fired when the data source is drained no matter what was the data source.
|
|
|
|
// It is not part of this library but rather from the NodeJS Stream API.
|
|
|
|
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
|
|
|
res.on('end', () => {
|
|
|
|
Logger.debug('Data has been drained')
|
|
|
|
})
|
|
|
|
|
|
|
|
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
|
|
|
archive.on('warning', function (err) {
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
// log warning
|
|
|
|
Logger.warn(`[DownloadManager] Archiver warning: ${err.message}`)
|
|
|
|
} else {
|
|
|
|
// throw error
|
|
|
|
Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
|
|
|
|
// throw err
|
|
|
|
reject(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
archive.on('error', function (err) {
|
|
|
|
Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// pipe archive data to the file
|
|
|
|
archive.pipe(res)
|
|
|
|
|
|
|
|
archive.directory(path, false)
|
|
|
|
|
|
|
|
archive.finalize()
|
|
|
|
})
|
2024-12-07 16:52:31 -07:00
|
|
|
}
|
2025-03-17 19:35:59 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a zip archive containing multiple directories and streams it to the response.
|
|
|
|
*
|
2025-03-18 17:28:49 -05:00
|
|
|
* @param {{ path: string, isFile: boolean }[]} pathObjects
|
2025-03-17 19:35:59 +01:00
|
|
|
* @param {string} filename - Name of the zip file to be sent as attachment.
|
2025-03-18 17:28:49 -05:00
|
|
|
* @param {Response} res - Response object to pipe the archive data to.
|
2025-03-17 19:35:59 +01:00
|
|
|
* @returns {Promise<void>} - Promise that resolves when the zip operation completes.
|
|
|
|
*/
|
2025-03-18 17:28:49 -05:00
|
|
|
module.exports.zipDirectoriesPipe = (pathObjects, filename, res) => {
|
2025-03-17 19:35:59 +01:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// create a file to stream archive data to
|
|
|
|
res.attachment(filename)
|
|
|
|
|
|
|
|
const archive = archiver('zip', {
|
|
|
|
zlib: { level: 0 } // Sets the compression level.
|
|
|
|
})
|
|
|
|
|
|
|
|
// listen for all archive data to be written
|
|
|
|
// 'close' event is fired only when a file descriptor is involved
|
|
|
|
res.on('close', () => {
|
|
|
|
Logger.info(archive.pointer() + ' total bytes')
|
|
|
|
Logger.debug('archiver has been finalized and the output file descriptor has closed.')
|
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
|
|
|
|
// This event is fired when the data source is drained no matter what was the data source.
|
|
|
|
// It is not part of this library but rather from the NodeJS Stream API.
|
|
|
|
// @see: https://nodejs.org/api/stream.html#stream_event_end
|
|
|
|
res.on('end', () => {
|
|
|
|
Logger.debug('Data has been drained')
|
|
|
|
})
|
|
|
|
|
|
|
|
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
|
|
|
archive.on('warning', function (err) {
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
// log warning
|
|
|
|
Logger.warn(`[DownloadManager] Archiver warning: ${err.message}`)
|
|
|
|
} else {
|
|
|
|
// throw error
|
|
|
|
Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
|
|
|
|
// throw err
|
|
|
|
reject(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
archive.on('error', function (err) {
|
|
|
|
Logger.error(`[DownloadManager] Archiver error: ${err.message}`)
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// pipe archive data to the file
|
|
|
|
archive.pipe(res)
|
|
|
|
|
|
|
|
// Add each path as a directory in the zip
|
2025-03-18 17:28:49 -05:00
|
|
|
pathObjects.forEach((pathObject) => {
|
|
|
|
if (!pathObject.isFile) {
|
2025-03-17 19:35:59 +01:00
|
|
|
// Add the directory to the archive with its name as the root folder
|
2025-03-18 17:28:49 -05:00
|
|
|
archive.directory(pathObject.path, Path.basename(pathObject.path))
|
2025-03-17 19:35:59 +01:00
|
|
|
} else {
|
2025-03-18 17:28:49 -05:00
|
|
|
archive.file(pathObject.path, { name: Path.basename(pathObject.path) })
|
2025-03-17 19:35:59 +01:00
|
|
|
}
|
2025-03-18 17:28:49 -05:00
|
|
|
})
|
2025-03-17 19:35:59 +01:00
|
|
|
|
|
|
|
archive.finalize()
|
|
|
|
})
|
|
|
|
}
|
2025-03-17 20:04:01 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles errors that occur during the download process.
|
|
|
|
*
|
2025-03-18 17:28:49 -05:00
|
|
|
* @param {*} error
|
|
|
|
* @param {Response} res
|
2025-03-17 20:04:01 +01:00
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
module.exports.handleDownloadError = (error, res) => {
|
|
|
|
if (!res.headersSent) {
|
|
|
|
if (error.code === 'ENOENT') {
|
|
|
|
return res.status(404).send('File not found')
|
|
|
|
} else {
|
|
|
|
return res.status(500).send('Download failed')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|