LocalFileWriter.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 'node:fs'
  13. import { pipeline } from 'node:stream'
  14. import { randomUUID } from 'node:crypto'
  15. import path from 'node:path'
  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(
  33. inStream,
  34. url,
  35. fileId,
  36. consumeOutStream,
  37. callback
  38. ) {
  39. const timer = new metrics.Timer('LocalFileWriter.writeStream')
  40. const fsPath = path.join(
  41. Settings.path.uploadFolder,
  42. randomUUID() + `-${fileId}`
  43. )
  44. const cleanup = _.once((streamError, res) => {
  45. return deleteFile(fsPath, function (cleanupError) {
  46. if (streamError) {
  47. OError.tag(streamError, 'error deleting temporary file', {
  48. fsPath,
  49. url,
  50. })
  51. }
  52. if (cleanupError) {
  53. OError.tag(cleanupError)
  54. }
  55. if (streamError && cleanupError) {
  56. // logging the cleanup error in case only the stream error is sent to the callback
  57. logger.error(cleanupError)
  58. }
  59. return callback(streamError || cleanupError, res)
  60. })
  61. })
  62. logger.debug({ fsPath, url }, 'writing file locally')
  63. const writeStream = fs.createWriteStream(fsPath)
  64. pipeline(inStream, writeStream, err => {
  65. if (err) {
  66. OError.tag(err, 'problem writing file locally', {
  67. fsPath,
  68. url,
  69. })
  70. return cleanup(err)
  71. }
  72. timer.done()
  73. // in future check inStream.response.headers for hash value here
  74. logger.debug({ fsPath, url }, 'stream closed after writing file locally')
  75. const fileSize = writeStream.bytesWritten
  76. return LargeFileManager.replaceWithStubIfNeeded(
  77. fsPath,
  78. fileId,
  79. fileSize,
  80. function (err, newFsPath) {
  81. if (err != null) {
  82. OError.tag(err, 'problem in large file manager', {
  83. newFsPath,
  84. fsPath,
  85. fileId,
  86. fileSize,
  87. })
  88. return cleanup(err)
  89. }
  90. return consumeOutStream(newFsPath, cleanup)
  91. }
  92. )
  93. })
  94. }
  95. export function deleteFile(fsPath, callback) {
  96. if (fsPath == null || fsPath === '') {
  97. return callback()
  98. }
  99. logger.debug({ fsPath }, 'removing local temp file')
  100. return fs.unlink(fsPath, function (err) {
  101. if (err != null && err.code !== 'ENOENT') {
  102. // ignore errors deleting the file when it was never created
  103. return callback(OError.tag(err))
  104. } else {
  105. return callback()
  106. }
  107. })
  108. }