mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-06-07 13:15:28 +02:00
DESCRIPT.ION support for oputil fb scan
This commit is contained in:
parent
f54ae16ce4
commit
d47f26004d
3 changed files with 134 additions and 8 deletions
66
core/descript_ion_file.js
Normal file
66
core/descript_ion_file.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -34,10 +34,25 @@ exports.handleFileBaseCommand = handleFileBaseCommand;
|
||||||
|
|
||||||
let fileArea; // required during init
|
let fileArea; // required during init
|
||||||
|
|
||||||
function finalizeEntryAndPersist(fileEntry, cb) {
|
function finalizeEntryAndPersist(fileEntry, descHandler, cb) {
|
||||||
async.series(
|
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 ) ) {
|
if(false === argv.prompt || ( fileEntry.desc && fileEntry.desc.length > 0 ) ) {
|
||||||
return callback(null);
|
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) {
|
function scanFileAreaForChanges(areaInfo, options, cb) {
|
||||||
|
|
||||||
const storageLocations = fileArea.getAreaStorageLocations(areaInfo).filter(sl => {
|
const storageLocations = fileArea.getAreaStorageLocations(areaInfo).filter(sl => {
|
||||||
|
@ -79,9 +106,18 @@ function scanFileAreaForChanges(areaInfo, options, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
async.eachSeries(storageLocations, (storageLoc, nextLocation) => {
|
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;
|
const physDir = storageLoc.dir;
|
||||||
|
|
||||||
fs.readdir(physDir, (err, files) => {
|
fs.readdir(physDir, (err, files) => {
|
||||||
|
@ -92,6 +128,11 @@ function scanFileAreaForChanges(areaInfo, options, cb) {
|
||||||
async.eachSeries(files, (fileName, nextFile) => {
|
async.eachSeries(files, (fileName, nextFile) => {
|
||||||
const fullPath = paths.join(physDir, fileName);
|
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) => {
|
fs.stat(fullPath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: Log me!
|
// :TODO: Log me!
|
||||||
|
@ -115,9 +156,7 @@ function scanFileAreaForChanges(areaInfo, options, cb) {
|
||||||
// :TODO: Log me!!!
|
// :TODO: Log me!!!
|
||||||
console.info(`Error: ${err.message}`);
|
console.info(`Error: ${err.message}`);
|
||||||
return nextFile(null); // try next anyway
|
return nextFile(null); // try next anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(dupeEntries.length > 0) {
|
if(dupeEntries.length > 0) {
|
||||||
// :TODO: Handle duplidates -- what to do here???
|
// :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);
|
return nextFile(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -304,6 +343,8 @@ function scanFileAreas() {
|
||||||
options.tags = tags.split(',');
|
options.tags = tags.split(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.descFile = argv['desc-file']; // --desc-file or --desc-file PATH
|
||||||
|
|
||||||
options.areaAndStorageInfo = getAreaAndStorage(argv._.slice(2));
|
options.areaAndStorageInfo = getAreaAndStorage(argv._.slice(2));
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
|
@ -311,6 +352,21 @@ function scanFileAreas() {
|
||||||
function init(callback) {
|
function init(callback) {
|
||||||
return initConfigAndDatabases(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) {
|
function scanAreas(callback) {
|
||||||
fileArea = require('../../core/file_base_area.js');
|
fileArea = require('../../core/file_base_area.js');
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,10 @@ actions:
|
||||||
|
|
||||||
scan args:
|
scan args:
|
||||||
--tags TAG1,TAG2,... specify tag(s) to assign to discovered entries
|
--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:
|
info args:
|
||||||
--show-desc display short description, if any
|
--show-desc display short description, if any
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue