* Added ability to serve static files from web server
* Web server can have custom error pages, e.g. 404.html * "file_area" stuff -> "file_base" * Fix some rare bugs in theme/art loading * Adjust tab order dynamically for file upload details
This commit is contained in:
parent
ff64a7aed5
commit
92772eb1a9
|
@ -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
|
10
core/art.js
10
core/art.js
|
@ -78,7 +78,7 @@ function getArtFromPath(path, options, cb) {
|
||||||
return iconv.decode(data, encoding);
|
return iconv.decode(data, encoding);
|
||||||
} else {
|
} else {
|
||||||
const eofMarker = defaultEofFromExtension(ext);
|
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) {
|
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) {
|
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
|
// :TODO: Implement the following
|
||||||
|
|
|
@ -224,6 +224,8 @@ function getDefaultConfig() {
|
||||||
web : {
|
web : {
|
||||||
domain : 'another-fine-enigma-bbs.org',
|
domain : 'another-fine-enigma-bbs.org',
|
||||||
|
|
||||||
|
staticRoot : paths.join(__dirname, './../www'),
|
||||||
|
|
||||||
http : {
|
http : {
|
||||||
enabled : false,
|
enabled : false,
|
||||||
port : 8080,
|
port : 8080,
|
||||||
|
@ -364,6 +366,9 @@ function getDefaultConfig() {
|
||||||
},
|
},
|
||||||
|
|
||||||
fileTransferProtocols : {
|
fileTransferProtocols : {
|
||||||
|
//
|
||||||
|
// See http://www.synchro.net/docs/sexyz.txt for information on SEXYZ
|
||||||
|
//
|
||||||
zmodem8kSexyz : {
|
zmodem8kSexyz : {
|
||||||
name : 'ZModem 8k (SEXYZ)',
|
name : 'ZModem 8k (SEXYZ)',
|
||||||
type : 'external',
|
type : 'external',
|
||||||
|
|
|
@ -41,15 +41,15 @@ class FileAreaWebAccess {
|
||||||
return self.load(callback);
|
return self.load(callback);
|
||||||
},
|
},
|
||||||
function addWebRoute(callback) {
|
function addWebRoute(callback) {
|
||||||
const webServer = getServer(WEB_SERVER_PACKAGE_NAME);
|
self.webServer = getServer(WEB_SERVER_PACKAGE_NAME);
|
||||||
if(!webServer) {
|
if(!self.webServer) {
|
||||||
return callback(Errors.DoesNotExist(`Server with package name "${WEB_SERVER_PACKAGE_NAME}" does not exist`));
|
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',
|
method : 'GET',
|
||||||
path : '/f/[a-zA-Z0-9]+$', // :TODO: allow this to be configurable
|
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'));
|
return callback(routeAdded ? null : Errors.General('Failed adding route'));
|
||||||
|
@ -217,13 +217,10 @@ class FileAreaWebAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileNotFound(resp) {
|
fileNotFound(resp) {
|
||||||
resp.writeHead(404, { 'Content-Type' : 'text/html' } );
|
this.webServer.instance.respondWithError(resp, 404, 'File not found.', 'File Not Found');
|
||||||
|
|
||||||
// :TODO: allow custom 404 - mods/<theme>/file_area_web-404.html
|
|
||||||
return resp.end('<html><body>Not found</html>');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
routeWebRequest(req, resp) {
|
routeWebRequestForFile(req, resp) {
|
||||||
const hashId = paths.basename(req.url);
|
const hashId = paths.basename(req.url);
|
||||||
|
|
||||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||||
|
@ -259,7 +256,7 @@ class FileAreaWebAccess {
|
||||||
});
|
});
|
||||||
|
|
||||||
const headers = {
|
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-Length' : stats.size,
|
||||||
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,11 +25,19 @@ const SYSTEM_EOL = require('os').EOL;
|
||||||
const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
|
const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Notes
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
See core/config.js for external protocol configuration
|
||||||
|
|
||||||
|
|
||||||
Resources
|
Resources
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
ZModem
|
ZModem
|
||||||
* http://gallium.inria.fr/~doligez/zmodem/zmodem.txt
|
* http://gallium.inria.fr/~doligez/zmodem/zmodem.txt
|
||||||
* https://github.com/protomouse/synchronet/blob/master/src/sbbs3/zmodem.c
|
* https://github.com/protomouse/synchronet/blob/master/src/sbbs3/zmodem.c
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
|
@ -6,7 +6,6 @@ const logger = require('./logger.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
const listeningServers = {}; // packageName -> info
|
const listeningServers = {}; // packageName -> info
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const paths = require('path');
|
||||||
|
const mimeTypes = require('mime-types');
|
||||||
|
|
||||||
const ModuleInfo = exports.moduleInfo = {
|
const ModuleInfo = exports.moduleInfo = {
|
||||||
name : 'Web',
|
name : 'Web',
|
||||||
|
@ -55,6 +57,14 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
this.enableHttps = Config.contentServers.web.https.enabled || false;
|
this.enableHttps = Config.contentServers.web.https.enabled || false;
|
||||||
|
|
||||||
this.routes = {};
|
this.routes = {};
|
||||||
|
|
||||||
|
if(Config.contentServers.web.staticRoot) {
|
||||||
|
this.addRoute({
|
||||||
|
method : 'GET',
|
||||||
|
path : '/static/.*$',
|
||||||
|
handler : this.routeStaticFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createServer() {
|
createServer() {
|
||||||
|
@ -116,8 +126,54 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
return route ? route.handler(req, resp) : this.accessDenied(resp);
|
return route ? route.handler(req, resp) : this.accessDenied(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
accessDenied(resp) {
|
respondWithError(resp, code, bodyText, title) {
|
||||||
resp.writeHead(401, { 'Content-Type' : 'text/html' } );
|
const customErrorPage = paths.join(Config.contentServers.web.staticRoot, `${code}.html`);
|
||||||
return resp.end('<html><body>Access denied</body></html>');
|
|
||||||
|
fs.readFile(customErrorPage, 'utf8', (err, data) => {
|
||||||
|
resp.writeHead(code, { 'Content-Type' : 'text/html' } );
|
||||||
|
|
||||||
|
if(err) {
|
||||||
|
return resp.end(`<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>${title}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<article>
|
||||||
|
<h2>${bodyText}</h2>
|
||||||
|
</article>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.end(data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -354,16 +354,16 @@ function setClientTheme(client, themeId) {
|
||||||
function getThemeArt(options, cb) {
|
function getThemeArt(options, cb) {
|
||||||
//
|
//
|
||||||
// options - required:
|
// options - required:
|
||||||
// name
|
// name
|
||||||
// client
|
|
||||||
//
|
//
|
||||||
// options - optional
|
// options - optional
|
||||||
// themeId
|
// client - needed for user's theme/etc.
|
||||||
// asAnsi
|
// themeId
|
||||||
// readSauce
|
// asAnsi
|
||||||
// random
|
// 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;
|
options.themeId = options.client.user.properties.theme_id;
|
||||||
} else {
|
} else {
|
||||||
options.themeId = Config.defaults.theme;
|
options.themeId = Config.defaults.theme;
|
||||||
|
@ -437,9 +437,13 @@ function getThemeArt(options, cb) {
|
||||||
],
|
],
|
||||||
function complete(err, artInfo) {
|
function complete(err, artInfo) {
|
||||||
if(err) {
|
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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const MenuModule = require('../core/menu_module.js').MenuModule;
|
const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
const ViewController = require('../core/view_controller.js').ViewController;
|
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 FileBaseFilters = require('../core/file_base_filter.js');
|
||||||
const stringFormat = require('../core/string_format.js');
|
const stringFormat = require('../core/string_format.js');
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ const theme = require('../core/theme.js');
|
||||||
const FileEntry = require('../core/file_entry.js');
|
const FileEntry = require('../core/file_entry.js');
|
||||||
const stringFormat = require('../core/string_format.js');
|
const stringFormat = require('../core/string_format.js');
|
||||||
const createCleanAnsi = require('../core/string_util.js').createCleanAnsi;
|
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 Errors = require('../core/enig_error.js').Errors;
|
||||||
const ArchiveUtil = require('../core/archive_util.js');
|
const ArchiveUtil = require('../core/archive_util.js');
|
||||||
const Config = require('../core/config.js').config;
|
const Config = require('../core/config.js').config;
|
||||||
|
@ -23,12 +23,6 @@ const cleanControlCodes = require('../core/string_util.js').cleanControlCodes;
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const paths = require('path');
|
|
||||||
|
|
||||||
/*
|
|
||||||
Misc TODO
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Area List',
|
name : 'File Area List',
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
// enigma-bbs
|
// enigma-bbs
|
||||||
const MenuModule = require('../core/menu_module.js').MenuModule;
|
const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
const stringFormat = require('../core/string_format.js');
|
const stringFormat = require('../core/string_format.js');
|
||||||
const getSortedAvailableFileAreas = require('../core/file_area.js').getSortedAvailableFileAreas;
|
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
|
||||||
const getAreaDefaultStorageDirectory = require('../core/file_area.js').getAreaDefaultStorageDirectory;
|
const getAreaDefaultStorageDirectory = require('../core/file_base_area.js').getAreaDefaultStorageDirectory;
|
||||||
const scanFile = require('../core/file_area.js').scanFile;
|
const scanFile = require('../core/file_base_area.js').scanFile;
|
||||||
const ansiGoto = require('../core/ansi_term.js').goto;
|
const ansiGoto = require('../core/ansi_term.js').goto;
|
||||||
const moveFileWithCollisionHandling = require('../core/file_util.js').moveFileWithCollisionHandling;
|
const moveFileWithCollisionHandling = require('../core/file_util.js').moveFileWithCollisionHandling;
|
||||||
const pathWithTerminatingSeparator = require('../core/file_util.js').pathWithTerminatingSeparator;
|
const pathWithTerminatingSeparator = require('../core/file_util.js').pathWithTerminatingSeparator;
|
||||||
|
@ -560,20 +560,21 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
},
|
},
|
||||||
function populateViews(callback) {
|
function populateViews(callback) {
|
||||||
const descView = self.viewControllers.fileDetails.getView(MciViewIds.fileDetails.desc);
|
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)) {
|
if(self.fileEntryHasDetectedDesc(fileEntry)) {
|
||||||
descView.setText(fileEntry.desc);
|
descView.setText(fileEntry.desc);
|
||||||
descView.setPropertyValue('mode', 'preview');
|
descView.setPropertyValue('mode', 'preview');
|
||||||
|
self.viewControllers.fileDetails.switchFocus(MciViewIds.fileDetails.tags);
|
||||||
// :TODO: it would be nice to take this out of the focus order
|
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);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -442,7 +442,7 @@ function fileAreaScan() {
|
||||||
return initConfigAndDatabases(callback);
|
return initConfigAndDatabases(callback);
|
||||||
},
|
},
|
||||||
function getFileArea(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);
|
const areaInfo = fileAreaMod.getFileAreaByTag(argv.scan);
|
||||||
if(!areaInfo) {
|
if(!areaInfo) {
|
||||||
|
|
Loading…
Reference in New Issue