GODT-1667: bridge-gui spawns bridge process. [skip-ci]
Other: renaming of bridge-gui. WIP: locate bridge exe. WIP: bridge process launch. WIP: cleaner closure of bridge. WIP: grpcClient connection retries. WIP: clean exit when bridge process is killed. Fixed issues from MR review. [skip-ci]. WIP: Fixed gRPC case in CMakelists.txt [skip-ci] It caused issues on Debian. WIP: update gRPC/protobuf and tweaked CMakeLists.txt. [skip-ci] WIP: Fixed a bug where splash screen could not be dismissed. [skip-ci]
@ -21,6 +21,8 @@
|
||||
#include "QMLBackend.h"
|
||||
#include "GRPC/GRPCClient.h"
|
||||
#include "Log.h"
|
||||
#include "BridgeMonitor.h"
|
||||
#include "Exception.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
@ -43,3 +45,23 @@ AppController::AppController()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The bridge worker, which can be null if the application was run in 'attach' mode (-a command-line switch).
|
||||
//****************************************************************************************************************************************************
|
||||
BridgeMonitor *AppController::bridgeMonitor() const
|
||||
{
|
||||
if (!bridgeOverseer_)
|
||||
return nullptr;
|
||||
|
||||
// null bridgeOverseer is OK, it means we run in 'attached' mode (app attached to an already runnning instance of Bridge).
|
||||
// but if bridgeOverseer is not null, its attached worker must be a valid BridgeMonitor instance.
|
||||
auto *monitor = dynamic_cast<BridgeMonitor*>(bridgeOverseer_->worker());
|
||||
if (!monitor)
|
||||
throw Exception("Could not retrieve bridge monitor");
|
||||
|
||||
return monitor;
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,8 @@
|
||||
class QMLBackend;
|
||||
class GRPCClient;
|
||||
class Log;
|
||||
|
||||
class Overseer;
|
||||
class BridgeMonitor;
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief App controller class.
|
||||
@ -42,6 +43,8 @@ public: // member functions.
|
||||
QMLBackend& backend() { return *backend_; } ///< Return a reference to the backend.
|
||||
GRPCClient& grpc() { return *grpc_; } ///< Return a reference to the GRPC client.
|
||||
Log& log() { return *log_; } ///< Return a reference to the log.
|
||||
std::unique_ptr<Overseer>& bridgeOverseer() { return bridgeOverseer_; }; ///< Returns a reference the bridge overseer
|
||||
BridgeMonitor* bridgeMonitor() const; ///< Return the bridge worker.
|
||||
|
||||
private: // member functions
|
||||
AppController(); ///< Default constructor.
|
||||
@ -50,6 +53,7 @@ private: // data members
|
||||
std::unique_ptr<QMLBackend> backend_; ///< The backend.
|
||||
std::unique_ptr<GRPCClient> grpc_; ///< The RPC client.
|
||||
std::unique_ptr<Log> log_; ///< The log.
|
||||
std::unique_ptr<Overseer> bridgeOverseer_; ///< The overseer for the bridge monitor worker.
|
||||
};
|
||||
|
||||
|
||||
110
internal/frontend/bridge-gui/BridgeMonitor.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge 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.
|
||||
//
|
||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "BridgeMonitor.h"
|
||||
#include "Exception.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
|
||||
/// \brief The file extension for the bridge executable file.
|
||||
#ifdef Q_OS_WIN32
|
||||
QString const exeSuffix = ".exe";
|
||||
#else
|
||||
QString const exeSuffix;
|
||||
#endif
|
||||
|
||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.
|
||||
QString const devDir = "cmd/Desktop-Bridge"; ///< The folder typically containg the bridge executable in a developer's environment.
|
||||
int const maxExeUpwardSeekingDepth = 5; ///< The maximum number of parent folder that will searched when trying to locate the bridge executable.
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path of the bridge executable.
|
||||
/// \return A null string if the executable could not be located.
|
||||
//****************************************************************************************************************************************************
|
||||
QString BridgeMonitor::locateBridgeExe()
|
||||
{
|
||||
QString const currentDir = QDir::current().absolutePath();
|
||||
QString const exeDir = QCoreApplication::applicationDirPath();
|
||||
QStringList dirs = {currentDir, exeDir};
|
||||
for (int i = 0; i <= maxExeUpwardSeekingDepth; ++i)
|
||||
{
|
||||
dirs.append(currentDir + QString("../").repeated(i) + devDir);
|
||||
dirs.append(exeDir + QString("../").repeated(i) + devDir);
|
||||
}
|
||||
|
||||
for (QString const &dir: dirs)
|
||||
{
|
||||
QFileInfo const fileInfo = QDir(dir).absoluteFilePath(exeName);
|
||||
if (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable())
|
||||
return fileInfo.absoluteFilePath();
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] exePath The path of the Bridge executable.
|
||||
/// \param[in] parent The parent object of the worker.
|
||||
//****************************************************************************************************************************************************
|
||||
BridgeMonitor::BridgeMonitor(QString const &exePath, QObject *parent)
|
||||
: Worker(parent)
|
||||
, exePath_(exePath)
|
||||
{
|
||||
QFileInfo fileInfo(exePath);
|
||||
if (!fileInfo.exists())
|
||||
throw Exception("Could not locate Bridge executable.");
|
||||
if ((!fileInfo.isFile()) || (!fileInfo.isExecutable()))
|
||||
throw Exception("Invalid bridge executable");
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void BridgeMonitor::run()
|
||||
{
|
||||
try
|
||||
{
|
||||
emit started();
|
||||
|
||||
QProcess p;
|
||||
p.start(exePath_);
|
||||
p.waitForStarted();
|
||||
|
||||
while (!p.waitForFinished(100))
|
||||
{
|
||||
// we discard output from bridge, it's logged to file on bridge side.
|
||||
p.readAllStandardError();
|
||||
p.readAllStandardOutput();
|
||||
}
|
||||
emit processExited(p.exitCode());
|
||||
emit finished();
|
||||
}
|
||||
catch (Exception const &e)
|
||||
{
|
||||
emit error(e.qwhat());
|
||||
}
|
||||
}
|
||||
54
internal/frontend/bridge-gui/BridgeMonitor.h
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge 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.
|
||||
//
|
||||
// Proton Mail Bridge 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
|
||||
#ifndef BRIDGE_GUI_BRIDGE_MONITOR_H
|
||||
#define BRIDGE_GUI_BRIDGE_MONITOR_H
|
||||
|
||||
|
||||
#include "Worker/Worker.h"
|
||||
|
||||
|
||||
//**********************************************************************************************************************
|
||||
/// \brief Bridge process launcher and monitor class.
|
||||
//**********************************************************************************************************************
|
||||
class BridgeMonitor: public Worker
|
||||
{
|
||||
Q_OBJECT
|
||||
public: // static member functions
|
||||
static QString locateBridgeExe(); ///< Try to find the bridge executable path.
|
||||
|
||||
public: // member functions.
|
||||
BridgeMonitor(QString const& exePath, QObject *parent); ///< Default constructor.
|
||||
BridgeMonitor(BridgeMonitor const&) = delete; ///< Disabled copy-constructor.
|
||||
BridgeMonitor(BridgeMonitor&&) = delete; ///< Disabled assignment copy-constructor.
|
||||
~BridgeMonitor() override = default; ///< Destructor.
|
||||
BridgeMonitor& operator=(BridgeMonitor const&) = delete; ///< Disabled assignment operator.
|
||||
BridgeMonitor& operator=(BridgeMonitor&&) = delete; ///< Disabled move assignment operator.
|
||||
void run() override; ///< Run the worker.
|
||||
|
||||
signals:
|
||||
void processExited(int code); ///< Slot for the exiting of the process
|
||||
|
||||
private: // data members
|
||||
QString const exePath_; ///< The path to the bridge executable.
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // BRIDGE_GUI_BRIDGE_MONITOR_H
|
||||
@ -32,7 +32,7 @@ endif()
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "toolchain")
|
||||
|
||||
|
||||
project(bridge_qt6 LANGUAGES CXX)
|
||||
project(bridge-gui LANGUAGES CXX)
|
||||
if (APPLE) # On macOS, we have some Objective-C++ code in DockIcon to deal with ... the dock icon.
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
@ -52,12 +52,12 @@ find_package(Protobuf CONFIG REQUIRED)
|
||||
message(STATUS "Using protobuf ${Protobuf_VERSION}")
|
||||
|
||||
|
||||
find_package(grpc CONFIG REQUIRED)
|
||||
find_package(gRPC CONFIG REQUIRED)
|
||||
message(STATUS "Using gRPC ${gRPC_VERSION}")
|
||||
|
||||
|
||||
if (APPLE) # We need to link the Cocoa framework for the dock icon.
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
find_library(COCOA_LIBRARY Cocoa REQ UIRED)
|
||||
endif()
|
||||
|
||||
|
||||
@ -67,9 +67,20 @@ find_package(Qt5 COMPONENTS
|
||||
Qml
|
||||
QuickControls2
|
||||
REQUIRED)
|
||||
message(STATUS "Using Qt ${Qt5_VERSION}")
|
||||
|
||||
find_program(PROTOC_EXE protoc REQUIRED)
|
||||
message(STATUS "protoc found ${PROTOC_EXE}")
|
||||
|
||||
message(STATUS "grpc_cpp_plugin ${grpc_cpp_plugin}")
|
||||
|
||||
find_program(PROTOC_EXE protoc)
|
||||
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
|
||||
if(GRPC_CPP_PLUGIN STREQUAL GRPC_CPP_PLUGIN-NOTFOUND)
|
||||
message(FATAL_ERROR "grpc_cpp_plugin exe could not be found. Please add it to your path. it should be located in \${VCPKG_ROOT}/installed/arm64-osx/tools/grpc")
|
||||
else()
|
||||
message(STATUS "grpc_cpp_plugin found at ${GRPC_CPP_PLUGIN}")
|
||||
endif()
|
||||
|
||||
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../grpc")
|
||||
set(PROTO_FILE "${PROTO_DIR}/bridge.proto")
|
||||
set(GRPC_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/GRPC")
|
||||
@ -102,10 +113,11 @@ add_custom_command(
|
||||
COMMENT "Generating gPRC/Protobuf C++ code"
|
||||
)
|
||||
|
||||
add_executable(bridge_qt6
|
||||
add_executable(bridge-gui
|
||||
Resources.qrc
|
||||
${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE}
|
||||
AppController.cpp AppController.h
|
||||
BridgeMonitor.cpp BridgeMonitor.h
|
||||
EventStreamWorker.cpp EventStreamWorker.h
|
||||
Exception.cpp Exception.h
|
||||
Log.cpp Log.h
|
||||
@ -121,9 +133,9 @@ add_executable(bridge_qt6
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_precompile_headers(bridge_qt6 PRIVATE Pch.h)
|
||||
target_precompile_headers(bridge-gui PRIVATE Pch.h)
|
||||
|
||||
target_link_libraries(bridge_qt6
|
||||
target_link_libraries(bridge-gui
|
||||
Qt5::Core
|
||||
Qt5::Quick
|
||||
Qt5::Qml
|
||||
@ -133,5 +145,5 @@ target_link_libraries(bridge_qt6
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
target_link_libraries(bridge_qt6 ${COCOA_LIBRARY})
|
||||
target_link_libraries(bridge-gui ${COCOA_LIBRARY})
|
||||
endif()
|
||||
@ -21,6 +21,7 @@
|
||||
#include "GRPCUtils.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "Exception.h"
|
||||
#include "AppController.h"
|
||||
|
||||
|
||||
using namespace google::protobuf;
|
||||
@ -56,6 +57,8 @@ M7SXYbNDiLF4LwPLsunoLsW133Ky7s99MA==
|
||||
Empty empty; // re-used across client calls.
|
||||
|
||||
|
||||
int const maxConnectionTimeSecs = 60; ///< Amount of time after which we consider connection attemps to the server have failed.
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -78,15 +81,25 @@ bool GRPCClient::connectToServer(QString &outError)
|
||||
if (!stub_)
|
||||
throw Exception("Stub creation failed.");
|
||||
|
||||
if (!channel_->WaitForConnected(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
|
||||
gpr_time_from_seconds(10, GPR_TIMESPAN))))
|
||||
throw Exception("Connection to the RPC server failed.");
|
||||
QDateTime const giveUpTime = QDateTime::currentDateTime().addSecs(maxConnectionTimeSecs); // if we reach giveUpTime without connecting, we give up
|
||||
int i = 0;
|
||||
while (true)
|
||||
{
|
||||
app().log().debug(QString("Connection to gRPC server. attempt #%1").arg(++i));
|
||||
|
||||
if (channel_->WaitForConnected(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_seconds(5, GPR_TIMESPAN))))
|
||||
break; // connection established.
|
||||
|
||||
if (QDateTime::currentDateTime() > giveUpTime)
|
||||
throw Exception("Connection to the RPC server failed.");
|
||||
}
|
||||
|
||||
if (channel_->GetState(true) != GRPC_CHANNEL_READY)
|
||||
throw Exception("connection check failed.");
|
||||
|
||||
QMLBackend *backend = &app().backend();
|
||||
QObject::connect(this, &GRPCClient::loginFreeUserError, backend, &QMLBackend::loginFreeUserError);
|
||||
app().log().debug("Successfully connected to gRPC server.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception const &e)
|
||||
@ -50,6 +50,12 @@ void QMLBackend::init()
|
||||
eventStreamOverseer_ = std::make_unique<Overseer>(new EventStreamReader(nullptr), nullptr);
|
||||
eventStreamOverseer_->startWorker(true);
|
||||
|
||||
// Grab from bridge the value that will not change during the execution of this app (or that will only change locally
|
||||
logGRPCCallStatus(app().grpc().showSplashScreen(showSplashScreen_), "showSplashScreen");
|
||||
logGRPCCallStatus(app().grpc().goos(goos_), "goos");
|
||||
logGRPCCallStatus(app().grpc().logsPath(logsPath_), "logsPath");
|
||||
logGRPCCallStatus(app().grpc().licensePath(licensePath_), "licensePath");
|
||||
|
||||
this->retrieveUserList();
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ public: // member functions.
|
||||
|
||||
public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise)
|
||||
Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) // _ bool `property:showOnStartup`
|
||||
Q_PROPERTY(bool showSplashScreen READ showSplashScreen NOTIFY showSplashScreenChanged) // _ bool `property:showSplashScreen`
|
||||
Q_PROPERTY(bool showSplashScreen READ showSplashScreen WRITE setShowSplashScreen NOTIFY showSplashScreenChanged) // _ bool `property:showSplashScreen`
|
||||
Q_PROPERTY(QString goos READ goos NOTIFY goosChanged) // _ string `property:"goos"`
|
||||
Q_PROPERTY(QUrl logsPath READ logsPath NOTIFY logsPathChanged) // _ core.QUrl `property:"logsPath"`
|
||||
Q_PROPERTY(QUrl licensePath READ licensePath NOTIFY licensePathChanged) // _ core.QUrl `property:"licensePath"`
|
||||
@ -79,10 +79,11 @@ public: // Qt/QML properties. Note that the NOTIFY-er signal is required even fo
|
||||
|
||||
// Qt Property system setters & getters.
|
||||
bool showOnStartup() const { bool v = false; logGRPCCallStatus(app().grpc().showOnStartup(v), "showOnStartup"); return v; };
|
||||
bool showSplashScreen() { bool show = false; logGRPCCallStatus(app().grpc().showSplashScreen(show), "showSplashScreen"); return show; }
|
||||
QString goos() { QString goos; logGRPCCallStatus(app().grpc().goos(goos), "goos"); return goos; }
|
||||
QUrl logsPath() const { QUrl path; logGRPCCallStatus(app().grpc().logsPath(path), "logsPath"); return path;}
|
||||
QUrl licensePath() const { QUrl path; logGRPCCallStatus(app().grpc().licensePath(path), "licensePath"); return path; }
|
||||
bool showSplashScreen() const { return showSplashScreen_; };
|
||||
void setShowSplashScreen(bool show) { if (show != showSplashScreen_) { showSplashScreen_ = show; emit showSplashScreenChanged(show); } }
|
||||
QString goos() { return goos_; }
|
||||
QUrl logsPath() const { return logsPath_; }
|
||||
QUrl licensePath() const { return licensePath_; }
|
||||
QUrl releaseNotesLink() const { return releaseNotesLink_; }
|
||||
void setReleaseNotesLink(QUrl const& url) { if (url != releaseNotesLink_) { releaseNotesLink_ = url; emit releaseNotesLinkChanged(url); } }
|
||||
QUrl dependencyLicensesLink() const { QUrl link; logGRPCCallStatus(app().grpc().dependencyLicensesLink(link), "dependencyLicensesLink"); return link; }
|
||||
@ -212,6 +213,10 @@ private: // member functions
|
||||
private: // data members
|
||||
UserList* users_ { nullptr }; ///< The user list. Owned by backend.
|
||||
std::unique_ptr<Overseer> eventStreamOverseer_; ///< The event stream overseer.
|
||||
bool showSplashScreen_ { false }; ///< The cached version of show splash screen. Retrieved on startup from bridge, and potentially modified locally.
|
||||
QString goos_; ///< The cached version of the GOOS variable.
|
||||
QUrl logsPath_; ///< The logs path. Retrieved from bridge on startup.
|
||||
QUrl licensePath_; ///< The license path. Retrieved from bridge on startup.
|
||||
QUrl releaseNotesLink_; /// Release notes is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
|
||||
QUrl landingPageLink_; /// Landing page link is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
|
||||
|
||||
@ -56,13 +56,15 @@ void Overseer::startWorker(bool autorelease) const
|
||||
|
||||
worker_->moveToThread(thread_);
|
||||
connect(thread_, &QThread::started, worker_, &Worker::run);
|
||||
connect(worker_, &Worker::finished, thread_, &QThread::quit);
|
||||
connect(worker_, &Worker::error, thread_, &QThread::quit);
|
||||
connect(worker_, &Worker::finished, [&]() {thread_->quit(); }); // for unkwown reason, connect to the QThread::quit slot does not work...
|
||||
connect(worker_, &Worker::error, [&]() { thread_->quit(); });
|
||||
|
||||
if (autorelease)
|
||||
{
|
||||
connect(worker_, &Worker::error, this, &Overseer::release);
|
||||
connect(worker_, &Worker::finished, this, &Overseer::release);
|
||||
}
|
||||
|
||||
thread_->start();
|
||||
}
|
||||
|
||||
@ -92,7 +94,7 @@ void Overseer::release()
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return true iff the worker is finished, release
|
||||
/// \return true iff the worker is finished.
|
||||
//****************************************************************************************************************************************************
|
||||
bool Overseer::isFinished() const
|
||||
{
|
||||
@ -101,3 +103,12 @@ bool Overseer::isFinished() const
|
||||
|
||||
return worker_->thread()->isFinished();
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The worker.
|
||||
//****************************************************************************************************************************************************
|
||||
Worker *Overseer::worker() const
|
||||
{
|
||||
return worker_;
|
||||
}
|
||||
@ -37,6 +37,7 @@ public: // member functions.
|
||||
Overseer& operator=(Overseer const&) = delete; ///< Disabled assignment operator.
|
||||
Overseer& operator=(Overseer&&) = delete; ///< Disabled move assignment operator.
|
||||
bool isFinished() const; ///< Check if the worker is finished.
|
||||
Worker *worker() const; ///< Return worker.
|
||||
|
||||
public slots:
|
||||
void startWorker(bool autorelease) const; ///< Run the worker.
|
||||
@ -20,7 +20,7 @@
|
||||
#include "Exception.h"
|
||||
#include "QMLBackend.h"
|
||||
#include "Log.h"
|
||||
#include "EventStreamWorker.h"
|
||||
#include "BridgeMonitor.h"
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
@ -51,18 +51,19 @@ std::shared_ptr<QGuiApplication> initQtApplication(int argc, char *argv[])
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
/// \return A reference to the log.
|
||||
//****************************************************************************************************************************************************
|
||||
void initLog()
|
||||
Log &initLog()
|
||||
{
|
||||
Log &log = app().log();
|
||||
log.setEchoInConsole(true);
|
||||
log.setLevel(Log::Level::Debug);
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] engine The QML engine.
|
||||
/// \param[in] engine The QML component.
|
||||
//****************************************************************************************************************************************************
|
||||
QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
|
||||
{
|
||||
@ -88,6 +89,72 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] exePath The path of the Bridge executable. If empty, the function will try to locate the bridge application.
|
||||
//****************************************************************************************************************************************************
|
||||
void launchBridge(QString const &exePath)
|
||||
{
|
||||
UPOverseer& overseer = app().bridgeOverseer();
|
||||
overseer.reset();
|
||||
|
||||
QString bridgeExePath = exePath;
|
||||
if (exePath.isEmpty())
|
||||
bridgeExePath = BridgeMonitor::locateBridgeExe();
|
||||
|
||||
if (bridgeExePath.isEmpty())
|
||||
throw Exception("Could not locate the bridge executable path");
|
||||
else
|
||||
app().log().debug(QString("Bridge executable path: %1").arg(QDir::toNativeSeparators(bridgeExePath)));
|
||||
|
||||
overseer = std::make_unique<Overseer>(new BridgeMonitor(bridgeExePath, nullptr), nullptr);
|
||||
overseer->startWorker(true);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc The number of command-line arguments.
|
||||
/// \param[in] argv The list of command line arguments.
|
||||
//****************************************************************************************************************************************************
|
||||
void parseArguments(int argc, char **argv, bool &outAttach, QString &outExePath)
|
||||
{
|
||||
// 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.
|
||||
QStringList args;
|
||||
for (int i = 0; i < argc; i++)
|
||||
args.append(QString::fromLocal8Bit(argv[i]));
|
||||
|
||||
// We do not want to 'advertise' the following switches, so we do not offer a '-h/--help' option.
|
||||
// we have not yet connected to Bridge, we do not know the application version number, so we do not offer a -v/--version switch.
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Proton Mail Bridge");
|
||||
QCommandLineOption attachOption(QStringList() << "attach" << "a", "attach to an existing bridge process");
|
||||
parser.addOption(attachOption);
|
||||
QCommandLineOption exePathOption(QStringList() << "bridge-exe-path" << "b", "bridge executable path", "path", QString());
|
||||
parser.addOption(exePathOption);
|
||||
|
||||
parser.process(args);
|
||||
outAttach = parser.isSet(attachOption);
|
||||
outExePath = parser.value(exePathOption);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void closeBridgeApp()
|
||||
{
|
||||
UPOverseer& overseer = app().bridgeOverseer();
|
||||
if (!overseer) // The app was ran in 'attach' mode and attached to an existing instance of Bridge. No need to close.
|
||||
return;
|
||||
|
||||
app().grpc().quit(); // this will cause the grpc service and the bridge app to close.
|
||||
while (!overseer->isFinished())
|
||||
{
|
||||
QThread::msleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] argc The number of command-line arguments.
|
||||
/// \param[in] argv The list of command-line arguments.
|
||||
@ -95,12 +162,18 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
|
||||
//****************************************************************************************************************************************************
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
std::shared_ptr<QGuiApplication> guiApp = initQtApplication(argc, argv);
|
||||
try
|
||||
{
|
||||
std::shared_ptr<QGuiApplication> guiApp = initQtApplication(argc, argv);
|
||||
initLog();
|
||||
|
||||
/// \todo GODT-1667 Locate & Launch go backend (and wait for it).
|
||||
bool attach = false;
|
||||
QString exePath;
|
||||
parseArguments(argc, argv, attach, exePath);
|
||||
|
||||
Log &log = initLog();
|
||||
|
||||
if (!attach)
|
||||
launchBridge(exePath);
|
||||
|
||||
app().backend().init();
|
||||
|
||||
@ -113,14 +186,21 @@ int main(int argc, char *argv[])
|
||||
rootObject->setProperty("backend", QVariant::fromValue(&app().backend()));
|
||||
rootComponent->completeCreate();
|
||||
|
||||
BridgeMonitor *bridgeMonitor = app().bridgeMonitor();
|
||||
bool bridgeExited = false;
|
||||
if (bridgeMonitor)
|
||||
QObject::connect(bridgeMonitor, &BridgeMonitor::processExited, [&](int returnCode) {
|
||||
// GODT-1671 We need to find a 'safe' way to check if brige crashed and restart instead of just quitting. Is returnCode enough?
|
||||
bridgeExited = true;
|
||||
qGuiApp->exit(returnCode);
|
||||
});
|
||||
|
||||
int result = QGuiApplication::exec();
|
||||
|
||||
app().log().info(QString("Exiting app with return code %1").arg(result));
|
||||
|
||||
app().grpc().stopEventStream();
|
||||
app().backend().clearUserList();
|
||||
|
||||
/// \todo GODT-1667 shutdown go backend.
|
||||
app().backend().clearUserList(); // required for proper exit. We may want to investigate why at some point.
|
||||
if (!bridgeExited)
|
||||
closeBridgeApp();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1013 B After Width: | Height: | Size: 1013 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 283 B After Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |