OutputFileArchiveManager.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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(
  33. { err, projectId, userId, build },
  34. 'error emitted when creating output files archive'
  35. )
  36. })
  37. archive.on('warning', err => {
  38. logger.warn(
  39. { err, projectId, userId, build },
  40. 'warning emitted when creating output files archive'
  41. )
  42. })
  43. const missingFiles = []
  44. for (const { path } of outputFiles) {
  45. let fileHandle
  46. try {
  47. fileHandle = await open(
  48. `${contentDir}${OutputCacheManager.path(build, path)}`
  49. )
  50. } catch (error) {
  51. logger.warn(
  52. { path, error, projectId, userId, build },
  53. 'error opening file to add to output files archive'
  54. )
  55. missingFiles.push(path)
  56. continue
  57. }
  58. const fileStream = fileHandle.createReadStream()
  59. archive.append(fileStream, { name: path })
  60. }
  61. if (missingFiles.length > 0) {
  62. archive.append(missingFiles.join('\n'), {
  63. name: 'missing_files.txt',
  64. })
  65. }
  66. archive.finalize().catch(error => {
  67. logger.error(
  68. { error, projectId, userId, build },
  69. 'error finalizing output files archive'
  70. )
  71. })
  72. return archive
  73. },
  74. async _getAllOutputFiles(contentDir, projectId, userId, build) {
  75. try {
  76. const { outputFiles } = await OutputFileFinder.promises.findOutputFiles(
  77. [],
  78. `${contentDir}${OutputCacheManager.path(build, '.')}`
  79. )
  80. return outputFiles.filter(
  81. // Ignore the pdf and also ignore the files ignored by the frontend.
  82. ({ path }) => path !== 'output.pdf' && !ignoreFiles.includes(path)
  83. )
  84. } catch (error) {
  85. if (
  86. error.code === 'ENOENT' ||
  87. error.code === 'ENOTDIR' ||
  88. error.code === 'EACCES'
  89. ) {
  90. throw new NotFoundError('Output files not found')
  91. }
  92. throw error
  93. }
  94. },
  95. }