diff --git a/art/themes/luciano_blocktronics/FMENU.ANS b/art/themes/luciano_blocktronics/FMENU.ANS index a187491b..55879dd7 100644 Binary files a/art/themes/luciano_blocktronics/FMENU.ANS and b/art/themes/luciano_blocktronics/FMENU.ANS differ diff --git a/art/themes/luciano_blocktronics/MSGMNU.ANS b/art/themes/luciano_blocktronics/MSGMNU.ANS index e27fed73..ce6f4815 100644 Binary files a/art/themes/luciano_blocktronics/MSGMNU.ANS and b/art/themes/luciano_blocktronics/MSGMNU.ANS differ diff --git a/art/themes/luciano_blocktronics/SETFNSDATE.ANS b/art/themes/luciano_blocktronics/SETFNSDATE.ANS new file mode 100644 index 00000000..8294a6f7 Binary files /dev/null and b/art/themes/luciano_blocktronics/SETFNSDATE.ANS differ diff --git a/art/themes/luciano_blocktronics/SETMNSDATE.ANS b/art/themes/luciano_blocktronics/SETMNSDATE.ANS new file mode 100644 index 00000000..4d3b43a3 Binary files /dev/null and b/art/themes/luciano_blocktronics/SETMNSDATE.ANS differ diff --git a/config/menu.hjson b/config/menu.hjson index 2225f274..8eaedb1e 100644 --- a/config/menu.hjson +++ b/config/menu.hjson @@ -1766,6 +1766,10 @@ value: { command: "]" } action: @systemMethod:nextArea } + { + value: { command: "D" } + action: @menu:messageAreaSetNewScanDate + } { value: 1 action: @menu:messageArea @@ -1803,6 +1807,47 @@ } } + messageAreaSetNewScanDate: { + module: set_newscan_date + desc: Message Base + art: SETMNSDATE + config: { + target: message + scanDateFormat: YYYYMMDD + } + form: { + 0: { + mci: { + ME1: { + focus: true + submit: true + argName: scanDate + maskPattern: "####/##/##" + } + SM2: { + argName: targetSelection + submit: false + justify: right + } + } + submit: { + *: [ + { + value: { scanDate: null } + action: @method:scanDateSubmit + } + ] + } + actionKeys: [ + { + keys: [ "escape", "q", "shift + q" ] + action: @systemMethod:prevMenu + } + ] + } + } + } + messageAreaChangeCurrentArea: { // :TODO: rename this art to ACHANGE art: CHANGE @@ -2493,9 +2538,49 @@ value: { menuOption: "S" } action: @menu:fileBaseSearch } + { + value: { menuOption: "P" } + action: @menu:fileBaseSetNewScanDate + } ] } + fileBaseSetNewScanDate: { + module: set_newscan_date + desc: File Base + art: SETFNSDATE + config: { + target: file + scanDateFormat: YYYYMMDD + } + form: { + 0: { + mci: { + ME1: { + focus: true + submit: true + argName: scanDate + maskPattern: "####/##/##" + } + } + submit: { + *: [ + { + value: { scanDate: null } + action: @method:scanDateSubmit + } + ] + } + actionKeys: [ + { + keys: [ "escape", "q", "shift + q" ] + action: @systemMethod:prevMenu + } + ] + } + } + } + fileBaseListEntries: { module: file_area_list desc: Browsing Files diff --git a/core/scanner_tossers/ftn_bso.js b/core/scanner_tossers/ftn_bso.js index 830e9daa..8da71d02 100644 --- a/core/scanner_tossers/ftn_bso.js +++ b/core/scanner_tossers/ftn_bso.js @@ -349,8 +349,8 @@ function FTNMessageScanTossModule() { message.meta.FtnKludge.FMPT = localAddress.point; } - if(_.get(message, 'meta.FtnProperty.ftn_dest_point', 0) > 0) { - message.meta.FtnKludge.TOPT = message.meta.FtnProperty.ftn_dest_point; + if(_.isNumber(options.destAddress.point) && options.destAddress.point > 0) { + message.meta.FtnKludge.TOPT = options.destAddress.point; } } else { // We need to set some destination info for EchoMail @@ -1954,8 +1954,7 @@ function FTNMessageScanTossModule() { // // // :TODO: fill out the rest of the consts here - // :TODO: this statement is crazy ugly - // :TODO: this should really check for extenral "flavor" and not-already-exported state_flags0 + // :TODO: this statement is crazy ugly -- use JOIN / NOT EXISTS for state_flags & 0x02 const getNewUuidsSql = `SELECT message_id, message_uuid FROM message m diff --git a/core/set_newscan_date.js b/core/set_newscan_date.js new file mode 100644 index 00000000..0e29e999 --- /dev/null +++ b/core/set_newscan_date.js @@ -0,0 +1,261 @@ +/* jslint node: true */ +'use strict'; + +// ENiGMA½ +const MenuModule = require('./menu_module.js').MenuModule; +const ViewController = require('./view_controller.js').ViewController; +const Errors = require('./enig_error.js').Errors; +const FileEntry = require('./file_entry.js'); +const FileBaseFilters = require('./file_base_filter.js'); +const { getAvailableFileAreaTags } = require('./file_base_area.js'); +const { + getSortedAvailMessageConferences, + getSortedAvailMessageAreasByConfTag, + updateMessageAreaLastReadId, + getMessageIdNewerThanTimestampByArea +} = require('./message_area.js'); +const stringFormat = require('./string_format.js'); + +// deps +const async = require('async'); +const moment = require('moment'); +const _ = require('lodash'); + +exports.moduleInfo = { + name : 'Set New Scan Date', + desc : 'Sets new scan date for applicable scans', + author : 'NuSkooler', +}; + +const MciViewIds = { + main : { + scanDate : 1, + targetSelection : 2, + } +}; + +// :TODO: for messages, we could insert "conf - all areas" into targets, and allow such + +exports.getModule = class SetNewScanDate extends MenuModule { + constructor(options) { + super(options); + + const config = this.menuConfig.config; + + this.target = config.target || 'message'; + this.scanDateFormat = config.scanDateFormat || 'YYYYMMDD'; + + this.menuMethods = { + scanDateSubmit : (formData, extraArgs, cb) => { + let scanDate = _.get(formData, 'value.scanDate'); + if(!scanDate) { + return cb(Errors.MissingParam('"scanDate" missing from form data')); + } + + scanDate = moment(scanDate, this.scanDateFormat); + if(!scanDate.isValid()) { + return cb(Errors.Invalid(`"${_.get(formData, 'value.scanDate')}" is not a valid date`)); + } + + const targetSelection = _.get(formData, 'value.targetSelection'); // may be undefined if N/A + + this[`setNewScanDateFor${_.capitalize(this.target)}Base`](targetSelection, scanDate, () => { + return this.prevMenu(cb); + }); + }, + }; + } + + setNewScanDateForMessageBase(targetSelection, scanDate, cb) { + const target = this.targetSelections[targetSelection]; + if(!target) { + return cb(Errors.UnexpectedState('Unable to get target in which to set new scan')); + } + + // selected area, or all of 'em + let updateAreaTags; + if('' === target.area.areaTag) { + updateAreaTags = this.targetSelections + .map( targetSelection => targetSelection.area.areaTag ) + .filter( areaTag => areaTag ); // remove the blank 'all' entry + } else { + updateAreaTags = [ target.area.areaTag ]; + } + + async.each(updateAreaTags, (areaTag, nextAreaTag) => { + getMessageIdNewerThanTimestampByArea(areaTag, scanDate, (err, messageId) => { + if(err) { + return nextAreaTag(err); + } + + if(!messageId) { + return nextAreaTag(null); // nothing to do + } + + messageId = Math.max(messageId - 1, 0); + + return updateMessageAreaLastReadId( + this.client.user.userId, + areaTag, + messageId, + true, // allowOlder + nextAreaTag + ); + }); + }, err => { + return cb(err); + }); + } + + setNewScanDateForFileBase(targetSelection, scanDate, cb) { + // + // ENiGMA doesn't currently have the concept of per-area + // scan pointers for users, so we use all areas avail + // to the user. + // + const filterCriteria = { + areaTag : getAvailableFileAreaTags(this.client), + newerThanTimestamp : scanDate, + limit : 1, + orderBy : 'upload_timestamp', + order : 'ascending', + }; + + FileEntry.findFiles(filterCriteria, (err, fileIds) => { + if(err) { + return cb(err); + } + + if(!fileIds || 0 === fileIds.length) { + // nothing to do + return cb(null); + } + + const pointerFileId = Math.max(fileIds[0] - 1, 0); + + return FileBaseFilters.setFileBaseLastViewedFileIdForUser( + this.client.user, + pointerFileId, + true, // allowOlder + cb + ); + }); + } + + loadAvailMessageBaseSelections(cb) { + // + // Create an array of objects with conf/area information per entry, + // sorted naturally or via the 'sort' member in config + // + const selections = []; + getSortedAvailMessageConferences(this.client).forEach(conf => { + getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } ).forEach(area => { + selections.push({ + conf : { + confTag : conf.confTag, + name : conf.conf.name, + desc : conf.conf.desc, + }, + area : { + areaTag : area.areaTag, + name : area.area.name, + desc : area.area.desc, + } + }); + }); + }); + + selections.unshift({ + conf : { + confTag : '', + name : 'All conferences', + desc : 'All conferences', + }, + area : { + areaTag : '', + name : 'All areas', + desc : 'All areas', + } + }); + + // Find current conf/area & move it directly under "All" + const currConfTag = this.client.user.properties.message_conf_tag; + const currAreaTag = this.client.user.properties.message_area_tag; + if(currConfTag && currAreaTag) { + const confAreaIndex = selections.findIndex( confArea => { + return confArea.conf.confTag === currConfTag && confArea.area.areaTag === currAreaTag; + }); + + if(confAreaIndex > -1) { + selections.splice(1, 0, selections.splice(confAreaIndex, 1)[0]); + } + } + + this.targetSelections = selections; + + return cb(null); + } + + mciReady(mciData, cb) { + super.mciReady(mciData, err => { + if(err) { + return cb(err); + } + + const self = this; + const vc = self.addViewController( 'main', new ViewController( { client : this.client } ) ); + + async.series( + [ + function validateConfig(callback) { + if(![ 'message', 'file' ].includes(self.target)) { + return callback(Errors.Invalid(`Invalid "target" in config: ${self.target}`)); + } + // :TOD0: validate scanDateFormat + return callback(null); + }, + function loadFromConfig(callback) { + return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback); + }, + function loadAvailSelections(callback) { + switch(self.target) { + case 'message' : + return self.loadAvailMessageBaseSelections(callback); + + default : + return callback(null); + } + }, + function populateForm(callback) { + const today = moment(); + + const scanDateView = vc.getView(MciViewIds.main.scanDate); + + // :TODO: MaskTextEditView needs some love: If setText() with input that matches the mask, we should ignore the non-mask chars! Hack in place for now + const scanDateFormat = self.scanDateFormat.replace(/[\/\-. ]/g, ''); + scanDateView.setText(today.format(scanDateFormat)); + + if('message' === self.target) { + const messageSelectionsFormat = self.menuConfig.config.messageSelectionsFormat || '{conf.name} - {area.name}'; + const messageSelectionFocusFormat = self.menuConfig.config.messageSelectionFocusFormat || messageSelectionsFormat; + + const targetSelectionView = vc.getView(MciViewIds.main.targetSelection); + + targetSelectionView.setItems(self.targetSelections.map(targetSelection => stringFormat(messageSelectionFocusFormat, targetSelection))); + targetSelectionView.setFocusItems(self.targetSelections.map(targetSelection => stringFormat(messageSelectionFocusFormat, targetSelection))); + + targetSelectionView.setFocusItemIndex(0); + } + + self.viewControllers.main.resetInitialFocus(); + //vc.switchFocus(MciViewIds.main.scanDate); + return callback(null); + } + ], + err => { + return cb(err); + } + ); + }); + } +};