/* jslint node: true */ 'use strict'; // ENiGMA½ const MenuModule = require('./menu_module.js').MenuModule; const { getModDatabasePath, getTransactionDatabase } = require('./database.js'); const ViewController = require('./view_controller.js').ViewController; const ansi = require('./ansi_term.js'); const theme = require('./theme.js'); const User = require('./user.js'); const stringFormat = require('./string_format.js'); // deps const async = require('async'); const sqlite3 = require('sqlite3'); const _ = require('lodash'); // :TODO: add notes field const moduleInfo = (exports.moduleInfo = { name: 'BBS List', desc: 'List of other BBSes', author: 'Andrew Pamment', packageName: 'com.magickabbs.enigma.bbslist', }); const MciViewIds = { view: { BBSList: 1, SelectedBBSName: 2, SelectedBBSSysOp: 3, SelectedBBSTelnet: 4, SelectedBBSWww: 5, SelectedBBSLoc: 6, SelectedBBSSoftware: 7, SelectedBBSNotes: 8, SelectedBBSSubmitter: 9, }, add: { BBSName: 1, Sysop: 2, Telnet: 3, Www: 4, Location: 5, Software: 6, Notes: 7, Error: 8, }, }; const FormIds = { View: 0, Add: 1, }; const SELECTED_MCI_NAME_TO_ENTRY = { SelectedBBSName: 'bbsName', SelectedBBSSysOp: 'sysOp', SelectedBBSTelnet: 'telnet', SelectedBBSWww: 'www', SelectedBBSLoc: 'location', SelectedBBSSoftware: 'software', SelectedBBSSubmitter: 'submitter', SelectedBBSSubmitterId: 'submitterUserId', SelectedBBSNotes: 'notes', }; exports.getModule = class BBSListModule extends MenuModule { constructor(options) { super(options); const self = this; this.menuMethods = { // // Validators // viewValidationListener: function (err, cb) { const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error); if (errMsgView) { if (err) { errMsgView.setText(err.friendlyText); } else { errMsgView.clearText(); } } return cb(err, null); }, // // Key & submit handlers // addBBS: function (formData, extraArgs, cb) { self.displayAddScreen(cb); }, deleteBBS: function (formData, extraArgs, cb) { if (!_.isNumber(self.selectedBBS) || 0 === self.entries.length) { return cb(null); } const entriesView = self.viewControllers.view.getView( MciViewIds.view.BBSList ); if ( self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp() ) { // must be owner or +op return cb(null); } const entry = self.entries[self.selectedBBS]; if (!entry) { return cb(null); } self.database.run( `DELETE FROM bbs_list WHERE id=?;`, [entry.id], err => { if (err) { self.client.log.error( { err: err }, 'Error deleting from BBS list' ); } else { self.entries.splice(self.selectedBBS, 1); self.setEntries(entriesView); if (self.entries.length > 0) { entriesView.focusPrevious(); } self.viewControllers.view.redrawAll(); } return cb(null); } ); }, submitBBS: function (formData, extraArgs, cb) { let ok = true; ['BBSName', 'Sysop', 'Telnet'].forEach(mciName => { if ( '' === self.viewControllers.add .getView(MciViewIds.add[mciName]) .getData() ) { ok = false; } }); if (!ok) { // validators should prevent this! return cb(null); } self.database.run( `INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes) VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, [ formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www, formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes, ], err => { if (err) { self.client.log.error( { err: err }, 'Error adding to BBS list' ); } self.clearAddForm(); self.displayBBSList(true, cb); } ); }, cancelSubmit: function (formData, extraArgs, cb) { self.clearAddForm(); self.displayBBSList(true, cb); }, }; } initSequence() { const self = this; async.series( [ function beforeDisplayArt(callback) { self.beforeArt(callback); }, function display(callback) { self.displayBBSList(false, callback); }, ], err => { if (err) { // :TODO: Handle me -- initSequence() should really take a completion callback } self.finishedLoading(); } ); } drawSelectedEntry(entry) { if (!entry) { Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => { this.setViewText('view', MciViewIds.view[mciName], ''); }); } else { const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)'; Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => { const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]]; if (MciViewIds.view[mciName]) { if ( 'SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId ) { this.setViewText( 'view', MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry) ); } else { this.setViewText('view', MciViewIds.view[mciName], t); } } }); } } setEntries(entriesView) { return entriesView.setItems(this.entries); } displayBBSList(clearScreen, cb) { const self = this; async.waterfall( [ function clearAndDisplayArt(callback) { if (self.viewControllers.add) { self.viewControllers.add.setFocus(false); } if (clearScreen) { self.client.term.rawWrite(ansi.resetScreen()); } theme.displayThemedAsset( self.menuConfig.config.art.entries, self.client, { font: self.menuConfig.font, trailingLF: false }, (err, artData) => { return callback(err, artData); } ); }, function initOrRedrawViewController(artData, callback) { if (_.isUndefined(self.viewControllers.add)) { const vc = self.addViewController( 'view', new ViewController({ client: self.client, formId: FormIds.View, }) ); const loadOpts = { callingMenu: self, mciMap: artData.mciMap, formId: FormIds.View, }; return vc.loadFromMenuConfig(loadOpts, callback); } else { self.viewControllers.view.setFocus(true); self.viewControllers.view .getView(MciViewIds.view.BBSList) .redraw(); return callback(null); } }, function fetchEntries(callback) { const entriesView = self.viewControllers.view.getView( MciViewIds.view.BBSList ); self.entries = []; self.database.each( `SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes FROM bbs_list;`, (err, row) => { if (!err) { self.entries.push({ text: row.bbs_name, // standard field id: row.id, bbsName: row.bbs_name, sysOp: row.sysop, telnet: row.telnet, www: row.www, location: row.location, software: row.software, submitterUserId: row.submitter_user_id, notes: row.notes, }); } }, err => { return callback(err, entriesView); } ); }, function getUserNames(entriesView, callback) { async.each( self.entries, (entry, next) => { User.getUserName(entry.submitterUserId, (err, username) => { if (username) { entry.submitter = username; } else { entry.submitter = 'N/A'; } return next(); }); }, () => { return callback(null, entriesView); } ); }, function populateEntries(entriesView, callback) { self.setEntries(entriesView); entriesView.on('index update', idx => { const entry = self.entries[idx]; self.drawSelectedEntry(entry); if (!entry) { self.selectedBBS = -1; } else { self.selectedBBS = idx; } }); if (self.selectedBBS >= 0) { entriesView.setFocusItemIndex(self.selectedBBS); self.drawSelectedEntry(self.entries[self.selectedBBS]); } else if (self.entries.length > 0) { self.selectedBBS = 0; entriesView.setFocusItemIndex(0); self.drawSelectedEntry(self.entries[0]); } entriesView.redraw(); return callback(null); }, ], err => { if (cb) { return cb(err); } } ); } displayAddScreen(cb) { const self = this; async.waterfall( [ function clearAndDisplayArt(callback) { self.viewControllers.view.setFocus(false); self.client.term.rawWrite(ansi.resetScreen()); theme.displayThemedAsset( self.menuConfig.config.art.add, self.client, { font: self.menuConfig.font }, (err, artData) => { return callback(err, artData); } ); }, function initOrRedrawViewController(artData, callback) { if (_.isUndefined(self.viewControllers.add)) { const vc = self.addViewController( 'add', new ViewController({ client: self.client, formId: FormIds.Add, }) ); const loadOpts = { callingMenu: self, mciMap: artData.mciMap, formId: FormIds.Add, }; return vc.loadFromMenuConfig(loadOpts, callback); } else { self.viewControllers.add.setFocus(true); self.viewControllers.add.redrawAll(); self.viewControllers.add.switchFocus(MciViewIds.add.BBSName); return callback(null); } }, ], err => { if (cb) { return cb(err); } } ); } clearAddForm() { [ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes', ].forEach(mciName => { this.setViewText('add', MciViewIds.add[mciName], ''); }); } initDatabase(cb) { const self = this; async.series( [ function openDatabase(callback) { self.database = getTransactionDatabase( new sqlite3.Database(getModDatabasePath(moduleInfo), callback) ); }, function createTables(callback) { self.database.serialize(() => { self.database.run( `CREATE TABLE IF NOT EXISTS bbs_list ( id INTEGER PRIMARY KEY, bbs_name VARCHAR NOT NULL, sysop VARCHAR NOT NULL, telnet VARCHAR NOT NULL, www VARCHAR, location VARCHAR, software VARCHAR, submitter_user_id INTEGER NOT NULL, notes VARCHAR );` ); }); callback(null); }, ], err => { return cb(err); } ); } beforeArt(cb) { super.beforeArt(err => { return err ? cb(err) : this.initDatabase(cb); }); } };