* Create bundle filenames to spec

* Better cp437 vs utf8 vs other encoding support
* Add some CP437 and related utils
This commit is contained in:
Bryan Ashby 2020-05-02 13:34:28 -06:00
parent 1f1813c14a
commit 8817113364
No known key found for this signature in database
GPG key ID: B49EB437951D2542
5 changed files with 191 additions and 25 deletions

View file

@ -11,6 +11,7 @@ const Config = require('./config').get;
const SysProps = require('./system_property');
const UserProps = require('./user_property');
const { numToMbf32 } = require('./mbf');
const { getEncodingFromCharacterSetIdentifier } = require('./ftn_util');
const { EventEmitter } = require('events');
const temptmp = require('temptmp');
@ -823,11 +824,11 @@ class QWKPacketWriter extends EventEmitter {
enableQWKE = true,
enableHeadersExtension = true,
enableAtKludges = true,
encoding = 'cp437',
systemDomain = 'enigma-bbs',
bbsID = '',
user = null,
archiveFormat = 'application/zip',
forceEncoding = null,
} = QWKPacketWriter.DefaultOptions)
{
super();
@ -840,7 +841,7 @@ class QWKPacketWriter extends EventEmitter {
bbsID,
user,
archiveFormat,
encoding : encoding.toLowerCase(),
forceEncoding : forceEncoding ? forceEncoding.toLowerCase() : null,
};
this.temptmp = temptmp.createTrackedSession('qwkpacketwriter');
@ -851,11 +852,11 @@ class QWKPacketWriter extends EventEmitter {
enableQWKE : true,
enableHeadersExtension : true,
enableAtKludges : true,
encoding : 'cp437',
systemDomain : 'enigma-bbs',
bbsID : '',
user : null,
archiveFormat :'application/zip',
forceEncoding : null,
};
}
@ -945,6 +946,8 @@ class QWKPacketWriter extends EventEmitter {
}
} else {
fullMessageBody += `@MSGID: ${this.makeMessageIdentifier(message)}\n`;
fullMessageBody += `@TZ: ${UTCOffsetToSMBTZ[moment().format('Z')]}\n`;
// :TODO: REPLY and REPLYTO
}
}
@ -953,14 +956,16 @@ class QWKPacketWriter extends EventEmitter {
fullMessageBody += `${line}\n`;
});
const encodedMessage = iconv.encode(fullMessageBody, this.options.encoding);
const encoding = this._getEncoding(message);
const encodedMessage = iconv.encode(fullMessageBody, encoding);
//
// QWK spec wants line feeds as 0xe3 for some reason, so we'll have
// to replace the \n's. If we're going against the spec and using UTF-8
// we can just leave them be.
//
if ('utf8' !== this.options.encoding) {
if ('utf8' !== encoding) {
replaceCharInBuffer(encodedMessage, 0x0a, QWKLF);
}
@ -989,7 +994,7 @@ class QWKPacketWriter extends EventEmitter {
this._updateIndexTracking(message);
if (this.options.enableHeadersExtension) {
this._appendHeadersExtensionData(message);
this._appendHeadersExtensionData(message, encoding);
}
// next message starts at this block
@ -999,6 +1004,38 @@ class QWKPacketWriter extends EventEmitter {
this.areaTagsSeen.add(message.areaTag);
}
_getEncoding(message) {
if (this.options.forceEncoding) {
return this.options.forceEncoding;
}
// If the system has stored an explicit encoding, use that.
let encoding = _.get(message.meta, 'System.explicit_encoding');
if (encoding) {
return encoding;
}
// If the message is already tagged with a supported encoding
// indicator such as FTN-style CHRS, try to use that.
encoding = _.get(message.meta, 'FtnKludge.CHRS');
if (encoding) {
// convert from CHRS to something standard
encoding = getEncodingFromCharacterSetIdentifier(encoding);
if (encoding) {
return encoding;
}
}
// The to-spec default is CP437/ASCII. If it can be encoded as
// such then do so.
if (message.isCP437Encodable()) {
return 'cp437';
}
// Something more modern...
return 'utf8';
}
_messageAddressedToUser(message) {
if (_.isUndefined(this.cachedCompareNames)) {
if (this.options.user) {
@ -1041,7 +1078,7 @@ class QWKPacketWriter extends EventEmitter {
}
finish(packetPath) {
finish(packetDirectory) {
async.series(
[
(callback) => {
@ -1066,7 +1103,7 @@ class QWKPacketWriter extends EventEmitter {
return this._createIndexes(callback);
},
(callback) => {
return this._producePacketArchive(packetPath, callback);
return this._producePacketArchive(packetDirectory, callback);
}
],
err => {
@ -1081,7 +1118,44 @@ class QWKPacketWriter extends EventEmitter {
)
}
_producePacketArchive(packetPath, cb) {
_getNextAvailPacketFileName(packetDirectory, cb) {
//
// According to http://wiki.synchro.net/ref:qwk filenames should
// start with .QWK -> .QW1 ... .QW9 -> .Q10 ... .Q99
//
let digits = 0;
async.doWhilst( callback => {
let ext;
if (0 === digits) {
ext = 'QWK';
} else if (digits < 10) {
ext = `QW${digits}`;
} else if (digits < 100) {
ext = `Q${digits}`;
} else {
return callback(Errors.UnexpectedState(`Unable to choose a valid QWK output filename`));
}
++digits;
const filename = `${this.options.bbsID}.${ext}`;
fs.stat(paths.join(packetDirectory, filename), (err, stats) => {
if (err && 'ENOENT' === err.code) {
return callback(null, filename);
} else {
return callback(null, null);
}
});
},
(filename, callback) => {
return callback(null, filename ? false : true);
},
(err, filename) => {
return cb(err, filename);
});
}
_producePacketArchive(packetDirectory, cb) {
const archiveUtil = ArchiveUtil.getInstance();
fs.readdir(this.workDir, (err, files) => {
@ -1089,15 +1163,22 @@ class QWKPacketWriter extends EventEmitter {
return cb(err);
}
archiveUtil.compressTo(
this.options.archiveFormat,
packetPath,
files,
this.workDir,
err => {
this._getNextAvailPacketFileName(packetDirectory, (err, filename) => {
if (err) {
return cb(err);
}
);
const packetPath = paths.join(packetDirectory, filename);
archiveUtil.compressTo(
this.options.archiveFormat,
packetPath,
files,
this.workDir,
err => {
return cb(err);
}
);
});
});
}
@ -1306,10 +1387,10 @@ class QWKPacketWriter extends EventEmitter {
return `${syncTimestamp} ${syncTZ}`;
}
_appendHeadersExtensionData(message) {
_appendHeadersExtensionData(message, encoding) {
const messageData = {
// Synchronet style
Utf8 : ('utf8' === this.options.encoding ? 'true' : 'false'),
Utf8 : ('utf8' === encoding ? 'true' : 'false'),
'Message-ID' : this.makeMessageIdentifier(message),
WhenWritten : this._makeSynchronetTimestamp(message.modTimestamp),
@ -1367,11 +1448,11 @@ class QWKPacketWriter extends EventEmitter {
messageData.Editor = `ENiGMA 1/2 BBS FSE v${enigmaVersion}`;
}
this.headersDatStream.write(iconv.encode(`[${this.currentMessageOffset.toString(16)}]\r\n`, this.options.encoding));
this.headersDatStream.write(iconv.encode(`[${this.currentMessageOffset.toString(16)}]\r\n`, encoding));
for (let [name, value] of Object.entries(messageData)) {
if (value) {
this.headersDatStream.write(iconv.encode(`${name}: ${value}\r\n`, this.options.encoding));
this.headersDatStream.write(iconv.encode(`${name}: ${value}\r\n`, encoding));
}
}