* WIP on upload scan/processing

* WIP on user add/edit data to uploads
* Add write access (upload) to area ACS
* Add upload collision handling
* Add upload stats
This commit is contained in:
Bryan Ashby 2017-01-11 22:51:00 -07:00
parent 4c1c05e4da
commit e265e3cc97
11 changed files with 479 additions and 133 deletions

View file

@ -10,10 +10,13 @@ const Errors = require('../core/enig_error.js').Errors;
const stringFormat = require('../core/string_format.js');
const getSortedAvailableFileAreas = require('../core/file_area.js').getSortedAvailableFileAreas;
const getAreaDefaultStorageDirectory = require('../core/file_area.js').getAreaDefaultStorageDirectory;
const scanFile = require('../core/file_area.js').scanFile;
const getAreaStorageDirectoryByTag = require('../core/file_area.js').getAreaStorageDirectoryByTag;
// deps
const async = require('async');
const _ = require('lodash');
const async = require('async');
const _ = require('lodash');
const paths = require('path');
exports.moduleInfo = {
name : 'Upload',
@ -23,7 +26,8 @@ exports.moduleInfo = {
const FormIds = {
options : 0,
fileDetails : 1,
processing : 1,
fileDetails : 2,
};
@ -35,10 +39,16 @@ const MciViewIds = {
navMenu : 4, // next/cancel/etc.
},
processing : {
// 10+ = customs
},
fileDetails : {
tags : 1, // tag(s) for item
desc : 2, // defaults to 'desc' (e.g. from FILE_ID.DIZ)
accept : 3, // accept fields & continue
desc : 1, // defaults to 'desc' (e.g. from FILE_ID.DIZ)
tags : 2, // tag(s) for item
estYear : 3,
accept : 4, // accept fields & continue
// 10+ = customs
}
};
@ -47,14 +57,15 @@ exports.getModule = class UploadModule extends MenuModule {
constructor(options) {
super(options);
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
this.recvFilePaths = options.lastMenuResult.recvFilePaths;
}
this.availAreas = getSortedAvailableFileAreas(this.client, { writeAcs : true } );
this.menuMethods = {
navContinue : (formData, extraArgs, cb) => {
optionsNavContinue : (formData, extraArgs, cb) => {
if(this.isBlindUpload()) {
// jump to fileDetails form
// :TODO: support blind
} else {
// jump to protocol selection
const areaUploadDir = this.getSelectedAreaUploadDirectory();
@ -66,20 +77,54 @@ exports.getModule = class UploadModule extends MenuModule {
};
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
} else {
// jump to fileDetails form
// :TODO: support non-blind: collect info/filename -> upload -> complete
}
},
fileDetailsContinue : (formData, extraArgs, cb) => {
// see notes in displayFileDetailsPageForEntry() about this hackery:
cb(null);
return this.fileDetailsCurrentEntrySubmitCallback(null, formData.value); // move on to the next entry, if any
}
};
}
getSaveState() {
const saveState = {
uploadType : this.uploadType,
};
if(this.isBlindUpload()) {
saveState.areaInfo = this.getSelectedAreaInfo();
}
return saveState;
}
restoreSavedState(savedState) {
if(savedState.areaInfo) {
this.areaInfo = savedState.areaInfo;
}
}
getSelectedAreaInfo() {
const areaSelectView = this.viewControllers.options.getView(MciViewIds.options.area);
return this.availAreas[areaSelectView.getData()];
}
getSelectedAreaUploadDirectory() {
const areaSelectView = this.viewControllers.options.getView(MciViewIds.options.area);
const selectedArea = this.availAreas[areaSelectView.getData()];
return getAreaDefaultStorageDirectory(selectedArea);
const areaInfo = this.getSelectedAreaInfo();
return getAreaDefaultStorageDirectory(areaInfo);
}
isBlindUpload() { return 'blind' === this.uploadType; }
isFileTransferComplete() { return !_.isUndefined(this.recvFilePaths); }
initSequence() {
const self = this;
@ -89,7 +134,11 @@ exports.getModule = class UploadModule extends MenuModule {
return self.beforeArt(callback);
},
function display(callback) {
return self.displayOptionsPage(false, callback);
if(self.isFileTransferComplete()) {
return self.displayProcessingPage(callback);
} else {
return self.displayOptionsPage(callback);
}
}
],
() => {
@ -98,6 +147,110 @@ exports.getModule = class UploadModule extends MenuModule {
);
}
finishedLoading() {
if(this.isFileTransferComplete()) {
return this.processUploadedFiles();
}
}
scanFiles(cb) {
const self = this;
const results = {
newEntries : [],
dupes : [],
};
async.eachSeries(this.recvFilePaths, (filePath, nextFilePath) => {
// :TODO: virus scanning/etc. should occur around here
// :TODO: update scanning status art or display line "scanning {fileName}..." type of thing
self.client.term.pipeWrite(`|00|07\nScanning ${paths.basename(filePath)}...`);
scanFile(
filePath,
{
areaTag : self.areaInfo.areaTag,
storageTag : self.areaInfo.storageTags[0],
},
(err, fileEntry, existingEntries) => {
if(err) {
return nextFilePath(err);
}
self.client.term.pipeWrite(' done\n');
// new or dupe?
if(existingEntries.length > 0) {
// 1:n dupes found
results.dupes = results.dupes.concat(existingEntries);
} else {
// new one
results.newEntries.push(fileEntry);
}
return nextFilePath(null);
}
);
}, err => {
return cb(err, results);
});
}
processUploadedFiles() {
//
// For each file uploaded, we need to process & gather information
//
const self = this;
async.waterfall(
[
function scan(callback) {
return self.scanFiles(callback);
},
function displayDupes(scanResults, callback) {
if(0 === scanResults.dupes.length) {
return callback(null, scanResults);
}
// :TODO: display dupe info
return callback(null, scanResults);
},
function prepDetails(scanResults, callback) {
async.eachSeries(scanResults.newEntries, (newEntry, nextEntry) => {
self.displayFileDetailsPageForEntry(newEntry, (err, newValues) => {
if(!err) {
// if the file entry did *not* have a desc, take the user desc
if(!self.fileEntryHasDetectedDesc(newEntry)) {
newEntry.desc = newValues.shortDesc.trim();
}
if(newValues.estYear.length > 0) {
newEntry.meta.est_release_year = newValues.estYear;
}
if(newValues.tags.length > 0) {
newEntry.setHashTags(newValues.tags);
}
}
return nextEntry(err);
});
}, err => {
delete self.fileDetailsCurrentEntrySubmitCallback;
return callback(err);
});
}
],
err => {
}
);
}
displayOptionsPage(cb) {
const self = this;
@ -130,6 +283,7 @@ exports.getModule = class UploadModule extends MenuModule {
}
});
self.uploadType = 'blind';
uploadTypeView.setFocusItemIndex(0); // default to blind
fileNameView.setText(blindFileNameText);
areaSelectView.redraw();
@ -145,4 +299,59 @@ exports.getModule = class UploadModule extends MenuModule {
);
}
displayProcessingPage(cb) {
// :TODO: If art is supplied, display & start processing + update status/etc.; if no art, we'll just write each status update on a new line
return cb(null);
}
fileEntryHasDetectedDesc(fileEntry) {
return (fileEntry.desc && fileEntry.desc.length > 0);
}
displayFileDetailsPageForEntry(fileEntry, cb) {
const self = this;
async.series(
[
function prepArtAndViewController(callback) {
return self.prepViewControllerWithArt(
'fileDetails',
FormIds.fileDetails,
{ clearScreen : true, trailingLF : false },
callback
);
},
function populateViews(callback) {
const descView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.desc);
if(self.fileEntryHasDetectedDesc(fileEntry)) {
descView.setText(fileEntry.desc);
descView.setPropertyValue('mode', 'preview');
// :TODO: it would be nice to take this out of the focus order
}
const tagsView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.tags);
tagsView.setText( Array.from(fileEntry.hashTags).join(',') ); // :TODO: optional 'hashTagsSep' like file list/browse
const yearView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.estYear);
yearView.setText(fileEntry.meta.est_release_year || '');
return callback(null);
}
],
err => {
//
// we only call |cb| here if there is an error
// else, wait for the current from to be submit - then call -
// this way we'll move on to the next file entry when ready
//
if(err) {
return cb(err);
}
self.fileDetailsCurrentEntrySubmitCallback = cb; // stash for moduleMethods.fileDetailsContinue
}
);
}
};