diff --git a/UPGRADE.TXT b/UPGRADE.TXT new file mode 100644 index 00000000..cd5ecb91 --- /dev/null +++ b/UPGRADE.TXT @@ -0,0 +1,49 @@ +INTRODUCTION +------------------------------------------------------------------------------- +This document covers basic upgrade notes for major ENiGMA½. + + +BEFORE UPGRADING +------------------------------------------------------------------------------- +* Always back ALL files in the 'db' directory +* Back up your menu.hjson (or renamed equivalent) + + +GENERAL NOTES +------------------------------------------------------------------------------- +Upgrades often come with changes to the default menu.hjson. It is wise to +use a *different* file name for your BBS's version of this file and point to +it via config.hjson. For example: + +general: { + menuFile: my_bbs.hjson +} + +After updating code, use a program such as DiffMerge to merge in updates to +my_bbs.hjson from the shipping menu.hjson. + + +FROM GITHUB +------------------------------------------------------------------------------- +Upgrading from GitHub is easy: + + cd /path/to/enigma-bbs + git pull + npm install + + +PROBLEMS +------------------------------------------------------------------------------- +Report your issue on Xibalba BBS, hop in #enigma-bbs on Freenet and chat, or +file a issue on GitHub. + + +0.0.1-alpha to 0.0.4-alpha +------------------------------------------------------------------------------- +* Manual Database Upgrade + sqlite3 db/message.sqlite + INSERT INTO message_fts(message_fts) VALUES('rebuild'); + +* Archiver Changes + If you have overridden or made additions to archivers in your config.hjson + you will need to update them. See docs/archive.md and core/config.js diff --git a/core/art.js b/core/art.js index d33c81da..3d3febe1 100644 --- a/core/art.js +++ b/core/art.js @@ -78,7 +78,7 @@ function getArtFromPath(path, options, cb) { return iconv.decode(data, encoding); } else { const eofMarker = defaultEofFromExtension(ext); - return iconv.decode(sliceAtEOF(data, eofMarker), encoding); + return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding); } } @@ -213,11 +213,15 @@ function getArt(name, options, cb) { } function defaultEncodingFromExtension(ext) { - return SUPPORTED_ART_TYPES[ext.toLowerCase()].defaultEncoding; + const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()]; + return artType ? artType.defaultEncoding : 'utf8'; } function defaultEofFromExtension(ext) { - return SUPPORTED_ART_TYPES[ext.toLowerCase()].eof; + const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()]; + if(artType) { + return artType.eof; + } } // :TODO: Implement the following diff --git a/core/config.js b/core/config.js index 32335803..373f6c49 100644 --- a/core/config.js +++ b/core/config.js @@ -223,6 +223,8 @@ function getDefaultConfig() { contentServers : { web : { domain : 'another-fine-enigma-bbs.org', + + staticRoot : paths.join(__dirname, './../www'), http : { enabled : false, @@ -364,6 +366,9 @@ function getDefaultConfig() { }, fileTransferProtocols : { + // + // See http://www.synchro.net/docs/sexyz.txt for information on SEXYZ + // zmodem8kSexyz : { name : 'ZModem 8k (SEXYZ)', type : 'external', diff --git a/core/file_area_web.js b/core/file_area_web.js index 841ce904..3f1b60ac 100644 --- a/core/file_area_web.js +++ b/core/file_area_web.js @@ -41,15 +41,15 @@ class FileAreaWebAccess { return self.load(callback); }, function addWebRoute(callback) { - const webServer = getServer(WEB_SERVER_PACKAGE_NAME); - if(!webServer) { + self.webServer = getServer(WEB_SERVER_PACKAGE_NAME); + if(!self.webServer) { return callback(Errors.DoesNotExist(`Server with package name "${WEB_SERVER_PACKAGE_NAME}" does not exist`)); } - const routeAdded = webServer.instance.addRoute({ + const routeAdded = self.webServer.instance.addRoute({ method : 'GET', path : '/f/[a-zA-Z0-9]+$', // :TODO: allow this to be configurable - handler : self.routeWebRequest.bind(self), + handler : self.routeWebRequestForFile.bind(self), }); return callback(routeAdded ? null : Errors.General('Failed adding route')); @@ -217,13 +217,10 @@ class FileAreaWebAccess { } fileNotFound(resp) { - resp.writeHead(404, { 'Content-Type' : 'text/html' } ); - - // :TODO: allow custom 404 - mods//file_area_web-404.html - return resp.end('Not found'); + this.webServer.instance.respondWithError(resp, 404, 'File not found.', 'File Not Found'); } - routeWebRequest(req, resp) { + routeWebRequestForFile(req, resp) { const hashId = paths.basename(req.url); this.loadServedHashId(hashId, (err, servedItem) => { @@ -259,7 +256,7 @@ class FileAreaWebAccess { }); const headers = { - 'Content-Type' : mimeTypes.contentType(paths.extname(filePath)) || mimeTypes.contentType('.bin'), + 'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'), 'Content-Length' : stats.size, 'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`, }; diff --git a/core/file_area.js b/core/file_base_area.js similarity index 100% rename from core/file_area.js rename to core/file_base_area.js diff --git a/core/transfer_file.js b/core/file_transfer.js similarity index 98% rename from core/transfer_file.js rename to core/file_transfer.js index 406acfaa..9735abe7 100644 --- a/core/transfer_file.js +++ b/core/file_transfer.js @@ -25,11 +25,19 @@ const SYSTEM_EOL = require('os').EOL; const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc. /* + Notes + ----------------------------------------------------------------------------- + + See core/config.js for external protocol configuration + + Resources + ----------------------------------------------------------------------------- ZModem * http://gallium.inria.fr/~doligez/zmodem/zmodem.txt * https://github.com/protomouse/synchronet/blob/master/src/sbbs3/zmodem.c + */ exports.moduleInfo = { diff --git a/core/listening_server.js b/core/listening_server.js index e1009338..94efd475 100644 --- a/core/listening_server.js +++ b/core/listening_server.js @@ -6,7 +6,6 @@ const logger = require('./logger.js'); // deps const async = require('async'); -const _ = require('lodash'); const listeningServers = {}; // packageName -> info diff --git a/core/servers/content/web.js b/core/servers/content/web.js index 6a184284..5a6f36d1 100644 --- a/core/servers/content/web.js +++ b/core/servers/content/web.js @@ -11,6 +11,8 @@ const http = require('http'); const https = require('https'); const _ = require('lodash'); const fs = require('fs'); +const paths = require('path'); +const mimeTypes = require('mime-types'); const ModuleInfo = exports.moduleInfo = { name : 'Web', @@ -55,6 +57,14 @@ exports.getModule = class WebServerModule extends ServerModule { this.enableHttps = Config.contentServers.web.https.enabled || false; this.routes = {}; + + if(Config.contentServers.web.staticRoot) { + this.addRoute({ + method : 'GET', + path : '/static/.*$', + handler : this.routeStaticFile, + }); + } } createServer() { @@ -116,8 +126,54 @@ exports.getModule = class WebServerModule extends ServerModule { return route ? route.handler(req, resp) : this.accessDenied(resp); } - accessDenied(resp) { - resp.writeHead(401, { 'Content-Type' : 'text/html' } ); - return resp.end('Access denied'); + respondWithError(resp, code, bodyText, title) { + const customErrorPage = paths.join(Config.contentServers.web.staticRoot, `${code}.html`); + + fs.readFile(customErrorPage, 'utf8', (err, data) => { + resp.writeHead(code, { 'Content-Type' : 'text/html' } ); + + if(err) { + return resp.end(` + + + + ${title} + + + +
+

