DESCRIPT.ION support for oputil fb scan
This commit is contained in:
parent
f54ae16ce4
commit
d47f26004d
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue