FileHandler.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. const { promisify } = require('util')
  2. const fs = require('fs')
  3. const PersistorManager = require('./PersistorManager')
  4. const LocalFileWriter = require('./LocalFileWriter')
  5. const FileConverter = require('./FileConverter')
  6. const KeyBuilder = require('./KeyBuilder')
  7. const async = require('async')
  8. const ImageOptimiser = require('./ImageOptimiser')
  9. const { ConversionError } = require('./Errors')
  10. module.exports = {
  11. insertFile,
  12. deleteFile,
  13. getFile,
  14. getFileSize,
  15. getDirectorySize,
  16. promises: {
  17. getFile: promisify(getFile),
  18. insertFile: promisify(insertFile),
  19. deleteFile: promisify(deleteFile),
  20. getFileSize: promisify(getFileSize),
  21. getDirectorySize: promisify(getDirectorySize)
  22. }
  23. }
  24. function insertFile(bucket, key, stream, callback) {
  25. const convertedKey = KeyBuilder.getConvertedFolderKey(key)
  26. PersistorManager.deleteDirectory(bucket, convertedKey, function(error) {
  27. if (error) {
  28. return callback(error)
  29. }
  30. PersistorManager.sendStream(bucket, key, stream, callback)
  31. })
  32. }
  33. function deleteFile(bucket, key, callback) {
  34. const convertedKey = KeyBuilder.getConvertedFolderKey(key)
  35. async.parallel(
  36. [
  37. done => PersistorManager.deleteFile(bucket, key, done),
  38. done => PersistorManager.deleteDirectory(bucket, convertedKey, done)
  39. ],
  40. callback
  41. )
  42. }
  43. function getFile(bucket, key, opts, callback) {
  44. opts = opts || {}
  45. if (!opts.format && !opts.style) {
  46. PersistorManager.getFileStream(bucket, key, opts, callback)
  47. } else {
  48. _getConvertedFile(bucket, key, opts, callback)
  49. }
  50. }
  51. function getFileSize(bucket, key, callback) {
  52. PersistorManager.getFileSize(bucket, key, callback)
  53. }
  54. function getDirectorySize(bucket, projectId, callback) {
  55. PersistorManager.directorySize(bucket, projectId, callback)
  56. }
  57. function _getConvertedFile(bucket, key, opts, callback) {
  58. const convertedKey = KeyBuilder.addCachingToKey(key, opts)
  59. PersistorManager.checkIfFileExists(bucket, convertedKey, (err, exists) => {
  60. if (err) {
  61. return callback(err)
  62. }
  63. if (exists) {
  64. PersistorManager.getFileStream(bucket, convertedKey, opts, callback)
  65. } else {
  66. _getConvertedFileAndCache(bucket, key, convertedKey, opts, callback)
  67. }
  68. })
  69. }
  70. function _getConvertedFileAndCache(bucket, key, convertedKey, opts, callback) {
  71. let convertedFsPath
  72. async.series(
  73. [
  74. cb => {
  75. _convertFile(bucket, key, opts, function(err, fileSystemPath) {
  76. convertedFsPath = fileSystemPath
  77. cb(err)
  78. })
  79. },
  80. cb => ImageOptimiser.compressPng(convertedFsPath, cb),
  81. cb => PersistorManager.sendFile(bucket, convertedKey, convertedFsPath, cb)
  82. ],
  83. function(err) {
  84. if (err) {
  85. LocalFileWriter.deleteFile(convertedFsPath, function() {})
  86. return callback(
  87. new ConversionError({
  88. message: 'failed to convert file',
  89. info: { opts, bucket, key, convertedKey }
  90. }).withCause(err)
  91. )
  92. }
  93. // Send back the converted file from the local copy to avoid problems
  94. // with the file not being present in S3 yet. As described in the
  95. // documentation below, we have already made a 'HEAD' request in
  96. // checkIfFileExists so we only have "eventual consistency" if we try
  97. // to stream it from S3 here. This was a cause of many 403 errors.
  98. //
  99. // "Amazon S3 provides read-after-write consistency for PUTS of new
  100. // objects in your S3 bucket in all regions with one caveat. The
  101. // caveat is that if you make a HEAD or GET request to the key name
  102. // (to find if the object exists) before creating the object, Amazon
  103. // S3 provides eventual consistency for read-after-write.""
  104. // https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel
  105. const readStream = fs.createReadStream(convertedFsPath)
  106. readStream.on('end', function() {
  107. LocalFileWriter.deleteFile(convertedFsPath, function() {})
  108. })
  109. callback(null, readStream)
  110. }
  111. )
  112. }
  113. function _convertFile(bucket, originalKey, opts, callback) {
  114. _writeFileToDisk(bucket, originalKey, opts, function(err, originalFsPath) {
  115. if (err) {
  116. return callback(
  117. new ConversionError({
  118. message: 'unable to write file to disk',
  119. info: { bucket, originalKey, opts }
  120. }).withCause(err)
  121. )
  122. }
  123. const done = function(err, destPath) {
  124. if (err) {
  125. return callback(
  126. new ConversionError({
  127. message: 'error converting file',
  128. info: { bucket, originalKey, opts }
  129. }).withCause(err)
  130. )
  131. }
  132. LocalFileWriter.deleteFile(originalFsPath, function() {})
  133. callback(err, destPath)
  134. }
  135. if (opts.format) {
  136. FileConverter.convert(originalFsPath, opts.format, done)
  137. } else if (opts.style === 'thumbnail') {
  138. FileConverter.thumbnail(originalFsPath, done)
  139. } else if (opts.style === 'preview') {
  140. FileConverter.preview(originalFsPath, done)
  141. } else {
  142. callback(
  143. new ConversionError({
  144. message: 'invalid file conversion options',
  145. info: {
  146. bucket,
  147. originalKey,
  148. opts
  149. }
  150. })
  151. )
  152. }
  153. })
  154. }
  155. function _writeFileToDisk(bucket, key, opts, callback) {
  156. PersistorManager.getFileStream(bucket, key, opts, function(err, fileStream) {
  157. if (err) {
  158. return callback(err)
  159. }
  160. LocalFileWriter.writeStream(fileStream, key, callback)
  161. })
  162. }