OutputFileArchiveManager.js 2.7 KB

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