ProjectZipStreamManager.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. let ProjectZipStreamManager
  2. const archiver = require('archiver')
  3. const async = require('async')
  4. const logger = require('@overleaf/logger')
  5. const ProjectEntityHandler = require('../Project/ProjectEntityHandler')
  6. const ProjectGetter = require('../Project/ProjectGetter')
  7. const FileStoreHandler = require('../FileStore/FileStoreHandler')
  8. module.exports = ProjectZipStreamManager = {
  9. createZipStreamForMultipleProjects(projectIds, callback) {
  10. // We'll build up a zip file that contains multiple zip files
  11. const archive = archiver('zip')
  12. archive.on('error', err =>
  13. logger.err(
  14. { err, projectIds },
  15. 'something went wrong building archive of project'
  16. )
  17. )
  18. callback(null, archive)
  19. const jobs = projectIds.map(projectId => cb => {
  20. ProjectGetter.getProject(projectId, { name: true }, (error, project) => {
  21. if (error) {
  22. return cb(error)
  23. }
  24. if (!project) {
  25. logger.log(
  26. { projectId },
  27. 'cannot append project to zip stream: project not found'
  28. )
  29. return cb()
  30. }
  31. logger.log(
  32. { projectId, name: project.name },
  33. 'appending project to zip stream'
  34. )
  35. ProjectZipStreamManager.createZipStreamForProject(
  36. projectId,
  37. (error, stream) => {
  38. if (error) {
  39. return cb(error)
  40. }
  41. archive.append(stream, { name: `${project.name}.zip` })
  42. stream.on('end', () => {
  43. logger.log({ projectId, name: project.name }, 'zip stream ended')
  44. cb()
  45. })
  46. }
  47. )
  48. })
  49. })
  50. async.series(jobs, () => {
  51. logger.log(
  52. { projectIds },
  53. 'finished creating zip stream of multiple projects'
  54. )
  55. archive.finalize()
  56. })
  57. },
  58. createZipStreamForProject(projectId, callback) {
  59. const archive = archiver('zip')
  60. // return stream immediately before we start adding things to it
  61. archive.on('error', err =>
  62. logger.err(
  63. { err, projectId },
  64. 'something went wrong building archive of project'
  65. )
  66. )
  67. callback(null, archive)
  68. this.addAllDocsToArchive(projectId, archive, error => {
  69. if (error) {
  70. logger.error(
  71. { err: error, projectId },
  72. 'error adding docs to zip stream'
  73. )
  74. }
  75. this.addAllFilesToArchive(projectId, archive, error => {
  76. if (error) {
  77. logger.error(
  78. { err: error, projectId },
  79. 'error adding files to zip stream'
  80. )
  81. }
  82. archive.finalize()
  83. })
  84. })
  85. },
  86. addAllDocsToArchive(projectId, archive, callback) {
  87. ProjectEntityHandler.getAllDocs(projectId, (error, docs) => {
  88. if (error) {
  89. return callback(error)
  90. }
  91. const jobs = Object.entries(docs).map(([path, doc]) => cb => {
  92. if (path[0] === '/') {
  93. path = path.slice(1)
  94. }
  95. logger.log({ projectId }, 'Adding doc')
  96. archive.append(doc.lines.join('\n'), { name: path })
  97. setImmediate(cb)
  98. })
  99. async.series(jobs, callback)
  100. })
  101. },
  102. addAllFilesToArchive(projectId, archive, callback) {
  103. ProjectEntityHandler.getAllFiles(projectId, (error, files) => {
  104. if (error) {
  105. return callback(error)
  106. }
  107. const jobs = Object.entries(files).map(([path, file]) => cb => {
  108. FileStoreHandler.getFileStream(
  109. projectId,
  110. file._id,
  111. {},
  112. (error, stream) => {
  113. if (error) {
  114. logger.warn(
  115. { err: error, projectId, file_id: file._id },
  116. 'something went wrong adding file to zip archive'
  117. )
  118. return cb(error)
  119. }
  120. if (path[0] === '/') {
  121. path = path.slice(1)
  122. }
  123. archive.append(stream, { name: path })
  124. stream.on('end', () => cb())
  125. }
  126. )
  127. })
  128. async.parallelLimit(jobs, 5, callback)
  129. })
  130. },
  131. }