2019-12-16 05:24:35 -05:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
let FileHandler;
|
|
|
|
const settings = require("settings-sharelatex");
|
|
|
|
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 Errors = require('./Errors');
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
module.exports = (FileHandler = {
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
insertFile(bucket, key, stream, callback){
|
|
|
|
const convertedKey = KeyBuilder.getConvertedFolderKey(key);
|
|
|
|
return PersistorManager.deleteDirectory(bucket, convertedKey, function(error) {
|
|
|
|
if (error != null) { return callback(error); }
|
|
|
|
return PersistorManager.sendStream(bucket, key, stream, callback);
|
|
|
|
});
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
deleteFile(bucket, key, callback){
|
|
|
|
const convertedKey = KeyBuilder.getConvertedFolderKey(key);
|
|
|
|
return async.parallel([
|
|
|
|
done => PersistorManager.deleteFile(bucket, key, done),
|
|
|
|
done => PersistorManager.deleteDirectory(bucket, convertedKey, done)
|
|
|
|
], callback);
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
getFile(bucket, key, opts, callback){
|
|
|
|
// In this call, opts can contain credentials
|
|
|
|
if (opts == null) { opts = {}; }
|
|
|
|
logger.log({bucket, key, opts:this._scrubSecrets(opts)}, "getting file");
|
|
|
|
if ((opts.format == null) && (opts.style == null)) {
|
|
|
|
return this._getStandardFile(bucket, key, opts, callback);
|
|
|
|
} else {
|
|
|
|
return this._getConvertedFile(bucket, key, opts, callback);
|
|
|
|
}
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
getFileSize(bucket, key, callback) {
|
|
|
|
return PersistorManager.getFileSize(bucket, key, callback);
|
|
|
|
},
|
2019-06-13 16:57:49 -04:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_getStandardFile(bucket, key, opts, callback){
|
|
|
|
return PersistorManager.getFileStream(bucket, key, opts, function(err, fileStream){
|
|
|
|
if ((err != null) && !(err instanceof Errors.NotFoundError)) {
|
|
|
|
logger.err({bucket, key, opts:FileHandler._scrubSecrets(opts)}, "error getting fileStream");
|
|
|
|
}
|
|
|
|
return callback(err, fileStream);
|
|
|
|
});
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_getConvertedFile(bucket, key, opts, callback){
|
|
|
|
const convertedKey = KeyBuilder.addCachingToKey(key, opts);
|
|
|
|
return PersistorManager.checkIfFileExists(bucket, convertedKey, (err, exists)=> {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
if (exists) {
|
|
|
|
return PersistorManager.getFileStream(bucket, convertedKey, opts, callback);
|
|
|
|
} else {
|
|
|
|
return this._getConvertedFileAndCache(bucket, key, convertedKey, opts, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_getConvertedFileAndCache(bucket, key, convertedKey, opts, callback){
|
|
|
|
let convertedFsPath = "";
|
|
|
|
const originalFsPath = "";
|
|
|
|
return async.series([
|
|
|
|
cb => {
|
|
|
|
return this._convertFile(bucket, key, opts, function(err, fileSystemPath, originalFsPath) {
|
|
|
|
convertedFsPath = fileSystemPath;
|
|
|
|
originalFsPath = originalFsPath;
|
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
cb => ImageOptimiser.compressPng(convertedFsPath, cb),
|
|
|
|
cb => PersistorManager.sendFile(bucket, convertedKey, convertedFsPath, cb)
|
|
|
|
], function(err){
|
|
|
|
if (err != null) {
|
|
|
|
LocalFileWriter.deleteFile(convertedFsPath, function() {});
|
|
|
|
LocalFileWriter.deleteFile(originalFsPath, function() {});
|
|
|
|
return callback(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
|
|
|
|
return LocalFileWriter.getStream(convertedFsPath, function(err, readStream) {
|
|
|
|
if (err != null) { return callback(err); }
|
|
|
|
readStream.on('end', function() {
|
|
|
|
logger.log({convertedFsPath}, "deleting temporary file");
|
|
|
|
return LocalFileWriter.deleteFile(convertedFsPath, function() {});
|
|
|
|
});
|
|
|
|
return callback(null, readStream);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_convertFile(bucket, originalKey, opts, callback){
|
|
|
|
return this._writeS3FileToDisk(bucket, originalKey, opts, function(err, originalFsPath){
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
const done = function(err, destPath){
|
|
|
|
if (err != null) {
|
|
|
|
logger.err({err, bucket, originalKey, opts:FileHandler._scrubSecrets(opts)}, "error converting file");
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
LocalFileWriter.deleteFile(originalFsPath, function() {});
|
|
|
|
return callback(err, destPath, originalFsPath);
|
|
|
|
};
|
2014-03-04 08:36:47 -05:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
logger.log({opts}, "converting file depending on opts");
|
2018-05-21 08:40:03 -04:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
if (opts.format != null) {
|
|
|
|
return FileConverter.convert(originalFsPath, opts.format, done);
|
|
|
|
} else if (opts.style === "thumbnail") {
|
|
|
|
return FileConverter.thumbnail(originalFsPath, done);
|
|
|
|
} else if (opts.style === "preview") {
|
|
|
|
return FileConverter.preview(originalFsPath, done);
|
|
|
|
} else {
|
|
|
|
return callback(new Error(`should have specified opts to convert file with ${JSON.stringify(opts)}`));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2014-02-14 11:39:05 -05:00
|
|
|
|
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_writeS3FileToDisk(bucket, key, opts, callback){
|
|
|
|
return PersistorManager.getFileStream(bucket, key, opts, function(err, fileStream){
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
return LocalFileWriter.writeStream(fileStream, key, callback);
|
|
|
|
});
|
|
|
|
},
|
2016-03-13 15:22:14 -04:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
getDirectorySize(bucket, project_id, callback){
|
|
|
|
logger.log({bucket, project_id}, "getting project size");
|
|
|
|
return PersistorManager.directorySize(bucket, project_id, function(err, size){
|
|
|
|
if (err != null) {
|
|
|
|
logger.err({bucket, project_id}, "error getting size");
|
|
|
|
}
|
|
|
|
return callback(err, size);
|
|
|
|
});
|
|
|
|
},
|
2018-07-04 11:41:31 -04:00
|
|
|
|
2019-12-16 05:24:35 -05:00
|
|
|
_scrubSecrets(opts){
|
|
|
|
const safe = Object.assign({}, opts);
|
|
|
|
delete safe.credentials;
|
|
|
|
return safe;
|
|
|
|
}
|
|
|
|
});
|