fb move FILENAME_WC ... DST support: Allow moving entries via their filenames inc. wildcard support

This commit is contained in:
Bryan Ashby 2017-05-23 21:55:22 -06:00
parent 1c92b349cd
commit 3a41a6b2e1
3 changed files with 106 additions and 42 deletions

View File

@ -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) { static findFiles(filter, cb) {
filter = filter || {}; filter = filter || {};

View File

@ -171,41 +171,42 @@ function dumpAreaInfo(areaInfo, areaAndStorageInfo, cb) {
return cb(null); return cb(null);
} }
function getSpecificFileEntry(pattern, cb) { function getFileEntries(pattern, cb) {
// spec: FILE_ID|SHA|PARTIAL_SHA // spec: FILENAME_WC|FILE_ID|SHA|PARTIAL_SHA
const FileEntry = require('../../core/file_entry.js'); const FileEntry = require('../../core/file_entry.js');
async.waterfall( async.waterfall(
[ [
function getByFileId(callback) { function tryByFileId(callback) {
const fileId = parseInt(pattern); const fileId = parseInt(pattern);
if(!/^[0-9]+$/.test(pattern) || isNaN(fileId)) { if(!/^[0-9]+$/.test(pattern) || isNaN(fileId)) {
return callback(null, null); return callback(null, null); // try SHA
} }
const fileEntry = new FileEntry(); const fileEntry = new FileEntry();
fileEntry.load(fileId, () => { fileEntry.load(fileId, err => {
return callback(null, fileEntry); // try SHA return callback(null, err ? null : [ fileEntry ] );
}); });
}, },
function getBySha(fileEntry, callback) { function tryByShaOrPartialSha(entries, callback) {
if(fileEntry) { if(entries) {
return callback(null, fileEntry); // already got it by SHA return callback(null, entries); // already got it by FILE_ID
} }
FileEntry.findFileBySha(pattern, (err, fileEntry) => { FileEntry.findFileBySha(pattern, (err, fileEntry) => {
return callback(null, fileEntry); // try by PATH return callback(null, fileEntry ? [ fileEntry ] : null );
}); });
}/*, },
function getByPath(fileEntry, callback) { function tryByFileNameWildcard(entries, callback) {
if(fileEntry) { if(entries) {
return callback(null, fileEntry); // already got by FILE_ID|SHA return callback(null, entries); // already got by FILE_ID|SHA
} }
return FileEntry.findByFileNameWildcard(pattern, callback);
} }
*/
], ],
(err, fileEntry) => { (err, entries) => {
return cb(err, fileEntry); return cb(err, entries);
} }
); );
} }
@ -214,8 +215,12 @@ function dumpFileInfo(shaOrFileId, cb) {
async.waterfall( async.waterfall(
[ [
function getEntry(callback) { function getEntry(callback) {
getSpecificFileEntry(shaOrFileId, (err, fileEntry) => { getFileEntries(shaOrFileId, (err, entries) => {
return callback(err, fileEntry); if(err) {
return callback(err);
}
return callback(null, entries[0]);
}); });
}, },
function dumpInfo(fileEntry, callback) { function dumpInfo(fileEntry, callback) {
@ -338,7 +343,7 @@ function moveFiles() {
// //
// oputil fb move SRC [SRC2 ...] DST // 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] // DST: AREA_TAG[@STORAGE_TAG]
// //
if(argv._.length < 4) { if(argv._.length < 4) {
@ -408,13 +413,14 @@ function moveFiles() {
}); });
} else { } else {
// PATH|FILE_ID|SHA|PARTIAL_SHA // FILENAME_WC|FILE_ID|SHA|PARTIAL_SHA
// :TODO: Implement by FILE|PATH support: find first path|file // :TODO: FULL_PATH -> entries
getSpecificFileEntry(areaAndStorage.pattern, (err, fileEntry) => { getFileEntries(areaAndStorage.pattern, (err, entries) => {
if(err) { if(err) {
return next(err); return next(err);
} }
srcEntries.push(fileEntry);
srcEntries = srcEntries.concat(entries);
return next(null); return next(null);
}); });
} }
@ -454,18 +460,30 @@ function moveFiles() {
); );
} }
function removeFiles() {
//
// REMOVE SHA|FILE_ID [SHA|FILE_ID ...]
}
function handleFileBaseCommand() { function handleFileBaseCommand() {
function errUsage() {
return printUsageAndSetExitCode(
getHelpFor('FileBase') + getHelpFor('FileOpsInfo'),
ExitCodes.ERROR
);
}
if(true === argv.help) { if(true === argv.help) {
return printUsageAndSetExitCode(getHelpFor('FileBase'), ExitCodes.ERROR); return errUsage();
} }
const action = argv._[1]; const action = argv._[1];
switch(action) { return ({
case 'info' : return displayFileAreaInfo(); info : displayFileAreaInfo,
case 'scan' : return scanFileAreas(); scan : scanFileAreas,
case 'move' : return moveFiles(); move : moveFiles,
remove : removeFiles,
default : return printUsageAndSetExitCode(getHelpFor('FileBase'), ExitCodes.ERROR); }[action] || errUsage)();
}
} }

View File

@ -9,7 +9,7 @@ exports.getHelpFor = getHelpFor;
const usageHelp = exports.USAGE_HELP = { const usageHelp = exports.USAGE_HELP = {
General : General :
`usage: optutil.js [--version] [--help] `usage: optutil.js [--version] [--help]
<command> [<args>] <command> [<args>]
global args: global args:
-c, --config PATH specify config path (${getDefaultConfigPath()}) -c, --config PATH specify config path (${getDefaultConfigPath()})
@ -19,7 +19,6 @@ commands:
user user utilities user user utilities
config config file management config config file management
fb file base management fb file base management
`, `,
User : User :
`usage: optutil.js user --user USERNAME <args> `usage: optutil.js user --user USERNAME <args>
@ -49,23 +48,35 @@ import-areas args:
`usage: oputil.js fb <action> [<args>] <AREA_TAG|SHA|FILE_ID[@STORAGE_TAG] ...> [<args>] `usage: oputil.js fb <action> [<args>] <AREA_TAG|SHA|FILE_ID[@STORAGE_TAG] ...> [<args>]
actions: actions:
scan AREA_TAG scan specified areas scan AREA_TAG[@STORAGE_TAG] scan specified area
AREA_TAG may be suffixed with @STORAGE_TAG; for example: retro@bbs
info AREA_TAG|SHA|FILE_ID display information about areas and/or files info AREA_TAG|SHA|FILE_ID display information about areas and/or files
SHA may be a full or partial SHA-256 SHA may be a full or partial SHA-256
move SRC DST move entry(s) from SRC to DST where: move SRC [SRC...]] DST move entry(s) from SRC to DST
SRC may be FILE_ID|SHA|AREA_TAG * SRC: FILENAME_WC|SHA|FILE_ID|AREA_TAG[@STORAGE_TAG]
DST may be AREA_TAG, optionally suffixed with @STORAGE_TAG; for example: retro@bbs * DST: AREA_TAG[@STORAGE_TAG]
SHA may be a full or partial SHA-256
multiple instances of SRC may exist: SRC1 SRC2 ... remove SHA|FILE_ID removes a entry from the system
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
info args: info args:
--show-desc display short description, if any --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
` `
}; };