fix(BRIDGE-8): more robust command-line args parser in bridge-gui.
fix(BRIDGE-8): add command-line invocation to log.
This commit is contained in:
parent
bb15efa711
commit
c692c21b87
|
@ -21,9 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func TestFindAndStrip(t *testing.T) {
|
||||
|
@ -31,53 +29,53 @@ func TestFindAndStrip(t *testing.T) {
|
|||
|
||||
result, found := findAndStrip(list, "a")
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"b", "c", "c", "b", "c"}))
|
||||
assert.Equal(t, result, []string{"b", "c", "c", "b", "c"})
|
||||
|
||||
result, found = findAndStrip(list, "c")
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"a", "b", "b"}))
|
||||
assert.Equal(t, result, []string{"a", "b", "b"})
|
||||
|
||||
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{}))
|
||||
assert.Equal(t, result, []string{})
|
||||
|
||||
result, found = findAndStrip(list, "A")
|
||||
assert.False(t, found)
|
||||
assert.True(t, xslices.Equal(result, list))
|
||||
assert.Equal(t, result, list)
|
||||
|
||||
result, found = findAndStrip([]string{}, "a")
|
||||
assert.False(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{}))
|
||||
assert.Equal(t, result, []string{})
|
||||
}
|
||||
|
||||
func TestFindAndStripWait(t *testing.T) {
|
||||
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
||||
assert.False(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
|
||||
assert.True(t, xslices.Equal(values, []string{}))
|
||||
assert.Equal(t, result, []string{"a", "b", "c"})
|
||||
assert.Equal(t, values, []string{})
|
||||
|
||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||
assert.True(t, xslices.Equal(values, []string{"b"}))
|
||||
assert.Equal(t, result, []string{"a"})
|
||||
assert.Equal(t, values, []string{"b"})
|
||||
|
||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
|
||||
assert.Equal(t, result, []string{"a"})
|
||||
assert.Equal(t, values, []string{"b", "c"})
|
||||
|
||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
||||
assert.True(t, found)
|
||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
||||
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
|
||||
assert.Equal(t, result, []string{"a"})
|
||||
assert.Equal(t, values, []string{"b", "c", "d"})
|
||||
}
|
||||
|
||||
func TestAppendOrModifySessionID(t *testing.T) {
|
||||
sessionID := string(logging.NewSessionID())
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID}))
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID}))
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID}))
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID}))
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID}))
|
||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--session-id", "<oldID>", "--cli"}, sessionID), []string{"--session-id", sessionID, "--cli"}))
|
||||
assert.Equal(t, appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID})
|
||||
assert.Equal(t, appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID})
|
||||
assert.Equal(t, appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||
assert.Equal(t, appendOrModifySessionID([]string{"--cli", "--session-id"}, sessionID), []string{"--cli", "--session-id", sessionID})
|
||||
assert.Equal(t, appendOrModifySessionID([]string{"--session-id", "<oldID>", "--cli"}, sessionID), []string{"--session-id", sessionID, "--cli"})
|
||||
}
|
||||
|
|
|
@ -15,113 +15,104 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "Pch.h"
|
||||
#include "CommandLine.h"
|
||||
#include "Settings.h"
|
||||
#include <bridgepp/CLI/CLIUtils.h>
|
||||
#include <bridgepp/SessionID/SessionID.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
QString const launcherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
|
||||
QString const noWindowFlag = "--no-window"; ///< The no-window command-line flag.
|
||||
QString const softwareRendererFlag = "--software-renderer"; ///< The 'software-renderer' command-line flag. enable software rendering for a single execution
|
||||
QString const setSoftwareRendererFlag = "--set-software-renderer"; ///< The 'set-software-renderer' command-line flag. Software rendering will be used for all subsequent executions of the application.
|
||||
QString const setHardwareRendererFlag = "--set-hardware-renderer"; ///< The 'set-hardware-renderer' command-line flag. Hardware rendering will be used for all subsequent executions of the application.
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief parse a command-line string argument as expected by go's CLI package.
|
||||
/// \param[in] argc The number of arguments passed to the application.
|
||||
/// \param[in] argv The list of arguments passed to the application.
|
||||
/// \param[in] paramNames the list of names for the parameter
|
||||
//****************************************************************************************************************************************************
|
||||
QString parseGoCLIStringArgument(int argc, char *argv[], QStringList paramNames) {
|
||||
// go cli package is pretty permissive when it comes to parsing arguments. For each name 'param', all the following seems to be accepted:
|
||||
// -param value
|
||||
// --param value
|
||||
// -param=value
|
||||
// --param=value
|
||||
for (QString const ¶mName: paramNames) {
|
||||
for (qsizetype i = 1; i < argc; ++i) {
|
||||
QString const arg(QString::fromLocal8Bit(argv[i]));
|
||||
if ((i < argc - 1) && ((arg == "-" + paramName) || (arg == "--" + paramName))) {
|
||||
return QString(argv[i + 1]);
|
||||
}
|
||||
|
||||
QRegularExpressionMatch match = QRegularExpression(QString("^-{1,2}%1=(.+)$").arg(paramName)).match(arg);
|
||||
if (match.hasMatch()) {
|
||||
return match.captured(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString const hyphenatedLauncherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
|
||||
QString const hyphenatedWindowFlag = "--no-window"; ///< The no-window command-line flag.
|
||||
QString const hyphenatedSoftwareRendererFlag = "--software-renderer"; ///< The 'software-renderer' command-line flag. enable software rendering for a single execution
|
||||
QString const hyphenatedSetSoftwareRendererFlag = "--set-software-renderer"; ///< The 'set-software-renderer' command-line flag. Software rendering will be used for all subsequent executions of the application.
|
||||
QString const hyphenatedSetHardwareRendererFlag = "--set-hardware-renderer"; ///< The 'set-hardware-renderer' command-line flag. Hardware rendering will be used for all subsequent executions of the application.
|
||||
QString const sessionIDFlag = "session-id";
|
||||
QString const hyphenatedSessionIDFlag = "--" + sessionIDFlag;
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Parse the log level from the command-line arguments.
|
||||
///
|
||||
/// \param[in] argc The number of arguments passed to the application.
|
||||
/// \param[in] argv The list of arguments passed to the application.
|
||||
/// \param[in] args The command-line arguments.
|
||||
/// \return The log level. if not specified on the command-line, the default log level is returned.
|
||||
//****************************************************************************************************************************************************
|
||||
Log::Level parseLogLevel(int argc, char *argv[]) {
|
||||
QString levelStr = parseGoCLIStringArgument(argc, argv, { "l", "log-level" });
|
||||
Log::Level parseLogLevel(QStringList const &args) {
|
||||
QStringList levelStr = parseGoCLIStringArgument(args, {"l", "log-level"});
|
||||
if (levelStr.isEmpty()) {
|
||||
return Log::defaultLevel;
|
||||
}
|
||||
|
||||
Log::Level level = Log::defaultLevel;
|
||||
Log::stringToLevel(levelStr, level);
|
||||
Log::stringToLevel(levelStr.back(), level);
|
||||
return level;
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Return the most recent sessionID parsed in command-line arguments
|
||||
///
|
||||
/// \param[in] args The command-line arguments.
|
||||
/// \return The most recent sessionID in the list. If the list is empty, a new sessionID is created.
|
||||
//****************************************************************************************************************************************************
|
||||
QString mostRecentSessionID(QStringList const& args) {
|
||||
QStringList const sessionIDs = parseGoCLIStringArgument(args, {sessionIDFlag});
|
||||
if (sessionIDs.isEmpty()) {
|
||||
return newSessionID();
|
||||
}
|
||||
|
||||
return std::ranges::max(sessionIDs, [](QString const &lhs, QString const &rhs) -> bool {
|
||||
return sessionIDToDateTime(lhs) < sessionIDToDateTime(rhs);
|
||||
});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc number of arguments passed to the application.
|
||||
/// \param[in] argv list of arguments passed to the application.
|
||||
/// \param[in] argv list of arguments passed to the application, including the exe name/path at index 0.
|
||||
/// \return The parsed options.
|
||||
//****************************************************************************************************************************************************
|
||||
CommandLineOptions parseCommandLine(int argc, char *argv[]) {
|
||||
CommandLineOptions parseCommandLine(QStringList const &argv) {
|
||||
CommandLineOptions options;
|
||||
bool flagFound = false;
|
||||
options.launcher = QString::fromLocal8Bit(argv[0]);
|
||||
bool launcherFlagFound = false;
|
||||
options.launcher = argv[0];
|
||||
// for unknown reasons, on Windows QCoreApplication::arguments() frequently returns an empty list, which is incorrect, so we rebuild the argument
|
||||
// list from the original argc and argv values.
|
||||
for (int i = 1; i < argc; i++) {
|
||||
QString const &arg = QString::fromLocal8Bit(argv[i]);
|
||||
for (int i = 1; i < argv.count(); i++) {
|
||||
QString const &arg = argv[i];
|
||||
// we can't use QCommandLineParser here since it will fail on unknown options.
|
||||
|
||||
// we skip session-id for now we'll process it later, with a special treatment for duplicates
|
||||
if (arg == hyphenatedSessionIDFlag) {
|
||||
i++; // we skip the next param, which if the flag's value.
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith(hyphenatedSessionIDFlag + "=")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Arguments may contain some bridge flags.
|
||||
if (arg == softwareRendererFlag) {
|
||||
if (arg == hyphenatedSoftwareRendererFlag) {
|
||||
options.bridgeGuiArgs.append(arg);
|
||||
options.useSoftwareRenderer = true;
|
||||
}
|
||||
if (arg == setSoftwareRendererFlag) {
|
||||
if (arg == hyphenatedSetSoftwareRendererFlag) {
|
||||
app().settings().setUseSoftwareRenderer(true);
|
||||
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
||||
}
|
||||
if (arg == setHardwareRendererFlag) {
|
||||
if (arg == hyphenatedSetHardwareRendererFlag) {
|
||||
app().settings().setUseSoftwareRenderer(false);
|
||||
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
||||
}
|
||||
if (arg == noWindowFlag) {
|
||||
if (arg == hyphenatedWindowFlag) {
|
||||
options.noWindow = true;
|
||||
}
|
||||
if (arg == launcherFlag) {
|
||||
if (arg == hyphenatedLauncherFlag) {
|
||||
options.bridgeArgs.append(arg);
|
||||
options.launcher = QString::fromLocal8Bit(argv[++i]);
|
||||
options.launcher = argv[++i];
|
||||
options.bridgeArgs.append(options.launcher);
|
||||
flagFound = true;
|
||||
launcherFlagFound = true;
|
||||
}
|
||||
#ifdef QT_DEBUG
|
||||
else if (arg == "--attach" || arg == "-a") {
|
||||
|
@ -135,22 +126,24 @@ CommandLineOptions parseCommandLine(int argc, char *argv[]) {
|
|||
options.bridgeGuiArgs.append(arg);
|
||||
}
|
||||
}
|
||||
if (!flagFound) {
|
||||
if (!launcherFlagFound) {
|
||||
// add bridge-gui as launcher
|
||||
options.bridgeArgs.append(launcherFlag);
|
||||
options.bridgeArgs.append(hyphenatedLauncherFlag);
|
||||
options.bridgeArgs.append(options.launcher);
|
||||
}
|
||||
|
||||
options.logLevel = parseLogLevel(argc, argv);
|
||||
|
||||
QString sessionID = parseGoCLIStringArgument(argc, argv, { "session-id" });
|
||||
if (sessionID.isEmpty()) {
|
||||
// The session ID was not passed to us on the command-line -> create one and add to the command-line for bridge
|
||||
sessionID = newSessionID();
|
||||
options.bridgeArgs.append("--session-id");
|
||||
options.bridgeArgs.append(sessionID);
|
||||
QStringList args;
|
||||
if (!argv.isEmpty()) {
|
||||
args = argv.last(argv.count() - 1);
|
||||
}
|
||||
|
||||
options.logLevel = parseLogLevel(args);
|
||||
|
||||
QString const sessionID = mostRecentSessionID(args);
|
||||
options.bridgeArgs.append(hyphenatedSessionIDFlag);
|
||||
options.bridgeArgs.append(sessionID);
|
||||
app().setSessionID(sessionID);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ struct CommandLineOptions {
|
|||
};
|
||||
|
||||
|
||||
CommandLineOptions parseCommandLine(int argc, char *argv[]); ///< Parse the command-line arguments
|
||||
CommandLineOptions parseCommandLine(QStringList const &argv); ///< Parse the command-line arguments
|
||||
|
||||
|
||||
#endif //BRIDGE_GUI_COMMAND_LINE_H
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "BridgeApp.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "CommandLine.h"
|
||||
|
@ -30,13 +29,12 @@
|
|||
#include <bridgepp/Log/LogUtils.h>
|
||||
#include <bridgepp/ProcessMonitor.h>
|
||||
|
||||
#include "bridgepp/CLI/CLIUtils.h"
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
|
||||
#include "MacOS/SecondInstance.h"
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
using namespace bridgepp;
|
||||
|
@ -50,17 +48,14 @@ QString const exeSuffix = ".exe";
|
|||
QString const exeSuffix;
|
||||
#endif
|
||||
|
||||
|
||||
QString const bridgeLock = "bridge-v3.lock"; ///< The file name used for the bridge-gui lock file.
|
||||
QString const bridgeGUILock = "bridge-v3-gui.lock"; ///< The file name used for the bridge-gui lock file.
|
||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
||||
qint64 const grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
||||
qint64 constexpr grpcServiceConfigWaitDelayMs = 180000; ///< The wait delay for the gRPC config file in milliseconds.
|
||||
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
||||
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path of the bridge executable.
|
||||
/// \return A null string if the executable could not be located.
|
||||
|
@ -70,7 +65,6 @@ QString locateBridgeExe() {
|
|||
return (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) ? fileInfo.absoluteFilePath() : QString();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// // initialize the Qt application.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -97,8 +91,6 @@ void initQtApplication() {
|
|||
#endif // #ifdef Q_OS_MACOS
|
||||
}
|
||||
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] engine The QML component.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -118,13 +110,12 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) {
|
|||
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
|
||||
if (rootComponent->status() != QQmlComponent::Status::Ready) {
|
||||
QString const &err = rootComponent->errorString();
|
||||
app().log().error(err);
|
||||
app().log().error(err);
|
||||
throw Exception("Could not load QML component", err);
|
||||
}
|
||||
return rootComponent;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] lock The lock file to be checked.
|
||||
/// \return True if the lock can be taken, false otherwise.
|
||||
|
@ -155,7 +146,6 @@ bool checkSingleInstance(QLockFile &lock) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return QUrl to reach the bridge API.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -184,7 +174,6 @@ QUrl getApiUrl() {
|
|||
return url;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Check if bridge is running.
|
||||
///
|
||||
|
@ -199,7 +188,6 @@ bool isBridgeRunning() {
|
|||
return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Use api to bring focus on existing bridge instance.
|
||||
//****************************************************************************************************************************************************
|
||||
|
@ -213,8 +201,7 @@ void focusOtherInstance() {
|
|||
if (!sc.load(path)) {
|
||||
throw Exception("The gRPC focus service configuration file is invalid.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw Exception("Server did not provide gRPC Focus service configuration.");
|
||||
}
|
||||
|
||||
|
@ -225,20 +212,18 @@ void focusOtherInstance() {
|
|||
if (!client.raise("focusOtherInstance").ok()) {
|
||||
throw Exception(QString("The raise call to the bridge focus service failed."));
|
||||
}
|
||||
}
|
||||
catch (Exception const &e) {
|
||||
} catch (Exception const &e) {
|
||||
app().log().error(e.qwhat());
|
||||
auto uuid = reportSentryException("Exception occurred during focusOtherInstance()", e);
|
||||
app().log().fatal(QString("reportID: %1 Captured exception: %2").arg(QByteArray(uuid.bytes, 16).toHex(), e.qwhat()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param [in] args list of arguments to pass to bridge.
|
||||
/// \return bridge executable path
|
||||
//****************************************************************************************************************************************************
|
||||
const QString launchBridge(QStringList const &args) {
|
||||
QString launchBridge(QStringList const &args) {
|
||||
UPOverseer &overseer = app().bridgeOverseer();
|
||||
overseer.reset();
|
||||
|
||||
|
@ -251,26 +236,38 @@ const QString launchBridge(QStringList const &args) {
|
|||
}
|
||||
|
||||
qint64 const pid = qApp->applicationPid();
|
||||
QStringList const params = QStringList { "--grpc", "--parent-pid", QString::number(pid) } + args;
|
||||
QStringList const params = QStringList{"--grpc", "--parent-pid", QString::number(pid)} + args;
|
||||
app().log().info(QString("Launching bridge process with command \"%1\" %2").arg(bridgeExePath, params.join(" ")));
|
||||
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, params, nullptr), nullptr);
|
||||
overseer->startWorker(true);
|
||||
return bridgeExePath;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void closeBridgeApp() {
|
||||
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
|
||||
|
||||
UPOverseer &overseer = app().bridgeOverseer();
|
||||
if (overseer) { // A null overseer means the app was run in 'attach' mode. We're not monitoring it.
|
||||
UPOverseer const &overseer = app().bridgeOverseer();
|
||||
if (overseer) {
|
||||
// A null overseer means the app was run in 'attach' mode. We're not monitoring it.
|
||||
// ReSharper disable once CppExpressionWithoutSideEffects
|
||||
overseer->wait(Overseer::maxTerminationWaitTimeMs);
|
||||
}
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argv The command-line argments, including the application name at index 0.
|
||||
//****************************************************************************************************************************************************
|
||||
void logCommandLineInvocation(QStringList argv) {
|
||||
Log &log = app().log();
|
||||
if (argv.isEmpty()) {
|
||||
log.error("The command line is empty");
|
||||
}
|
||||
log.info("bridge-gui executable: " + argv.front());
|
||||
log.info("Command-line invocation: " + (argv.size() > 1 ? argv.last(argv.size() - 1).join(" ") : "<none>"));
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc The number of command-line arguments.
|
||||
|
@ -289,12 +286,11 @@ int main(int argc, char *argv[]) {
|
|||
auto sentryCloser = qScopeGuard([] { sentry_close(); });
|
||||
|
||||
try {
|
||||
QString const& configDir = bridgepp::userConfigDir();
|
||||
|
||||
QString const &configDir = bridgepp::userConfigDir();
|
||||
|
||||
initQtApplication();
|
||||
|
||||
CommandLineOptions const cliOptions = parseCommandLine(argc, argv);
|
||||
QStringList const argvList = cliArgsToStringList(argc, argv);
|
||||
CommandLineOptions const cliOptions = parseCommandLine(argvList);
|
||||
Log &log = initLog();
|
||||
log.setLevel(cliOptions.logLevel);
|
||||
|
||||
|
@ -309,6 +305,8 @@ int main(int argc, char *argv[]) {
|
|||
setDockIconVisibleState(!cliOptions.noWindow);
|
||||
#endif
|
||||
|
||||
logCommandLineInvocation(argvList);
|
||||
|
||||
// In attached mode, we do not intercept stderr and stdout of bridge, as we did not launch it ourselves, so we output the log to the console.
|
||||
// When not in attached mode, log entries are forwarded to bridge, which output it on stdout/stderr. bridge-gui's process monitor intercept
|
||||
// these outputs and output them on the command-line.
|
||||
|
@ -348,7 +346,6 @@ int main(int argc, char *argv[]) {
|
|||
QQuickWindow::setSceneGraphBackend((app().settings().useSoftwareRenderer() || cliOptions.useSoftwareRenderer) ? "software" : "rhi");
|
||||
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
||||
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
||||
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
|
||||
|
@ -374,7 +371,7 @@ int main(int argc, char *argv[]) {
|
|||
app().log().debug(QString("Monitoring Bridge PID : %1").arg(status.pid));
|
||||
|
||||
connection = QObject::connect(bridgeMonitor, &ProcessMonitor::processExited, [&](int returnCode) {
|
||||
bridgeExited = true;// clazy:exclude=lambda-in-connect
|
||||
bridgeExited = true; // clazy:exclude=lambda-in-connect
|
||||
qGuiApp->exit(returnCode);
|
||||
});
|
||||
}
|
||||
|
@ -383,7 +380,7 @@ int main(int argc, char *argv[]) {
|
|||
int result = 0;
|
||||
if (!startError) {
|
||||
// we succeeded in launching bridge, so we can be set as mainExecutable.
|
||||
QString mainexec = QString::fromLocal8Bit(argv[0]);
|
||||
QString const mainexec = argvList[0];
|
||||
app().grpc().setMainExecutable(mainexec);
|
||||
QStringList args = cliOptions.bridgeGuiArgs;
|
||||
args.append(waitFlag);
|
||||
|
@ -412,8 +409,7 @@ int main(int argc, char *argv[]) {
|
|||
// release the lock file
|
||||
lock.unlock();
|
||||
return result;
|
||||
}
|
||||
catch (Exception const &e) {
|
||||
} catch (Exception const &e) {
|
||||
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
||||
QString message = e.qwhat();
|
||||
if (e.showSupportLink()) {
|
||||
|
|
|
@ -23,16 +23,15 @@
|
|||
using namespace bridgepp;
|
||||
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
TEST(CLI, stripStringParameterFromCommandLine) {
|
||||
struct Test {
|
||||
struct TestData {
|
||||
QStringList input;
|
||||
QStringList expectedOutput;
|
||||
};
|
||||
QList<Test> const tests = {
|
||||
QList<TestData> const tests = {
|
||||
{{}, {}},
|
||||
{{ "--a", "-b", "--C" }, { "--a", "-b", "--C" } },
|
||||
{{ "--string", "value" }, {} },
|
||||
|
@ -44,7 +43,36 @@ TEST(CLI, stripStringParameterFromCommandLine) {
|
|||
{{ "--string", "--string", "value", "-b", "--string"}, { "value", "-b" } },
|
||||
};
|
||||
|
||||
for (Test const& test: tests) {
|
||||
for (TestData const& test: tests) {
|
||||
EXPECT_EQ(stripStringParameterFromCommandLine("--string", test.input), test.expectedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(CLI, parseGoCLIStringArgument) {
|
||||
struct TestData {
|
||||
QStringList args;
|
||||
QStringList params;
|
||||
QStringList expectedOutput;
|
||||
};
|
||||
|
||||
QList<TestData> const tests = {
|
||||
{ {}, {}, {} },
|
||||
{ {"-param"}, {"param"}, {} },
|
||||
{ {"--param", "1"}, {"param"}, { "1" } },
|
||||
{ {"--param", "1","p", "-p", "2", "-flag", "-param=3", "--p=4"}, {"param", "p"}, { "1", "2", "3", "4" } },
|
||||
{ {"--param", "--param", "1"}, {"param"}, { "--param" } },
|
||||
};
|
||||
|
||||
for (TestData const& test: tests) {
|
||||
EXPECT_EQ(parseGoCLIStringArgument(test.args, test.params), test.expectedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CLI, cliArgsToStringList) {
|
||||
int constexpr argc = 3;
|
||||
char *argv[] = { const_cast<char *>("1"), const_cast<char *>("2"), const_cast<char *>("3") };
|
||||
QStringList const strList { "1", "2", "3" };
|
||||
EXPECT_EQ(cliArgsToStringList(argc,argv), strList);
|
||||
EXPECT_EQ(cliArgsToStringList(0, nullptr), QStringList {});
|
||||
}
|
||||
|
|
|
@ -42,4 +42,51 @@ QStringList stripStringParameterFromCommandLine(QString const ¶mName, QStrin
|
|||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// The flags may be present more than once in the args. All values are returned in order of appearance.
|
||||
///
|
||||
/// \param[in] args The arguments
|
||||
/// \param[in] paramNames the list of names for the parameter, without any prefix hypen.
|
||||
/// \return The values found for the flag.
|
||||
//****************************************************************************************************************************************************
|
||||
QStringList parseGoCLIStringArgument(QStringList const &args, QStringList const& paramNames) {
|
||||
// go cli package is pretty permissive when it comes to parsing arguments. For each name 'param', all the following seems to be accepted:
|
||||
// -param value
|
||||
// --param value
|
||||
// -param=value
|
||||
// --param=value
|
||||
|
||||
QStringList result;
|
||||
qsizetype const argCount = args.count();
|
||||
for (qsizetype i = 0; i < args.size(); ++i) {
|
||||
for (QString const ¶mName: paramNames) {
|
||||
if ((i < argCount - 1) && ((args[i] == "-" + paramName) || (args[i] == "--" + paramName))) {
|
||||
result.append(args[i + 1]);
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (QRegularExpressionMatch match = QRegularExpression(QString("^-{1,2}%1=(.+)$").arg(paramName)).match(args[i]); match.hasMatch()) {
|
||||
result.append(match.captured(1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc The number of command-line arguments.
|
||||
/// \param[in] argv The list of command-line arguments.
|
||||
/// \return A QStringList representing the arguments list.
|
||||
//****************************************************************************************************************************************************
|
||||
QStringList cliArgsToStringList(int argc, char **argv) {
|
||||
QStringList result;
|
||||
result.reserve(argc);
|
||||
for (qsizetype i = 0; i < argc; ++i) {
|
||||
result.append(QString::fromLocal8Bit(argv[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace bridgepp
|
||||
|
|
|
@ -15,18 +15,15 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGEPP_CLI_UTILS_H
|
||||
#define BRIDGEPP_CLI_UTILS_H
|
||||
|
||||
|
||||
namespace bridgepp {
|
||||
|
||||
|
||||
QStringList stripStringParameterFromCommandLine(QString const ¶mName, QStringList const &commandLineParams); ///< Remove a string parameter from a list of command-line parameters.
|
||||
|
||||
QStringList parseGoCLIStringArgument(QStringList const &args, QStringList const ¶mNames); ///< Parse a command-line string argument as expected by go's CLI package.
|
||||
QStringList cliArgsToStringList(int argc, char **argv); ///< Converts C-style command-line arguments to a string list.
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // BRIDGEPP_CLI_UTILS_H
|
||||
|
|
Loading…
Reference in New Issue