Other: fix bug in login screen <-> main window transition. [skip ci]

Other: fixed bug with split mode toggle. [skip ci]

Other: fix QML warnings. [skip ci]

Other: fix showMainWindow gRPC event binding. [skip ci].

QML Fixes [skip ci]

Other: wait for EventStreamReader thread to finish on exit.

Other: made BridgeMonitor generic, as ProcessMonitor. [skip ci]
This commit is contained in:
Xavier Michelon
2022-08-11 11:27:05 +02:00
committed by Jakub
parent 4ed9625959
commit ed904c2bdd
25 changed files with 149 additions and 125 deletions

View File

@ -18,9 +18,9 @@
#include "AppController.h"
#include "QMLBackend.h"
#include "BridgeMonitor.h"
#include <bridgepp/GRPC/GRPCClient.h>
#include <bridgepp/Exception/Exception.h>
#include <bridgepp/ProcessMonitor.h>
#include <bridgepp/Log/Log.h>
@ -51,14 +51,14 @@ 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
ProcessMonitor *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());
// but if bridgeOverseer is not null, its attached worker must be a valid ProcessMonitor instance.
auto *monitor = dynamic_cast<ProcessMonitor*>(bridgeOverseer_->worker());
if (!monitor)
throw Exception("Could not retrieve bridge monitor");

View File

@ -21,7 +21,6 @@
class QMLBackend;
class BridgeMonitor;
namespace bridgepp
@ -29,6 +28,7 @@ namespace bridgepp
class Log;
class Overseer;
class GRPCClient;
class ProcessMonitor;
}
@ -50,7 +50,7 @@ public: // member functions.
bridgepp::GRPCClient& grpc() { return *grpc_; } ///< Return a reference to the GRPC client.
bridgepp::Log& log() { return *log_; } ///< Return a reference to the log.
std::unique_ptr<bridgepp::Overseer>& bridgeOverseer() { return bridgeOverseer_; }; ///< Returns a reference the bridge overseer
BridgeMonitor* bridgeMonitor() const; ///< Return the bridge worker.
bridgepp::ProcessMonitor* bridgeMonitor() const; ///< Return the bridge worker.
private: // member functions
AppController(); ///< Default constructor.

View File

@ -1,112 +0,0 @@
// 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 <bridgepp/Exception/Exception.h>
using namespace bridgepp;
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 = "proton-bridge" + exeSuffix; ///< The bridge executable file name.*
}
//****************************************************************************************************************************************************
/// \return The path of the bridge executable.
/// \return A null string if the executable could not be located.
//****************************************************************************************************************************************************
QString BridgeMonitor::locateBridgeExe()
{
QFileInfo const fileInfo(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(exeName));
return (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) ? fileInfo.absoluteFilePath() : QString();
}
//****************************************************************************************************************************************************
/// \param[in] exePath The path of the Bridge executable.
/// \param[in] parent The parent object of the worker.
//****************************************************************************************************************************************************
BridgeMonitor::BridgeMonitor(QString const &exePath, QStringList const &args, QObject *parent)
: Worker(parent)
, exePath_(exePath)
, args_(args)
{
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_, args_);
p.waitForStarted();
status_.running = true;
status_.pid = p.processId();
while (!p.waitForFinished(100))
{
// we discard output from bridge, it's logged to file on bridge side.
p.readAllStandardError();
p.readAllStandardOutput();
}
status_.running = false;
status_.returnCode = p.exitCode();
emit processExited(status_.returnCode );
emit finished();
}
catch (Exception const &e)
{
emit error(e.qwhat());
}
}
//****************************************************************************************************************************************************
/// \return status of the monitored process
//****************************************************************************************************************************************************
const BridgeMonitor::MonitorStatus& BridgeMonitor::getStatus()
{
return status_;
}

View File

