diff --git a/internal/frontend/bridge-gui/CMakeLists.txt b/internal/frontend/bridge-gui/CMakeLists.txt index f873fbfe..cca7559d 100644 --- a/internal/frontend/bridge-gui/CMakeLists.txt +++ b/internal/frontend/bridge-gui/CMakeLists.txt @@ -31,4 +31,4 @@ project(frontend) add_subdirectory(bridgepp) add_subdirectory(bridge-gui) - +add_subdirectory(bridge-gui-tester) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/AppController.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/AppController.cpp new file mode 100644 index 00000000..d06fc0c9 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/AppController.cpp @@ -0,0 +1,106 @@ +// 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 . + + +#include "AppController.h" +#include "GRPCService.h" +#include +#include +#include "MainWindow.h" +#include + + +using namespace bridgepp; + + +//**************************************************************************************************************************************************** +/// \return A reference to the application controller. +//**************************************************************************************************************************************************** +AppController &app() +{ + static AppController app; + return app; +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +AppController::AppController() + : log_(std::make_unique()) + , bridgeGUILog_(std::make_unique()) + , grpc_(std::make_unique()) +{ + +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +AppController::~AppController() // NOLINT(modernize-use-equals-default): implementation in cpp file is required because of forward declaration of Log in header +{ + +} + + +//**************************************************************************************************************************************************** +/// \param[in] mainWindow The main window. +//**************************************************************************************************************************************************** +void AppController::setMainWindow(MainWindow *mainWindow) +{ + mainWindow_ = mainWindow; + grpc_->connectProxySignals(); +} + + +//**************************************************************************************************************************************************** +/// \return The main window. +//**************************************************************************************************************************************************** +MainWindow &AppController::mainWindow() +{ + if (!mainWindow_) + throw Exception("mainWindow has not yet been registered."); + return *mainWindow_; +} + + +//**************************************************************************************************************************************************** +/// \return A reference to the log. +//**************************************************************************************************************************************************** +bridgepp::Log &AppController::log() +{ + return *log_; +} + + +//**************************************************************************************************************************************************** +/// \return A reference to the bridge-gui log. +//**************************************************************************************************************************************************** +bridgepp::Log &AppController::bridgeGUILog() +{ + return *bridgeGUILog_; +} + + +//**************************************************************************************************************************************************** +/// \return A reference to the gRPC service. +//**************************************************************************************************************************************************** +GRPCService &AppController::grpc() +{ + return *grpc_; +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/AppController.h b/internal/frontend/bridge-gui/bridge-gui-tester/AppController.h new file mode 100644 index 00000000..379b3510 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/AppController.h @@ -0,0 +1,63 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_APP_CONTROLLER_H +#define BRIDGE_GUI_TESTER_APP_CONTROLLER_H + + +class MainWindow; +class GRPCService; +namespace grpc { class StreamEvent; } +namespace bridgepp { class Log; } + + +//********************************************************************************************************************** +/// \brief Application controller class +//********************************************************************************************************************** +class AppController : public QObject +{ +Q_OBJECT +public: // member functions. + friend AppController &app(); + + AppController(AppController const &) = delete; ///< Disabled copy-constructor. + AppController(AppController &&) = delete; ///< Disabled assignment copy-constructor. + ~AppController() override; ///< Destructor. + AppController &operator=(AppController const &) = delete; ///< Disabled assignment operator. + AppController &operator=(AppController &&) = delete; ///< Disabled move assignment operator. + void setMainWindow(MainWindow *mainWindow); ///< Set the main window. + MainWindow &mainWindow(); ///< Return the main window. + bridgepp::Log &log(); ///< Return a reference to the log. + bridgepp::Log &bridgeGUILog(); ///< Return a reference to the bridge-gui log. + GRPCService &grpc(); ///< Return a reference to the gRPC service. + +private: // member functions. + AppController(); ///< Default constructor. + +private: // data members. + MainWindow *mainWindow_ { nullptr }; ///< The main window. + std::unique_ptr log_; ///< The log. + std::unique_ptr bridgeGUILog_; ///< The bridge-gui log. + std::unique_ptr grpc_; ///< The gRPC service. +}; + + +AppController &app(); ///< Return a reference to the app controller. + + +#endif // BRIDGE_GUI_TESTER_APP_CONTROLLER_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/CMakeLists.txt b/internal/frontend/bridge-gui/bridge-gui-tester/CMakeLists.txt new file mode 100644 index 00000000..325ada4c --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/CMakeLists.txt @@ -0,0 +1,90 @@ +# 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 . + + +cmake_minimum_required(VERSION 3.22) + + +set(VCPKG_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../extern/vcpkg") +include(../BridgeSetup.cmake) + + +#***************************************************************************************************************************************************** +# Project +#***************************************************************************************************************************************************** + + +project(bridge-gui-tester LANGUAGES CXX) + +if (NOT DEFINED BRIDGE_APP_VERSION) + message(FATAL_ERROR "BRIDGE_APP_VERSION is not defined.") +else() + message(STATUS "Bridge version is ${BRIDGE_APP_VERSION}") +endif() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +#***************************************************************************************************************************************************** +# Qt +#***************************************************************************************************************************************************** + + +if (NOT DEFINED ENV{QT6DIR}) + message(FATAL_ERROR "QT6DIR needs to be defined and point to the root of your Qt 6 folder (e.g. /Users/MyName/Qt/6.3.1/clang_64).") +endif() + +set(CMAKE_PREFIX_PATH $ENV{QT6DIR} ${CMAKE_PREFIX_PATH}) +find_package(Qt6 COMPONENTS Core Gui Widgets Qml REQUIRED) +qt_standard_project_setup() +message(STATUS "Using Qt ${Qt6_VERSION}") + + +#***************************************************************************************************************************************************** +# Source files and output +#***************************************************************************************************************************************************** + +if (NOT TARGET bridgepp) +add_subdirectory(../bridgepp bridgepp) +endif() + +add_executable(bridge-gui-tester + AppController.cpp AppController.h + main.cpp + MainWindow.cpp MainWindow.h + GRPCQtProxy.cpp GRPCQtProxy.h + GRPCService.cpp GRPCService.h + GRPCServerWorker.cpp GRPCServerWorker.h + Tabs/SettingsTab.cpp Tabs/SettingsTab.h + Tabs/UsersTab.cpp Tabs/UsersTab.h + UserDialog.cpp UserDialog.h + UserTable.cpp UserTable.h + ) + +target_precompile_headers(bridge-gui-tester PRIVATE Pch.h) +target_include_directories(bridge-gui-tester PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_definitions(bridge-gui-tester PRIVATE BRIDGE_APP_VERSION=\"${BRIDGE_APP_VERSION}\") + + +target_link_libraries(bridge-gui-tester + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Qml + bridgepp + ) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp new file mode 100644 index 00000000..936d7304 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.cpp @@ -0,0 +1,210 @@ +// 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 . + + +#include "GRPCQtProxy.h" +#include "MainWindow.h" + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +GRPCQtProxy::GRPCQtProxy() + : QObject(nullptr) +{ +} + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCQtProxy::connectSignals() +{ + MainWindow &mainWindow = app().mainWindow(); + SettingsTab &settingsTab = mainWindow.settingsTab(); + UsersTab &usersTab = mainWindow.usersTab(); + connect(this, &GRPCQtProxy::delayedEventRequested, &mainWindow, &MainWindow::sendDelayedEvent); + connect(this, &GRPCQtProxy::setIsAutostartOnReceived, &settingsTab, &SettingsTab::setIsAutostartOn); + connect(this, &GRPCQtProxy::setIsBetaEnabledReceived, &settingsTab, &SettingsTab::setIsBetaEnabled); + connect(this, &GRPCQtProxy::setColorSchemeNameReceived, &settingsTab, &SettingsTab::setColorSchemeName); + connect(this, &GRPCQtProxy::reportBugReceived, &settingsTab, &SettingsTab::setBugReport); + connect(this, &GRPCQtProxy::setIsStreamingReceived, &settingsTab, &SettingsTab::setIsStreaming); + connect(this, &GRPCQtProxy::setClientPlatformReceived, &settingsTab, &SettingsTab::setClientPlatform); + connect(this, &GRPCQtProxy::changePortsReceived, &settingsTab, &SettingsTab::changePorts); + connect(this, &GRPCQtProxy::setUseSSLForSMTPReceived, &settingsTab, &SettingsTab::setUseSSLForSMTP); + connect(this, &GRPCQtProxy::setIsDoHEnabledReceived, &settingsTab, &SettingsTab::setIsDoHEnabled); + connect(this, &GRPCQtProxy::changeLocalCacheReceived, &settingsTab, &SettingsTab::changeLocalCache); + connect(this, &GRPCQtProxy::setIsAutomaticUpdateOnReceived, &settingsTab, &SettingsTab::setIsAutomaticUpdateOn); + connect(this, &GRPCQtProxy::setUserSplitModeReceived, &usersTab, &UsersTab::setUserSplitMode); + connect(this, &GRPCQtProxy::removeUserReceived, &usersTab, &UsersTab::removeUser); + connect(this, &GRPCQtProxy::logoutUserReceived, &usersTab, &UsersTab::logoutUser); + connect(this, &GRPCQtProxy::setUserSplitModeReceived, &usersTab, &UsersTab::setUserSplitMode); + connect(this, &GRPCQtProxy::configureUserAppleMailReceived, &usersTab, &UsersTab::configureUserAppleMail); +} + + +//**************************************************************************************************************************************************** +/// \param[in] event The event. +//**************************************************************************************************************************************************** +void GRPCQtProxy::sendDelayedEvent(bridgepp::SPStreamEvent const &event) +{ + emit delayedEventRequested(event); +} + + +//**************************************************************************************************************************************************** +/// \param[in] on The value. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setIsAutostartOn(bool on) +{ + emit setIsAutostartOnReceived(on); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled The value. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setIsBetaEnabled(bool enabled) +{ + emit setIsBetaEnabledReceived(enabled); +} + + +//**************************************************************************************************************************************************** +/// \param[in] name The color scheme. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setColorSchemeName(QString const &name) +{ + emit setColorSchemeNameReceived(name); +} + + +//**************************************************************************************************************************************************** +/// \param[in] osType The OS type. +/// \param[in] osVersion The OS version. +/// \param[in] emailClient The email client. +/// \param[in] address The address. +/// \param[in] description The description. +/// \param[in] includeLogs Should the logs be included. +//**************************************************************************************************************************************************** +void GRPCQtProxy::reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, + QString const &description, bool includeLogs) +{ + emit reportBugReceived(osType, osVersion, emailClient, address, description, includeLogs); +} + + +//**************************************************************************************************************************************************** +/// \param[in] isStreaming Is the gRPC server streaming. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setIsStreaming(bool isStreaming) +{ + emit setIsStreamingReceived(isStreaming); +} + + +//**************************************************************************************************************************************************** +/// \param[in] clientPlatform The client platform. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setClientPlatform(QString const &clientPlatform) +{ + emit setClientPlatformReceived(clientPlatform); +} + + +//**************************************************************************************************************************************************** +/// \param[in] imapPort The IMAP port +/// \param[in] smtpPort The SMTP port +//**************************************************************************************************************************************************** +void GRPCQtProxy::changePorts(qint32 imapPort, qint32 smtpPort) +{ + emit changePortsReceived(imapPort, smtpPort); +} + + +//**************************************************************************************************************************************************** +/// \param[in] use Should SMTP use SSL? +//**************************************************************************************************************************************************** +void GRPCQtProxy::setUseSSLForSMTP(bool use) +{ + emit setUseSSLForSMTPReceived(use); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled Is DoH enabled? +//**************************************************************************************************************************************************** +void GRPCQtProxy::setIsDoHEnabled(bool enabled) +{ + emit setIsDoHEnabledReceived(enabled); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled is cache on disk enabled? +/// \param[in] path The path for the cache on disk. +//**************************************************************************************************************************************************** +void GRPCQtProxy::changeLocalCache(bool enabled, QString const &path) +{ + emit changeLocalCacheReceived(enabled, path); +} + + +//**************************************************************************************************************************************************** +/// \param[in] on Is automatic update on? +//**************************************************************************************************************************************************** +void GRPCQtProxy::setIsAutomaticUpdateOn(bool on) +{ + emit setIsAutomaticUpdateOnReceived(on); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] makeItActive Should split mode be active. +//**************************************************************************************************************************************************** +void GRPCQtProxy::setUserSplitMode(QString const &userID, bool makeItActive) +{ + emit setUserSplitModeReceived(userID, makeItActive); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void GRPCQtProxy::logoutUser(QString const &userID) +{ + emit logoutUserReceived(userID); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void GRPCQtProxy::removeUser(QString const &userID) +{ + emit removeUserReceived(userID); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] address The address. +//**************************************************************************************************************************************************** +void GRPCQtProxy::configureUserAppleMail(QString const &userID, QString const &address) +{ + emit configureUserAppleMailReceived(userID, address); +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h new file mode 100644 index 00000000..7d3a1c45 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCQtProxy.h @@ -0,0 +1,80 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_GRPC_QT_PROXY_H +#define BRIDGE_GUI_TESTER_GRPC_QT_PROXY_H + + +#include + + +//**************************************************************************************************************************************************** +/// \brief Proxy object used by the gRPC service (which does not inherit QObject) to use the Qt Signal/Slot system. +//**************************************************************************************************************************************************** +class GRPCQtProxy : public QObject +{ +Q_OBJECT +public: // member functions. + GRPCQtProxy(); ///< Default constructor. + GRPCQtProxy(GRPCQtProxy const &) = delete; ///< Disabled copy-constructor. + GRPCQtProxy(GRPCQtProxy &&) = delete; ///< Disabled assignment copy-constructor. + ~GRPCQtProxy() override = default; ///< Destructor. + GRPCQtProxy &operator=(GRPCQtProxy const &) = delete; ///< Disabled assignment operator. + GRPCQtProxy &operator=(GRPCQtProxy &&) = delete; ///< Disabled move assignment operator. + + void connectSignals(); // connect the signals to the main window. + void sendDelayedEvent(bridgepp::SPStreamEvent const &event); ///< Sends a delayed stream event. + void setIsAutostartOn(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal. + void setIsBetaEnabled(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal. + void setColorSchemeName(QString const &name); ///< Forward a SetColorSchemeName call via a Qt Signal + void reportBug(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, + QString const &description, bool includeLogs); ///< Forwards a ReportBug call via a Qt signal. + void setIsStreaming(bool isStreaming); ///< Forward a isStreaming internal messages via a Qt signal. + void setClientPlatform(QString const &clientPlatform); ///< Forward s setClientPlatform call via a Qt signal. + void changePorts(qint32 imapPort, qint32 smtpPort); ///< Forwards a ChangePorts call via a Qt signal. + void setUseSSLForSMTP(bool use); ///< Forwards a SetUseSSLForSMTP call via a Qt signal. + void setIsDoHEnabled(bool enabled); ///< Forwards a setIsDoHEnabled call via a Qt signal. + void changeLocalCache(bool enabled, QString const &path); ///< Forwards a ChangeLocalPath call via a Qt signal. + void setIsAutomaticUpdateOn(bool on); ///< Forwards a SetIsAutomaticUpdateOn call via a Qt signal. + void setUserSplitMode(QString const &userID, bool makeItActive); ///< Forwards a setUserSplitMode call via a Qt signal. + void logoutUser(QString const &userID); ///< Forwards a logoutUser call via a Qt signal. + void removeUser(QString const &userID); ///< Forwards a removeUser call via a Qt signal. + void configureUserAppleMail(QString const &userID, QString const &address); ///< Forwards a configureUserAppleMail call via a Qt signal. + +signals: + void delayedEventRequested(bridgepp::SPStreamEvent const &event); ///< Signal for sending a delayed event. delayed is set in the UI. + void setIsAutostartOnReceived(bool on); ///< Forwards a SetIsAutostartOn call via a Qt signal. + void setIsBetaEnabledReceived(bool enabled); ///< Forwards a SetIsBetaEnabled call via a Qt signal. + void setColorSchemeNameReceived(QString const &name); ///< Forward a SetColorScheme call via a Qt Signal + void reportBugReceived(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, + QString const &description, bool includeLogs); ///< Signal for the ReportBug gRPC call + void setIsStreamingReceived(bool isStreaming); ///< Signal for the IsStreaming internal message. + void setClientPlatformReceived(QString const &clientPlatform); ///< Signal for the SetClientPlatform gRPC call. + void changePortsReceived(qint32 imapPort, qint32 smtpPort); ///< Signal for the ChangePorts gRPC call. + void setUseSSLForSMTPReceived(bool use); ///< Signal for the SetUseSSLForSMTP gRPC call. + void setIsDoHEnabledReceived(bool enabled); ///< Signal for the SetIsDoHEnabled gRPC call. + void changeLocalCacheReceived(bool enabled, QString const &path); ///< Signal for the ChangeLocalPath gRPC call. + void setIsAutomaticUpdateOnReceived(bool on); ///< Signal for the SetIsAutomaticUpdateOn gRPC call. + void setUserSplitModeReceived(QString const &userID, bool makeItActive); ///< Signal for the SetUserSplitModeReceived gRPC call. + void logoutUserReceived(QString const &userID); ///< Signal for the LogoutUserReceived gRPC call. + void removeUserReceived(QString const &userID); ///< Signal for the RemoveUserReceived gRPC call. + void configureUserAppleMailReceived(QString const &userID, QString const &address); ///< Signal for the ConfigureAppleMail gRPC call. +}; + + +#endif //BRIDGE_GUI_TESTER_GRPC_QT_PROXY_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp new file mode 100644 index 00000000..f15ae895 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp @@ -0,0 +1,109 @@ +// 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 . + + +#include "GRPCServerWorker.h" +#include "GRPCService.h" +#include +#include + + +using namespace bridgepp; +using namespace grpc; + + +namespace +{ + + +//**************************************************************************************************************************************************** +/// \return The content of the file. +//**************************************************************************************************************************************************** +QString loadAsciiTextFile(QString const &path) { + QFile file(path); + return file.open(QIODevice::ReadOnly | QIODevice::Text) ? QString::fromLocal8Bit(file.readAll()) : QString(); +} + + +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +GRPCServerWorker::GRPCServerWorker(QObject *parent) + : Worker(parent) +{ + +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCServerWorker::run() +{ + try + { + emit started(); + + QString cert = loadAsciiTextFile(serverCertificatePath()); + if (cert.isEmpty()) + throw Exception("Could not locate server certificate. Make sure to launch bridge once before starting this application"); + + QString key = loadAsciiTextFile(serverKeyPath()); + if (key.isEmpty()) + throw Exception("Could not locate server key. Make sure to launch bridge once before starting this application"); + + SslServerCredentialsOptions::PemKeyCertPair pair; + pair.private_key = key.toStdString(); + pair.cert_chain = cert.toStdString(); + SslServerCredentialsOptions ssl_opts; + ssl_opts.pem_root_certs=""; + ssl_opts.pem_key_cert_pairs.push_back(pair); + std::shared_ptr credentials = grpc::SslServerCredentials(ssl_opts); + + ServerBuilder builder; + builder.AddListeningPort("127.0.0.1:9292", credentials); + builder.RegisterService(&app().grpc()); + server_ = builder.BuildAndStart(); + if (!server_) + throw Exception("Could not create gRPC server."); + app().log().debug("gRPC Server started."); + + server_->Wait(); + emit finished(); + app().log().debug("gRPC Server exited."); + } + catch (Exception const &e) + { + if (server_) + server_->Shutdown(); + + emit error(e.qwhat()); + } +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCServerWorker::stop() +{ + if (server_) + server_->Shutdown(); +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.h new file mode 100644 index 00000000..8a881ba8 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.h @@ -0,0 +1,50 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_SERVER_WORKER_H +#define BRIDGE_GUI_TESTER_SERVER_WORKER_H + + +#include +#include "GRPCService.h" +#include + + +//********************************************************************************************************************** +/// \brief gRPC server worker +//********************************************************************************************************************** +class GRPCServerWorker : public bridgepp::Worker +{ +Q_OBJECT +public: // member functions. + explicit GRPCServerWorker(QObject *parent); ///< Default constructor. + GRPCServerWorker(GRPCServerWorker const &) = delete; ///< Disabled copy-constructor. + GRPCServerWorker(GRPCServerWorker &&) = delete; ///< Disabled assignment copy-constructor. + ~GRPCServerWorker() override = default; ///< Destructor. + GRPCServerWorker &operator=(GRPCServerWorker const &) = delete; ///< Disabled assignment operator. + GRPCServerWorker &operator=(GRPCServerWorker &&) = delete; ///< Disabled move assignment operator. + + void run() override; ///< Run the worker. + void stop(); ///< Stop the gRPC service. + +private: // data members + std::unique_ptr server_ { nullptr }; ///< The gRPC server. +}; + + +#endif // BRIDGE_GUI_TESTER_SERVER_WORKER_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp new file mode 100644 index 00000000..db6571c0 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -0,0 +1,878 @@ +// 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 . + + +#include "GRPCService.h" +#include "MainWindow.h" +#include +#include + + +using namespace grpc; +using namespace google::protobuf; +using namespace bridgepp; + +namespace +{ + + +QString const defaultKeychain = "defaultKeychain"; ///< The default keychain. + + +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void GRPCService::connectProxySignals() +{ + qtProxy_.connectSignals(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the service is streaming events. +//**************************************************************************************************************************************************** +bool GRPCService::isStreaming() const +{ + QMutexLocker locker(&eventStreamMutex_); + return isStreaming_; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request the request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::AddLogEntry(ServerContext *, AddLogEntryRequest const *request, Empty *) +{ + app().bridgeGUILog().addEntry(logLevelFromGRPC(request->level()), QString::fromStdString(request->message())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::GuiReady(ServerContext *, Empty const *, Empty *) +{ + app().log().debug(__FUNCTION__); + app().mainWindow().settingsTab().setGUIReady(true); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Quit(ServerContext *, Empty const *, Empty *) +{ + // We do not actually quit. + app().log().debug(__FUNCTION__); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Restart(ServerContext *, Empty const *, Empty *) +{ + // we do not actually restart. + app().log().debug(__FUNCTION__); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ShowOnStartup(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().showOnStartup()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ShowSplashScreen(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().showSplashScreen()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsFirstGuiStart(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isFirstGUIStart()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetIsAutostartOn(ServerContext *, BoolValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + app().mainWindow().settingsTab().setIsAutostartOn(request->value()); + qtProxy_.sendDelayedEvent(newToggleAutostartFinishedEvent()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsAutostartOn(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isAutostartOn()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetIsBetaEnabled(ServerContext *, BoolValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setIsBetaEnabled(request->value()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsBetaEnabled(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isBetaEnabled()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::GoOs(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().os().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::TriggerReset(ServerContext *, Empty const *, Empty *) +{ + app().log().debug(__FUNCTION__); + app().log().info("Bridge GUI requested a reset"); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +grpc::Status GRPCService::Version(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().bridgeVersion().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::LogsPath(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().logsPath().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::LicensePath(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().licensePath().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ReleaseNotesPageLink(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().releaseNotesPageLink().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::DependencyLicensesLink(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().dependencyLicenseLink().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::LandingPageLink(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().landingPageLink().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetColorSchemeName(ServerContext *, StringValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setColorSchemeName(QString::fromStdString(request->value())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ColorSchemeName(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().colorSchemeName().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::CurrentEmailClient(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().currentEmailClient().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ForceLauncher(ServerContext *, StringValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + app().log().info(QString("ForceLauncher: %1").arg(QString::fromStdString(request->value()))); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request +//**************************************************************************************************************************************************** +Status GRPCService::ReportBug(ServerContext *, ReportBugRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + SettingsTab &tab = app().mainWindow().settingsTab(); + qtProxy_.reportBug(QString::fromStdString(request->ostype()), QString::fromStdString(request->osversion()), + QString::fromStdString(request->emailclient()), QString::fromStdString(request->address()), QString::fromStdString(request->description()), + request->includelogs()); + qtProxy_.sendDelayedEvent(tab.nextBugReportWillSucceed() ? newReportBugSuccessEvent() : newReportBugErrorEvent()); + qtProxy_.sendDelayedEvent(newReportBugFinishedEvent()); + + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Login(ServerContext *, LoginRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + UsersTab &usersTab = app().mainWindow().usersTab(); + loginUsername_ = QString::fromStdString(request->username()); + if (usersTab.nextUserUsernamePasswordError()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::USERNAME_PASSWORD_ERROR, "Username/password error.")); + return Status::OK; + } + if (usersTab.nextUserFreeUserError()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::FREE_USER, "Free user error.")); + return Status::OK; + } + if (usersTab.nextUserTFARequired()) + { + qtProxy_.sendDelayedEvent(newLoginTfaRequestedEvent(loginUsername_)); + return Status::OK; + } + if (usersTab.nextUserTwoPasswordsRequired()) + { + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + return Status::OK; + } + + SPUser const user = randomUser(); + QString const userID = user->id(); + user->setUsername(QString::fromStdString(request->username())); + usersTab.userTable().append(user); + + if (usersTab.nextUserAlreadyLoggedIn()) + qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(userID)); + qtProxy_.sendDelayedEvent(newLoginFinishedEvent(userID)); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Login2FA(ServerContext *, LoginRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + UsersTab &usersTab = app().mainWindow().usersTab(); + if (usersTab.nextUserTFAError()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TFA_ERROR, "2FA Error.")); + return Status::OK; + } + if (usersTab.nextUserTFAAbort()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TFA_ABORT, "2FA Abort.")); + return Status::OK; + } + if (usersTab.nextUserTwoPasswordsRequired()) + { + qtProxy_.sendDelayedEvent(newLoginTwoPasswordsRequestedEvent()); + return Status::OK; + } + + SPUser const user = randomUser(); + QString const userID = user->id(); + user->setUsername(QString::fromStdString(request->username())); + usersTab.userTable().append(user); + + if (usersTab.nextUserAlreadyLoggedIn()) + qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(userID)); + qtProxy_.sendDelayedEvent(newLoginFinishedEvent(userID)); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Login2Passwords(ServerContext *, LoginRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + UsersTab &usersTab = app().mainWindow().usersTab(); + + if (usersTab.nextUserTwoPasswordsError()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TWO_PASSWORDS_ERROR, "Two Passwords error.")); + return Status::OK; + } + + if (usersTab.nextUserTwoPasswordsAbort()) + { + qtProxy_.sendDelayedEvent(newLoginError(LoginErrorType::TWO_PASSWORDS_ABORT, "Two Passwords abort.")); + return Status::OK; + } + + SPUser const user = randomUser(); + QString const userID = user->id(); + user->setUsername(QString::fromStdString(request->username())); + usersTab.userTable().append(user); + + if (usersTab.nextUserAlreadyLoggedIn()) + qtProxy_.sendDelayedEvent(newLoginAlreadyLoggedInEvent(userID)); + qtProxy_.sendDelayedEvent(newLoginFinishedEvent(userID)); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::LoginAbort(ServerContext *, LoginAbortRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + loginUsername_ = QString(); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::CheckUpdate(ServerContext *, Empty const *, Empty *) +{ + /// \todo simulate update availability. + app().log().debug(__FUNCTION__); + app().log().info("Check for updates"); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::InstallUpdate(ServerContext *, Empty const *, Empty *) +{ + /// Simulate update availability. + app().log().debug(__FUNCTION__); + app().log().info("Install update"); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetIsAutomaticUpdateOn(ServerContext *, BoolValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setIsAutomaticUpdateOn(request->value()); + return Status::OK; +} + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsAutomaticUpdateOn(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isAutomaticUpdateOn()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The response. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::IsCacheOnDiskEnabled(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isCacheOnDiskEnabled()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] response The response. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::DiskCachePath(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().diskCachePath().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ChangeLocalCache(ServerContext *, ChangeLocalCacheRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + SettingsTab &tab = app().mainWindow().settingsTab(); + QString const path = QString::fromStdString(request->diskcachepath()); + + // we mimic the behaviour of Bridge + if (!tab.nextCacheChangeWillSucceed()) + qtProxy_.sendDelayedEvent(newCacheErrorEvent(grpc::CacheErrorType(tab.cacheError()))); + else + qtProxy_.sendDelayedEvent(newCacheLocationChangeSuccessEvent()); + qtProxy_.sendDelayedEvent(newDiskCachePathChanged(path)); + qtProxy_.sendDelayedEvent(newIsCacheOnDiskEnabledChanged(request->enablediskcache())); + qtProxy_.sendDelayedEvent(newChangeLocalCacheFinishedEvent()); + + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetIsDoHEnabled(ServerContext *, BoolValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setIsDoHEnabled(request->value()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsDoHEnabled(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isDoHEnabled()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetUseSslForSmtp(ServerContext *, BoolValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setUseSSLForSMTP(request->value()); + qtProxy_.sendDelayedEvent(newUseSslForSmtpFinishedEvent()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::UseSslForSmtp(ServerContext *, Empty const *, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().useSSLForSMTP()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::Hostname(ServerContext *, Empty const *, StringValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().hostname().toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ImapPort(ServerContext *, Empty const *, Int32Value *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().imapPort()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SmtpPort(ServerContext *, Empty const *, Int32Value *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().smtpPort()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::ChangePorts(ServerContext *, ChangePortsRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.changePorts(request->imapport(), request->smtpport()); + qtProxy_.sendDelayedEvent(newChangePortsFinishedEvent()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response the response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::IsPortFree(ServerContext *, Int32Value const *request, BoolValue *response) +{ + app().log().debug(__FUNCTION__); + response->set_value(app().mainWindow().settingsTab().isPortFree()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::AvailableKeychains(ServerContext *, Empty const *, AvailableKeychainsResponse *response) +{ + /// \todo Implement keychains configuration. + app().log().debug(__FUNCTION__); + response->clear_keychains(); + response->add_keychains(defaultKeychain.toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::SetCurrentKeychain(ServerContext *, StringValue const *request, Empty *) +{ + /// \todo Implement keychains configuration. + app().log().debug(__FUNCTION__); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::CurrentKeychain(ServerContext *, Empty const *, StringValue *response) +{ + /// \todo Implement keychains configuration. + app().log().debug(__FUNCTION__); + response->set_value(defaultKeychain.toStdString()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[out] response The response. +/// \return The status for the call +//**************************************************************************************************************************************************** +Status GRPCService::GetUserList(ServerContext *, Empty const *, UserListResponse *response) +{ + app().log().debug(__FUNCTION__); + response->clear_users(); + + QList userList = app().mainWindow().usersTab().userTable().users(); + RepeatedPtrField *users = response->mutable_users(); + for (SPUser const &user: userList) + { + if (!user) + continue; + users->Add(); + grpc::User &grpcUser = (*users)[users->size() - 1]; + userToGRPC(*user, grpcUser); + } + + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \param[out] response The response. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::GetUser(ServerContext *, StringValue const *request, grpc::User *response) +{ + app().log().debug(__FUNCTION__); + QString userID = QString::fromStdString(request->value()); + SPUser user = app().mainWindow().usersTab().userWithID(userID); + if (!user) + return Status(NOT_FOUND, QString("user not found %1").arg(userID).toStdString()); + + userToGRPC(*user, *response); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::SetUserSplitMode(ServerContext *, UserSplitModeRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.setUserSplitMode(QString::fromStdString(request->userid()), request->active()); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::LogoutUser(ServerContext *, StringValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.logoutUser(QString::fromStdString(request->value())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::RemoveUser(ServerContext *, StringValue const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.removeUser(QString::fromStdString(request->value())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request. +//**************************************************************************************************************************************************** +Status GRPCService::ConfigureUserAppleMail(ServerContext *, ConfigureAppleMailRequest const *request, Empty *) +{ + app().log().debug(__FUNCTION__); + qtProxy_.configureUserAppleMail(QString::fromStdString(request->userid()), QString::fromStdString(request->address())); + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] request The request +/// \param[in] writer The writer +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::StartEventStream(ServerContext *, EventStreamRequest const *request, ServerWriter *writer) +{ + app().log().debug(__FUNCTION__); + { + QMutexLocker locker(&eventStreamMutex_); + if (isStreaming_) + return { grpc::ALREADY_EXISTS, "the service is already streaming" }; + isStreaming_ = true; + qtProxy_.setIsStreaming(true); + qtProxy_.setClientPlatform(QString::fromStdString(request->clientplatform())); + eventStreamShouldStop_ = false; + } + + while (true) + { + QMutexLocker locker(&eventStreamMutex_); + if (eventStreamShouldStop_) + { + qtProxy_.setIsStreaming(false); + qtProxy_.setClientPlatform(QString()); + isStreaming_ = false; + return Status::OK; + } + + + if (eventQueue_.isEmpty()) + { + locker.unlock(); + QThread::msleep(100); + continue; + } + SPStreamEvent const event = eventQueue_.front(); + eventQueue_.pop_front(); + locker.unlock(); + + if (writer->Write(*event)) + app().log().debug(QString("event sent: %1").arg(QString::fromStdString(event->ShortDebugString()))); + else + app().log().error(QString("Could not send event: %1").arg(QString::fromStdString(event->ShortDebugString()))); + } +} + + +//**************************************************************************************************************************************************** +/// \return The status for the call. +//**************************************************************************************************************************************************** +Status GRPCService::StopEventStream(ServerContext *, Empty const *, Empty *) +{ + app().log().debug(__FUNCTION__); + QMutexLocker mutex(&eventStreamMutex_); + if (!isStreaming_) + return Status(NOT_FOUND, "The service is not streaming"); + eventStreamShouldStop_ = true; + return Status::OK; +} + + +//**************************************************************************************************************************************************** +/// \param[in] event The event +/// \return true if the event was queued, and false if the server in not streaming. +//**************************************************************************************************************************************************** +bool GRPCService::sendEvent(SPStreamEvent const &event) +{ + QMutexLocker mutexLocker(&eventStreamMutex_); + if (isStreaming_) + eventQueue_.push_back(event); + return isStreaming_; +} + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h new file mode 100644 index 00000000..703a74ea --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -0,0 +1,112 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_GRPC_SERVER_H +#define BRIDGE_GUI_TESTER_GRPC_SERVER_H + + +#include "GRPCQtProxy.h" +#include +#include + + +//********************************************************************************************************************** +/// \brief gRPC server implementation. +//********************************************************************************************************************** +class GRPCService : public grpc::Bridge::Service +{ + +public: // member functions. + GRPCService() = default; ///< Default constructor. + GRPCService(GRPCService const &) = delete; ///< Disabled copy-constructor. + GRPCService(GRPCService &&) = delete; ///< Disabled assignment copy-constructor. + ~GRPCService() override = default; ///< Destructor. + GRPCService &operator=(GRPCService const &) = delete; ///< Disabled assignment operator. + GRPCService &operator=(GRPCService &&) = delete; ///< Disabled move assignment operator. + void connectProxySignals(); ///< Connect the signals of the Qt Proxy to the GUI components + bool isStreaming() const; ///< Check if the service is currently streaming events. + + grpc::Status AddLogEntry(::grpc::ServerContext *context, ::grpc::AddLogEntryRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status GuiReady(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status Quit(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status Restart(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status ShowOnStartup(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status ShowSplashScreen(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status IsFirstGuiStart(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status SetIsAutostartOn(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status IsAutostartOn(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status SetIsBetaEnabled(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status IsBetaEnabled(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status GoOs(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status TriggerReset(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status Version(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status LogsPath(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status LicensePath(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status ReleaseNotesPageLink(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status DependencyLicensesLink(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status LandingPageLink(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status SetColorSchemeName(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status ColorSchemeName(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status CurrentEmailClient(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status ReportBug(::grpc::ServerContext *context, ::grpc::ReportBugRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status ForceLauncher(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status Login(::grpc::ServerContext *context, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status Login2FA(::grpc::ServerContext *context, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status Login2Passwords(::grpc::ServerContext *context, ::grpc::LoginRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status LoginAbort(::grpc::ServerContext *context, ::grpc::LoginAbortRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status CheckUpdate(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status InstallUpdate(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + grpc::Status SetIsAutomaticUpdateOn(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status IsAutomaticUpdateOn(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status IsCacheOnDiskEnabled(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status DiskCachePath(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status ChangeLocalCache(::grpc::ServerContext *context, ::grpc::ChangeLocalCacheRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status SetIsDoHEnabled(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status IsDoHEnabled(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status SetUseSslForSmtp(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status UseSslForSmtp(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status Hostname(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status ImapPort(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Int32Value *response) override; + grpc::Status SmtpPort(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Int32Value *response) override; + grpc::Status ChangePorts(::grpc::ServerContext *context, ::grpc::ChangePortsRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status IsPortFree(::grpc::ServerContext *context, ::google::protobuf::Int32Value const *request, ::google::protobuf::BoolValue *response) override; + grpc::Status AvailableKeychains(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::grpc::AvailableKeychainsResponse *response) override; + grpc::Status SetCurrentKeychain(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status CurrentKeychain(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::StringValue *response) override; + grpc::Status GetUserList(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::grpc::UserListResponse *response) override; + grpc::Status GetUser(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::grpc::User *response) override; + grpc::Status SetUserSplitMode(::grpc::ServerContext *context, ::grpc::UserSplitModeRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status LogoutUser(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status RemoveUser(::grpc::ServerContext *context, ::google::protobuf::StringValue const *request, ::google::protobuf::Empty *response) override; + grpc::Status ConfigureUserAppleMail(::grpc::ServerContext *context, ::grpc::ConfigureAppleMailRequest const *request, ::google::protobuf::Empty *response) override; + grpc::Status StartEventStream(::grpc::ServerContext *context, ::grpc::EventStreamRequest const *request, ::grpc::ServerWriter<::grpc::StreamEvent> *writer) override; + grpc::Status StopEventStream(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::Empty *response) override; + + bool sendEvent(bridgepp::SPStreamEvent const &event); ///< Queue an event for sending through the event stream. + +private: // data member + mutable QMutex eventStreamMutex_; ///< Mutex used to access eventQueue_, isStreaming_ and shouldStopStreaming_; + QList eventQueue_; ///< The event queue. Acces protected by eventStreamMutex_; + bool isStreaming_; ///< Is the gRPC stream running. Access protected by eventStreamMutex_; + bool eventStreamShouldStop_; ///< Should the stream be stopped? Access protected by eventStreamMutex + QString loginUsername_; ///< The username used for the current login procedure. + GRPCQtProxy qtProxy_; ///< Qt Proxy used to send signals, as this class is not a QObject. +}; + + +#endif // BRIDGE_GUI_TESTER_GRPC_SERVER_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.cpp new file mode 100644 index 00000000..606dd867 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.cpp @@ -0,0 +1,111 @@ +// 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 . + + +#include "MainWindow.h" +#include + + +using namespace bridgepp; + + +namespace +{ + + +//**************************************************************************************************************************************************** +/// \param[in] level The log level. +/// \param[in] message The log message. +/// \param[in] logEdit The plain text edit widget that displays the log. +//**************************************************************************************************************************************************** +void addEntryToLogEdit(bridgepp::Log::Level level, const QString &message, QPlainTextEdit &logEdit) +{ + /// \todo This may cause performance issue when log grows big. A better alternative should be implemented. + QString log = logEdit.toPlainText().trimmed(); + if (!log.isEmpty()) + log += "\n"; + logEdit.setPlainText(log + Log::logEntryToString(level, message)); +} + + +} // Anonymous namespace + + +//**************************************************************************************************************************************************** +/// \param[in] parent The parent widget of the window. +//**************************************************************************************************************************************************** +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + ui_.setupUi(this); + ui_.tabTop->setCurrentIndex(0); + ui_.tabBottom->setCurrentIndex(0); + ui_.splitter->setStretchFactor(0, 0); + ui_.splitter->setStretchFactor(1, 1); + ui_.splitter->setSizes({100, 10000}); + connect(&app().log(), &Log::entryAdded, this, &MainWindow::addLogEntry); + connect(&app().bridgeGUILog(), &Log::entryAdded, this, &MainWindow::addBridgeGUILogEntry); +} + + +//**************************************************************************************************************************************************** +/// \return A reference to the 'General' tab. +//**************************************************************************************************************************************************** +SettingsTab &MainWindow::settingsTab() +{ + return *ui_.settingsTab; +} + + +//**************************************************************************************************************************************************** +/// \return A reference to the users tab. +//**************************************************************************************************************************************************** +UsersTab &MainWindow::usersTab() +{ + return *ui_.usersTab; +} + + +//**************************************************************************************************************************************************** +/// \param[in] level The log level. +/// \param[in] message The log message +//**************************************************************************************************************************************************** +void MainWindow::addLogEntry(bridgepp::Log::Level level, const QString &message) +{ + addEntryToLogEdit(level, message, *ui_.editLog); +} + + +//**************************************************************************************************************************************************** +/// \param[in] level The log level. +/// \param[in] message The log message +//**************************************************************************************************************************************************** +void MainWindow::addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message) +{ + addEntryToLogEdit(level, message, *ui_.editBridgeGUILog); +} + + +//**************************************************************************************************************************************************** +/// \param[in] event The event. +//**************************************************************************************************************************************************** +void MainWindow::sendDelayedEvent(SPStreamEvent const &event) +{ + QTimer::singleShot(this->settingsTab().eventDelayMs(), [event] { app().grpc().sendEvent(event); }); +} + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.h b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.h new file mode 100644 index 00000000..d0c7c108 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.h @@ -0,0 +1,57 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_MAIN_WINDOW_H +#define BRIDGE_GUI_TESTER_MAIN_WINDOW_H + + +#include "ui_MainWindow.h" +#include "GRPCService.h" +#include + + +//********************************************************************************************************************** +/// \brief Main window class +//********************************************************************************************************************** +class MainWindow : public QMainWindow +{ +Q_OBJECT +public: // member functions. + explicit MainWindow(QWidget *parent); ///< Default constructor. + MainWindow(MainWindow const &) = delete; ///< Disabled copy-constructor. + MainWindow(MainWindow &&) = delete; ///< Disabled assignment copy-constructor. + ~MainWindow() override = default; ///< Destructor. + MainWindow &operator=(MainWindow const &) = delete; ///< Disabled assignment operator. + MainWindow &operator=(MainWindow &&) = delete; ///< Disabled move assignment operator. + + SettingsTab &settingsTab(); ///< Returns a reference the 'Settings' tab. + UsersTab &usersTab(); ///< Returns a reference to the 'Users' tab. + +public slots: + void sendDelayedEvent(bridgepp::SPStreamEvent const& event); ///< Sends a gRPC event after the delay specified in the UI. The call is non blocking. + +private slots: + void addLogEntry(bridgepp::Log::Level level, QString const &message); ///< Add an entry to the log. + void addBridgeGUILogEntry(bridgepp::Log::Level level, const QString &message); ///< Add an entry to the log. + +private: + Ui::MainWindow ui_ {}; ///< The GUI for the window. +}; + + +#endif // BRIDGE_GUI_TESTER_MAIN_WINDOW_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.ui b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.ui new file mode 100644 index 00000000..703e875f --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/MainWindow.ui @@ -0,0 +1,102 @@ + + + MainWindow + + + + 0 + 0 + 1226 + 1086 + + + + MainWindow + + + + + + + Qt::Vertical + + + + 0 + + + + Settings + + + + + Users + + + + + + 0 + + + + Log + + + + + + + Monaco + + + + true + + + + + + + + Bridge-GUI Log + + + + + + + Monaco + + + + true + + + + + + + + + + + + + + SettingsTab + QWidget +
Tabs/SettingsTab.h
+ 1 +
+ + UsersTab + QWidget +
Tabs/UsersTab.h
+ 1 +
+
+ + +
diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Pch.h b/internal/frontend/bridge-gui/bridge-gui-tester/Pch.h new file mode 100644 index 00000000..cb05660f --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Pch.h @@ -0,0 +1,29 @@ +// 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 . + + +#ifndef BRIDGE_GUI_PCH_H +#define BRIDGE_GUI_PCH_H + + +#include +#include +#include +#include "AppController.h" + + +#endif // BRIDGE_GUI_PCH_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp new file mode 100644 index 00000000..f410c469 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.cpp @@ -0,0 +1,496 @@ +// 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 . + + +#include "SettingsTab.h" +#include "GRPCService.h" +#include +#include + + +using namespace bridgepp; + + +namespace +{ +QString const colorSchemeDark = "dark"; ///< The dark color scheme name. +QString const colorSchemeLight = "light"; ///< THe light color scheme name. +} + + +//**************************************************************************************************************************************************** +/// \param[in] parent The parent widget of the tab. +//**************************************************************************************************************************************************** +SettingsTab::SettingsTab(QWidget *parent) + : QWidget(parent) +{ + ui_.setupUi(this); + + connect(ui_.buttonInternetOn, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(true)); }); + connect(ui_.buttonInternetOff, &QPushButton::clicked, []() { app().grpc().sendEvent(newInternetStatusEvent(false)); }); + connect(ui_.buttonShowMainWindow, &QPushButton::clicked, []() { app().grpc().sendEvent(newShowMainWindowEvent()); }); + connect(ui_.checkNextCacheChangeWillSucceed, &QCheckBox::toggled, this, &SettingsTab::updateGUIState); + this->resetUI(); + this->updateGUIState(); +} + + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void SettingsTab::updateGUIState() +{ + bool connected = app().grpc().isStreaming(); + for (QWidget *widget: { ui_.groupVersion, ui_.groupGeneral, ui_.groupMail, ui_.groupPaths, ui_.groupCache }) + widget->setEnabled(!connected); + ui_.comboCacheError -> setEnabled(!ui_.checkNextCacheChangeWillSucceed->isChecked()); +} + + +//**************************************************************************************************************************************************** +/// \param[in] isStreaming Is the event stream on? +//**************************************************************************************************************************************************** +void SettingsTab::setIsStreaming(bool isStreaming) +{ + ui_.labelStreamingValue->setText(isStreaming ? "Yes" : "No"); + this->updateGUIState(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] clientPlatform The client platform. +//**************************************************************************************************************************************************** +void SettingsTab::setClientPlatform(QString const &clientPlatform) +{ + ui_.labelClientPlatformValue->setText(clientPlatform); +} + + +//**************************************************************************************************************************************************** +/// \return The version of Bridge +//**************************************************************************************************************************************************** +QString SettingsTab::bridgeVersion() const +{ + return ui_.editVersion->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The OS as a Go GOOS compatible value ("darwin", "linux" or "windows"). +//**************************************************************************************************************************************************** +QString SettingsTab::os() const +{ + return ui_.comboOS->currentText(); +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'Current Email Client' edit. +//**************************************************************************************************************************************************** +QString SettingsTab::currentEmailClient() const +{ + return ui_.editCurrentEmailClient->text(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] ready Is the GUI ready? +//**************************************************************************************************************************************************** +void SettingsTab::setGUIReady(bool ready) +{ + this->updateGUIState(); + ui_.labelGUIReadyValue->setText(ready ? "Yes" : "No"); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the 'Show On Startup' check box is checked. +//**************************************************************************************************************************************************** +bool SettingsTab::showOnStartup() const +{ + return ui_.checkShowOnStartup->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the 'Show Splash Screen' check box is checked. +//**************************************************************************************************************************************************** +bool SettingsTab::showSplashScreen() const +{ + return ui_.checkShowSplashScreen->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the 'Show Splash Screen' check box is checked. +//**************************************************************************************************************************************************** +bool SettingsTab::isFirstGUIStart() const +{ + return ui_.checkIsFirstGUIStart->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the 'Show Splash Screen' check box is checked. +//**************************************************************************************************************************************************** +bool SettingsTab::isAutostartOn() const +{ + return ui_.checkAutostart->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the 'Show Splash Screen' check box is checked. +//**************************************************************************************************************************************************** +void SettingsTab::setIsAutostartOn(bool on) +{ + ui_.checkAutostart->setChecked(on); +} + + +//**************************************************************************************************************************************************** +/// \return true if the 'Use Dark Theme' check box is checked. +//**************************************************************************************************************************************************** +QString SettingsTab::colorSchemeName() const +{ + return ui_.checkDarkTheme->isChecked() ? colorSchemeDark : colorSchemeLight; +} + + +//**************************************************************************************************************************************************** +/// \param[in] name True if the 'Use Dark Theme' check box should be checked. +//**************************************************************************************************************************************************** +void SettingsTab::setColorSchemeName(QString const &name) +{ + ui_.checkDarkTheme->setChecked(name == colorSchemeDark); +} + + +//**************************************************************************************************************************************************** +/// \return true if the 'Beta Enabled' check box is checked. +//**************************************************************************************************************************************************** +bool SettingsTab::isBetaEnabled() const +{ + return ui_.checkBetaEnabled->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled The new state for the 'Beta Enabled' check box. +//**************************************************************************************************************************************************** +void SettingsTab::setIsBetaEnabled(bool enabled) +{ + ui_.checkBetaEnabled->setChecked(enabled); +} + + +//**************************************************************************************************************************************************** +/// \return The delay to apply before sending automatically generated events. +//**************************************************************************************************************************************************** +qint32 SettingsTab::eventDelayMs() const +{ + return ui_.spinEventDelay->value(); +} + + +//**************************************************************************************************************************************************** +/// \return The path +//**************************************************************************************************************************************************** +QString SettingsTab::logsPath() const +{ + return ui_.editLogsPath->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The path +//**************************************************************************************************************************************************** +QString SettingsTab::licensePath() const +{ + return ui_.editLicensePath->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The link. +//**************************************************************************************************************************************************** +QString SettingsTab::releaseNotesPageLink() const +{ + return ui_.editReleaseNotesLink->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The link. +//**************************************************************************************************************************************************** +QString SettingsTab::dependencyLicenseLink() const +{ + return ui_.editDependencyLicenseLink->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The link. +//**************************************************************************************************************************************************** +QString SettingsTab::landingPageLink() const +{ + return ui_.editLandingPageLink->text(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] osType The OS type. +/// \param[in] osVersion The OS version. +/// \param[in] emailClient The email client. +/// \param[in] address The email address. +/// \param[in] description The description. +/// \param[in] includeLogs Are the log included. +//**************************************************************************************************************************************************** +void SettingsTab::setBugReport(QString const &osType, QString const &osVersion, QString const &emailClient, QString const &address, + QString const &description, bool includeLogs) +{ + ui_.editOSType->setText(osType); + ui_.editOSVersion->setText(osVersion); + ui_.editEmailClient->setText(emailClient); + ui_.editAddress->setText(address); + ui_.editDescription->setPlainText(description); + ui_.labelIncludeLogsValue->setText(includeLogs ? "Yes" : "No"); +} + + +//**************************************************************************************************************************************************** +/// \return The state of the check box. +//**************************************************************************************************************************************************** +bool SettingsTab::nextBugReportWillSucceed() const +{ + return ui_.checkNextBugReportWillSucceed->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return The value of the 'Hostname' edit. +//**************************************************************************************************************************************************** +QString SettingsTab::hostname() const +{ + return ui_.editHostname->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The value of the IMAP port spin box. +//**************************************************************************************************************************************************** +qint32 SettingsTab::imapPort() +{ + return ui_.spinPortIMAP->value(); +} + + +//**************************************************************************************************************************************************** +/// \return The value of the SMTP port spin box. +//**************************************************************************************************************************************************** +qint32 SettingsTab::smtpPort() +{ + return ui_.spinPortSMTP->value(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] imapPort The IMAP port. +/// \param[in] smtpPort The SMTP port. +//**************************************************************************************************************************************************** +void SettingsTab::changePorts(qint32 imapPort, qint32 smtpPort) +{ + ui_.spinPortIMAP->setValue(imapPort); + ui_.spinPortSMTP->setValue(smtpPort); +} + + +//**************************************************************************************************************************************************** +/// \return The state of the 'Use SSL for SMTP' check box. +//**************************************************************************************************************************************************** +bool SettingsTab::useSSLForSMTP() const +{ + return ui_.checkUseSSLForSMTP->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] use The state of the 'Use SSL for SMTP' check box. +//**************************************************************************************************************************************************** +void SettingsTab::setUseSSLForSMTP(bool use) +{ + ui_.checkUseSSLForSMTP->setChecked(use); +} + + +//**************************************************************************************************************************************************** +/// \return The state of the the 'DoH enabled' check box. +//**************************************************************************************************************************************************** +bool SettingsTab::isDoHEnabled() const +{ + return ui_.checkDoHEnabled->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled The state of the 'DoH enabled' check box. +//**************************************************************************************************************************************************** +void SettingsTab::setIsDoHEnabled(bool enabled) +{ + ui_.checkDoHEnabled->setChecked(enabled); +} + + +//**************************************************************************************************************************************************** +/// \return The reply for the next IsPortFree gRPC call. +//**************************************************************************************************************************************************** +bool SettingsTab::isPortFree() const +{ + return ui_.checkIsPortFree->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff cache on disk is enabled. +//**************************************************************************************************************************************************** +bool SettingsTab::isCacheOnDiskEnabled() const +{ + return ui_.checkCacheOnDiskEnabled->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled Is the cache on disk enabled? +//**************************************************************************************************************************************************** +void SettingsTab::changeLocalCache(bool enabled, QString const &path) +{ + ui_.checkCacheOnDiskEnabled->setChecked(enabled); + ui_.editDiskCachePath->setText(path); +} + + +//**************************************************************************************************************************************************** +/// \return The disk cache path. +//**************************************************************************************************************************************************** +QString SettingsTab::diskCachePath() const +{ + return ui_.editDiskCachePath->text(); +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'Next Cache Change Will Succeed' check box. +//**************************************************************************************************************************************************** +bool SettingsTab::nextCacheChangeWillSucceed() const +{ + return ui_.checkNextCacheChangeWillSucceed->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return The index of the selected cache error. +//**************************************************************************************************************************************************** +qint32 SettingsTab::cacheError() const +{ + return ui_.comboCacheError->currentIndex(); +} + + +//**************************************************************************************************************************************************** +/// \return the value for the 'Automatic Update' check. +//**************************************************************************************************************************************************** +bool SettingsTab::isAutomaticUpdateOn() const +{ + return ui_.checkAutomaticUpdate->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] on The value for the 'Automatic Update' check. +//**************************************************************************************************************************************************** +void SettingsTab::setIsAutomaticUpdateOn(bool on) +{ + ui_.checkAutomaticUpdate->setChecked(on); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void SettingsTab::resetUI() +{ + this->setGUIReady(false); + this->setIsStreaming(false); + this->setClientPlatform("Unknown"); + + ui_.editVersion->setText(BRIDGE_APP_VERSION); + ui_.comboOS->setCurrentText(bridgepp::goos()); + ui_.editCurrentEmailClient->setText("Thunderbird/102.0.3"); + ui_.checkShowOnStartup->setChecked(true); + ui_.checkShowSplashScreen->setChecked(false); + ui_.checkIsFirstGUIStart->setChecked(false); + ui_.checkAutostart->setChecked(true); + ui_.checkBetaEnabled->setChecked(true); + ui_.checkDarkTheme->setChecked(false); + + QString const tmpDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + + QString const logsDir = QDir(tmpDir).absoluteFilePath("logs"); + QDir().mkpath(logsDir); + ui_.editLogsPath->setText(QDir::toNativeSeparators(logsDir)); + + QString const filePath = QDir(tmpDir).absoluteFilePath("LICENSE.txt"); + QFile file(filePath); + if (!file.exists()) + { + // we don't really care if it fails. + file.open(QIODevice::WriteOnly | QIODevice::Text); + file.write(QString("This is were the license should be.").toLocal8Bit()); + file.close(); + } + ui_.editLicensePath->setText(filePath); + + ui_.editReleaseNotesLink->setText("https://en.wikipedia.org/wiki/Release_notes"); + ui_.editDependencyLicenseLink->setText("https://en.wikipedia.org/wiki/Dependency_relation"); + ui_.editLandingPageLink->setText("https://proton.me"); + + ui_.editOSType->setText(QString()); + ui_.editOSVersion->setText(QString()); + ui_.editEmailClient->setText(QString()); + ui_.editAddress->setText(QString()); + ui_.editDescription->setPlainText(QString()); + ui_.labelIncludeLogsValue->setText(QString()); + ui_.checkNextBugReportWillSucceed->setChecked(true); + + ui_.editHostname->setText("localhost"); + ui_.spinPortIMAP->setValue(1143); + ui_.spinPortSMTP->setValue(1025); + ui_.checkUseSSLForSMTP->setChecked(false); + ui_.checkDoHEnabled->setChecked(true); + ui_.checkIsPortFree->setChecked(true); + + ui_.checkCacheOnDiskEnabled->setChecked(true); + QString const cacheDir = QDir(tmpDir).absoluteFilePath("cache"); + QDir().mkpath(cacheDir); + ui_.editDiskCachePath->setText(QDir::toNativeSeparators(cacheDir)); + ui_.checkNextCacheChangeWillSucceed->setChecked(true); + ui_.comboCacheError->setCurrentIndex(0); + + ui_.checkAutomaticUpdate->setChecked(true); +} + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h new file mode 100644 index 00000000..66f453f5 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/SettingsTab.h @@ -0,0 +1,92 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_GENERAL_TAB_H +#define BRIDGE_GUI_TESTER_GENERAL_TAB_H + + +#include "Tab/ui_SettingsTab.h" + + +//**************************************************************************************************************************************************** +/// \brief The 'General' tab of the main window. +//**************************************************************************************************************************************************** +class SettingsTab : public QWidget +{ +Q_OBJECT +public: // member functions. + explicit SettingsTab(QWidget *parent = nullptr); ///< Default constructor. + SettingsTab(SettingsTab const &) = delete; ///< Disabled copy-constructor. + SettingsTab(SettingsTab &&) = delete; ///< Disabled assignment copy-constructor. + ~SettingsTab() = default; ///< Destructor. + SettingsTab &operator=(SettingsTab const &) = delete; ///< Disabled assignment operator. + SettingsTab &operator=(SettingsTab &&) = delete; ///< Disabled move assignment operator. + + QString bridgeVersion() const; ///< Get the Bridge version. + QString os() const; ///< Return the OS string. + QString currentEmailClient() const; ///< Return the content of the current email client + void setGUIReady(bool ready); ///< Set the GUI as ready. + bool showOnStartup() const; ///< Get the value for the 'Show On Startup' check. + bool showSplashScreen() const; ///< Get the value for the 'Show Splash Screen' check. + bool isFirstGUIStart() const; ///< Get the value for the 'Is First GUI Start' check. + bool isAutostartOn() const; ///< Get the value for the 'Autostart' check. + bool isBetaEnabled() const; ///< Get the value for the 'Beta Enabled' check. + QString colorSchemeName() const; ///< Get the value of the 'Use Dark Theme' checkbox. + qint32 eventDelayMs() const; ///< Get the delay for sending automatically generated events. + QString logsPath() const; ///< Get the content of the 'Logs Path' edit. + QString licensePath() const; ///< Get the content of the 'License Path' edit. + QString releaseNotesPageLink() const; ///< Get the content of the 'Release Notes Page Link' edit. + QString dependencyLicenseLink() const; ///< Get the content of the 'Dependency License Link' edit. + QString landingPageLink() const; ///< Get the content of the 'Landing Page Link' edit. + bool nextBugReportWillSucceed() const; ///< Get the status of the 'Next Bug Report Will Fail' check box. + QString hostname() const; ///< Get the value of the 'Hostname' edit. + qint32 imapPort(); ///< Get the value of the IMAP port spin. + qint32 smtpPort(); ///< Get the value of the SMTP port spin. + bool useSSLForSMTP() const; ///< Get the value for the 'Use SSL for SMTP' check box. + bool isDoHEnabled() const; ///< Get the value for the 'DoH Enabled' check box. + bool isPortFree() const; ///< Get the value for the "Is Port Free" check box. + bool isCacheOnDiskEnabled() const; ///< get the value for the 'Cache On Disk Enabled' check box. + QString diskCachePath() const; ///< Get the value for the 'Disk Cache Path' edit. + bool nextCacheChangeWillSucceed() const; ///< Get the value for the 'Next Cache Change will succeed' edit. + qint32 cacheError() const; ///< Return the index of the selected cache error. + bool isAutomaticUpdateOn() const; /// + + SettingsTab + + + + 0 + 0 + 1066 + 808 + + + + Form + + + + + + + + + + + 0 + 0 + + + + Version Info + + + + + + + + Bridge Version + + + + + + + + + + + + + + OS + + + + + + + + darwin + + + + + linux + + + + + windows + + + + + + + + Current Email Client + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + General Settings + + + + + + Show On Startup + + + false + + + + + + + Show Splash Screen + + + false + + + + + + + Is FIrst GUI Start + + + false + + + + + + + Autostart + + + false + + + + + + + Beta Enabled + + + false + + + + + + + Dark Theme + + + false + + + + + + + Automatic Update + + + + + + + + + + Mail + + + + + + + + Hostname + + + + + + + + 200 + 0 + + + + + + + + + + + + IMAP Port + + + + + + + QAbstractSpinBox::NoButtons + + + 1 + + + 65535 + + + + + + + SMTP Port + + + + + + + QAbstractSpinBox::NoButtons + + + 1 + + + 65535 + + + + + + + Use SSL For SMTP + + + + + + + + + DoH Enabled + + + + + + + + + + + 0 + 0 + + + + Paths && Links + + + + + + Logs Path + + + + + + + + + + License Path + + + + + + + + + + Release Notes Page Link + + + + + + + + + + Dependency License Link + + + + + + + + + + Landing Page Link + + + + + + + + + + + + + + 0 + 0 + + + + Cache + + + + + + Cache On Disk Enabled + + + + + + + + + Disk Cache Path + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + 0 + + + + Status + + + + + + + + GUI Ready: + + + + + + + + 50 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + + + + Streaming: + + + + + + + + 0 + 0 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Client Platform: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + 0 + 0 + + + + Bug Report + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + true + + + + + + + + 0 + 0 + + + + true + + + + + + + OS Type + + + + + + + Include Logs + + + + + + + Address + + + + + + + OS Version + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + true + + + + + + + ? + + + + + + + Description + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Email Cient + + + + + + + true + + + + + + + + + + + 0 + 0 + + + + Events && Errors + + + + + + + + Delay applied before sending automatically generated events + + + Delay for asynchronous events + + + + + + + ms + + + 3600000 + + + 100 + + + 1000 + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + + + + Internet Off + + + + + + + Qt::Horizontal + + + + 0 + 10 + + + + + + + + Internet On + + + + + + + Qt::Horizontal + + + + 0 + 10 + + + + + + + + Show Main Window + + + + + + + + + Reply true to the next 'Is Port Free' request. + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Next Cache Change will succeed + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + + Cache Unavailable + + + + + Can't Move Cache + + + + + Disk Full + + + + + + + + + + Next Bug Report Will Succeed + + + + + + + + + + + + + + checkShowOnStartup + checkShowSplashScreen + checkIsFirstGUIStart + checkAutostart + checkBetaEnabled + checkDarkTheme + editVersion + comboOS + editCurrentEmailClient + editOSType + editOSVersion + editEmailClient + editAddress + editDescription + editLogsPath + editLicensePath + editReleaseNotesLink + editDependencyLicenseLink + editLandingPageLink + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp new file mode 100644 index 00000000..76ff5650 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp @@ -0,0 +1,318 @@ +// 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 . + + +#include "UsersTab.h" +#include "MainWindow.h" +#include "UserDialog.h" +#include +#include +#include + + +using namespace bridgepp; + + +//**************************************************************************************************************************************************** +/// \param[in] parent The parent widget of the tab. +//**************************************************************************************************************************************************** +UsersTab::UsersTab(QWidget *parent) + : QWidget(parent) + , users_(nullptr) +{ + ui_.setupUi(this); + + ui_.tableUserList->setModel(&users_); + + QItemSelectionModel *model = ui_.tableUserList->selectionModel(); + if (!model) + throw Exception("Could not get user table selection model."); + connect(model, &QItemSelectionModel::selectionChanged, this, &UsersTab::onSelectionChanged); + + ui_.tableUserList->setColumnWidth(0, 150); + ui_.tableUserList->setColumnWidth(1, 250); + ui_.tableUserList->setColumnWidth(2, 350); + + connect(ui_.buttonNewUser, &QPushButton::clicked, this, &UsersTab::onAddUserButton); + connect(ui_.buttonEditUser, &QPushButton::clicked, this, &UsersTab::onEditUserButton); + connect(ui_.tableUserList, &QTableView::doubleClicked, this, &UsersTab::onEditUserButton); + connect(ui_.buttonRemoveUser, &QPushButton::clicked, this, &UsersTab::onRemoveUserButton); + + users_.append(randomUser()); + + this->updateGUIState(); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onAddUserButton() +{ + SPUser user = randomUser(); + UserDialog dialog(user, this); + if (QDialog::Accepted != dialog.exec()) + return; + users_.append(user); + GRPCService &grpc = app().grpc(); + if (grpc.isStreaming()) + grpc.sendEvent(newLoginFinishedEvent(user->id())); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onEditUserButton() +{ + int index = selectedIndex(); + if ((index < 0) || (index >= users_.userCount())) + return; + + SPUser user = this->selectedUser(); + UserDialog dialog(user, this); + if (QDialog::Accepted != dialog.exec()) + return; + + users_.touch(index); + GRPCService &grpc = app().grpc(); + if (grpc.isStreaming()) + grpc.sendEvent(newUserChangedEvent(user->id())); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onRemoveUserButton() +{ + int index = selectedIndex(); + if ((index < 0) || (index >= users_.userCount())) + return; + + SPUser const user = users_.userAtIndex(index); + users_.remove(index); + GRPCService &grpc = app().grpc(); + if (grpc.isStreaming()) + grpc.sendEvent(newUserChangedEvent(user->id())); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onSelectionChanged(QItemSelection, QItemSelection) +{ + this->updateGUIState(); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::updateGUIState() +{ + bool const hasSelectedUser = ui_.tableUserList->selectionModel()->hasSelection(); + ui_.buttonEditUser->setEnabled(hasSelectedUser); + ui_.buttonRemoveUser->setEnabled(hasSelectedUser); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +qint32 UsersTab::selectedIndex() const +{ + return ui_.tableUserList->selectionModel()->hasSelection() ? ui_.tableUserList->currentIndex().row() : -1; +} + + +//**************************************************************************************************************************************************** +/// \return The selected user. +/// \return A null pointer if no user is selected. +//**************************************************************************************************************************************************** +bridgepp::SPUser UsersTab::selectedUser() +{ + return users_.userAtIndex(this->selectedIndex()); +} + + +//**************************************************************************************************************************************************** +/// \return The list of users. +//**************************************************************************************************************************************************** +UserTable &UsersTab::userTable() +{ + return users_; +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The user with the given userID. +/// \return A null pointer if the user is not in the list. +//**************************************************************************************************************************************************** +bridgepp::SPUser UsersTab::userWithID(QString const &userID) +{ + return users_.userWithID(userID); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a username/password error. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserUsernamePasswordError() const +{ + return ui_.checkUsernamePasswordError->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a free user error. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserFreeUserError() const +{ + return ui_.checkFreeUserError->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt will require 2FA. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTFARequired() const +{ + return ui_.checkTFARequired->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a 2FA error. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTFAError() const +{ + return ui_.checkTFAError->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a 2FA error with abort. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTFAAbort() const +{ + return ui_.checkTFAAbort->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt will require a 2nd password. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTwoPasswordsRequired() const +{ + return ui_.checkTwoPasswordsRequired->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a 2nd password error. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTwoPasswordsError() const +{ + return ui_.checkTwoPasswordsError->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a 2nd password error with abort. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserTwoPasswordsAbort() const +{ + return ui_.checkTwoPasswordsAbort->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the next login attempt should trigger a 2nd password error with abort. +//**************************************************************************************************************************************************** +bool UsersTab::nextUserAlreadyLoggedIn() const +{ + return ui_.checkAlreadyLoggedIn->isChecked(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] makeItActive Should split mode be activated. +//**************************************************************************************************************************************************** +void UsersTab::setUserSplitMode(QString const &userID, bool makeItActive) +{ + qint32 const index = users_.indexOfUser(userID); + SPUser const user = users_.userAtIndex(index); + if (!user) + { + app().log().error(QString("%1 failed. unknown user %1").arg(__FUNCTION__, userID)); + return; + } + user->setSplitMode(makeItActive); + users_.touch(index); + MainWindow &mainWindow = app().mainWindow(); + mainWindow.sendDelayedEvent(newUserChangedEvent(userID)); + mainWindow.sendDelayedEvent(newToggleSplitModeFinishedEvent(userID)); +} + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void UsersTab::logoutUser(QString const &userID) +{ + qint32 const index = users_.indexOfUser(userID); + SPUser const user = users_.userAtIndex(index); + if (!user) + { + app().log().error(QString("%1 failed. unknown user %1").arg(__FUNCTION__, userID)); + return; + } + user->setLoggedIn(false); + users_.touch(index); + app().mainWindow().sendDelayedEvent(newUserChangedEvent(userID)); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void UsersTab::removeUser(QString const &userID) +{ + qint32 const index = users_.indexOfUser(userID); + SPUser const user = users_.userAtIndex(index); + if (!user) + { + app().log().error(QString("%1 failed. unknown user %1").arg(__FUNCTION__, userID)); + return; + } + users_.remove(index); + app().mainWindow().sendDelayedEvent(newUserChangedEvent(userID)); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] address The address. +//**************************************************************************************************************************************************** +void UsersTab::configureUserAppleMail(QString const &userID, QString const &address) +{ + app().log().info(QString("Apple mail configuration was requested for user %1, address %2").arg(userID, address)); + +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h new file mode 100644 index 00000000..55a44c49 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h @@ -0,0 +1,75 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_USERS_TAB_H +#define BRIDGE_GUI_TESTER_USERS_TAB_H + + +#include "Tabs/ui_UsersTab.h" +#include "UserTable.h" + + +//**************************************************************************************************************************************************** +/// \brief The 'Users' tab of the main window. +//**************************************************************************************************************************************************** +class UsersTab : public QWidget +{ +Q_OBJECT +public: // member functions. + explicit UsersTab(QWidget *parent = nullptr); ///< Default constructor. + UsersTab(UsersTab const &) = delete; ///< Disabled copy-constructor. + UsersTab(UsersTab &&) = delete; ///< Disabled assignment copy-constructor. + ~UsersTab() override = default; ///< Destructor. + UsersTab &operator=(UsersTab const &) = delete; ///< Disabled assignment operator. + UsersTab &operator=(UsersTab &&) = delete; ///< Disabled move assignment operator. + UserTable &userTable(); ///< Returns a reference to the user table. + bridgepp::SPUser userWithID(QString const &userID); ///< Get the user with the given ID. + bool nextUserUsernamePasswordError() const; ///< Check if next user login should trigger a username/password error. + bool nextUserFreeUserError() const; ///< Check if next user login should trigger a Free user error. + bool nextUserTFARequired() const; ///< Check if next user login should requires 2FA. + bool nextUserTFAError() const; ///< Check if next user login should trigger 2FA error + bool nextUserTFAAbort() const; ///< Check if next user login should trigger 2FA abort. + bool nextUserTwoPasswordsRequired() const; ///< Check if next user login requires 2nd password + bool nextUserTwoPasswordsError() const; ///< Check if next user login should trigger 2nd password error. + bool nextUserTwoPasswordsAbort() const; ///< Check if next user login should trigger 2nd password abort. + bool nextUserAlreadyLoggedIn() const; ///< Check if next user login should report user as already logged in. + +public slots: + void setUserSplitMode(QString const &userID, bool makeItActive); ///< Slot for the split mode. + void logoutUser(QString const &userID); ///< slot for the logging out of a user. + void removeUser(QString const &userID); ///< Slot for the removal of a user. + void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail. + +private slots: + void onAddUserButton(); ///< Add a user to the user list. + void onEditUserButton(); ///< Edit the currently selected user. + void onRemoveUserButton(); ///< Remove the currently selected user. + void onSelectionChanged(QItemSelection, QItemSelection); ///< Slot for the change of the selection. + +private: // member functions. + void updateGUIState(); ///< Update the GUI state. + qint32 selectedIndex() const; ///< Get the index of the selected row. + bridgepp::SPUser selectedUser(); ///< Get the selected user. + +private: // data members. + Ui::UsersTab ui_ {}; ///< The UI for the tab. + UserTable users_; ///< The User list. +}; + + +#endif //BRIDGE_GUI_TESTER_USERS_TAB_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui new file mode 100644 index 00000000..ce297a51 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui @@ -0,0 +1,159 @@ + + + UsersTab + + + + 0 + 0 + 1207 + 709 + + + + Form + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + + + New User + + + + + + + Edit User + + + + + + + Remove User + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 100 + + + + Next Login Attempt + + + + + + Username/password error + + + + + + + Free user error + + + + + + + 2FA required + + + + + + + 2FA error + + + + + + + 2FA abort + + + + + + + 2nd password required + + + + + + + 2nd password error + + + + + + + 2nd password abort + + + + + + + Already logged in + + + + + + + + + + + + buttonNewUser + buttonEditUser + buttonRemoveUser + tableUserList + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.cpp new file mode 100644 index 00000000..1003aedf --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.cpp @@ -0,0 +1,65 @@ +// 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 . + + +#include "UserDialog.h" + + +//**************************************************************************************************************************************************** +/// \param[in] user The user. +/// \param[in] parent The parent widget of the dialog. +//**************************************************************************************************************************************************** +UserDialog::UserDialog(bridgepp::SPUser &user, QWidget *parent) + : QDialog(parent) + , user_(user) +{ + ui_.setupUi(this); + + connect(ui_.buttonOK, &QPushButton::clicked, this, &UserDialog::onOK); + connect(ui_.buttonCancel, &QPushButton::clicked, this, &UserDialog::reject); + + ui_.editUserID->setText(user_->id()); + ui_.editUsername->setText(user_->username()); + ui_.editPassword->setText(user->password()); + ui_.editAddresses->setPlainText(user->addresses().join("\n")); + ui_.editAvatarText->setText(user_->avatarText()); + ui_.checkLoggedIn->setChecked(user_->loggedIn()); + ui_.checkSplitMode->setChecked(user_->splitMode()); + ui_.checkSetupGuideSeen->setChecked(user_->setupGuideSeen()); + ui_.spinUsedBytes->setValue(user->usedBytes()); + ui_.spinTotalBytes->setValue(user->totalBytes()); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UserDialog::onOK() +{ + user_->setID(ui_.editUserID->text()); + user_->setUsername(ui_.editUsername->text()); + user_->setPassword(ui_.editPassword->text()); + user_->setAddresses(ui_.editAddresses->toPlainText().split(QRegularExpression(R"(\s+)"), Qt::SkipEmptyParts)); + user_->setAvatarText(ui_.editAvatarText->text()); + user_->setLoggedIn(ui_.checkLoggedIn->isChecked()); + user_->setSplitMode(ui_.checkSplitMode->isChecked()); + user_->setSetupGuideSeen(ui_.checkSetupGuideSeen->isChecked()); + user_->setUsedBytes(float(ui_.spinUsedBytes->value())); + user_->setTotalBytes(float(ui_.spinTotalBytes->value())); + + this->accept(); +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.h b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.h new file mode 100644 index 00000000..2ec081ca --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.h @@ -0,0 +1,49 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_USER_DIALOG_H +#define BRIDGE_GUI_TESTER_USER_DIALOG_H + + +#include "ui_UserDialog.h" +#include + +//**************************************************************************************************************************************************** +/// \brief User dialog class. +//**************************************************************************************************************************************************** +class UserDialog : public QDialog +{ +Q_OBJECT +public: // member functions. + UserDialog(bridgepp::SPUser &user, QWidget *parent); ///< Default constructor. + UserDialog(UserDialog const &) = delete; ///< Disabled copy-constructor. + UserDialog(UserDialog &&) = delete; ///< Disabled assignment copy-constructor. + ~UserDialog() override = default; ///< Destructor. + UserDialog &operator=(UserDialog const &) = delete; ///< Disabled assignment operator. + UserDialog &operator=(UserDialog &&) = delete; ///< Disabled move assignment operator. + +private slots: + void onOK(); ///< Slot for the OK button. + +private: + Ui::UserDialog ui_ {}; ///< The UI for the dialog. + bridgepp::SPUser user_; ///< The user +}; + + +#endif //BRIDGE_GUI_TESTER_USER_DIALOG_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.ui b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.ui new file mode 100644 index 00000000..138ab000 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/UserDialog.ui @@ -0,0 +1,220 @@ + + + UserDialog + + + + 0 + 0 + 521 + 432 + + + + User + + + + + + + + Avatar Text + + + + + + + Password + + + + + + + Account Name + + + + + + + UserID + + + + + + + Used Bytes + + + + + + + Setup Guide Seen + + + + + + + + + + Total Bytes + + + + + + + + + + Adresses + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 0 + 100 + + + + + 0 + 150 + + + + true + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 1000000000000000.000000000000000 + + + + + + + true + + + + + + + Logged in + + + + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 1000000000000000.000000000000000 + + + + + + + Split Mode + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + &Cancel + + + + + + + &OK + + + true + + + + + + + + + editUserID + editUsername + editPassword + editAddresses + editAvatarText + spinUsedBytes + spinTotalBytes + checkLoggedIn + checkSplitMode + checkSetupGuideSeen + buttonOK + buttonCancel + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.cpp new file mode 100644 index 00000000..1411bc2e --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.cpp @@ -0,0 +1,207 @@ +// 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 . + + +#include "UserTable.h" + + +using namespace bridgepp; + + +//**************************************************************************************************************************************************** +/// \param[in] parent The parent object of the class +//**************************************************************************************************************************************************** +UserTable::UserTable(QObject *parent) + : QAbstractTableModel(parent) +{ + +} + + +//**************************************************************************************************************************************************** +/// \return The number of rows in the table. +//**************************************************************************************************************************************************** +int UserTable::rowCount(QModelIndex const &) const +{ + return users_.size(); +} + + +//**************************************************************************************************************************************************** +/// \return The number of columns in the table. +//**************************************************************************************************************************************************** +int UserTable::columnCount(QModelIndex const &) const +{ + return 3; +} + + +//**************************************************************************************************************************************************** +/// \param[in] +//**************************************************************************************************************************************************** +QVariant UserTable::data(QModelIndex const &index, int role) const +{ + int const row = index.row(); + if ((row < 0) || (row >= users_.size()) || (Qt::DisplayRole != role)) + return QVariant(); + + SPUser const user = users_[row]; + if (!user) + return QVariant(); + + switch (index.column()) + { + case 0: + return user->property("username"); + case 1: + return user->property("addresses").toStringList().join(" "); + case 2: + return user->property("id"); + default: + return QVariant(); + } +} + + +//**************************************************************************************************************************************************** +/// \param[in] section The section (column). +/// \param[in] orientation The orientation. +/// \param[in] role The role to retrieve data +//**************************************************************************************************************************************************** +QVariant UserTable::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (Qt::DisplayRole != role) + return QAbstractTableModel::headerData(section, orientation, role); + + if (Qt::Horizontal != orientation) + return QString(); + + switch (section) + { + case 0: + return "UserName"; + case 1: + return "Addresses"; + case 2: + return "UserID"; + default: + return QString(); + } +} + + +//**************************************************************************************************************************************************** +/// \param[in] user The user to add. +//**************************************************************************************************************************************************** +void UserTable::append(SPUser const &user) +{ + qint32 const count = users_.size(); + this->beginInsertRows(QModelIndex(), count, count); + users_.append(user); + this->endInsertRows(); +} + + +//**************************************************************************************************************************************************** +/// \return The number of users in the table. +//**************************************************************************************************************************************************** +qint32 UserTable::userCount() const +{ + return users_.count(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] index The index of the user in the list. +/// \return the user at the given index. +/// \return null if the index is out of bounds. +//**************************************************************************************************************************************************** +bridgepp::SPUser UserTable::userAtIndex(qint32 index) +{ + return isIndexValid(index) ? users_[index] : nullptr; +} + + +//**************************************************************************************************************************************************** +/// \return The user with the given userID. +/// \return A null pointer if the user is not in the list. +//**************************************************************************************************************************************************** +bridgepp::SPUser UserTable::userWithID(QString const &userID) +{ + QList::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const& user) -> bool { + return user->id() == userID; + }); + + return it == users_.end() ? nullptr : *it; +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return the index of the user. +/// \return -1 if the user could not be found. +//**************************************************************************************************************************************************** +qint32 UserTable::indexOfUser(QString const &userID) +{ + QList::const_iterator it = std::find_if(users_.constBegin(), users_.constEnd(), [&userID](SPUser const& user) -> bool { + return user->id() == userID; + }); + + return it == users_.end() ? -1 : it - users_.constBegin(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] index The index of the user in the list. +//**************************************************************************************************************************************************** +void UserTable::touch(qint32 index) +{ + if (isIndexValid(index)) + emit dataChanged(this->index(index, 0), this->index(index, this->columnCount(QModelIndex()) - 1)); +} + + +//**************************************************************************************************************************************************** +/// \param[in] index The index of the user in the list. +//**************************************************************************************************************************************************** +void UserTable::remove(qint32 index) +{ + if (!isIndexValid(index)) + return; + + this->beginRemoveRows(QModelIndex(), index, index); + users_.removeAt(index); + this->endRemoveRows(); +} + + +//**************************************************************************************************************************************************** +/// \return true iff the index is valid. +//**************************************************************************************************************************************************** +bool UserTable::isIndexValid(qint32 index) const +{ + return (index >= 0) && (index < users_.count()); +} + + +//**************************************************************************************************************************************************** +/// \return The user list. +//**************************************************************************************************************************************************** +QList UserTable::users() const +{ + return users_; +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.h b/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.h new file mode 100644 index 00000000..4705a789 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/UserTable.h @@ -0,0 +1,61 @@ +// 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 . + + + +#ifndef BRIDGE_GUI_TESTER_USER_TABLE_H +#define BRIDGE_GUI_TESTER_USER_TABLE_H + + +#include + +//**************************************************************************************************************************************************** +/// \brief User table model class +//**************************************************************************************************************************************************** + +class UserTable : public QAbstractTableModel +{ +Q_OBJECT +public: // member functions. + explicit UserTable(QObject *parent); ///< Default constructor. + UserTable(UserTable const &) = delete; ///< Disabled copy-constructor. + UserTable(UserTable &&) = delete; ///< Disabled assignment copy-constructor. + ~UserTable() = default; ///< Destructor. + UserTable &operator=(UserTable const &) = delete; ///< Disabled assignment operator. + UserTable &operator=(UserTable &&) = delete; ///< Disabled move assignment operator. + qint32 userCount() const; ///< Return the number of users in the table. + void append(bridgepp::SPUser const& user); ///< Append a user. + bridgepp::SPUser userAtIndex(qint32 index); ///< Return the user at the given index. + bridgepp::SPUser userWithID(QString const &userID); ///< Return the user with a given id. + qint32 indexOfUser(QString const& userID); ///< Return the index of a given User. + void touch(qint32 index); ///< touch the user at a given index (indicates it has been modified). + void remove(qint32 index); ///< Remove the user at a given index. + QList users() const; ///< Return a copy of the user list. + +private: // data members. + int rowCount(QModelIndex const &parent) const override; ///< Get the number of rows in the table. + int columnCount(QModelIndex const &parent) const override; ///< Get the number of columns in the table. + QVariant data(QModelIndex const &index, int role) const override; ///< Get the data for a role at a given index. + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; ///< Get header data. + bool isIndexValid(qint32 index) const; ///< return true iff the index is valid. + +public: + QList users_; +}; + + +#endif //BRIDGE_GUI_TESTER_USER_TABLE_H diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp new file mode 100644 index 00000000..0801bfb3 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp @@ -0,0 +1,98 @@ +// 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 . + + +#include "MainWindow.h" +#include "AppController.h" +#include "GRPCServerWorker.h" +#include +#include +#include + + +#ifndef BRIDGE_APP_VERSION +#error "BRIDGE_APP_VERSION is not defined" +#endif + + +namespace +{ + + +QString const applicationName = "Proton Mail Bridge GUI Tester"; ///< The name of the application. + + +} + + +using namespace bridgepp; + + +//**************************************************************************************************************************************************** +/// \param[in] argc The number of command-line arguments. +/// \param[in] argv The list of command-line arguments. +/// \return The exit code for the application. +//**************************************************************************************************************************************************** +int main(int argc, char **argv) +{ + + try + { + QApplication a(argc, argv); + QApplication::setApplicationName(applicationName); + QApplication::setOrganizationName("Proton AG"); + QApplication::setOrganizationDomain("proton.ch"); + QApplication::setQuitOnLastWindowClosed(true); + + app().log().setEchoInConsole(true); + app().log().info(QString("%1 started.").arg(applicationName)); + + MainWindow window(nullptr); + app().setMainWindow(&window); + window.setWindowTitle(QApplication::applicationName()); + window.show(); + + GRPCServerWorker *serverWorker = new GRPCServerWorker(nullptr); + QObject::connect(serverWorker, &Worker::started, []() { app().log().info("Server worker started."); }); + QObject::connect(serverWorker, &Worker::finished, []() { app().log().info("Server worker finished."); }); + QObject::connect(serverWorker, &Worker::error, [](QString const &message) { + throw Exception(QString("gRPC Server encountered an error: %1").arg(message)); + }); + UPOverseer overseer = std::make_unique(serverWorker, nullptr); + overseer->startWorker(true); + + qint32 const exitCode = QApplication::exec(); + + serverWorker->stop(); + while (!overseer->isFinished()) + { + QThread::msleep(10); + } + + app().log().info(QString("%1 exiting with code %2.").arg(applicationName).arg(exitCode)); + + return exitCode; + + } + catch (Exception const &e) + { + QTextStream(stderr) << QString("A fatal error occurred: %1\n").arg(e.qwhat()); + return EXIT_FAILURE; + } +} + + diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 34e59a12..dde3a11d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -108,8 +108,10 @@ void QMLBackend::connectGrpcEvents() connect(client, &GRPCClient::login2PasswordError, this, &QMLBackend::login2PasswordError); connect(client, &GRPCClient::login2PasswordErrorAbort, this, &QMLBackend::login2PasswordErrorAbort); connect(client, &GRPCClient::loginFinished, this, [&](QString const &userID) { + this->retrieveUserList(); qint32 const index = users_->rowOfUserID(userID); emit loginFinished(index); }); connect(client, &GRPCClient::loginAlreadyLoggedIn, this, [&](QString const &userID) { + this->retrieveUserList(); qint32 const index = users_->rowOfUserID(userID); emit loginAlreadyLoggedIn(index); }); // update events diff --git a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt index 951f0549..8ab7fb51 100644 --- a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt @@ -104,6 +104,7 @@ add_library(bridgepp bridgepp/BridgeUtils.cpp bridgepp/BridgeUtils.h bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp bridgepp/GRPC/GRPCClient.cpp bridgepp/GRPC/GRPCClient.h + bridgepp/GRPC/EventFactory.cpp bridgepp/GRPC/EventFactory.h bridgepp/GRPC/GRPCUtils.cpp bridgepp/GRPC/GRPCUtils.h ${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE} bridgepp/Log/Log.h bridgepp/Log/Log.cpp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp index 1631cbe4..5620f98f 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp @@ -18,19 +18,55 @@ #include "BridgeUtils.h" #include "Exception/Exception.h" +#include namespace bridgepp { + namespace { + QString const configFolder = "protonmail/bridge"; +QMutex rngMutex; ///< the mutex to use when accessing the rng. + +QStringList const firstNames { + "James", "John", "Robert", "Michael", "William", "David", "Richard", "Charles", "Joseph", "Thomas", "Christopher", "Daniel", "Paul", "Mark", + "Donald", "George", "Kenneth", "Steven", "Edward", "Brian", "Ronald", "Anthony", "Kevin", "Jason", "Matthew", "Gary", "Timothy", "Jose", "Larry", + "Jeffrey", "Frank", "Scott", "Eric", "Stephen", "Andrew", "Raymond", "Jack", "Gregory", "Joshua", "Jerry", "Dennis", "Walter", "Patrick", "Peter", + "Harold", "Douglas", "Henry", "Carl", "Arthur", "Ryan", "Mary", "Patricia", "Barbara", "Linda", "Elizabeth", "Maria", "Jennifer", "Susan", + "Margaret", "Dorothy", "Lisa", "Nancy", "Karen", "Betty", "Helen", "Sandra", "Donna", "Ruth", "Sharon", "Michelle", "Laura", "Sarah", "Kimberly", + "Deborah", "Jessica", "Shirley", "Cynthia", "Angela", "Emily", "Brenda", "Amy", "Anna", "Rebecca", "Virginia", "Kathleen", "Pamela", "Martha", + "Debra", "Amanda", "Stephanie", "Caroline", "Christine", "Marie", "Janet", "Catherine", "Frances", "Ann", "Joyce", "Diane", "Alice", +}; ///< List of common US first names. (source: census.gov) +QStringList const lastNames { + "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", + "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", + "Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", "Adams", + "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts", "Gomez", "Phillips", "Evans", "Turner", "Diaz", "Parker", + "Cruz", "Edwards", "Collins", "Reyes", "Stewart", "Morris", "Morales", "Murphy", "Cook", "Rogers", "Gutierrez", "Ortiz", "Morgan", "Cooper", + "Peterson", "Bailey", "Reed", "Kelly", "Howard", "Ramos", "Kim", "Cox", "Ward", "Richardson", "Watson", "Brooks", "Chavez", "Wood", "James", + "Bennett", "Gray", "Mendoza", "Ruiz", "Hughes", "Price", "Alvarez", "Castillo", "Sanders", "Patel", "Myers", "Long", "Ross", "Foster", "Jimenez" +}; ///< List of common US last names (source: census.gov) + + +//**************************************************************************************************************************************************** +/// \brief Return the 64 bit Mersenne twister random number generator. +//**************************************************************************************************************************************************** +std::mt19937_64& rng() +{ + // Do not use for crypto. std::random_device is not good enough. + static std::mt19937_64 generator = std::mt19937_64(std::random_device()()); + return generator; } +} // anonymous namespace + + //**************************************************************************************************************************************************** /// \return user configuration directory used by bridge (based on Golang OS/File's UserConfigDir). //**************************************************************************************************************************************************** @@ -93,4 +129,73 @@ QString userCacheDir() } +//**************************************************************************************************************************************************** +/// \return The value GOOS would return for the current platform. +//**************************************************************************************************************************************************** +QString goos() +{ +#if defined(Q_OS_DARWIN) + return "darwin"; +#elif defined(Q_OS_WINDOWS) + return "windows"; +#else + return "linux"; +#endif +} + + +//**************************************************************************************************************************************************** +/// Slow, but not biased. Not for use in crypto functions though, as the RNG use std::random_device as a seed. +/// +/// \return a random number in the range [0, n-1] +//**************************************************************************************************************************************************** +qint64 randN(qint64 n) +{ + QMutexLocker locker(&rngMutex); + return (n > 0) ? std::uniform_int_distribution(0, n - 1)(rng()) : 0; +} + + +//**************************************************************************************************************************************************** +/// \return A random first name. +//**************************************************************************************************************************************************** +QString randomFirstName() +{ + return firstNames[randN(firstNames.size())]; +} + + +//**************************************************************************************************************************************************** +/// \return A random last name. +//**************************************************************************************************************************************************** +QString randomLastName() +{ + return lastNames[randN(lastNames.size())]; +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +SPUser randomUser() +{ + SPUser user = User::newUser(nullptr); + user->setID(QUuid::createUuid().toString()); + QString const firstName = randomFirstName(); + QString const lastName = randomLastName(); + QString const username = QString("%1.%2").arg(firstName.toLower(), lastName.toLower()); + user->setUsername(username); + user->setAddresses(QStringList() << (username + "@proton.me") << (username + "@protonmail.com") ); + user->setPassword(QUuid().createUuid().toString(QUuid::StringFormat::WithoutBraces).left(20)); + user->setAvatarText(firstName.left(1) + lastName.left(1)); + user->setLoggedIn(true); + user->setSplitMode(false); + user->setSetupGuideSeen(true); + qint64 const totalBytes = (500 + randN(2501)) * 1000000; + user->setUsedBytes(float(bridgepp::randN(totalBytes + 1)) * 1.05f); // we maybe slightly over quota + user->setTotalBytes(float(totalBytes)); + return user; +} + + } // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h index 43f9f9e3..8dc05854 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h @@ -20,12 +20,19 @@ #define BRIDGE_GUI_TESTER_BRIDGE_UTILS_H +#include + + namespace bridgepp { QString userConfigDir(); ///< Get the path of the user configuration folder. QString userCacheDir(); ///< Get the path of the user cache folder. - +QString goos(); ///< return the value of Go's GOOS for the current platform ("darwin", "linux" and "windows" are supported). +qint64 randN(qint64 n); ///< return a random integer in the half open range [0,n) +QString randomFirstName(); ///< Get a random first name from a pre-determined list. +QString randomLastName(); ///< Get a random first name from a pre-determined list. +SPUser randomUser(); ///< Get a random user. } // namespace diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp new file mode 100644 index 00000000..760294d9 --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp @@ -0,0 +1,620 @@ +// 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 . + + +#include "EventFactory.h" + + +namespace bridgepp +{ + + +namespace +{ + + +//**************************************************************************************************************************************************** +/// \return a new SPStreamEvent +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent newStreamEvent() +{ + return std::make_shared(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] appEvent The app event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapAppEvent(grpc::AppEvent *appEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_app(appEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] loginEvent The login event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapLoginEvent(grpc::LoginEvent *loginEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_login(loginEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] updateEvent The app event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapUpdateEvent(grpc::UpdateEvent *updateEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_update(updateEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] cacheEvent The cache event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapCacheEvent(grpc::CacheEvent *cacheEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_cache(cacheEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] mailSettingsEvent The mail settings event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapMailSettingsEvent(grpc::MailSettingsEvent *mailSettingsEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_mailsettings(mailSettingsEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] keychainEvent The keychain event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapKeychainEvent(grpc::KeychainEvent *keychainEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_keychain(keychainEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] mailEvent The mail event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapMailEvent(grpc::MailEvent *mailEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_mail(mailEvent); + return event; +} + + +//**************************************************************************************************************************************************** +/// \param[in] userEvent The user event. +/// \return The stream event. +//**************************************************************************************************************************************************** +bridgepp::SPStreamEvent wrapUserEvent(grpc::UserEvent *userEvent) +{ + auto event = newStreamEvent(); + event->set_allocated_user(userEvent); + return event; +} + + +} // namespace + +//**************************************************************************************************************************************************** +/// \param[in] connected The internet status. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newInternetStatusEvent(bool connected) +{ + auto *internetStatusEvent = new grpc::InternetStatusEvent(); + internetStatusEvent->set_connected(connected); + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_internetstatus(internetStatusEvent); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newToggleAutostartFinishedEvent() +{ + auto *event = new grpc::ToggleAutostartFinishedEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_toggleautostartfinished(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newResetFinishedEvent() +{ + auto event = new grpc::ResetFinishedEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_resetfinished(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newReportBugFinishedEvent() +{ + auto event = new grpc::ReportBugFinishedEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_reportbugfinished(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newReportBugSuccessEvent() +{ + auto event = new grpc::ReportBugSuccessEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_reportbugsuccess(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newReportBugErrorEvent() +{ + auto event = new grpc::ReportBugErrorEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_reportbugerror(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newShowMainWindowEvent() +{ + auto event = new grpc::ShowMainWindowEvent; + auto appEvent = new grpc::AppEvent; + appEvent->set_allocated_showmainwindow(event); + return wrapAppEvent(appEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] error The error. +/// \param[in] message The message. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message) +{ + auto event = new ::grpc::LoginErrorEvent; + event->set_type(error); + event->set_message(message.toStdString()); + auto loginEvent = new grpc::LoginEvent; + loginEvent->set_allocated_error(event); + return wrapLoginEvent(loginEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] username The username. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newLoginTfaRequestedEvent(QString const &username) +{ + auto event = new ::grpc::LoginTfaRequestedEvent; + event->set_username(username.toStdString()); + auto loginEvent = new grpc::LoginEvent; + loginEvent->set_allocated_tfarequested(event); + return wrapLoginEvent(loginEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] username The username. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newLoginTwoPasswordsRequestedEvent() +{ + auto event = new ::grpc::LoginTwoPasswordsRequestedEvent; + auto loginEvent = new grpc::LoginEvent; + loginEvent->set_allocated_twopasswordrequested(event); + return wrapLoginEvent(loginEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newLoginFinishedEvent(QString const &userID) +{ + auto event = new ::grpc::LoginFinishedEvent; + event->set_userid(userID.toStdString()); + auto loginEvent = new grpc::LoginEvent; + loginEvent->set_allocated_finished(event); + return wrapLoginEvent(loginEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newLoginAlreadyLoggedInEvent(QString const &userID) +{ + auto event = new ::grpc::LoginFinishedEvent; + event->set_userid(userID.toStdString()); + auto loginEvent = new grpc::LoginEvent; + loginEvent->set_allocated_alreadyloggedin(event); + return wrapLoginEvent(loginEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] errorType The error type. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateErrorEvent(grpc::UpdateErrorType errorType) +{ + auto event = new grpc::UpdateErrorEvent; + event->set_type(errorType); + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_error(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] version The version. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateManualReadyEvent(QString const &version) +{ + auto event = new grpc::UpdateManualReadyEvent; + event->set_version(version.toStdString()); + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_manualready(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \return the event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateManualRestartNeededEvent() +{ + auto event = new grpc::UpdateManualRestartNeededEvent; + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_manualrestartneeded(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] version The version. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateForceEvent(QString const &version) +{ + auto event = new grpc::UpdateForceEvent; + event->set_version(version.toStdString()); + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_force(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \return the event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateSilentRestartNeeded() +{ + auto event = new grpc::UpdateSilentRestartNeeded; + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_silentrestartneeded(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateIsLatestVersion() +{ + auto event = new grpc::UpdateIsLatestVersion; + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_islatestversion(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUpdateCheckFinished() +{ + auto event = new grpc::UpdateCheckFinished; + auto updateEvent = new grpc::UpdateEvent; + updateEvent->set_allocated_checkfinished(event); + return wrapUpdateEvent(updateEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] errorType The error type. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCacheErrorEvent(grpc::CacheErrorType errorType) +{ + auto event = new grpc::CacheErrorEvent; + event->set_type(errorType); + auto cacheEvent = new grpc::CacheEvent; + cacheEvent->set_allocated_error(event); + return wrapCacheEvent(cacheEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newCacheLocationChangeSuccessEvent() +{ + auto event = new grpc::CacheLocationChangeSuccessEvent; + auto cacheEvent = new grpc::CacheEvent; + cacheEvent->set_allocated_locationchangedsuccess(event); + return wrapCacheEvent(cacheEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newChangeLocalCacheFinishedEvent() +{ + auto event = new grpc::ChangeLocalCacheFinishedEvent; + auto cacheEvent = new grpc::CacheEvent; + cacheEvent->set_allocated_changelocalcachefinished(event); + return wrapCacheEvent(cacheEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enabled The new state of the cache. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newIsCacheOnDiskEnabledChanged(bool enabled) +{ + auto event = new grpc::IsCacheOnDiskEnabledChanged; + event->set_enabled(enabled); + auto cacheEvent = new grpc::CacheEvent; + cacheEvent->set_allocated_iscacheondiskenabledchanged(event); + return wrapCacheEvent(cacheEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] path The path of the cache. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newDiskCachePathChanged(QString const &path) +{ + auto event = new grpc::DiskCachePathChanged; + event->set_path(path.toStdString()); + auto cacheEvent = new grpc::CacheEvent; + cacheEvent->set_allocated_diskcachepathchanged(event); + return wrapCacheEvent(cacheEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] errorType The error type. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newMailSettingsErrorEvent(grpc::MailSettingsErrorType errorType) +{ + auto event = new grpc::MailSettingsErrorEvent; + event->set_type(errorType); + auto mailSettingsEvent = new grpc::MailSettingsEvent; + mailSettingsEvent->set_allocated_error(event); + return wrapMailSettingsEvent(mailSettingsEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUseSslForSmtpFinishedEvent() +{ + auto event = new grpc::UseSslForSmtpFinishedEvent; + auto mailSettingsEvent = new grpc::MailSettingsEvent; + mailSettingsEvent->set_allocated_usesslforsmtpfinished(event); + return wrapMailSettingsEvent(mailSettingsEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newChangePortsFinishedEvent() +{ + auto event = new grpc::ChangePortsFinishedEvent; + auto mailSettingsEvent = new grpc::MailSettingsEvent; + mailSettingsEvent->set_allocated_changeportsfinished(event); + return wrapMailSettingsEvent(mailSettingsEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newChangeKeychainFinishedEvent() +{ + auto event = new grpc::ChangeKeychainFinishedEvent; + auto keychainEvent = new grpc::KeychainEvent; + keychainEvent->set_allocated_changekeychainfinished(event); + return wrapKeychainEvent(keychainEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newHasNoKeychainEvent() +{ + auto event = new grpc::HasNoKeychainEvent; + auto keychainEvent = new grpc::KeychainEvent; + keychainEvent->set_allocated_hasnokeychain(event); + return wrapKeychainEvent(keychainEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newRebuildKeychainEvent() +{ + auto event = new grpc::RebuildKeychainEvent; + auto keychainEvent = new grpc::KeychainEvent; + keychainEvent->set_allocated_rebuildkeychain(event); + return wrapKeychainEvent(keychainEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] email The email. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newNoActiveKeyForRecipientEvent(QString const &email) +{ + auto event = new grpc::NoActiveKeyForRecipientEvent; + event->set_email(email.toStdString()); + auto mailEvent = new grpc::MailEvent; + mailEvent->set_allocated_noactivekeyforrecipientevent(event); + return wrapMailEvent(mailEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] address The address. +/// /// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newAddressChangedEvent(QString const &address) +{ + auto event = new grpc::AddressChangedEvent; + event->set_address(address.toStdString()); + auto mailEvent = new grpc::MailEvent; + mailEvent->set_allocated_addresschanged(event); + return wrapMailEvent(mailEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] address The address. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newAddressChangedLogoutEvent(QString const &address) +{ + auto event = new grpc::AddressChangedLogoutEvent; + event->set_address(address.toStdString()); + auto mailEvent = new grpc::MailEvent; + mailEvent->set_allocated_addresschangedlogout(event); + return wrapMailEvent(mailEvent); +} + + +//**************************************************************************************************************************************************** +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newApiCertIssueEvent() +{ + auto event = new grpc::ApiCertIssueEvent; + auto mailEvent = new grpc::MailEvent; + mailEvent->set_allocated_apicertissue(event); + return wrapMailEvent(mailEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newToggleSplitModeFinishedEvent(QString const &userID) +{ + auto event = new grpc::ToggleSplitModeFinishedEvent; + event->set_userid(userID.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_togglesplitmodefinished(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] username The username. +/// /// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUserDisconnectedEvent(QString const &username) +{ + auto event = new grpc::UserDisconnectedEvent; + event->set_username(username.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_userdisconnected(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newUserChangedEvent(QString const &userID) +{ + auto event = new grpc::UserChangedEvent; + event->set_userid(userID.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_userchanged(event); + return wrapUserEvent(userEvent); +} + + +} // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h new file mode 100644 index 00000000..3f28b7ec --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h @@ -0,0 +1,88 @@ +// 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 . + + +#ifndef BRIDGE_GUI_TESTER_EVENT_FACTORY_H +#define BRIDGE_GUI_TESTER_EVENT_FACTORY_H + + +#include "bridge.grpc.pb.h" +#include "GRPCUtils.h" + + +namespace bridgepp +{ + + +// App events +SPStreamEvent newInternetStatusEvent(bool connected); ///< Create a new InternetStatusEvent event. +SPStreamEvent newToggleAutostartFinishedEvent(); ///< Create a new ToggleAutostartFinishedEvent event. +SPStreamEvent newResetFinishedEvent(); ///< Create a new ResetFinishedEvent event. +SPStreamEvent newReportBugFinishedEvent(); ///< Create a new ReportBugFinishedEvent event. +SPStreamEvent newReportBugSuccessEvent(); ///< Create a new ReportBugSuccessEvent event. +SPStreamEvent newReportBugErrorEvent(); ///< Create a new ReportBugErrorEvent event. +SPStreamEvent newShowMainWindowEvent(); ///< Create a new ShowMainWindowEvent event. + +// Login events +SPStreamEvent newLoginError(grpc::LoginErrorType error, QString const &message); ///< Create a new LoginError event. +SPStreamEvent newLoginTfaRequestedEvent(QString const &username); ///< Create a new LoginTfaRequestedEvent event. +SPStreamEvent newLoginTwoPasswordsRequestedEvent(); ///< Create a new LoginTwoPasswordsRequestedEvent event. +SPStreamEvent newLoginFinishedEvent(QString const &userID); ///< Create a new LoginFinishedEvent event. +SPStreamEvent newLoginAlreadyLoggedInEvent(QString const &userID); ///< Create a new LoginAlreadyLoggedInEvent event. + +// Update related events +SPStreamEvent newUpdateErrorEvent(grpc::UpdateErrorType errorType); ///< Create a new UpdateErrorEvent event. +SPStreamEvent newUpdateManualReadyEvent(QString const &version); ///< Create a new UpdateManualReadyEvent event. +SPStreamEvent newUpdateManualRestartNeededEvent(); ///< Create a new UpdateManualRestartNeededEvent event. +SPStreamEvent newUpdateForceEvent(QString const &version); ///< Create a new UpdateForceEvent event. +SPStreamEvent newUpdateSilentRestartNeeded(); ///< Create a new UpdateSilentRestartNeeded event. +SPStreamEvent newUpdateIsLatestVersion(); ///< Create a new UpdateIsLatestVersion event. +SPStreamEvent newUpdateCheckFinished(); ///< Create a new UpdateCheckFinished event. + +// Cache on disk related events +SPStreamEvent newCacheErrorEvent(grpc::CacheErrorType errorType); ///< Create a new CacheErrorEvent event. +SPStreamEvent newCacheLocationChangeSuccessEvent(); ///< Create a new CacheLocationChangeSuccessEvent event. +SPStreamEvent newChangeLocalCacheFinishedEvent(); ///< Create a new ChangeLocalCacheFinishedEvent event. +SPStreamEvent newIsCacheOnDiskEnabledChanged(bool enabled); ///< Create a new IsCacheOnDiskEnabledChanged event. +SPStreamEvent newDiskCachePathChanged(QString const &path); ///< Create a new DiskCachePathChanged event. + +// Mail settings related events +SPStreamEvent newMailSettingsErrorEvent(grpc::MailSettingsErrorType errorType); ///< Create a new MailSettingsErrorEvent event. +SPStreamEvent newUseSslForSmtpFinishedEvent(); ///< Create a new UseSslForSmtpFinishedEvent event. +SPStreamEvent newChangePortsFinishedEvent(); ///< Create a new ChangePortsFinishedEvent event. + +// keychain related events +SPStreamEvent newChangeKeychainFinishedEvent(); ///< Create a new ChangeKeychainFinishedEvent event. +SPStreamEvent newHasNoKeychainEvent(); ///< Create a new HasNoKeychainEvent event. +SPStreamEvent newRebuildKeychainEvent(); ///< Create a new RebuildKeychainEvent event. + +// Mail related events +SPStreamEvent newNoActiveKeyForRecipientEvent(QString const &email); ///< Create a new NoActiveKeyForRecipientEvent event. +SPStreamEvent newAddressChangedEvent(QString const &address); ///< Create a new AddressChangedEvent event. +SPStreamEvent newAddressChangedLogoutEvent(QString const &address); ///< Create a new AddressChangedLogoutEvent event. +SPStreamEvent newApiCertIssueEvent(); ///< Create a new ApiCertIssueEvent event. + +// User list related event +SPStreamEvent newToggleSplitModeFinishedEvent(QString const &userID); ///< Create a new ToggleSplitModeFinishedEvent event. +SPStreamEvent newUserDisconnectedEvent(QString const &username); ///< Create a new UserDisconnectedEvent event. +SPStreamEvent newUserChangedEvent(QString const &userID); ///< Create a new UserChangedEvent event. + + +} // namespace bridgepp + + +#endif //BRIDGE_GUI_TESTER_EVENT_FACTORY_H diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h index 9fee503a..5c4ad567 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h @@ -29,6 +29,9 @@ namespace bridgepp { +typedef std::shared_ptr SPStreamEvent; ///< Type definition for shared pointer to grpc::StreamEvent. + + QString serverCertificatePath(); ///< Return the path of the server certificate. QString serverKeyPath(); ///< Return the path of the server key. grpc::LogLevel logLevelToGRPC(Log::Level level); ///< Convert a Log::Level to gRPC enum value.