LocalFileWriter.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /* eslint-disable
  2. no-unused-vars,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS102: Remove unnecessary code created because of implicit returns
  9. * DS207: Consider shorter variations of null checks
  10. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  11. */
  12. import fs from 'fs'
  13. import { randomUUID } from 'crypto'
  14. import path from 'path'
  15. import Url from 'url'
  16. import _ from 'lodash'
  17. import logger from '@overleaf/logger'
  18. import metrics from '@overleaf/metrics'
  19. import Settings from '@overleaf/settings'
  20. import OError from '@overleaf/o-error'
  21. import * as LargeFileManager from './LargeFileManager.js'
  22. //
  23. // This method takes a stream and provides you a new stream which is now
  24. // reading from disk.
  25. //
  26. // This is useful if we're piping one network stream to another. If the stream
  27. // we're piping to can't consume data as quickly as the one we're consuming
  28. // from then large quantities of data may be held in memory. Instead the read
  29. // stream can be passed to this method, the data will then be held on disk
  30. // rather than in memory and will be cleaned up once it has been consumed.
  31. //
  32. export function bufferOnDisk(inStream, fileId, consumeOutStream, callback) {
  33. if (consumeOutStream == null) {
  34. consumeOutStream = function (fsPath, done) {}
  35. }
  36. if (callback == null) {
  37. callback = function () {}
  38. }
  39. const timer = new metrics.Timer('LocalFileWriter.writeStream')
  40. // capture the stream url for logging
  41. const url = inStream.uri && Url.format(inStream.uri)
  42. const fsPath = path.join(
  43. Settings.path.uploadFolder,
  44. randomUUID() + `-${fileId}`
  45. )
  46. let cleaningUp = false
  47. const cleanup = _.once((streamError, res) => {
  48. cleaningUp = true
  49. return deleteFile(fsPath, function (cleanupError) {
  50. if (streamError) {
  51. OError.tag(streamError, 'error deleting temporary file', {
  52. fsPath,
  53. url,
  54. })
  55. }
  56. if (cleanupError) {
  57. OError.tag(cleanupError)
  58. }
  59. if (streamError && cleanupError) {
  60. // logging the cleanup error in case only the stream error is sent to the callback
  61. logger.error(cleanupError)
  62. }
  63. return callback(streamError || cleanupError, res)
  64. })
  65. })
  66. logger.debug({ fsPath, url }, 'writing file locally')
  67. inStream.on('error', function (err) {
  68. OError.tag(err, 'problem writing file locally, with read stream', {
  69. fsPath,
  70. url,
  71. })
  72. return cleanup(err)
  73. })
  74. const writeStream = fs.createWriteStream(fsPath)
  75. writeStream.on('error', function (err) {
  76. OError.tag(err, 'problem writing file locally, with write stream', {
  77. fsPath,
  78. url,
  79. })
  80. return cleanup(err)
  81. })
  82. writeStream.on('finish', function () {
  83. timer.done()
  84. // in future check inStream.response.headers for hash value here
  85. logger.debug({ fsPath, url }, 'stream closed after writing file locally')
  86. if (!cleaningUp) {
  87. const fileSize = writeStream.bytesWritten
  88. return LargeFileManager.replaceWithStubIfNeeded(
  89. fsPath,
  90. fileId,
  91. fileSize,
  92. function (err, newFsPath) {
  93. if (err != null) {
  94. OError.tag(err, 'problem in large file manager', {
  95. newFsPath,
  96. fsPath,
  97. fileId,
  98. fileSize,
  99. })
  100. return cleanup(err)
  101. }
  102. return consumeOutStream(newFsPath, cleanup)
  103. }
  104. )
  105. }
  106. })
  107. return inStream.pipe(writeStream)
  108. }
  109. export function deleteFile(fsPath, callback) {
  110. if (fsPath == null || fsPath === '') {
  111. return callback()
  112. }
  113. logger.debug({ fsPath }, 'removing local temp file')
  114. return fs.unlink(fsPath, function (err) {
  115. if (err != null && err.code !== 'ENOENT') {
  116. // ignore errors deleting the file when it was never created
  117. return callback(OError.tag(err))
  118. } else {
  119. return callback()
  120. }
  121. })
  122. }