${bodyText}

+
+ + ` + ); + } + + return resp.end(data); + }); } -} \ No newline at end of file + + accessDenied(resp) { + return this.respondWithError(resp, 401, 'Access denied.', 'Access Denied'); + } + + routeStaticFile(req, resp) { + const fileName = req.url.substr(req.url.indexOf('/', 1)); + const filePath = paths.join(Config.contentServers.web.staticRoot, fileName); + + fs.stat(filePath, (err, stats) => { + if(err) { + return this.respondWithError(resp, 404, 'File not found.', 'File Not Found'); + } + + const headers = { + 'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'), + 'Content-Length' : stats.size, + }; + + const readStream = fs.createReadStream(filePath); + resp.writeHead(200, headers); + return readStream.pipe(resp); + }); + } +}; diff --git a/core/theme.js b/core/theme.js index ee67b14e..19ed9ff4 100644 --- a/core/theme.js +++ b/core/theme.js @@ -354,16 +354,16 @@ function setClientTheme(client, themeId) { function getThemeArt(options, cb) { // // options - required: - // name - // client + // name // // options - optional - // themeId - // asAnsi - // readSauce - // random + // client - needed for user's theme/etc. + // themeId + // asAnsi + // readSauce + // random // - if(!options.themeId && _.has(options.client, 'user.properties.theme_id')) { + if(!options.themeId && _.has(options, 'client.user.properties.theme_id')) { options.themeId = options.client.user.properties.theme_id; } else { options.themeId = Config.defaults.theme; @@ -437,9 +437,13 @@ function getThemeArt(options, cb) { ], function complete(err, artInfo) { if(err) { - options.client.log.debug( { error : err }, 'Cannot find art'); + if(options.client) { + options.client.log.debug( { error : err.message }, 'Cannot find theme art' ); + } else { + Log.debug( { error : err.message }, 'Cannot find theme art' ); + } } - cb(err, artInfo); + return cb(err, artInfo); } ); } diff --git a/mods/file_area_filter_edit.js b/mods/file_area_filter_edit.js index 1f7e846c..5cb211ac 100644 --- a/mods/file_area_filter_edit.js +++ b/mods/file_area_filter_edit.js @@ -4,7 +4,7 @@ // ENiGMA½ const MenuModule = require('../core/menu_module.js').MenuModule; const ViewController = require('../core/view_controller.js').ViewController; -const getSortedAvailableFileAreas = require('../core/file_area.js').getSortedAvailableFileAreas; +const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas; const FileBaseFilters = require('../core/file_base_filter.js'); const stringFormat = require('../core/string_format.js'); diff --git a/mods/file_area_list.js b/mods/file_area_list.js index f973a70e..b946497b 100644 --- a/mods/file_area_list.js +++ b/mods/file_area_list.js @@ -9,7 +9,7 @@ const theme = require('../core/theme.js'); const FileEntry = require('../core/file_entry.js'); const stringFormat = require('../core/string_format.js'); const createCleanAnsi = require('../core/string_util.js').createCleanAnsi; -const FileArea = require('../core/file_area.js'); +const FileArea = require('../core/file_base_area.js'); const Errors = require('../core/enig_error.js').Errors; const ArchiveUtil = require('../core/archive_util.js'); const Config = require('../core/config.js').config; @@ -23,12 +23,6 @@ const cleanControlCodes = require('../core/string_util.js').cleanControlCodes; const async = require('async'); const _ = require('lodash'); const moment = require('moment'); -const paths = require('path'); - -/* - Misc TODO - -*/ exports.moduleInfo = { name : 'File Area List', diff --git a/mods/upload.js b/mods/upload.js index c260d1f3..d06b1fa6 100644 --- a/mods/upload.js +++ b/mods/upload.js @@ -4,9 +4,9 @@ // enigma-bbs const MenuModule = require('../core/menu_module.js').MenuModule; 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 getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas; +const getAreaDefaultStorageDirectory = require('../core/file_base_area.js').getAreaDefaultStorageDirectory; +const scanFile = require('../core/file_base_area.js').scanFile; const ansiGoto = require('../core/ansi_term.js').goto; const moveFileWithCollisionHandling = require('../core/file_util.js').moveFileWithCollisionHandling; const pathWithTerminatingSeparator = require('../core/file_util.js').pathWithTerminatingSeparator; @@ -560,20 +560,21 @@ exports.getModule = class UploadModule extends MenuModule { }, function populateViews(callback) { const descView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.desc); - + const tagsView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.tags); + const yearView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.estYear); + + tagsView.setText( Array.from(fileEntry.hashTags).join(',') ); // :TODO: optional 'hashTagsSep' like file list/browse + yearView.setText(fileEntry.meta.est_release_year || ''); + 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 + self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.tags); + descView.acceptsFocus = false; + } else { + self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.desc); } - 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); } ], diff --git a/oputil.js b/oputil.js index b00d6428..20ae59da 100755 --- a/oputil.js +++ b/oputil.js @@ -442,7 +442,7 @@ function fileAreaScan() { return initConfigAndDatabases(callback); }, function getFileArea(callback) { - const fileAreaMod = require('./core/file_area.js'); + const fileAreaMod = require('./core/file_base_area.js'); const areaInfo = fileAreaMod.getFileAreaByTag(argv.scan); if(!areaInfo) { diff --git a/www/.gitkeep b/www/.gitkeep new file mode 100644 index 00000000..e69de29b