fb move FILENAME_WC ... DST support: Allow moving entries via their filenames inc. wildcard support
This commit is contained in:
parent
1c92b349cd
commit
3a41a6b2e1
|
@ -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 || {};
|
||||||
|
|
||||||
|
|
|
@ -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)();
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue