From d47f26004d40c8a977f4b9ed00f42cf64f0bcddc Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Thu, 24 Aug 2017 20:22:50 -0600 Subject: [PATCH] DESCRIPT.ION support for oputil fb scan --- core/descript_ion_file.js | 66 ++++++++++++++++++++++++++++++ core/oputil/oputil_file_base.js | 72 +++++++++++++++++++++++++++++---- core/oputil/oputil_help.js | 4 ++ 3 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 core/descript_ion_file.js diff --git a/core/descript_ion_file.js b/core/descript_ion_file.js new file mode 100644 index 00000000..9d19e76f --- /dev/null +++ b/core/descript_ion_file.js @@ -0,0 +1,66 @@ +/* jslint node: true */ +'use strict'; + +// deps +const fs = require('graceful-fs'); +const iconv = require('iconv-lite'); +const async = require('async'); + +module.exports = class DescriptIonFile { + constructor() { + this.entries = new Map(); + } + + get(fileName) { + return this.entries.get(fileName); + } + + getDescription(fileName) { + const entry = this.get(fileName); + if(entry) { + return entry.desc; + } + } + + static createFromFile(path, cb) { + fs.readFile(path, (err, descData) => { + if(err) { + return cb(err); + } + + const descIonFile = new DescriptIonFile(); + + // DESCRIPT.ION entries are terminated with a CR and/or LF + const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g); + + async.each(lines, (entryData, nextLine) => { + // + // We allow quoted (long) filenames or non-quoted filenames. + // FILENAMEDESC<0x04> + // + const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex + if(!parts) { + return nextLine(null); + } + + const fileName = parts[1] || parts[2]; + const desc = parts[3].replace(/\\r\\n|\\n/g, '\r\n'); // un-escape CR/LF's + + descIonFile.entries.set( + fileName, + { + desc : desc, + programId : parts[4], + programData : parts[5], + } + ); + + return nextLine(null); + }, + () => { + return cb(null, descIonFile); + }); + }); + } +}; + diff --git a/core/oputil/oputil_file_base.js b/core/oputil/oputil_file_base.js index 9b376e7c..a1cfd345 100644 --- a/core/oputil/oputil_file_base.js +++ b/core/oputil/oputil_file_base.js @@ -34,10 +34,25 @@ exports.handleFileBaseCommand = handleFileBaseCommand; let fileArea; // required during init -function finalizeEntryAndPersist(fileEntry, cb) { +function finalizeEntryAndPersist(fileEntry, descHandler, cb) { async.series( [ - function getDescIfNeeded(callback) { + function getDescFromHandlerIfNeeded(callback) { + if((fileEntry.desc && fileEntry.desc.length > 0 ) && !argv['desc-file']) { + return callback(null); // we have a desc already and are NOT overriding with desc file + } + + if(!descHandler) { + return callback(null); // not much we can do! + } + + const desc = descHandler.getDescription(fileEntry.fileName); + if(desc) { + fileEntry.desc = desc; + } + return callback(null); + }, + function getDescFromUserIfNeeded(callback) { if(false === argv.prompt || ( fileEntry.desc && fileEntry.desc.length > 0 ) ) { return callback(null); } @@ -70,6 +85,18 @@ function finalizeEntryAndPersist(fileEntry, cb) { ); } +const SCAN_IGNORE_FILENAMES = [ 'DESCRIPT.ION', 'FILES.BBS' ]; + +function loadDescHandler(path, cb) { + const DescIon = require('../../core/descript_ion_file.js'); + + // :TODO: support FILES.BBS also + + DescIon.createFromFile(path, (err, descHandler) => { + return cb(err, descHandler); + }); +} + function scanFileAreaForChanges(areaInfo, options, cb) { const storageLocations = fileArea.getAreaStorageLocations(areaInfo).filter(sl => { @@ -79,9 +106,18 @@ function scanFileAreaForChanges(areaInfo, options, cb) { }); async.eachSeries(storageLocations, (storageLoc, nextLocation) => { - async.series( + async.waterfall( [ - function scanPhysFiles(callback) { + function initDescFile(callback) { + if(options.descFileHandler) { + return callback(null, options.descFileHandler); // we're going to use the global handler + } + + loadDescHandler(paths.join(storageLoc.dir, 'DESCRIPT.ION'), (err, descHandler) => { + return callback(null, descHandler); + }); + }, + function scanPhysFiles(descHandler, callback) { const physDir = storageLoc.dir; fs.readdir(physDir, (err, files) => { @@ -92,6 +128,11 @@ function scanFileAreaForChanges(areaInfo, options, cb) { async.eachSeries(files, (fileName, nextFile) => { const fullPath = paths.join(physDir, fileName); + if(SCAN_IGNORE_FILENAMES.includes(fileName.toUpperCase())) { + process.stdout.write(`Ignoring ${fullPath}`); + return nextFile(null); + } + fs.stat(fullPath, (err, stats) => { if(err) { // :TODO: Log me! @@ -115,9 +156,7 @@ function scanFileAreaForChanges(areaInfo, options, cb) { // :TODO: Log me!!! console.info(`Error: ${err.message}`); return nextFile(null); // try next anyway - } - - + } if(dupeEntries.length > 0) { // :TODO: Handle duplidates -- what to do here??? @@ -131,7 +170,7 @@ function scanFileAreaForChanges(areaInfo, options, cb) { }); } - finalizeEntryAndPersist(fileEntry, err => { + finalizeEntryAndPersist(fileEntry, descHandler, err => { return nextFile(err); }); } @@ -304,6 +343,8 @@ function scanFileAreas() { options.tags = tags.split(','); } + options.descFile = argv['desc-file']; // --desc-file or --desc-file PATH + options.areaAndStorageInfo = getAreaAndStorage(argv._.slice(2)); async.series( @@ -311,6 +352,21 @@ function scanFileAreas() { function init(callback) { return initConfigAndDatabases(callback); }, + function initGlobalDescHandler(callback) { + // + // If options.descFile is a String, it represents a FILE|PATH. We'll init + // the description handler now. Else, we'll attempt to look for a description + // file in each storage location. + // + if(!_.isString(options.descFile)) { + return callback(null); + } + + loadDescHandler(options.descFile, (err, descHandler) => { + options.descFileHandler = descHandler; + return callback(null); + }); + }, function scanAreas(callback) { fileArea = require('../../core/file_base_area.js'); diff --git a/core/oputil/oputil_help.js b/core/oputil/oputil_help.js index 0344145b..5c20e3a5 100644 --- a/core/oputil/oputil_help.js +++ b/core/oputil/oputil_help.js @@ -61,6 +61,10 @@ actions: scan args: --tags TAG1,TAG2,... specify tag(s) to assign to discovered entries + --desc-file [PATH] prefer file descriptions from DESCRIPT.ION file over + other sources such as FILE_ID.DIZ. + if PATH is specified, use DESCRIPT.ION at PATH instead + of looking in specific storage locations info args: --show-desc display short description, if any