ProjectZipStreamManager.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. let ProjectZipStreamManager
  2. const archiver = require('archiver')
  3. const async = require('async')
  4. const logger = require('logger-sharelatex')
  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. logger.log(
  25. { projectId, name: project.name },
  26. 'appending project to zip stream'
  27. )
  28. ProjectZipStreamManager.createZipStreamForProject(
  29. projectId,
  30. (error, stream) => {
  31. if (error) {
  32. return cb(error)
  33. }
  34. archive.append(stream, { name: `${project.name}.zip` })
  35. stream.on('end', () => {
  36. logger.log({ projectId, name: project.name }, 'zip stream ended')
  37. cb()
  38. })
  39. }
  40. )
  41. })
  42. })
  43. async.series(jobs, () => {
  44. logger.log(
  45. { projectIds },
  46. 'finished creating zip stream of multiple projects'
  47. )
  48. archive.finalize()
  49. })
  50. },
  51. createZipStreamForProject(projectId, callback) {
  52. const archive = archiver('zip')
  53. // return stream immediately before we start adding things to it
  54. archive.on('error', err =>
  55. logger.err(
  56. { err, projectId },
  57. 'something went wrong building archive of project'
  58. )
  59. )
  60. callback(null, archive)
  61. this.addAllDocsToArchive(projectId, archive, error => {
  62. if (error) {
  63. logger.error(
  64. { err: error, projectId },
  65. 'error adding docs to zip stream'
  66. )
  67. }
  68. this.addAllFilesToArchive(projectId, archive, error => {
  69. if (error) {
  70. logger.error(
  71. { err: error, projectId },
  72. 'error adding files to zip stream'
  73. )
  74. }
  75. archive.finalize()
  76. })
  77. })
  78. },
  79. addAllDocsToArchive(projectId, archive, callback) {
  80. ProjectEntityHandler.getAllDocs(projectId, (error, docs) => {
  81. if (error) {
  82. return callback(error)
  83. }
  84. const jobs = Object.entries(docs).map(([path, doc]) => cb => {
  85. if (path[0] === '/') {
  86. path = path.slice(1)
  87. }
  88. logger.log({ projectId }, 'Adding doc')
  89. archive.append(doc.lines.join('\n'), { name: path })
  90. setImmediate(cb)
  91. })
  92. async.series(jobs, callback)
  93. })
  94. },
  95. addAllFilesToArchive(projectId, archive, callback) {
  96. ProjectEntityHandler.getAllFiles(projectId, (error, files) => {
  97. if (error) {
  98. return callback(error)
  99. }
  100. const jobs = Object.entries(files).map(([path, file]) => cb => {
  101. FileStoreHandler.getFileStream(
  102. projectId,
  103. file._id,
  104. {},
  105. (error, stream) => {
  106. if (error) {
  107. logger.warn(
  108. { err: error, projectId, file_id: file._id },
  109. 'something went wrong adding file to zip archive'
  110. )
  111. return cb(error)
  112. }
  113. if (path[0] === '/') {
  114. path = path.slice(1)
  115. }
  116. archive.append(stream, { name: path })
  117. stream.on('end', () => cb())
  118. }
  119. )
  120. })
  121. async.parallelLimit(jobs, 5, callback)
  122. })
  123. },
  124. }