From 3a41a6b2e11693aaced21d9af3b498481bba6998 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 23 May 2017 21:55:22 -0600 Subject: [PATCH] fb move FILENAME_WC ... DST support: Allow moving entries via their filenames inc. wildcard support --- core/file_entry.js | 35 ++++++++++++++ core/oputil/oputil_file_base.js | 84 ++++++++++++++++++++------------- core/oputil/oputil_help.js | 29 ++++++++---- 3 files changed, 106 insertions(+), 42 deletions(-) diff --git a/core/file_entry.js b/core/file_entry.js index 49e4b8a3..06fb6fe1 100644 --- a/core/file_entry.js +++ b/core/file_entry.js @@ -353,6 +353,41 @@ module.exports = class FileEntry { ); } + static findByFileNameWildcard(wc, cb) { + // convert any * -> % and ? -> _ for SQLite syntax - see https://www.sqlite.org/lang_expr.html + wc = wc.replace(/\*/g, '%').replace(/\?/g, '_'); + + fileDb.all( + `SELECT file_id + FROM file + WHERE file_name LIKE "${wc}" + `, + (err, fileIdRows) => { + if(err) { + return cb(err); + } + + if(!fileIdRows || 0 === fileIdRows.length) { + return cb(Errors.DoesNotExist('No matches')); + } + + const entries = []; + async.each(fileIdRows, (row, nextRow) => { + const fileEntry = new FileEntry(); + fileEntry.load(row.file_id, err => { + if(!err) { + entries.push(fileEntry); + } + return nextRow(err); + }); + }, + err => { + return cb(err, entries); + }); + } + ); + } + static findFiles(filter, cb) { filter = filter || {}; diff --git a/core/oputil/oputil_file_base.js b/core/oputil/oputil_file_base.js index b67b76e2..9b376e7c 100644 --- a/core/oputil/oputil_file_base.js +++ b/core/oputil/oputil_file_base.js @@ -171,41 +171,42 @@ function dumpAreaInfo(areaInfo, areaAndStorageInfo, cb) { return cb(null); } -function getSpecificFileEntry(pattern, cb) { - // spec: FILE_ID|SHA|PARTIAL_SHA +function getFileEntries(pattern, cb) { + // spec: FILENAME_WC|FILE_ID|SHA|PARTIAL_SHA const FileEntry = require('../../core/file_entry.js'); async.waterfall( [ - function getByFileId(callback) { + function tryByFileId(callback) { const fileId = parseInt(pattern); if(!/^[0-9]+$/.test(pattern) || isNaN(fileId)) { - return callback(null, null); + return callback(null, null); // try SHA } const fileEntry = new FileEntry(); - fileEntry.load(fileId, () => { - return callback(null, fileEntry); // try SHA + fileEntry.load(fileId, err => { + return callback(null, err ? null : [ fileEntry ] ); }); }, - function getBySha(fileEntry, callback) { - if(fileEntry) { - return callback(null, fileEntry); // already got it by SHA + function tryByShaOrPartialSha(entries, callback) { + if(entries) { + return callback(null, entries); // already got it by FILE_ID } FileEntry.findFileBySha(pattern, (err, fileEntry) => { - return callback(null, fileEntry); // try by PATH + return callback(null, fileEntry ? [ fileEntry ] : null ); }); - }/*, - function getByPath(fileEntry, callback) { - if(fileEntry) { - return callback(null, fileEntry); // already got by FILE_ID|SHA - } + }, + function tryByFileNameWildcard(entries, callback) { + if(entries) { + return callback(null, entries); // already got by FILE_ID|SHA + } + + return FileEntry.findByFileNameWildcard(pattern, callback); } - */ ], - (err, fileEntry) => { - return cb(err, fileEntry); + (err, entries) => { + return cb(err, entries); } ); } @@ -214,8 +215,12 @@ function dumpFileInfo(shaOrFileId, cb) { async.waterfall( [ function getEntry(callback) { - getSpecificFileEntry(shaOrFileId, (err, fileEntry) => { - return callback(err, fileEntry); + getFileEntries(shaOrFileId, (err, entries) => { + if(err) { + return callback(err); + } + + return callback(null, entries[0]); }); }, function dumpInfo(fileEntry, callback) { @@ -338,7 +343,7 @@ function moveFiles() { // // oputil fb move SRC [SRC2 ...] DST // - // SRC: PATH|FILE_ID|SHA|AREA_TAG[@STORAGE_TAG] + // SRC: FILENAME_WC|FILE_ID|SHA|AREA_TAG[@STORAGE_TAG] // DST: AREA_TAG[@STORAGE_TAG] // if(argv._.length < 4) { @@ -408,13 +413,14 @@ function moveFiles() { }); } else { - // PATH|FILE_ID|SHA|PARTIAL_SHA - // :TODO: Implement by FILE|PATH support: find first path|file - getSpecificFileEntry(areaAndStorage.pattern, (err, fileEntry) => { + // FILENAME_WC|FILE_ID|SHA|PARTIAL_SHA + // :TODO: FULL_PATH -> entries + getFileEntries(areaAndStorage.pattern, (err, entries) => { if(err) { return next(err); } - srcEntries.push(fileEntry); + + srcEntries = srcEntries.concat(entries); return next(null); }); } @@ -454,18 +460,30 @@ function moveFiles() { ); } +function removeFiles() { + // + // REMOVE SHA|FILE_ID [SHA|FILE_ID ...] +} + function handleFileBaseCommand() { + + function errUsage() { + return printUsageAndSetExitCode( + getHelpFor('FileBase') + getHelpFor('FileOpsInfo'), + ExitCodes.ERROR + ); + } + if(true === argv.help) { - return printUsageAndSetExitCode(getHelpFor('FileBase'), ExitCodes.ERROR); + return errUsage(); } const action = argv._[1]; - switch(action) { - case 'info' : return displayFileAreaInfo(); - case 'scan' : return scanFileAreas(); - case 'move' : return moveFiles(); - - default : return printUsageAndSetExitCode(getHelpFor('FileBase'), ExitCodes.ERROR); - } + return ({ + info : displayFileAreaInfo, + scan : scanFileAreas, + move : moveFiles, + remove : removeFiles, + }[action] || errUsage)(); } \ No newline at end of file diff --git a/core/oputil/oputil_help.js b/core/oputil/oputil_help.js index ebbc970e..0344145b 100644 --- a/core/oputil/oputil_help.js +++ b/core/oputil/oputil_help.js @@ -9,7 +9,7 @@ exports.getHelpFor = getHelpFor; const usageHelp = exports.USAGE_HELP = { General : `usage: optutil.js [--version] [--help] - [] + [] global args: -c, --config PATH specify config path (${getDefaultConfigPath()}) @@ -19,7 +19,6 @@ commands: user user utilities config config file management fb file base management - `, User : `usage: optutil.js user --user USERNAME @@ -49,23 +48,35 @@ import-areas args: `usage: oputil.js fb [] [] actions: - scan AREA_TAG scan specified areas - AREA_TAG may be suffixed with @STORAGE_TAG; for example: retro@bbs + scan AREA_TAG[@STORAGE_TAG] scan specified area info AREA_TAG|SHA|FILE_ID display information about areas and/or files SHA may be a full or partial SHA-256 - move SRC DST move entry(s) from SRC to DST where: - SRC may be FILE_ID|SHA|AREA_TAG - DST may be AREA_TAG, optionally suffixed with @STORAGE_TAG; for example: retro@bbs - SHA may be a full or partial SHA-256 - multiple instances of SRC may exist: SRC1 SRC2 ... + move SRC [SRC...]] DST move entry(s) from SRC to DST + * SRC: FILENAME_WC|SHA|FILE_ID|AREA_TAG[@STORAGE_TAG] + * DST: AREA_TAG[@STORAGE_TAG] + + remove SHA|FILE_ID removes a entry from the system scan args: --tags TAG1,TAG2,... specify tag(s) to assign to discovered entries info args: --show-desc display short description, if any + +remove args: + --delete also remove underlying physical file +`, + FileOpsInfo : +` +general information: + AREA_TAG[@STORAGE_TAG] can specify an area tag and optionally, a storage specific tag + example: retro@bbs + + FILENAME_WC filename with * and ? wildcard support. may match 0:n entries + SHA full or partial SHA-256 + FILE_ID a file identifier. see file.sqlite3 ` };