2016-10-25 03:49:45 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// ENiGMA½
|
2022-06-05 20:04:25 +00:00
|
|
|
const Config = require('./config.js').get;
|
|
|
|
const FileDb = require('./database.js').dbs.file;
|
2018-06-23 03:26:46 +00:00
|
|
|
const getISOTimestampString = require('./database.js').getISOTimestampString;
|
2022-06-05 20:04:25 +00:00
|
|
|
const FileEntry = require('./file_entry.js');
|
|
|
|
const getServer = require('./listening_server.js').getServer;
|
|
|
|
const Errors = require('./enig_error.js').Errors;
|
|
|
|
const ErrNotEnabled = require('./enig_error.js').ErrorReasons.NotEnabled;
|
|
|
|
const StatLog = require('./stat_log.js');
|
|
|
|
const User = require('./user.js');
|
|
|
|
const Log = require('./logger.js').log;
|
2018-06-23 03:26:46 +00:00
|
|
|
const getConnectionByUserId = require('./client_connections.js').getConnectionByUserId;
|
2022-06-05 20:04:25 +00:00
|
|
|
const webServerPackageName = require('./servers/content/web.js').moduleInfo.packageName;
|
|
|
|
const Events = require('./events.js');
|
|
|
|
const UserProps = require('./user_property.js');
|
|
|
|
const SysProps = require('./system_menu_method.js');
|
2018-06-23 03:26:46 +00:00
|
|
|
|
|
|
|
// deps
|
2022-06-05 20:04:25 +00:00
|
|
|
const hashids = require('hashids/cjs');
|
|
|
|
const moment = require('moment');
|
|
|
|
const paths = require('path');
|
|
|
|
const async = require('async');
|
|
|
|
const fs = require('graceful-fs');
|
|
|
|
const mimeTypes = require('mime-types');
|
|
|
|
const yazl = require('yazl');
|
2016-10-25 03:49:45 +00:00
|
|
|
|
2017-02-18 16:56:23 +00:00
|
|
|
function notEnabledError() {
|
2018-06-22 05:15:04 +00:00
|
|
|
return Errors.General('Web server is not enabled', ErrNotEnabled);
|
2017-02-18 16:56:23 +00:00
|
|
|
}
|
|
|
|
|
2016-10-25 03:49:45 +00:00
|
|
|
class FileAreaWebAccess {
|
2018-06-22 05:15:04 +00:00
|
|
|
constructor() {
|
2022-06-05 20:04:25 +00:00
|
|
|
this.hashids = new hashids(Config().general.boardName);
|
|
|
|
this.expireTimers = {}; // hashId->timer
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
startup(cb) {
|
|
|
|
const self = this;
|
|
|
|
|
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function initFromDb(callback) {
|
|
|
|
return self.load(callback);
|
|
|
|
},
|
|
|
|
function addWebRoute(callback) {
|
|
|
|
self.webServer = getServer(webServerPackageName);
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!self.webServer) {
|
|
|
|
return callback(
|
|
|
|
Errors.DoesNotExist(
|
|
|
|
`Server with package name "${webServerPackageName}" does not exist`
|
|
|
|
)
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
if (self.isEnabled()) {
|
2018-06-22 05:15:04 +00:00
|
|
|
const routeAdded = self.webServer.instance.addRoute({
|
2022-06-05 20:04:25 +00:00
|
|
|
method: 'GET',
|
|
|
|
path: Config().fileBase.web.routePath,
|
|
|
|
handler: self.routeWebRequest.bind(self),
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
2022-06-05 20:04:25 +00:00
|
|
|
return callback(
|
|
|
|
routeAdded ? null : Errors.General('Failed adding route')
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
} else {
|
2022-06-05 20:04:25 +00:00
|
|
|
return callback(null); // not enabled, but no error
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
2022-06-05 20:04:25 +00:00
|
|
|
},
|
2018-06-22 05:15:04 +00:00
|
|
|
],
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
shutdown(cb) {
|
|
|
|
return cb(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
isEnabled() {
|
|
|
|
return this.webServer.instance.isEnabled();
|
|
|
|
}
|
|
|
|
|
|
|
|
static getHashIdTypes() {
|
|
|
|
return {
|
2022-06-05 20:04:25 +00:00
|
|
|
SingleFile: 0,
|
|
|
|
BatchArchive: 1,
|
2018-06-22 05:15:04 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
load(cb) {
|
|
|
|
//
|
2018-06-23 03:26:46 +00:00
|
|
|
// Load entries, register expiration timers
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
|
|
|
FileDb.each(
|
|
|
|
`SELECT hash_id, expire_timestamp
|
2018-06-23 03:26:46 +00:00
|
|
|
FROM file_web_serve;`,
|
2018-06-22 05:15:04 +00:00
|
|
|
(err, row) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (row) {
|
2018-06-22 05:15:04 +00:00
|
|
|
this.scheduleExpire(row.hash_id, moment(row.expire_timestamp));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
removeEntry(hashId) {
|
|
|
|
//
|
2018-06-23 03:26:46 +00:00
|
|
|
// Delete record from DB, and our timer
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
|
|
|
FileDb.run(
|
|
|
|
`DELETE FROM file_web_serve
|
2018-06-23 03:26:46 +00:00
|
|
|
WHERE hash_id = ?;`,
|
2022-06-05 20:04:25 +00:00
|
|
|
[hashId]
|
2018-06-22 05:15:04 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
delete this.expireTimers[hashId];
|
|
|
|
}
|
|
|
|
|
|
|
|
scheduleExpire(hashId, expireTime) {
|
2018-06-23 03:26:46 +00:00
|
|
|
// remove any previous entry for this hashId
|
2018-06-22 05:15:04 +00:00
|
|
|
const previous = this.expireTimers[hashId];
|
2022-06-05 20:04:25 +00:00
|
|
|
if (previous) {
|
2018-06-22 05:15:04 +00:00
|
|
|
clearTimeout(previous);
|
|
|
|
delete this.expireTimers[hashId];
|
|
|
|
}
|
|
|
|
|
|
|
|
const timeoutMs = expireTime.diff(moment());
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
if (timeoutMs <= 0) {
|
|
|
|
setImmediate(() => {
|
2018-06-22 05:15:04 +00:00
|
|
|
this.removeEntry(hashId);
|
|
|
|
});
|
|
|
|
} else {
|
2022-06-05 20:04:25 +00:00
|
|
|
this.expireTimers[hashId] = setTimeout(() => {
|
2018-06-22 05:15:04 +00:00
|
|
|
this.removeEntry(hashId);
|
|
|
|
}, timeoutMs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadServedHashId(hashId, cb) {
|
|
|
|
FileDb.get(
|
|
|
|
`SELECT expire_timestamp FROM
|
2018-06-23 03:26:46 +00:00
|
|
|
file_web_serve
|
|
|
|
WHERE hash_id = ?`,
|
2022-06-05 20:04:25 +00:00
|
|
|
[hashId],
|
2018-06-22 05:15:04 +00:00
|
|
|
(err, result) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err || !result) {
|
|
|
|
return cb(
|
|
|
|
err ? err : Errors.DoesNotExist('Invalid or missing hash ID')
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const decoded = this.hashids.decode(hashId);
|
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// decode() should provide an array of [ userId, hashIdType, id, ... ]
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!Array.isArray(decoded) || decoded.length < 3) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(Errors.Invalid('Invalid or unknown hash ID'));
|
|
|
|
}
|
|
|
|
|
|
|
|
const servedItem = {
|
2022-06-05 20:04:25 +00:00
|
|
|
hashId: hashId,
|
|
|
|
userId: decoded[0],
|
|
|
|
hashIdType: decoded[1],
|
|
|
|
expireTimestamp: moment(result.expire_timestamp),
|
2018-06-22 05:15:04 +00:00
|
|
|
};
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
if (
|
|
|
|
FileAreaWebAccess.getHashIdTypes().SingleFile ===
|
|
|
|
servedItem.hashIdType
|
|
|
|
) {
|
2018-06-22 05:15:04 +00:00
|
|
|
servedItem.fileIds = decoded.slice(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cb(null, servedItem);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
getSingleFileHashId(client, fileEntry) {
|
2022-06-05 20:04:25 +00:00
|
|
|
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [
|
|
|
|
fileEntry.fileId,
|
|
|
|
]);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getBatchArchiveHashId(client, batchId) {
|
2022-06-05 20:04:25 +00:00
|
|
|
return this.getHashId(
|
|
|
|
client,
|
|
|
|
FileAreaWebAccess.getHashIdTypes().BatchArchive,
|
|
|
|
batchId
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getHashId(client, hashIdType, identifier) {
|
|
|
|
return this.hashids.encode(client.user.userId, hashIdType, identifier);
|
|
|
|
}
|
|
|
|
|
|
|
|
buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
|
|
|
|
hashId = hashId || this.getSingleFileHashId(client, fileEntry);
|
|
|
|
|
|
|
|
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
buildBatchArchiveTempDownloadLink(client, hashId) {
|
|
|
|
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!this.isEnabled()) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(notEnabledError());
|
|
|
|
}
|
|
|
|
|
|
|
|
const hashId = this.getSingleFileHashId(client, fileEntry);
|
|
|
|
this.loadServedHashId(hashId, (err, servedItem) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
servedItem.url = this.buildSingleFileTempDownloadLink(client, fileEntry);
|
|
|
|
|
|
|
|
return cb(null, servedItem);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_addOrUpdateHashIdRecord(dbOrTrans, hashId, expireTime, cb) {
|
2018-06-23 03:26:46 +00:00
|
|
|
// add/update rec with hash id and (latest) timestamp
|
2018-06-22 05:15:04 +00:00
|
|
|
dbOrTrans.run(
|
|
|
|
`REPLACE INTO file_web_serve (hash_id, expire_timestamp)
|
2018-06-23 03:26:46 +00:00
|
|
|
VALUES (?, ?);`,
|
2022-06-05 20:04:25 +00:00
|
|
|
[hashId, getISOTimestampString(expireTime)],
|
2018-06-22 05:15:04 +00:00
|
|
|
err => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.scheduleExpire(hashId, expireTime);
|
|
|
|
|
|
|
|
return cb(null);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
createAndServeTempDownload(client, fileEntry, options, cb) {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!this.isEnabled()) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(notEnabledError());
|
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
const hashId = this.getSingleFileHashId(client, fileEntry);
|
|
|
|
const url = this.buildSingleFileTempDownloadLink(client, fileEntry, hashId);
|
|
|
|
options.expireTime = options.expireTime || moment().add(2, 'days');
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
this._addOrUpdateHashIdRecord(FileDb, hashId, options.expireTime, err => {
|
|
|
|
return cb(err, url);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
createAndServeTempBatchDownload(client, fileEntries, options, cb) {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!this.isEnabled()) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(notEnabledError());
|
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
const batchId = moment().utc().unix();
|
|
|
|
const hashId = this.getBatchArchiveHashId(client, batchId);
|
|
|
|
const url = this.buildBatchArchiveTempDownloadLink(client, hashId);
|
|
|
|
options.expireTime = options.expireTime || moment().add(2, 'days');
|
2018-06-22 05:15:04 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
FileDb.beginTransaction((err, trans) => {
|
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._addOrUpdateHashIdRecord(trans, hashId, options.expireTime, err => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
|
|
|
return trans.rollback(() => {
|
2018-06-22 05:15:04 +00:00
|
|
|
return cb(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
async.eachSeries(
|
|
|
|
fileEntries,
|
|
|
|
(entry, nextEntry) => {
|
|
|
|
trans.run(
|
|
|
|
`INSERT INTO file_web_serve_batch (hash_id, file_id)
|
2018-06-23 03:26:46 +00:00
|
|
|
VALUES (?, ?);`,
|
2022-06-05 20:04:25 +00:00
|
|
|
[hashId, entry.fileId],
|
|
|
|
err => {
|
|
|
|
return nextEntry(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
trans[err ? 'rollback' : 'commit'](() => {
|
|
|
|
return cb(err, url);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fileNotFound(resp) {
|
|
|
|
return this.webServer.instance.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
routeWebRequest(req, resp) {
|
|
|
|
const hashId = paths.basename(req.url);
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
Log.debug({ hashId: hashId, url: req.url }, 'File area web request');
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
this.loadServedHashId(hashId, (err, servedItem) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
const hashIdTypes = FileAreaWebAccess.getHashIdTypes();
|
2022-06-05 20:04:25 +00:00
|
|
|
switch (servedItem.hashIdType) {
|
|
|
|
case hashIdTypes.SingleFile:
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.routeWebRequestForSingleFile(servedItem, req, resp);
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
case hashIdTypes.BatchArchive:
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.routeWebRequestForBatchArchive(servedItem, req, resp);
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
default:
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
routeWebRequestForSingleFile(servedItem, req, resp) {
|
2022-06-05 20:04:25 +00:00
|
|
|
Log.debug({ servedItem: servedItem }, 'Single file web request');
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
const fileEntry = new FileEntry();
|
|
|
|
|
|
|
|
servedItem.fileId = servedItem.fileIds[0];
|
|
|
|
|
|
|
|
fileEntry.load(servedItem.fileId, err => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = fileEntry.filePath;
|
2022-06-05 20:04:25 +00:00
|
|
|
if (!filePath) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
fs.stat(filePath, (err, stats) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.on('close', () => {
|
2018-06-23 03:26:46 +00:00
|
|
|
// connection closed *before* the response was fully sent
|
|
|
|
// :TODO: Log and such
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
resp.on('finish', () => {
|
2018-06-23 03:26:46 +00:00
|
|
|
// transfer completed fully
|
2022-06-05 20:04:25 +00:00
|
|
|
this.updateDownloadStatsForUserIdAndSystem(
|
|
|
|
servedItem.userId,
|
|
|
|
stats.size,
|
|
|
|
[fileEntry]
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const headers = {
|
2022-06-05 20:04:25 +00:00
|
|
|
'Content-Type':
|
|
|
|
mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
|
|
|
|
'Content-Length': stats.size,
|
|
|
|
'Content-Disposition': `attachment; filename="${fileEntry.fileName}"`,
|
2018-06-22 05:15:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const readStream = fs.createReadStream(filePath);
|
|
|
|
resp.writeHead(200, headers);
|
|
|
|
return readStream.pipe(resp);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
routeWebRequestForBatchArchive(servedItem, req, resp) {
|
2022-06-05 20:04:25 +00:00
|
|
|
Log.debug({ servedItem: servedItem }, 'Batch file web request');
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
//
|
2018-06-23 03:26:46 +00:00
|
|
|
// We are going to build an on-the-fly zip file stream of 1:n
|
|
|
|
// files in the batch.
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
2018-06-23 03:26:46 +00:00
|
|
|
// First, collect all file IDs
|
2018-06-22 05:15:04 +00:00
|
|
|
//
|
|
|
|
const self = this;
|
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function fetchFileIds(callback) {
|
|
|
|
FileDb.all(
|
|
|
|
`SELECT file_id
|
2018-06-23 03:26:46 +00:00
|
|
|
FROM file_web_serve_batch
|
|
|
|
WHERE hash_id = ?;`,
|
2022-06-05 20:04:25 +00:00
|
|
|
[servedItem.hashId],
|
2018-06-22 05:15:04 +00:00
|
|
|
(err, fileIdRows) => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (
|
|
|
|
err ||
|
|
|
|
!Array.isArray(fileIdRows) ||
|
|
|
|
0 === fileIdRows.length
|
|
|
|
) {
|
|
|
|
return callback(
|
|
|
|
Errors.DoesNotExist(
|
|
|
|
'Could not get file IDs for batch'
|
|
|
|
)
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
return callback(
|
|
|
|
null,
|
|
|
|
fileIdRows.map(r => r.file_id)
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
function loadFileEntries(fileIds, callback) {
|
2022-06-05 20:04:25 +00:00
|
|
|
async.map(
|
|
|
|
fileIds,
|
|
|
|
(fileId, nextFileId) => {
|
|
|
|
const fileEntry = new FileEntry();
|
|
|
|
fileEntry.load(fileId, err => {
|
|
|
|
return nextFileId(err, fileEntry);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
(err, fileEntries) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(
|
|
|
|
Errors.DoesNotExist(
|
|
|
|
'Could not load file IDs for batch'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2018-06-22 05:15:04 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
return callback(null, fileEntries);
|
|
|
|
}
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
},
|
|
|
|
function createAndServeStream(fileEntries, callback) {
|
|
|
|
const filePaths = fileEntries.map(fe => fe.filePath);
|
2022-06-05 20:04:25 +00:00
|
|
|
Log.trace(
|
|
|
|
{ filePaths: filePaths },
|
|
|
|
'Creating zip archive for batch web request'
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
|
|
|
|
const zipFile = new yazl.ZipFile();
|
|
|
|
|
|
|
|
zipFile.on('error', err => {
|
2022-06-05 20:04:25 +00:00
|
|
|
Log.warn(
|
|
|
|
{ error: err.message },
|
|
|
|
'Error adding file to batch web request archive'
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
filePaths.forEach(fp => {
|
|
|
|
zipFile.addFile(
|
2022-06-05 20:04:25 +00:00
|
|
|
fp, // path to physical file
|
2018-06-23 03:26:46 +00:00
|
|
|
paths.basename(fp), // filename/path *stored in archive*
|
2018-06-22 05:15:04 +00:00
|
|
|
{
|
2022-06-05 20:04:25 +00:00
|
|
|
compress: false, // :TODO: do this smartly - if ext is in set = false, else true via isArchive() or such... mimeDB has this for us.
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
zipFile.end(finalZipSize => {
|
|
|
|
if (-1 === finalZipSize) {
|
|
|
|
return callback(
|
|
|
|
Errors.UnexpectedState('Unable to acquire final zip size')
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp.on('close', () => {
|
2018-06-23 03:26:46 +00:00
|
|
|
// connection closed *before* the response was fully sent
|
|
|
|
// :TODO: Log and such
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
resp.on('finish', () => {
|
2018-06-23 03:26:46 +00:00
|
|
|
// transfer completed fully
|
2022-06-05 20:04:25 +00:00
|
|
|
self.updateDownloadStatsForUserIdAndSystem(
|
|
|
|
servedItem.userId,
|
|
|
|
finalZipSize,
|
|
|
|
fileEntries
|
|
|
|
);
|
2018-06-22 05:15:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const batchFileName = `batch_${servedItem.hashId}.zip`;
|
|
|
|
|
|
|
|
const headers = {
|
2022-06-05 20:04:25 +00:00
|
|
|
'Content-Type':
|
|
|
|
mimeTypes.contentType(batchFileName) ||
|
|
|
|
mimeTypes.contentType('.bin'),
|
|
|
|
'Content-Length': finalZipSize,
|
|
|
|
'Content-Disposition': `attachment; filename="${batchFileName}"`,
|
2018-06-22 05:15:04 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
resp.writeHead(200, headers);
|
|
|
|
return zipFile.outputStream.pipe(resp);
|
|
|
|
});
|
2022-06-05 20:04:25 +00:00
|
|
|
},
|
2018-06-22 05:15:04 +00:00
|
|
|
],
|
|
|
|
err => {
|
2022-06-05 20:04:25 +00:00
|
|
|
if (err) {
|
2018-06-23 03:26:46 +00:00
|
|
|
// :TODO: Log me!
|
2018-06-22 05:15:04 +00:00
|
|
|
return this.fileNotFound(resp);
|
|
|
|
}
|
|
|
|
|
2018-06-23 03:26:46 +00:00
|
|
|
// ...otherwise, we would have called resp() already.
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
|
2022-06-05 20:04:25 +00:00
|
|
|
async.waterfall([
|
|
|
|
function fetchActiveUser(callback) {
|
|
|
|
const clientForUserId = getConnectionByUserId(userId);
|
|
|
|
if (clientForUserId) {
|
|
|
|
return callback(null, clientForUserId.user);
|
|
|
|
}
|
2018-06-22 05:15:04 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
// not online now - look 'em up
|
|
|
|
User.getUser(userId, (err, assocUser) => {
|
|
|
|
return callback(err, assocUser);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function updateStats(user, callback) {
|
|
|
|
StatLog.incrementUserStat(user, UserProps.FileDlTotalCount, 1);
|
|
|
|
StatLog.incrementUserStat(user, UserProps.FileDlTotalBytes, dlBytes);
|
2018-11-26 02:05:16 +00:00
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, 1);
|
|
|
|
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, dlBytes);
|
2018-06-22 05:15:04 +00:00
|
|
|
|
2022-06-12 20:12:03 +00:00
|
|
|
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayCount, 1);
|
|
|
|
StatLog.incrementNonPersistentSystemStat(
|
|
|
|
SysProps.FileDlTodayBytes,
|
|
|
|
dlBytes
|
|
|
|
);
|
2020-11-26 22:53:21 +00:00
|
|
|
|
2022-06-12 20:12:03 +00:00
|
|
|
return callback(null, user);
|
|
|
|
},
|
|
|
|
function sendEvent(user, callback) {
|
|
|
|
Events.emit(Events.getSystemEvents().UserDownload, {
|
|
|
|
user: user,
|
|
|
|
files: fileEntries,
|
|
|
|
});
|
|
|
|
return callback(null);
|
|
|
|
},
|
|
|
|
]);
|
2018-06-22 05:15:04 +00:00
|
|
|
}
|
2016-10-25 03:49:45 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 20:04:25 +00:00
|
|
|
module.exports = new FileAreaWebAccess();
|