2017-06-07 21:07:31 +00:00
|
|
|
const AWS = require('aws-sdk');
|
|
|
|
const s3 = new AWS.S3();
|
2017-09-06 20:25:27 +00:00
|
|
|
const mkdirp = require('mkdirp');
|
2017-06-07 21:07:31 +00:00
|
|
|
|
2017-08-24 21:54:02 +00:00
|
|
|
const config = require('./config');
|
2017-06-07 21:07:31 +00:00
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
|
|
|
|
2017-08-24 21:54:02 +00:00
|
|
|
const mozlog = require('./log');
|
2017-06-08 20:45:28 +00:00
|
|
|
|
2017-07-11 19:34:49 +00:00
|
|
|
const log = mozlog('send.storage');
|
2017-06-08 20:45:28 +00:00
|
|
|
|
2017-08-25 20:58:51 +00:00
|
|
|
const redis_lib =
|
|
|
|
config.env === 'development' && config.redis_host === 'localhost'
|
|
|
|
? 'redis-mock'
|
|
|
|
: 'redis';
|
|
|
|
|
|
|
|
const redis = require(redis_lib);
|
2017-06-07 23:46:48 +00:00
|
|
|
const redis_client = redis.createClient({
|
2017-08-24 21:54:02 +00:00
|
|
|
host: config.redis_host,
|
2017-06-23 18:14:33 +00:00
|
|
|
connect_timeout: 10000
|
2017-06-07 23:46:48 +00:00
|
|
|
});
|
2017-06-07 23:16:38 +00:00
|
|
|
|
|
|
|
redis_client.on('error', err => {
|
2017-08-04 04:47:03 +00:00
|
|
|
log.error('Redis:', err);
|
2017-06-07 23:16:38 +00:00
|
|
|
});
|
|
|
|
|
2017-09-06 20:25:27 +00:00
|
|
|
const fileDir = config.file_dir;
|
2017-08-24 21:54:02 +00:00
|
|
|
|
|
|
|
if (config.s3_bucket) {
|
2017-06-07 21:07:31 +00:00
|
|
|
module.exports = {
|
2017-06-08 22:01:04 +00:00
|
|
|
exists: exists,
|
2017-07-20 22:16:00 +00:00
|
|
|
ttl: ttl,
|
2017-06-07 23:16:38 +00:00
|
|
|
length: awsLength,
|
|
|
|
get: awsGet,
|
|
|
|
set: awsSet,
|
2017-06-20 21:33:28 +00:00
|
|
|
setField: setField,
|
2017-06-07 23:16:38 +00:00
|
|
|
delete: awsDelete,
|
2017-06-23 18:14:33 +00:00
|
|
|
forceDelete: awsForceDelete,
|
2017-06-29 17:27:36 +00:00
|
|
|
ping: awsPing,
|
2017-07-11 19:47:40 +00:00
|
|
|
flushall: flushall,
|
|
|
|
quit: quit,
|
2017-06-29 17:27:36 +00:00
|
|
|
metadata
|
2017-06-07 21:07:31 +00:00
|
|
|
};
|
|
|
|
} else {
|
2017-09-06 20:25:27 +00:00
|
|
|
mkdirp.sync(config.file_dir);
|
|
|
|
log.info('fileDir', fileDir);
|
2017-06-07 21:07:31 +00:00
|
|
|
module.exports = {
|
2017-06-08 22:01:04 +00:00
|
|
|
exists: exists,
|
2017-07-20 22:16:00 +00:00
|
|
|
ttl: ttl,
|
2017-06-07 23:16:38 +00:00
|
|
|
length: localLength,
|
|
|
|
get: localGet,
|
|
|
|
set: localSet,
|
2017-06-20 21:33:28 +00:00
|
|
|
setField: setField,
|
2017-06-07 23:16:38 +00:00
|
|
|
delete: localDelete,
|
2017-06-23 18:14:33 +00:00
|
|
|
forceDelete: localForceDelete,
|
2017-06-29 17:27:36 +00:00
|
|
|
ping: localPing,
|
2017-07-11 19:47:40 +00:00
|
|
|
flushall: flushall,
|
|
|
|
quit: quit,
|
2017-06-29 17:27:36 +00:00
|
|
|
metadata
|
2017-06-07 21:07:31 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-07-11 19:47:40 +00:00
|
|
|
function flushall() {
|
|
|
|
redis_client.flushdb();
|
|
|
|
}
|
|
|
|
|
|
|
|
function quit() {
|
|
|
|
redis_client.quit();
|
|
|
|
}
|
|
|
|
|
2017-06-29 17:27:36 +00:00
|
|
|
function metadata(id) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
redis_client.hgetall(id, (err, reply) => {
|
2017-07-25 19:58:44 +00:00
|
|
|
if (err || !reply) {
|
|
|
|
return reject(err);
|
2017-06-29 17:27:36 +00:00
|
|
|
}
|
2017-07-25 19:58:44 +00:00
|
|
|
resolve(reply);
|
2017-06-29 17:30:08 +00:00
|
|
|
});
|
|
|
|
});
|
2017-06-29 17:27:36 +00:00
|
|
|
}
|
|
|
|
|
2017-07-20 22:16:00 +00:00
|
|
|
function ttl(id) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
redis_client.ttl(id, (err, reply) => {
|
2017-07-25 19:58:44 +00:00
|
|
|
if (err || !reply) {
|
|
|
|
return reject(err);
|
2017-07-20 22:16:00 +00:00
|
|
|
}
|
2017-07-25 19:58:44 +00:00
|
|
|
resolve(reply * 1000);
|
2017-07-22 00:01:26 +00:00
|
|
|
});
|
|
|
|
});
|
2017-07-20 22:16:00 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 22:01:04 +00:00
|
|
|
function exists(id) {
|
2017-06-08 20:45:28 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-06-08 22:01:04 +00:00
|
|
|
redis_client.exists(id, (rediserr, reply) => {
|
2017-06-23 18:42:51 +00:00
|
|
|
if (reply === 1 && !rediserr) {
|
|
|
|
resolve();
|
|
|
|
} else {
|
|
|
|
reject(rediserr);
|
|
|
|
}
|
2017-06-08 20:45:28 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-20 21:33:28 +00:00
|
|
|
function setField(id, key, value) {
|
|
|
|
redis_client.hset(id, key, value);
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function localLength(id) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
2017-09-06 20:25:27 +00:00
|
|
|
resolve(fs.statSync(path.join(fileDir, id)).size);
|
2017-06-07 21:07:31 +00:00
|
|
|
} catch (err) {
|
|
|
|
reject();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function localGet(id) {
|
2017-09-06 20:25:27 +00:00
|
|
|
return fs.createReadStream(path.join(fileDir, id));
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
|
2017-08-31 16:43:36 +00:00
|
|
|
function localSet(newId, file, meta) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-09-06 20:25:27 +00:00
|
|
|
const filepath = path.join(fileDir, newId);
|
2017-07-20 19:50:20 +00:00
|
|
|
const fstream = fs.createWriteStream(filepath);
|
2017-06-07 21:07:31 +00:00
|
|
|
file.pipe(fstream);
|
2017-07-20 19:50:20 +00:00
|
|
|
file.on('limit', () => {
|
|
|
|
file.unpipe(fstream);
|
|
|
|
fstream.destroy(new Error('limit'));
|
|
|
|
});
|
|
|
|
fstream.on('finish', () => {
|
2017-07-10 19:19:20 +00:00
|
|
|
redis_client.hmset(newId, meta);
|
2017-08-24 21:54:02 +00:00
|
|
|
redis_client.expire(newId, config.expire_seconds);
|
2017-07-10 19:19:20 +00:00
|
|
|
log.info('localSet:', 'Upload Finished of ' + newId);
|
|
|
|
resolve(meta.delete);
|
2017-06-07 21:07:31 +00:00
|
|
|
});
|
2017-06-07 23:16:38 +00:00
|
|
|
|
2017-07-20 19:50:20 +00:00
|
|
|
fstream.on('error', err => {
|
2017-07-10 19:19:20 +00:00
|
|
|
log.error('localSet:', 'Failed upload of ' + newId);
|
2017-07-20 19:50:20 +00:00
|
|
|
fs.unlinkSync(filepath);
|
|
|
|
reject(err);
|
2017-06-09 23:31:34 +00:00
|
|
|
});
|
2017-06-07 21:07:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function localDelete(id, delete_token) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-06-07 23:16:38 +00:00
|
|
|
redis_client.hget(id, 'delete', (err, reply) => {
|
2017-06-07 21:07:31 +00:00
|
|
|
if (!reply || delete_token !== reply) {
|
2017-06-07 23:16:38 +00:00
|
|
|
reject();
|
2017-06-07 21:07:31 +00:00
|
|
|
} else {
|
2017-06-07 23:16:38 +00:00
|
|
|
redis_client.del(id);
|
2017-08-04 04:47:03 +00:00
|
|
|
log.info('Deleted:', id);
|
2017-09-06 20:25:27 +00:00
|
|
|
resolve(fs.unlinkSync(path.join(fileDir, id)));
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function localForceDelete(id) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-06-07 23:16:38 +00:00
|
|
|
redis_client.del(id);
|
2017-09-06 20:25:27 +00:00
|
|
|
resolve(fs.unlinkSync(path.join(fileDir, id)));
|
2017-06-07 21:07:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-23 18:14:33 +00:00
|
|
|
function localPing() {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
redis_client.ping(err => {
|
|
|
|
return err ? reject() : resolve();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function awsLength(id) {
|
2017-06-09 17:44:12 +00:00
|
|
|
const params = {
|
2017-08-24 21:54:02 +00:00
|
|
|
Bucket: config.s3_bucket,
|
2017-06-07 21:07:31 +00:00
|
|
|
Key: id
|
|
|
|
};
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
s3.headObject(params, function(err, data) {
|
2017-06-07 23:16:38 +00:00
|
|
|
if (!err) {
|
|
|
|
resolve(data.ContentLength);
|
|
|
|
} else {
|
|
|
|
reject();
|
|
|
|
}
|
2017-06-07 21:07:31 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function awsGet(id) {
|
2017-06-09 17:44:12 +00:00
|
|
|
const params = {
|
2017-08-24 21:54:02 +00:00
|
|
|
Bucket: config.s3_bucket,
|
2017-06-07 21:07:31 +00:00
|
|
|
Key: id
|
|
|
|
};
|
|
|
|
|
2017-06-20 17:21:11 +00:00
|
|
|
try {
|
|
|
|
return s3.getObject(params).createReadStream();
|
2017-06-20 19:52:01 +00:00
|
|
|
} catch (err) {
|
2017-06-20 17:21:11 +00:00
|
|
|
return null;
|
|
|
|
}
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
|
2017-08-31 16:43:36 +00:00
|
|
|
function awsSet(newId, file, meta) {
|
2017-06-09 17:44:12 +00:00
|
|
|
const params = {
|
2017-08-24 21:54:02 +00:00
|
|
|
Bucket: config.s3_bucket,
|
2017-07-10 19:19:20 +00:00
|
|
|
Key: newId,
|
2017-06-07 21:07:31 +00:00
|
|
|
Body: file
|
|
|
|
};
|
2017-07-20 19:50:20 +00:00
|
|
|
let hitLimit = false;
|
|
|
|
const upload = s3.upload(params);
|
|
|
|
file.on('limit', () => {
|
|
|
|
hitLimit = true;
|
|
|
|
upload.abort();
|
|
|
|
});
|
2017-07-22 00:01:26 +00:00
|
|
|
return upload.promise().then(
|
|
|
|
() => {
|
2017-07-20 19:50:20 +00:00
|
|
|
redis_client.hmset(newId, meta);
|
2017-08-24 21:54:02 +00:00
|
|
|
redis_client.expire(newId, config.expire_seconds);
|
2017-07-20 19:50:20 +00:00
|
|
|
},
|
|
|
|
err => {
|
|
|
|
if (hitLimit) {
|
|
|
|
throw new Error('limit');
|
2017-06-07 21:07:31 +00:00
|
|
|
} else {
|
2017-07-20 19:50:20 +00:00
|
|
|
throw err;
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
2017-07-22 00:01:26 +00:00
|
|
|
}
|
|
|
|
);
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function awsDelete(id, delete_token) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-06-07 23:16:38 +00:00
|
|
|
redis_client.hget(id, 'delete', (err, reply) => {
|
2017-06-07 21:07:31 +00:00
|
|
|
if (!reply || delete_token !== reply) {
|
2017-06-07 23:16:38 +00:00
|
|
|
reject();
|
2017-06-07 21:07:31 +00:00
|
|
|
} else {
|
2017-06-09 17:44:12 +00:00
|
|
|
const params = {
|
2017-08-24 21:54:02 +00:00
|
|
|
Bucket: config.s3_bucket,
|
2017-06-07 21:07:31 +00:00
|
|
|
Key: id
|
|
|
|
};
|
|
|
|
|
2017-06-09 17:44:12 +00:00
|
|
|
s3.deleteObject(params, function(err, _data) {
|
2017-07-18 17:52:32 +00:00
|
|
|
redis_client.del(id);
|
2017-06-19 20:37:56 +00:00
|
|
|
err ? reject(err) : resolve(err);
|
2017-06-07 23:16:38 +00:00
|
|
|
});
|
2017-06-07 21:07:31 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-06-07 23:16:38 +00:00
|
|
|
function awsForceDelete(id) {
|
2017-06-07 21:07:31 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-06-09 17:44:12 +00:00
|
|
|
const params = {
|
2017-08-24 21:54:02 +00:00
|
|
|
Bucket: config.s3_bucket,
|
2017-06-07 21:07:31 +00:00
|
|
|
Key: id
|
|
|
|
};
|
|
|
|
|
2017-06-09 17:44:12 +00:00
|
|
|
s3.deleteObject(params, function(err, _data) {
|
2017-07-18 17:52:32 +00:00
|
|
|
redis_client.del(id);
|
2017-08-04 04:47:03 +00:00
|
|
|
err ? reject(err) : resolve();
|
2017-06-07 23:16:38 +00:00
|
|
|
});
|
2017-06-07 21:07:31 +00:00
|
|
|
});
|
|
|
|
}
|
2017-06-23 18:14:33 +00:00
|
|
|
|
|
|
|
function awsPing() {
|
|
|
|
return localPing().then(() =>
|
2017-08-24 21:54:02 +00:00
|
|
|
s3.headBucket({ Bucket: config.s3_bucket }).promise()
|
2017-06-23 18:14:33 +00:00
|
|
|
);
|
|
|
|
}
|