forked from Silverfish/proton-bridge
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:
@ -21,9 +21,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindAndStrip(t *testing.T) {
|
func TestFindAndStrip(t *testing.T) {
|
||||||
@ -31,53 +29,53 @@ func TestFindAndStrip(t *testing.T) {
|
|||||||
|
|
||||||
result, found := findAndStrip(list, "a")
|
result, found := findAndStrip(list, "a")
|
||||||
assert.True(t, found)
|
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")
|
result, found = findAndStrip(list, "c")
|
||||||
assert.True(t, found)
|
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")
|
result, found = findAndStrip([]string{"c", "c", "c"}, "c")
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{}))
|
assert.Equal(t, result, []string{})
|
||||||
|
|
||||||
result, found = findAndStrip(list, "A")
|
result, found = findAndStrip(list, "A")
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, list))
|
assert.Equal(t, result, list)
|
||||||
|
|
||||||
result, found = findAndStrip([]string{}, "a")
|
result, found = findAndStrip([]string{}, "a")
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{}))
|
assert.Equal(t, result, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindAndStripWait(t *testing.T) {
|
func TestFindAndStripWait(t *testing.T) {
|
||||||
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
result, found, values := findAndStripWait([]string{"a", "b", "c"})
|
||||||
assert.False(t, found)
|
assert.False(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a", "b", "c"}))
|
assert.Equal(t, result, []string{"a", "b", "c"})
|
||||||
assert.True(t, xslices.Equal(values, []string{}))
|
assert.Equal(t, values, []string{})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b"}))
|
assert.Equal(t, values, []string{"b"})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b", "c"}))
|
assert.Equal(t, values, []string{"b", "c"})
|
||||||
|
|
||||||
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
result, found, values = findAndStripWait([]string{"a", "--wait", "b", "--wait", "c", "--wait", "d"})
|
||||||
assert.True(t, found)
|
assert.True(t, found)
|
||||||
assert.True(t, xslices.Equal(result, []string{"a"}))
|
assert.Equal(t, result, []string{"a"})
|
||||||
assert.True(t, xslices.Equal(values, []string{"b", "c", "d"}))
|
assert.Equal(t, values, []string{"b", "c", "d"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAppendOrModifySessionID(t *testing.T) {
|
func TestAppendOrModifySessionID(t *testing.T) {
|
||||||
sessionID := string(logging.NewSessionID())
|
sessionID := string(logging.NewSessionID())
|
||||||
assert.True(t, slices.Equal(appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID}))
|
assert.Equal(t, appendOrModifySessionID(nil, sessionID), []string{"--session-id", sessionID})
|
||||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID}))
|
assert.Equal(t, appendOrModifySessionID([]string{}, sessionID), []string{"--session-id", sessionID})
|
||||||
assert.True(t, slices.Equal(appendOrModifySessionID([]string{"--cli"}, sessionID), []string{"--cli", "--session-id", sessionID}))
|
assert.Equal(t, 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.Equal(t, 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.Equal(t, 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([]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
|
// 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/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
#include "Pch.h"
|
#include "Pch.h"
|
||||||
#include "CommandLine.h"
|
#include "CommandLine.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
#include <bridgepp/CLI/CLIUtils.h>
|
||||||
#include <bridgepp/SessionID/SessionID.h>
|
#include <bridgepp/SessionID/SessionID.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
QString const hyphenatedLauncherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
|
||||||
QString const launcherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
|
QString const hyphenatedWindowFlag = "--no-window"; ///< The no-window command-line flag.
|
||||||
QString const noWindowFlag = "--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 softwareRendererFlag = "--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 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 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 setHardwareRendererFlag = "--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 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \brief Parse the log level from the command-line arguments.
|
/// \brief Parse the log level from the command-line arguments.
|
||||||
///
|
///
|
||||||
/// \param[in] argc The number of arguments passed to the application.
|
/// \param[in] args The command-line arguments.
|
||||||
/// \param[in] argv The list of arguments passed to the application.
|
|
||||||
/// \return The log level. if not specified on the command-line, the default log level is returned.
|
/// \return The log level. if not specified on the command-line, the default log level is returned.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
Log::Level parseLogLevel(int argc, char *argv[]) {
|
Log::Level parseLogLevel(QStringList const &args) {
|
||||||
QString levelStr = parseGoCLIStringArgument(argc, argv, { "l", "log-level" });
|
QStringList levelStr = parseGoCLIStringArgument(args, {"l", "log-level"});
|
||||||
if (levelStr.isEmpty()) {
|
if (levelStr.isEmpty()) {
|
||||||
return Log::defaultLevel;
|
return Log::defaultLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::Level level = Log::defaultLevel;
|
Log::Level level = Log::defaultLevel;
|
||||||
Log::stringToLevel(levelStr, level);
|
Log::stringToLevel(levelStr.back(), level);
|
||||||
return 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
|
} // anonymous namespace
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] argc number of arguments passed to the application.
|
/// \param[in] argv list of arguments passed to the application, including the exe name/path at index 0.
|
||||||
/// \param[in] argv list of arguments passed to the application.
|
|
||||||
/// \return The parsed options.
|
/// \return The parsed options.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
CommandLineOptions parseCommandLine(int argc, char *argv[]) {
|
CommandLineOptions parseCommandLine(QStringList const &argv) {
|
||||||
CommandLineOptions options;
|
CommandLineOptions options;
|
||||||
bool flagFound = false;
|
bool launcherFlagFound = false;
|
||||||
options.launcher = QString::fromLocal8Bit(argv[0]);
|
options.launcher = argv[0];
|
||||||
// for unknown reasons, on Windows QCoreApplication::arguments() frequently returns an empty list, which is incorrect, so we rebuild the argument
|
// 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.
|
// list from the original argc and argv values.
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argv.count(); i++) {
|
||||||
QString const &arg = QString::fromLocal8Bit(argv[i]);
|
QString const &arg = argv[i];
|
||||||
// we can't use QCommandLineParser here since it will fail on unknown options.
|
// 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.
|
// Arguments may contain some bridge flags.
|
||||||
if (arg == softwareRendererFlag) {
|
if (arg == hyphenatedSoftwareRendererFlag) {
|
||||||
options.bridgeGuiArgs.append(arg);
|
options.bridgeGuiArgs.append(arg);
|
||||||
options.useSoftwareRenderer = true;
|
options.useSoftwareRenderer = true;
|
||||||
}
|
}
|
||||||
if (arg == setSoftwareRendererFlag) {
|
if (arg == hyphenatedSetSoftwareRendererFlag) {
|
||||||
app().settings().setUseSoftwareRenderer(true);
|
app().settings().setUseSoftwareRenderer(true);
|
||||||
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
||||||
}
|
}
|
||||||
if (arg == setHardwareRendererFlag) {
|
if (arg == hyphenatedSetHardwareRendererFlag) {
|
||||||
app().settings().setUseSoftwareRenderer(false);
|
app().settings().setUseSoftwareRenderer(false);
|
||||||
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
continue; // setting is permanent. no need to keep/pass it to bridge for restart.
|
||||||
}
|
}
|
||||||
if (arg == noWindowFlag) {
|
if (arg == hyphenatedWindowFlag) {
|
||||||
options.noWindow = true;
|
options.noWindow = true;
|
||||||
}
|
}
|
||||||
if (arg == launcherFlag) {
|
if (arg == hyphenatedLauncherFlag) {
|
||||||
options.bridgeArgs.append(arg);
|
options.bridgeArgs.append(arg);
|
||||||
options.launcher = QString::fromLocal8Bit(argv[++i]);
|
options.launcher = argv[++i];
|
||||||
options.bridgeArgs.append(options.launcher);
|
options.bridgeArgs.append(options.launcher);
|
||||||
flagFound = true;
|
launcherFlagFound = true;
|
||||||
}
|
}
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
else if (arg == "--attach" || arg == "-a") {
|
else if (arg == "--attach" || arg == "-a") {
|
||||||
@ -135,22 +126,24 @@ CommandLineOptions parseCommandLine(int argc, char *argv[]) {
|
|||||||
options.bridgeGuiArgs.append(arg);
|
options.bridgeGuiArgs.append(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!flagFound) {
|
if (!launcherFlagFound) {
|
||||||
// add bridge-gui as launcher
|
// add bridge-gui as launcher
|
||||||
options.bridgeArgs.append(launcherFlag);
|
options.bridgeArgs.append(hyphenatedLauncherFlag);
|
||||||
options.bridgeArgs.append(options.launcher);
|
options.bridgeArgs.append(options.launcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.logLevel = parseLogLevel(argc, argv);
|
QStringList args;
|
||||||
|
if (!argv.isEmpty()) {
|
||||||
QString sessionID = parseGoCLIStringArgument(argc, argv, { "session-id" });
|
args = argv.last(argv.count() - 1);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.logLevel = parseLogLevel(args);
|
||||||
|
|
||||||
|
QString const sessionID = mostRecentSessionID(args);
|
||||||
|
options.bridgeArgs.append(hyphenatedSessionIDFlag);
|
||||||
|
options.bridgeArgs.append(sessionID);
|
||||||
app().setSessionID(sessionID);
|
app().setSessionID(sessionID);
|
||||||
|
|
||||||
return options;
|
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
|
#endif //BRIDGE_GUI_COMMAND_LINE_H
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
#include "BridgeApp.h"
|
#include "BridgeApp.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "CommandLine.h"
|
#include "CommandLine.h"
|
||||||
@ -30,13 +29,12 @@
|
|||||||
#include <bridgepp/Log/LogUtils.h>
|
#include <bridgepp/Log/LogUtils.h>
|
||||||
#include <bridgepp/ProcessMonitor.h>
|
#include <bridgepp/ProcessMonitor.h>
|
||||||
|
|
||||||
|
#include "bridgepp/CLI/CLIUtils.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
|
||||||
|
|
||||||
#include "MacOS/SecondInstance.h"
|
#include "MacOS/SecondInstance.h"
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
@ -50,17 +48,14 @@ QString const exeSuffix = ".exe";
|
|||||||
QString const exeSuffix;
|
QString const exeSuffix;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
QString const bridgeLock = "bridge-v3.lock"; ///< The file name used for the bridge-gui lock file.
|
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 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.*
|
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.
|
QString const waitFlag = "--wait"; ///< The wait command-line flag.
|
||||||
|
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \return The path of the bridge executable.
|
/// \return The path of the bridge executable.
|
||||||
/// \return A null string if the executable could not be located.
|
/// \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();
|
return (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) ? fileInfo.absoluteFilePath() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// // initialize the Qt application.
|
/// // initialize the Qt application.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -97,8 +91,6 @@ void initQtApplication() {
|
|||||||
#endif // #ifdef Q_OS_MACOS
|
#endif // #ifdef Q_OS_MACOS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] engine The QML component.
|
/// \param[in] engine The QML component.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -118,13 +110,12 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) {
|
|||||||
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
|
rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml"));
|
||||||
if (rootComponent->status() != QQmlComponent::Status::Ready) {
|
if (rootComponent->status() != QQmlComponent::Status::Ready) {
|
||||||
QString const &err = rootComponent->errorString();
|
QString const &err = rootComponent->errorString();
|
||||||
app().log().error(err);
|
app().log().error(err);
|
||||||
throw Exception("Could not load QML component", err);
|
throw Exception("Could not load QML component", err);
|
||||||
}
|
}
|
||||||
return rootComponent;
|
return rootComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \param[in] lock The lock file to be checked.
|
/// \param[in] lock The lock file to be checked.
|
||||||
/// \return True if the lock can be taken, false otherwise.
|
/// \return True if the lock can be taken, false otherwise.
|
||||||
@ -155,7 +146,6 @@ bool checkSingleInstance(QLockFile &lock) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \return QUrl to reach the bridge API.
|
/// \return QUrl to reach the bridge API.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -184,7 +174,6 @@ QUrl getApiUrl() {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \brief Check if bridge is running.
|
/// \brief Check if bridge is running.
|
||||||
///
|
///
|
||||||
@ -199,7 +188,6 @@ bool isBridgeRunning() {
|
|||||||
return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError);
|
return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \brief Use api to bring focus on existing bridge instance.
|
/// \brief Use api to bring focus on existing bridge instance.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -213,8 +201,7 @@ void focusOtherInstance() {
|
|||||||
if (!sc.load(path)) {
|
if (!sc.load(path)) {
|
||||||
throw Exception("The gRPC focus service configuration file is invalid.");
|
throw Exception("The gRPC focus service configuration file is invalid.");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
throw Exception("Server did not provide gRPC Focus service configuration.");
|
throw Exception("Server did not provide gRPC Focus service configuration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,20 +212,18 @@ void focusOtherInstance() {
|
|||||||
if (!client.raise("focusOtherInstance").ok()) {
|
if (!client.raise("focusOtherInstance").ok()) {
|
||||||
throw Exception(QString("The raise call to the bridge focus service failed."));
|
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());
|
app().log().error(e.qwhat());
|
||||||
auto uuid = reportSentryException("Exception occurred during focusOtherInstance()", e);
|
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()));
|
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.
|
/// \param [in] args list of arguments to pass to bridge.
|
||||||
/// \return bridge executable path
|
/// \return bridge executable path
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
const QString launchBridge(QStringList const &args) {
|
QString launchBridge(QStringList const &args) {
|
||||||
UPOverseer &overseer = app().bridgeOverseer();
|
UPOverseer &overseer = app().bridgeOverseer();
|
||||||
overseer.reset();
|
overseer.reset();
|
||||||
|
|
||||||
@ -251,26 +236,38 @@ const QString launchBridge(QStringList const &args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qint64 const pid = qApp->applicationPid();
|
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(" ")));
|
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 = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, params, nullptr), nullptr);
|
||||||
overseer->startWorker(true);
|
overseer->startWorker(true);
|
||||||
return bridgeExePath;
|
return bridgeExePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void closeBridgeApp() {
|
void closeBridgeApp() {
|
||||||
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
|
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
|
||||||
|
|
||||||
UPOverseer &overseer = app().bridgeOverseer();
|
UPOverseer const &overseer = app().bridgeOverseer();
|
||||||
if (overseer) { // A null overseer means the app was run in 'attach' mode. We're not monitoring it.
|
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);
|
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.
|
/// \param[in] argc The number of command-line arguments.
|
||||||
@ -289,12 +286,11 @@ int main(int argc, char *argv[]) {
|
|||||||
auto sentryCloser = qScopeGuard([] { sentry_close(); });
|
auto sentryCloser = qScopeGuard([] { sentry_close(); });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
QString const& configDir = bridgepp::userConfigDir();
|
QString const &configDir = bridgepp::userConfigDir();
|
||||||
|
|
||||||
|
|
||||||
initQtApplication();
|
initQtApplication();
|
||||||
|
QStringList const argvList = cliArgsToStringList(argc, argv);
|
||||||
CommandLineOptions const cliOptions = parseCommandLine(argc, argv);
|
CommandLineOptions const cliOptions = parseCommandLine(argvList);
|
||||||
Log &log = initLog();
|
Log &log = initLog();
|
||||||
log.setLevel(cliOptions.logLevel);
|
log.setLevel(cliOptions.logLevel);
|
||||||
|
|
||||||
@ -309,6 +305,8 @@ int main(int argc, char *argv[]) {
|
|||||||
setDockIconVisibleState(!cliOptions.noWindow);
|
setDockIconVisibleState(!cliOptions.noWindow);
|
||||||
#endif
|
#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.
|
// 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
|
// 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.
|
// 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");
|
QQuickWindow::setSceneGraphBackend((app().settings().useSoftwareRenderer() || cliOptions.useSoftwareRenderer) ? "software" : "rhi");
|
||||||
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend()));
|
||||||
|
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
||||||
std::unique_ptr<QObject> rootObject(rootComponent->create(engine.rootContext()));
|
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));
|
app().log().debug(QString("Monitoring Bridge PID : %1").arg(status.pid));
|
||||||
|
|
||||||
connection = QObject::connect(bridgeMonitor, &ProcessMonitor::processExited, [&](int returnCode) {
|
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);
|
qGuiApp->exit(returnCode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -383,7 +380,7 @@ int main(int argc, char *argv[]) {
|
|||||||
int result = 0;
|
int result = 0;
|
||||||
if (!startError) {
|
if (!startError) {
|
||||||
// we succeeded in launching bridge, so we can be set as mainExecutable.
|
// 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);
|
app().grpc().setMainExecutable(mainexec);
|
||||||
QStringList args = cliOptions.bridgeGuiArgs;
|
QStringList args = cliOptions.bridgeGuiArgs;
|
||||||
args.append(waitFlag);
|
args.append(waitFlag);
|
||||||
@ -412,8 +409,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// release the lock file
|
// release the lock file
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
return result;
|
return result;
|
||||||
}
|
} catch (Exception const &e) {
|
||||||
catch (Exception const &e) {
|
|
||||||
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
sentry_uuid_s const uuid = reportSentryException("Exception occurred during main", e);
|
||||||
QString message = e.qwhat();
|
QString message = e.qwhat();
|
||||||
if (e.showSupportLink()) {
|
if (e.showSupportLink()) {
|
||||||
|
|||||||
@ -23,16 +23,15 @@
|
|||||||
using namespace bridgepp;
|
using namespace bridgepp;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
TEST(CLI, stripStringParameterFromCommandLine) {
|
TEST(CLI, stripStringParameterFromCommandLine) {
|
||||||
struct Test {
|
struct TestData {
|
||||||
QStringList input;
|
QStringList input;
|
||||||
QStringList expectedOutput;
|
QStringList expectedOutput;
|
||||||
};
|
};
|
||||||
QList<Test> const tests = {
|
QList<TestData> const tests = {
|
||||||
{{}, {}},
|
{{}, {}},
|
||||||
{{ "--a", "-b", "--C" }, { "--a", "-b", "--C" } },
|
{{ "--a", "-b", "--C" }, { "--a", "-b", "--C" } },
|
||||||
{{ "--string", "value" }, {} },
|
{{ "--string", "value" }, {} },
|
||||||
@ -44,7 +43,36 @@ TEST(CLI, stripStringParameterFromCommandLine) {
|
|||||||
{{ "--string", "--string", "value", "-b", "--string"}, { "value", "-b" } },
|
{{ "--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);
|
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
|
} // namespace bridgepp
|
||||||
|
|||||||
@ -15,18 +15,15 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
#ifndef BRIDGEPP_CLI_UTILS_H
|
#ifndef BRIDGEPP_CLI_UTILS_H
|
||||||
#define BRIDGEPP_CLI_UTILS_H
|
#define BRIDGEPP_CLI_UTILS_H
|
||||||
|
|
||||||
|
|
||||||
namespace bridgepp {
|
namespace bridgepp {
|
||||||
|
|
||||||
|
|
||||||
QStringList stripStringParameterFromCommandLine(QString const ¶mName, QStringList const &commandLineParams); ///< Remove a string parameter from a list of command-line parameters.
|
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
|
#endif // BRIDGEPP_CLI_UTILS_H
|
||||||
|
|||||||
Reference in New Issue
Block a user