2016-09-29 03:54:25 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// ENiGMA½
|
2016-10-01 19:25:32 +00:00
|
|
|
const Config = require('./config.js').config;
|
|
|
|
const Errors = require('./enig_error.js').Errors;
|
|
|
|
const sortAreasOrConfs = require('./conf_area_util.js').sortAreasOrConfs;
|
2016-10-03 03:40:37 +00:00
|
|
|
const FileEntry = require('./file_entry.js');
|
|
|
|
const FileDb = require('./database.js').dbs.file;
|
|
|
|
const ArchiveUtil = require('./archive_util.js');
|
2016-09-29 03:54:25 +00:00
|
|
|
|
|
|
|
// deps
|
|
|
|
const _ = require('lodash');
|
2016-10-01 19:25:32 +00:00
|
|
|
const async = require('async');
|
2016-10-03 03:40:37 +00:00
|
|
|
const fs = require('fs');
|
|
|
|
const crypto = require('crypto');
|
|
|
|
const paths = require('path');
|
2016-09-29 03:54:25 +00:00
|
|
|
|
|
|
|
exports.getAvailableFileAreas = getAvailableFileAreas;
|
2016-10-01 19:25:32 +00:00
|
|
|
exports.getSortedAvailableFileAreas = getSortedAvailableFileAreas;
|
|
|
|
exports.getDefaultFileArea = getDefaultFileArea;
|
2016-09-29 03:54:25 +00:00
|
|
|
exports.getFileAreaByTag = getFileAreaByTag;
|
2016-10-01 19:25:32 +00:00
|
|
|
exports.changeFileAreaWithOptions = changeFileAreaWithOptions;
|
2016-10-03 03:40:37 +00:00
|
|
|
//exports.addOrUpdateFileEntry = addOrUpdateFileEntry;
|
|
|
|
exports.scanFileAreaForChanges = scanFileAreaForChanges;
|
2016-10-01 19:25:32 +00:00
|
|
|
|
|
|
|
const WellKnownAreaTags = exports.WellKnownAreaTags = {
|
|
|
|
Invalid : '',
|
|
|
|
MessageAreaAttach : 'message_area_attach',
|
|
|
|
};
|
2016-09-29 03:54:25 +00:00
|
|
|
|
|
|
|
function getAvailableFileAreas(client, options) {
|
|
|
|
options = options || { includeSystemInternal : false };
|
|
|
|
|
|
|
|
// perform ACS check per conf & omit system_internal if desired
|
|
|
|
return _.omit(Config.fileAreas.areas, (area, areaTag) => {
|
2016-10-01 19:25:32 +00:00
|
|
|
if(!options.includeSystemInternal && WellKnownAreaTags.MessageAreaAttach === areaTag) {
|
2016-09-29 03:54:25 +00:00
|
|
|
return true;
|
2016-10-01 19:25:32 +00:00
|
|
|
}
|
2016-09-29 03:54:25 +00:00
|
|
|
|
|
|
|
return !client.acs.hasFileAreaRead(area);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-10-01 19:25:32 +00:00
|
|
|
function getSortedAvailableFileAreas(client, options) {
|
|
|
|
const areas = _.map(getAvailableFileAreas(client, options), (v, k) => {
|
|
|
|
return {
|
|
|
|
areaTag : k,
|
|
|
|
area : v
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
sortAreasOrConfs(areas, 'area');
|
|
|
|
return areas;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultFileArea(client, disableAcsCheck) {
|
|
|
|
let defaultArea = _.findKey(Config.fileAreas, o => o.default);
|
|
|
|
if(defaultArea) {
|
|
|
|
const area = Config.fileAreas.areas[defaultArea];
|
|
|
|
if(true === disableAcsCheck || client.acs.hasFileAreaRead(area)) {
|
|
|
|
return defaultArea;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// just use anything we can
|
|
|
|
defaultArea = _.findKey(Config.fileAreas.areas, (area, areaTag) => {
|
|
|
|
return WellKnownAreaTags.MessageAreaAttach !== areaTag && (true === disableAcsCheck || client.acs.hasFileAreaRead(area));
|
|
|
|
});
|
|
|
|
|
|
|
|
return defaultArea;
|
|
|
|
}
|
2016-09-29 03:54:25 +00:00
|
|
|
|
|
|
|
function getFileAreaByTag(areaTag) {
|
2016-10-03 03:40:37 +00:00
|
|
|
const areaInfo = Config.fileAreas.areas[areaTag];
|
|
|
|
if(areaInfo) {
|
|
|
|
areaInfo.areaTag = areaTag; // convienence!
|
|
|
|
return areaInfo;
|
|
|
|
}
|
2016-10-01 19:25:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function changeFileAreaWithOptions(client, areaTag, options, cb) {
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function getArea(callback) {
|
|
|
|
const area = getFileAreaByTag(areaTag);
|
|
|
|
return callback(area ? null : Errors.Invalid('Invalid file areaTag'), area);
|
|
|
|
},
|
|
|
|
function validateAccess(area, callback) {
|
|
|
|
if(!client.acs.hasFileAreaRead(area)) {
|
|
|
|
return callback(Errors.AccessDenied('No access to this area'));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function changeArea(area, callback) {
|
|
|
|
if(true === options.persist) {
|
|
|
|
client.user.persistProperty('file_area_tag', areaTag, err => {
|
|
|
|
return callback(err, area);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
client.user.properties['file_area_tag'] = areaTag;
|
|
|
|
return callback(null, area);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
],
|
|
|
|
(err, area) => {
|
|
|
|
if(!err) {
|
|
|
|
client.log.info( { areaTag : areaTag, area : area }, 'Current file area changed');
|
|
|
|
} else {
|
|
|
|
client.log.warn( { areaTag : areaTag, area : area, error : err.message }, 'Could not change file area');
|
|
|
|
}
|
|
|
|
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2016-10-03 03:40:37 +00:00
|
|
|
|
|
|
|
function getAreaStorageDirectory(areaInfo) {
|
|
|
|
return paths.join(Config.fileBase.areaStoragePrefix, areaInfo.storageDir || '');
|
|
|
|
}
|
|
|
|
|
|
|
|
function getExistingFileEntriesBySha1(sha1, cb) {
|
|
|
|
const entries = [];
|
|
|
|
|
|
|
|
FileDb.each(
|
|
|
|
`SELECT file_id, area_tag
|
|
|
|
FROM file
|
|
|
|
WHERE file_sha1=?;`,
|
|
|
|
[ sha1 ],
|
|
|
|
(err, fileRow) => {
|
|
|
|
if(fileRow) {
|
|
|
|
entries.push({
|
|
|
|
fileId : fileRow.file_id,
|
|
|
|
areaTag : fileRow.area_tag,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
err => {
|
|
|
|
return cb(err, entries);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addNewArchiveFileEnty(fileEntry, filePath, archiveType, cb) {
|
2016-10-03 04:21:37 +00:00
|
|
|
const archiveUtil = ArchiveUtil.getInstance();
|
|
|
|
|
2016-10-03 03:40:37 +00:00
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function getArchiveFileList(callback) {
|
|
|
|
// :TODO: get list of files in archive
|
2016-10-03 04:21:37 +00:00
|
|
|
archiveUtil.listEntries(filePath, archiveType, (err, entries) => {
|
|
|
|
return callback(err);
|
|
|
|
});
|
2016-10-03 03:40:37 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addNewFileEntry(fileEntry, filePath, cb) {
|
|
|
|
const archiveUtil = ArchiveUtil.getInstance();
|
|
|
|
|
|
|
|
// :TODO: Use detectTypeWithBuf() once avail - we *just* read some file data
|
|
|
|
archiveUtil.detectType(filePath, (err, archiveType) => {
|
|
|
|
if(archiveType) {
|
|
|
|
return addNewArchiveFileEnty(fileEntry, filePath, archiveType, cb);
|
|
|
|
} else {
|
|
|
|
// :TODO:addNewNonArchiveFileEntry
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function addOrUpdateFileEntry(areaInfo, fileName, options, cb) {
|
|
|
|
|
|
|
|
const fileEntry = new FileEntry({
|
|
|
|
areaTag : areaInfo.areaTag,
|
|
|
|
meta : options.meta,
|
|
|
|
hashTags : options.hashTags, // Set() or Array
|
|
|
|
});
|
|
|
|
|
|
|
|
const filePath = paths.join(getAreaStorageDirectory(areaInfo), fileName);
|
|
|
|
|
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
function processPhysicalFile(callback) {
|
|
|
|
const stream = fs.createReadStream(filePath);
|
|
|
|
|
|
|
|
let byteSize = 0;
|
|
|
|
const sha1 = crypto.createHash('sha1');
|
|
|
|
const sha256 = crypto.createHash('sha256');
|
|
|
|
const md5 = crypto.createHash('md5');
|
|
|
|
|
|
|
|
|
|
|
|
// :TODO: crc32
|
|
|
|
|
|
|
|
stream.on('data', data => {
|
|
|
|
byteSize += data.length;
|
|
|
|
|
|
|
|
sha1.update(data);
|
|
|
|
sha256.update(data);
|
|
|
|
md5.update(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
stream.on('end', () => {
|
|
|
|
fileEntry.meta.byte_size = byteSize;
|
|
|
|
|
|
|
|
// sha-1 is in basic file entry
|
|
|
|
fileEntry.fileSha1 = sha1.digest('hex');
|
|
|
|
|
|
|
|
// others are meta
|
|
|
|
fileEntry.meta.file_sha256 = sha256.digest('hex');
|
|
|
|
fileEntry.meta.file_md5 = md5.digest('hex');
|
|
|
|
|
|
|
|
return callback(null);
|
|
|
|
});
|
|
|
|
|
|
|
|
stream.on('error', err => {
|
|
|
|
return callback(err);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function fetchExistingEntry(callback) {
|
|
|
|
getExistingFileEntriesBySha1(fileEntry.fileSha1, (err, existingEntries) => {
|
|
|
|
return callback(err, existingEntries);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function addOrUpdate(callback, existingEntries) {
|
|
|
|
if(existingEntries.length > 0) {
|
|
|
|
|
|
|
|
} else {
|
|
|
|
return addNewFileEntry(fileEntry, filePath, callback);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
],
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function scanFileAreaForChanges(areaInfo, cb) {
|
|
|
|
const areaPhysDir = getAreaStorageDirectory(areaInfo);
|
|
|
|
|
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
function scanPhysFiles(callback) {
|
|
|
|
fs.readdir(areaPhysDir, (err, files) => {
|
|
|
|
if(err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
async.each(files, (fileName, next) => {
|
|
|
|
const fullPath = paths.join(areaPhysDir, fileName);
|
|
|
|
|
|
|
|
fs.stat(fullPath, (err, stats) => {
|
|
|
|
if(err) {
|
|
|
|
// :TODO: Log me!
|
|
|
|
return next(null); // always try next file
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!stats.isFile()) {
|
|
|
|
return next(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
addOrUpdateFileEntry(areaInfo, fileName, err => {
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, err => {
|
|
|
|
return callback(err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
function scanDbEntries(callback) {
|
|
|
|
// :TODO: Look @ db entries for area that were *not* processed above
|
|
|
|
return callback(null);
|
|
|
|
}
|
|
|
|
],
|
|
|
|
err => {
|
|
|
|
return cb(err);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|