@ -1,63 +0,0 @@
// 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 <bridgepp/Worker/Worker.h>
//**********************************************************************************************************************
/// \brief Bridge process launcher and monitor class.
//**********************************************************************************************************************
class BridgeMonitor: public bridgepp::Worker
{
Q_OBJECT
public: // static member functions
static QString locateBridgeExe(); ///< Try to find the bridge executable path.
struct MonitorStatus {
bool running = false;
int returnCode = 0;
qint64 pid = 0;
};
public: // member functions.
BridgeMonitor(QString const& exePath, QStringList const &args, 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.
const MonitorStatus& getStatus();
signals:
void processExited(int code); ///< Slot for the exiting of the process.
private: // data members
QString const exePath_; ///< The path to the bridge executable.
QStringList args_; ///< arguments to be passed to the brigde.
MonitorStatus status_; ///< Status of the monitoring.
};
#endif // BRIDGE_GUI_BRIDGE_MONITOR_H

View File

@ -82,7 +82,6 @@ endif()
add_executable(bridge-gui
Resources.qrc
AppController.cpp AppController.h
BridgeMonitor.cpp BridgeMonitor.h
EventStreamWorker.cpp EventStreamWorker.h
main.cpp
Pch.h

View File

@ -31,8 +31,8 @@ using namespace bridgepp;
EventStreamReader::EventStreamReader(QObject *parent)
: Worker(parent)
{
connect(this, &EventStreamReader::started, [&]() { app().log().debug("EventStreamReader started");});
connect(this, &EventStreamReader::finished, [&]() { app().log().debug("EventStreamReader finished");});
connect(this, &EventStreamReader::started, [&]() { app().log().debug("EventStreamReader started"); });
connect(this, &EventStreamReader::finished, [&]() { app().log().debug("EventStreamReader finished"); });
connect(this, &EventStreamReader::error, &app().log(), &Log::error);
}

View File

@ -73,6 +73,18 @@ void QMLBackend::init()
this->retrieveUserList();
}
//****************************************************************************************************************************************************
/// \param timeoutMs The timeout after which the function should return false if the event stream reader is not finished. if -1 one, the function
/// never times out.
/// \return false if and only if the timeout delay was reached.
//****************************************************************************************************************************************************
bool QMLBackend::waitForEventStreamReaderToFinish(qint32 timeoutMs)
{
return eventStreamOverseer_->wait(timeoutMs);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
@ -87,6 +99,7 @@ void QMLBackend::connectGrpcEvents()
connect(client, &GRPCClient::reportBugFinished, this, &QMLBackend::reportBugFinished);
connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess);
connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError);
connect(client, &GRPCClient::showMainWindow, this, &QMLBackend::showMainWindow);
// cache events
connect(client, &GRPCClient::isCacheOnDiskEnabledChanged, this, &QMLBackend::isDiskCacheEnabledChanged);
@ -168,15 +181,6 @@ void QMLBackend::retrieveUserList()
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::clearUserList()
{
users_->reset();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
@ -348,4 +352,4 @@ void QMLBackend::onResetFinished()
{
emit resetFinished();
this->restart();
}
}

View File

@ -42,7 +42,7 @@ public: // member functions.
QMLBackend &operator=(QMLBackend const &) = delete; ///< Disabled assignment operator.
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
void init(); ///< Initialize the backend.
void clearUserList(); ///< Clear the user list.
bool waitForEventStreamReaderToFinish(qint32 timeoutMs); ///< Wait for the event stream reader to finish.
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
Q_INVOKABLE static QPoint getCursorPos(); // _ func() *core.QPoint `slot:"getCursorPos"`

View File

@ -91,6 +91,7 @@ void UserList::reset()
this->beginResetModel();
users_.clear();
this->endResetModel();
emit countChanged(0);
}
@ -102,6 +103,7 @@ void UserList::reset(QList<SPUser> const &users)
this->beginResetModel();
users_ = users;
this->endResetModel();
emit countChanged(users_.size());
}
@ -114,6 +116,7 @@ void UserList::appendUser(SPUser const &user)
this->beginInsertRows(QModelIndex(), size, size);
users_.append(user);
this->endInsertRows();
emit countChanged(users_.size());
}
@ -127,6 +130,7 @@ void UserList::removeUserAt(int row)
this->beginRemoveRows(QModelIndex(), row, row);
users_.removeAt(row);
this->endRemoveRows();
emit countChanged(users_.size());
}

View File

@ -19,6 +19,6 @@
#ifndef BRIDGE_GUI_VERSION_H
#define BRIDGE_GUI_VERSION_H
#define PROJECT_VER "2.2.1+git"
#define PROJECT_VER "2.2.2+git"
#endif // BRIDGE_GUI_VERSION_H

View File

