DESCRIPT.ION support for oputil fb scan

This commit is contained in:
Bryan Ashby 2017-08-24 20:22:50 -06:00
parent f54ae16ce4
commit d47f26004d
3 changed files with 134 additions and 8 deletions

66
core/descript_ion_file.js Normal file
View File

@ -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.
// FILENAME<SPC>DESC<0x04><program data><CR/LF>
//
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);
});
});
}
};

View File

@ -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');

View File

@ -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