| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- const { promisify } = require('util')
- const fs = require('fs')
- const PersistorManager = require('./PersistorManager')
- const LocalFileWriter = require('./LocalFileWriter')
- const logger = require('logger-sharelatex')
- const FileConverter = require('./FileConverter')
- const KeyBuilder = require('./KeyBuilder')
- const async = require('async')
- const ImageOptimiser = require('./ImageOptimiser')
- const {
- WriteError,
- ReadError,
- ConversionError,
- NotFoundError
- } = require('./Errors')
- module.exports = {
- insertFile,
- deleteFile,
- getFile,
- getFileSize,
- getDirectorySize,
- promises: {
- getFile: promisify(getFile),
- insertFile: promisify(insertFile),
- deleteFile: promisify(deleteFile),
- getFileSize: promisify(getFileSize),
- getDirectorySize: promisify(getDirectorySize)
- }
- }
- function insertFile(bucket, key, stream, callback) {
- const convertedKey = KeyBuilder.getConvertedFolderKey(key)
- PersistorManager.deleteDirectory(bucket, convertedKey, function(error) {
- if (error) {
- return callback(new WriteError('error inserting file').withCause(error))
- }
- PersistorManager.sendStream(bucket, key, stream, callback)
- })
- }
- function deleteFile(bucket, key, callback) {
- const convertedKey = KeyBuilder.getConvertedFolderKey(key)
- async.parallel(
- [
- done => PersistorManager.deleteFile(bucket, key, done),
- done => PersistorManager.deleteDirectory(bucket, convertedKey, done)
- ],
- callback
- )
- }
- function getFile(bucket, key, opts, callback) {
- // In this call, opts can contain credentials
- if (!opts) {
- opts = {}
- }
- logger.log({ bucket, key, opts: _scrubSecrets(opts) }, 'getting file')
- if (!opts.format && !opts.style) {
- _getStandardFile(bucket, key, opts, callback)
- } else {
- _getConvertedFile(bucket, key, opts, callback)
- }
- }
- function getFileSize(bucket, key, callback) {
- PersistorManager.getFileSize(bucket, key, callback)
- }
- function getDirectorySize(bucket, projectId, callback) {
- logger.log({ bucket, project_id: projectId }, 'getting project size')
- PersistorManager.directorySize(bucket, projectId, function(err, size) {
- if (err) {
- return callback(
- new ReadError('error getting project size').withCause(err)
- )
- }
- callback(null, size)
- })
- }
- function _getStandardFile(bucket, key, opts, callback) {
- PersistorManager.getFileStream(bucket, key, opts, function(err, fileStream) {
- if (err && !(err instanceof NotFoundError)) {
- logger.err(
- { bucket, key, opts: _scrubSecrets(opts) },
- 'error getting fileStream'
- )
- }
- callback(err, fileStream)
- })
- }
- function _getConvertedFile(bucket, key, opts, callback) {
- const convertedKey = KeyBuilder.addCachingToKey(key, opts)
- PersistorManager.checkIfFileExists(bucket, convertedKey, (err, exists) => {
- if (err) {
- return callback(err)
- }
- if (exists) {
- PersistorManager.getFileStream(bucket, convertedKey, opts, callback)
- } else {
- _getConvertedFileAndCache(bucket, key, convertedKey, opts, callback)
- }
- })
- }
- function _getConvertedFileAndCache(bucket, key, convertedKey, opts, callback) {
- let convertedFsPath
- async.series(
- [
- cb => {
- _convertFile(bucket, key, opts, function(err, fileSystemPath) {
- convertedFsPath = fileSystemPath
- cb(err)
- })
- },
- cb => ImageOptimiser.compressPng(convertedFsPath, cb),
- cb => PersistorManager.sendFile(bucket, convertedKey, convertedFsPath, cb)
- ],
- function(err) {
- if (err) {
- LocalFileWriter.deleteFile(convertedFsPath, function() {})
- return callback(
- new ConversionError('failed to convert file').withCause(err)
- )
- }
- // Send back the converted file from the local copy to avoid problems
- // with the file not being present in S3 yet. As described in the
- // documentation below, we have already made a 'HEAD' request in
- // checkIfFileExists so we only have "eventual consistency" if we try
- // to stream it from S3 here. This was a cause of many 403 errors.
- //
- // "Amazon S3 provides read-after-write consistency for PUTS of new
- // objects in your S3 bucket in all regions with one caveat. The
- // caveat is that if you make a HEAD or GET request to the key name
- // (to find if the object exists) before creating the object, Amazon
- // S3 provides eventual consistency for read-after-write.""
- // https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel
- const readStream = fs.createReadStream(convertedFsPath)
- readStream.on('end', function() {
- LocalFileWriter.deleteFile(convertedFsPath, function() {})
- })
- callback(null, readStream)
- }
- )
- }
- function _convertFile(bucket, originalKey, opts, callback) {
- _writeFileToDisk(bucket, originalKey, opts, function(err, originalFsPath) {
- if (err) {
- return callback(
- new ConversionError('unable to write file to disk').withCause(err)
- )
- }
- const done = function(err, destPath) {
- if (err) {
- logger.err(
- { err, bucket, originalKey, opts: _scrubSecrets(opts) },
- 'error converting file'
- )
- return callback(
- new ConversionError('error converting file').withCause(err)
- )
- }
- LocalFileWriter.deleteFile(originalFsPath, function() {})
- callback(err, destPath)
- }
- logger.log({ opts }, 'converting file depending on opts')
- if (opts.format) {
- FileConverter.convert(originalFsPath, opts.format, done)
- } else if (opts.style === 'thumbnail') {
- FileConverter.thumbnail(originalFsPath, done)
- } else if (opts.style === 'preview') {
- FileConverter.preview(originalFsPath, done)
- } else {
- callback(
- new ConversionError(
- `should have specified opts to convert file with ${JSON.stringify(
- opts
- )}`
- )
- )
- }
- })
- }
- function _writeFileToDisk(bucket, key, opts, callback) {
- PersistorManager.getFileStream(bucket, key, opts, function(err, fileStream) {
- if (err) {
- return callback(
- new ReadError('unable to get read stream for file').withCause(err)
- )
- }
- LocalFileWriter.writeStream(fileStream, key, callback)
- })
- }
- function _scrubSecrets(opts) {
- const safe = Object.assign({}, opts)
- delete safe.credentials
- return safe
- }
|