@ -17,11 +17,11 @@
#include "QMLBackend.h"
#include "BridgeMonitor.h"
#include "Version.h"
#include <bridgepp/Log/Log.h>
#include <bridgepp/BridgeUtils.h>
#include <bridgepp/Exception/Exception.h>
#include <bridgepp/ProcessMonitor.h>
using namespace bridgepp;
@ -29,11 +29,30 @@ using namespace bridgepp;
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 launcherFlag = "--launcher"; ///< launcher flag parameter used for bridge.
QString const bridgeLock = "bridge-gui.lock"; ///< file name used for the lock file.
QString const exeName = "proton-bridge" + exeSuffix; ///< The bridge executable file name.*
}
//****************************************************************************************************************************************************
/// \return The path of the bridge executable.
/// \return A null string if the executable could not be located.
//****************************************************************************************************************************************************
QString locateBridgeExe()
{
QFileInfo const fileInfo(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(exeName));
return (fileInfo.exists() && fileInfo.isFile() && fileInfo.isExecutable()) ? fileInfo.absoluteFilePath() : QString();
}
//****************************************************************************************************************************************************
/// // initialize the Qt application.
//****************************************************************************************************************************************************
@ -93,7 +112,7 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
//****************************************************************************************************************************************************
/// \param[in] lock The lock file to be checked.
/// \return True if the lock can be taken, false otherwize.
/// \return True if the lock can be taken, false otherwise.
//****************************************************************************************************************************************************
bool checkSingleInstance(QLockFile &lock)
{
@ -168,14 +187,14 @@ void launchBridge(QStringList const &args)
UPOverseer& overseer = app().bridgeOverseer();
overseer.reset();
const QString bridgeExePath = BridgeMonitor::locateBridgeExe();
const QString bridgeExePath = 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, args, nullptr), nullptr);
overseer = std::make_unique<Overseer>(new ProcessMonitor(bridgeExePath, args, nullptr), nullptr);
overseer->startWorker(true);
}
@ -234,16 +253,16 @@ int main(int argc, char *argv[])
if (!rootObject)
throw Exception("Could not create root object.");
BridgeMonitor *bridgeMonitor = app().bridgeMonitor();
ProcessMonitor *bridgeMonitor = app().bridgeMonitor();
bool bridgeExited = false;
bool startError = false;
QMetaObject::Connection connection;
if (bridgeMonitor)
{
const BridgeMonitor::MonitorStatus& status = bridgeMonitor->getStatus();
const ProcessMonitor::MonitorStatus& status = bridgeMonitor->getStatus();
if (!status.running && !attach)
{
// BridgeMonitor already stopped meaning we are attached to an orphan Bridge.
// ProcessMonitor already stopped meaning we are attached to an orphan Bridge.
// Restart the full process to be sure there is no more bridge orphans
app().log().error("Found orphan bridge, need to restart.");
app().backend().forceLauncher(launcher);
@ -254,7 +273,7 @@ int main(int argc, char *argv[])
else
{
app().log().debug(QString("Monitoring Bridge PID : %1").arg(status.pid));
connection = QObject::connect(bridgeMonitor, &BridgeMonitor::processExited, [&](int returnCode) {
connection = QObject::connect(bridgeMonitor, &ProcessMonitor::processExited, [&](int returnCode) {
bridgeExited = true;// clazy:exclude=lambda-in-connect
qGuiApp->exit(returnCode);
});
@ -267,6 +286,8 @@ int main(int argc, char *argv[])
QObject::disconnect(connection);
app().grpc().stopEventStream();
if (!app().backend().waitForEventStreamReaderToFinish(5000))
log.warn("Event stream reader took too long to finish.");
// We manually delete the QML components to avoid warnings error due to order of deletion of C++ / JS objects and singletons.
rootObject.reset();
@ -280,7 +301,7 @@ int main(int argc, char *argv[])
}
catch (Exception const &e)
{
app().log().error(e.qwhat());
QTextStream(stderr) << e.qwhat() << "\n";
return EXIT_FAILURE;
}
}

View File

@ -258,7 +258,7 @@ Item {
signIn.username = this.user.username
rightContent.showSignIn()
}
onShowSetupGuide: {
onShowSetupGuide: function(user, address) {
root.showSetupGuide(user,address)
}
}

View File

@ -45,7 +45,6 @@ SettingsView {
type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
Layout.maximumWidth: this.parent.Layout.maximumWidth
wrapMode: Text.WordWrap
}

View File

@ -52,7 +52,7 @@ ApplicationWindow {
target: Backend.users
function onRowsInserted(parent, first, last) {
// considerring that users are added one-by-one
// considering that users are added one-by-one
var user = Backend.users.get(first)
if (!user.loggedIn) {
@ -130,7 +130,7 @@ ApplicationWindow {
Layout.fillHeight: true
Layout.fillWidth: true
onShowSetupGuide: {
onShowSetupGuide: function(user, address) {
root.showSetup(user,address)
}
}

View File

@ -40,7 +40,6 @@ SettingsView {
type: Label.Body
color: root.colorScheme.text_weak
Layout.fillWidth: true
Layout.maximumWidth: this.parent.Layout.maximumWidth
wrapMode: Text.WordWrap
}