OutputFileArchiveManager.js 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. const archiver = require('archiver')
  2. const OutputCacheManager = require('./OutputCacheManager')
  3. const OutputFileFinder = require('./OutputFileFinder')
  4. const Settings = require('@overleaf/settings')
  5. const { open } = require('node:fs/promises')
  6. const path = require('path')
  7. const { NotFoundError } = require('./Errors')
  8. const logger = require('@overleaf/logger')
  9. // NOTE: Updating this list requires a corresponding change in
  10. // * services/web/frontend/js/features/pdf-preview/util/file-list.js
  11. const ignoreFiles = ['output.fls', 'output.fdb_latexmk']
  12. function getContentDir(projectId, userId) {
  13. let subDir
  14. if (userId != null) {
  15. subDir = `${projectId}-${userId}`
  16. } else {
  17. subDir = projectId
  18. }
  19. return `${Settings.path.outputDir}/${subDir}/`
  20. }
  21. module.exports = {
  22. async archiveFilesForBuild(projectId, userId, build) {
  23. logger.debug({ projectId, userId, build }, 'Will create zip file')
  24. const validFiles = await this._getAllOutputFiles(projectId, userId, build)
  25. const archive = archiver('zip')
  26. archive.on('error', err => {
  27. logger.warn({ err }, 'error emitted when creating output files archive')
  28. })
  29. const missingFiles = []
  30. for (const file of validFiles) {
  31. try {
  32. const fileHandle = await open(file, 'r')
  33. const fileStream = fileHandle.createReadStream()
  34. archive.append(fileStream, { name: path.basename(file) })
  35. } catch (error) {
  36. missingFiles.push(file)
  37. }
  38. }
  39. if (missingFiles.length > 0) {
  40. archive.append(missingFiles.join('\n'), {
  41. name: 'missing_files.txt',
  42. })
  43. }
  44. await archive.finalize()
  45. return archive
  46. },
  47. async _getAllOutputFiles(projectId, userId, build) {
  48. const contentDir = getContentDir(projectId, userId)
  49. try {
  50. const { outputFiles } = await OutputFileFinder.promises.findOutputFiles(
  51. [],
  52. `${contentDir}${OutputCacheManager.path(build, '.')}`
  53. )
  54. return outputFiles
  55. .filter(
  56. // Ignore the pdf and also ignore the files ignored by the frontend.
  57. ({ path }) => path !== 'output.pdf' && !ignoreFiles.includes(path)
  58. )
  59. .map(
  60. ({ path }) => `${contentDir}${OutputCacheManager.path(build, path)}`
  61. )
  62. } catch (error) {
  63. if (
  64. error.code === 'ENOENT' ||
  65. error.code === 'ENOTDIR' ||
  66. error.code === 'EACCES'
  67. ) {
  68. throw new NotFoundError('Output files not found')
  69. }
  70. throw error
  71. }
  72. },
  73. }