Files
2022-06-26 19:14:49 +02:00

401 lines
10 KiB
C++

/* ScummVM Tools
*
* ScummVM Tools is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Common base class for all tools (implementation) */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include "common/file.h"
#include "tool.h"
#include "version.h"
Tool::Tool(const std::string &name, ToolType type) {
_name = name;
_type = type;
_outputToDirectory = true;
_supportsProgressBar = false;
_supportsMultipleRuns = false;
_internalPrint = standardPrint;
_print_udata = NULL;
_internalProgress = standardProgress;
_progress_udata = this;
_internalSubprocess = standardSpawnSubprocess;
_subprocess_udata = NULL;
_abort = false;
_helptext = "\nUsage: tool [-o outputname] <infile>";
}
Tool::~Tool() {
// ...
}
int Tool::run(const std::deque<std::string> &args) {
_arguments = args;
// Pop the first argument (name of ourselves)
_arguments.pop_front();
// Check for help
if (_arguments.empty() || _arguments.front() == "-h" || _arguments.front() == "--help") {
print(getHelp());
return 2;
}
// Check for version
if (_arguments.front() == "--version") {
print(getVersion());
return 2;
}
// Read standard arguments
parseAudioArguments();
parseOutputArguments();
// Read tool specific arguments
parseExtraArguments();
if (!_arguments.empty() && _arguments.front()[0] == '-') {
std::string s = "Possibly ignored option " + _arguments.front() + ".";
print(s);
}
// Make sure we have enough input files.
if (_arguments.size() < _inputPaths.size()) {
print("Too few input files!");
return -2;
}
// Read input files from CLI and match them to expected input.
// First make sure the all the input paths are unset
clearInputPaths();
// Then match the remaining arguments with the input paths.
int nbExpectedInputs = _inputPaths.size();
for (int i = 0 ; i < nbExpectedInputs ; ++i) {
std::string in = _arguments.front();
_arguments.pop_front();
if (!addInputPath(in)) {
print("Unexpected input file '%s'!", in.c_str());
return -2;
}
}
// We should have parsed all arguments by now
if (!_arguments.empty()) {
std::ostringstream os;
os << "Too many inputs files ( ";
while (!_arguments.empty()) {
os << "'" << _arguments.front() << "' ";
_arguments.pop_front();
}
os << ")";
print(os.str());
return -2;
}
if (_inputPaths.empty()) {
// Display help text if we got no input
print(_helptext);
return 2;
}
// Run the tool, with error handling
try {
run();
} catch(ToolException &err) {
const char *what = err.what();
print("Fatal Error : %s", what);
return err._retcode;
}
return 0;
}
void Tool::clearInputPaths() {
for (ToolInputs::iterator iter = _inputPaths.begin(); iter != _inputPaths.end(); ++iter)
iter->path.clear();
}
bool Tool::addInputPath(const std::string& in) {
// Check the input is acceptable. Usually this is done when calling inspectInput(filename, format),
// but inspectInput(filename) might be reimplemented to be more restrictive (especially when the
// format is "*.*").
Common::Filename input(in);
if (inspectInput(input) == IMATCH_AWFUL) {
// The tool may expect a directory as input, but we may be given a file. So if the given path
// is not a directory, and is an awful match, try the directory as well.
if (input.directory())
return false;
input = input.getPath();
if (inspectInput(input) == IMATCH_AWFUL)
return false;
}
// Now we know it matches. Look for the best match.
int bestMatchIndex = -1;
InspectionMatch bestMatch = IMATCH_AWFUL;
for (ToolInputs::iterator iter = _inputPaths.begin(); iter != _inputPaths.end(); ++iter) {
if (!iter->path.empty())
continue;
InspectionMatch match = inspectInput(input, iter->file ? iter->format : std::string("/"));
if (match == IMATCH_PERFECT) {
bestMatch = IMATCH_PERFECT;
bestMatchIndex = (iter - _inputPaths.begin());
break;
} else if (bestMatch == IMATCH_AWFUL && match == IMATCH_POSSIBLE) {
bestMatch = IMATCH_POSSIBLE;
bestMatchIndex = (iter - _inputPaths.begin());
}
}
if (bestMatch == IMATCH_AWFUL) {
return false;
}
_inputPaths[bestMatchIndex].path = input.getFullPath();
if (!_inputPaths[bestMatchIndex].file) {
// Append '/' to input if it's not already done
size_t s = _inputPaths[bestMatchIndex].path.size();
if (_inputPaths[bestMatchIndex].path[s-1] != '/' && _inputPaths[bestMatchIndex].path[s-1] != '\\') {
_inputPaths[bestMatchIndex].path += '/';
}
}
return true;
}
void Tool::run() {
// Reset abort state
_abort = false;
setTempFileName();
// Change output to directory if necessary
if (_outputToDirectory && _outputPath.empty() == false) {
// Ensure last character is a /, this way we force directory output
char lastchr = _outputPath.getFullPath()[_outputPath.getFullPath().size() - 1];
if (lastchr != '/' && lastchr != '\\') {
_outputPath._path += '/';
}
}
execute();
}
InspectionMatch Tool::inspectInput(const Common::Filename &filename) {
InspectionMatch bestMatch = IMATCH_AWFUL;
for (ToolInputs::iterator iter = _inputPaths.begin(); iter != _inputPaths.end(); ++iter) {
InspectionMatch match = inspectInput(filename, iter->file ? iter->format : std::string("/"));
if (match == IMATCH_PERFECT)
return IMATCH_PERFECT;
else if (match == IMATCH_POSSIBLE)
bestMatch = match;
}
// Didn't match any of our inputs
return bestMatch;
}
InspectionMatch Tool::inspectInput(const Common::Filename &filename, const std::string& format) {
// Case were we expect a directory
if (format == "/") {
if (filename.directory())
return IMATCH_POSSIBLE;
return IMATCH_AWFUL;
}
// We expect a file.
// First check this is not a directory.
if (filename.directory())
return IMATCH_AWFUL;
Common::Filename cmp_filename = format;
if (cmp_filename.getName() == "*") {
if (cmp_filename.getExtension() == "*")
// Match anything!
return IMATCH_POSSIBLE;
else if (scumm_stricmp(cmp_filename.getExtension().c_str(), filename.getExtension().c_str()) == 0)
// Extensions are the same
return IMATCH_PERFECT;
} else {
// Match on filename
if (cmp_filename.getName() == filename.getName()) {
if (cmp_filename.getExtension() == "*")
return IMATCH_PERFECT;
else if (scumm_stricmp(cmp_filename.getExtension().c_str(), filename.getExtension().c_str()) == 0)
// Filenames are identical
return IMATCH_PERFECT;
}
}
return IMATCH_AWFUL;
}
void Tool::setPrintFunction(void (*f)(void *, const char *), void *udata) {
_internalPrint = f;
_print_udata = udata;
}
void Tool::setProgressFunction(void (*f)(void *, int, int), void *udata) {
_internalProgress = f;
_progress_udata = udata;
}
void Tool::setSubprocessFunction(int (*f)(void *, const char *), void *udata) {
_internalSubprocess = f;
_subprocess_udata = udata;
}
int Tool::spawnSubprocess(const char *cmd) {
return _internalSubprocess(_subprocess_udata, cmd);
}
void Tool::abort() {
// Set abort safe
// (Non-concurrent) writes are atomic on x86
_abort = true;
}
void Tool::error(const char *format, ...) {
char buf[4096];
va_list va;
va_start(va, format);
vsnprintf(buf, 4096, format, va);
va_end(va);
throw ToolException(std::string(buf) + "\n");
}
void Tool::warning(const char *format, ...) {
char buf[4096];
va_list va;
va_start(va, format);
vsnprintf(buf, 4096, format, va);
va_end(va);
_internalPrint(_print_udata, (std::string("Warning: ") + buf + "\n").c_str());
}
void Tool::print(const char *format, ...) {
char buf[4096] = "";
va_list va;
va_start(va, format);
vsnprintf(buf, 4096, format, va);
va_end(va);
_internalPrint(_print_udata, (std::string(buf) + "\n").c_str());
// We notify of progress here
// This way, almost all tools will be able to exit gracefully (as they print stuff)
notifyProgress(false);
}
void Tool::print(const std::string &msg) {
_internalPrint(_print_udata, (msg + "\n").c_str());
// We notify of progress here
// This way, almost all tools will be able to exit gracefully (as they print stuff)
notifyProgress(false);
}
void Tool::notifyProgress(bool print_dot) {
if (_abort)
throw AbortException();
if (print_dot)
_internalProgress(_progress_udata, 0, 0);
}
void Tool::updateProgress(int done, int total) {
if (_abort)
throw AbortException();
_internalProgress(_progress_udata, done, total);
}
void Tool::parseAudioArguments() {
}
void Tool::setTempFileName() {
}
void Tool::parseOutputArguments() {
if (_arguments.empty())
return;
if (_arguments.front() == "-o" || _arguments.front() == "--output") {
// It's an -o argument
_arguments.pop_front();
if (_arguments.empty())
throw ToolException("Could not parse arguments: Expected path after '-o' or '--output'.");
_outputPath = _arguments.front();
_arguments.pop_front();
}
}
void Tool::parseExtraArguments() {
}
std::string Tool::getName() const {
return _name;
}
std::string Tool::getHelp() const {
return _helptext;
}
std::string Tool::getShortHelp() const {
if (_shorthelp.empty()) {
if (getHelp().size() && getHelp()[0] == '\n')
return getHelp().substr(1);
return getHelp();
}
return _shorthelp;
}
std::string Tool::getVersion() const {
return gScummVMToolsFullVersion;
}
ToolType Tool::getType() const {
return _type;
}
// Standard print function
void Tool::standardPrint(void * /*udata*/, const char *text) {
fputs(text, stdout);
}
// Standard progress function (does nothing)
void Tool::standardProgress(void *udata, int done, int total) {
}
// Standard subprocess function
int Tool::standardSpawnSubprocess(void *udata, const char *cmd) {
return system(cmd);
}