feat(GODT-2261): sync progress in GUI.

This commit is contained in:
Xavier Michelon
2023-02-21 15:25:50 +01:00
parent 5007d451c2
commit 89112baf96
34 changed files with 3243 additions and 606 deletions

View File

@ -49,6 +49,48 @@ qt_standard_project_setup()
message(STATUS "Using Qt ${Qt6_VERSION}") message(STATUS "Using Qt ${Qt6_VERSION}")
#*****************************************************************************************************************************************************
# bridgelib
#*****************************************************************************************************************************************************
find_program(GO_BIN "go")
if (NOT GO_BIN)
message(FATAL_ERROR "Could not location go compiler")
endif()
message(STATUS "go compiler is ${GO_BIN}")
if (APPLE) # set some env variable for go compiler on macOS. Note the CGO_ENABLED=1 is required when cross-compiling.
if (CMAKE_OSX_ARCHITECTURES STREQUAL "arm64")
set(GO_BIN "MACOSX_DEPLOYMENT_TARGET=11.0" "GOARCH=arm64" "CGO_CFLAGS=\"-mmacosx-version-min=11.0\"" CGO_ENABLED=1 ${GO_BIN})
else ()
set(GO_BIN "MACOSX_DEPLOYMENT_TARGET=10.15" "GOARCH=amd64" "CGO_CFLAGS=\"-mmacosx-version-min=10.15\"" CGO_ENABLED=1 ${GO_BIN})
endif()
endif()
file(REAL_PATH "pkg/bridgelib" BRIDGELIB_DIR BASE_DIRECTORY "${BRIDGE_REPO_ROOT}")
message(STATUS "bridgelib folder is ${BRIDGELIB_DIR}")
set(BRIDGELIB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(BRIDGELIB_BASE_NAME "bridgelib")
if (UNIX AND NOT APPLE)
set(BRIDGELIB_LIB_FILE "${BRIDGELIB_BASE_NAME}.so")
endif()
if (APPLE)
set(BRIDGELIB_LIB_FILE "${BRIDGELIB_BASE_NAME}.dylib")
endif()
if (WIN32)
set(BRIDGELIB_LIB_FILE "${BRIDGELIB_BASE_NAME}.dll")
endif()
set(BRIDGELIB_OUTPUT_PATH "${BRIDGELIB_OUTPUT_DIR}/${BRIDGELIB_LIB_FILE}")
add_custom_target(
bridgelib
COMMAND ${GO_BIN} build -o ${BRIDGELIB_OUTPUT_PATH} --buildmode c-shared
WORKING_DIRECTORY ${BRIDGELIB_DIR}
COMMENT "Compile bridgelib library"
)
#***************************************************************************************************************************************************** #*****************************************************************************************************************************************************
# Source files and output # Source files and output
#***************************************************************************************************************************************************** #*****************************************************************************************************************************************************
@ -72,6 +114,8 @@ add_executable(bridge-gui-tester
UserTable.cpp UserTable.h UserTable.cpp UserTable.h
) )
add_dependencies(bridge-gui-tester bridgelib)
target_precompile_headers(bridge-gui-tester PRIVATE Pch.h) target_precompile_headers(bridge-gui-tester PRIVATE Pch.h)
target_include_directories(bridge-gui-tester PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 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_compile_definitions(bridge-gui-tester PRIVATE BRIDGE_APP_VERSION=\"${BRIDGE_APP_VERSION}\")

View File

@ -19,6 +19,7 @@
#include "GRPCServerWorker.h" #include "GRPCServerWorker.h"
#include "Cert.h" #include "Cert.h"
#include "GRPCService.h" #include "GRPCService.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/GRPC/GRPCUtils.h> #include <bridgepp/GRPC/GRPCUtils.h>
#include <bridgepp/GRPC/GRPCConfig.h> #include <bridgepp/GRPC/GRPCConfig.h>
@ -33,7 +34,6 @@ using namespace grpc;
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
GRPCServerWorker::GRPCServerWorker(QObject *parent) GRPCServerWorker::GRPCServerWorker(QObject *parent)
: Worker(parent) { : Worker(parent) {
} }
@ -81,7 +81,7 @@ void GRPCServerWorker::run() {
config.port = port; config.port = port;
QString err; QString err;
if (!config.save(grpcServerConfigPath(), &err)) { if (!config.save(grpcServerConfigPath(bridgelib::userConfigDir()), &err)) {
throw Exception(QString("Could not save gRPC server config. %1").arg(err)); throw Exception(QString("Could not save gRPC server config. %1").arg(err));
} }

View File

@ -192,17 +192,6 @@ Status GRPCService::IsAllMailVisible(ServerContext *, Empty const *request, Bool
} }
//****************************************************************************************************************************************************
/// \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. /// \return The status for the call.
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************

View File

@ -51,7 +51,6 @@ public: // member functions.
grpc::Status IsBetaEnabled(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::BoolValue *response) override; grpc::Status IsBetaEnabled(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::BoolValue *response) override;
grpc::Status SetIsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; grpc::Status SetIsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override;
grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override;
grpc::Status GoOs(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override; grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override;
grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;
grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override;

View File

@ -18,6 +18,7 @@
#include "SettingsTab.h" #include "SettingsTab.h"
#include "GRPCService.h" #include "GRPCService.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/GRPC/EventFactory.h> #include <bridgepp/GRPC/EventFactory.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
@ -438,7 +439,7 @@ void SettingsTab::resetUI() {
this->setClientPlatform("Unknown"); this->setClientPlatform("Unknown");
ui_.editVersion->setText(BRIDGE_APP_VERSION); ui_.editVersion->setText(BRIDGE_APP_VERSION);
ui_.comboOS->setCurrentText(bridgepp::goos()); ui_.comboOS->setCurrentText(bridgelib::goos());
ui_.editCurrentEmailClient->setText("Thunderbird/102.0.3"); ui_.editCurrentEmailClient->setText("Thunderbird/102.0.3");
ui_.checkShowOnStartup->setChecked(true); ui_.checkShowOnStartup->setChecked(true);
ui_.checkShowSplashScreen->setChecked(false); ui_.checkShowSplashScreen->setChecked(false);

View File

@ -55,6 +55,8 @@ UsersTab::UsersTab(QWidget *parent)
connect(ui_.buttonImapLoginFailed, &QPushButton::clicked, this, &UsersTab::onSendIMAPLoginFailedEvent); connect(ui_.buttonImapLoginFailed, &QPushButton::clicked, this, &UsersTab::onSendIMAPLoginFailedEvent);
connect(ui_.buttonUsedBytesChanged, &QPushButton::clicked, this, &UsersTab::onSendUsedBytesChangedEvent); connect(ui_.buttonUsedBytesChanged, &QPushButton::clicked, this, &UsersTab::onSendUsedBytesChangedEvent);
connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState); connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState);
connect(ui_.checkSync, &QCheckBox::toggled, this, &UsersTab::onCheckSyncToggled);
connect(ui_.sliderSync, &QSlider::valueChanged, this, &UsersTab::onSliderSyncValueChanged);
users_.append(randomUser()); users_.append(randomUser());
@ -217,9 +219,19 @@ void UsersTab::updateGUIState() {
ui_.groupBoxUsedSpace->setEnabled(hasSelectedUser && (UserState::Connected == state)); ui_.groupBoxUsedSpace->setEnabled(hasSelectedUser && (UserState::Connected == state));
ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked()); ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked());
ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0); ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0);
ui_.groupboxSync->setEnabled(user.get());
if (user) if (user)
ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername()); ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername());
QSignalBlocker b(ui_.checkSync);
bool const syncing = user && user->isSyncing();
ui_.checkSync->setChecked(syncing);
b = QSignalBlocker(ui_.sliderSync);
ui_.sliderSync->setEnabled(syncing);
qint32 const progressPercent = syncing ? qint32(user->syncProgress() * 100.0f) : 0;
ui_.sliderSync->setValue(progressPercent);
ui_.labelSync->setText(syncing ? QString("%1%").arg(progressPercent) : "" );
} }
@ -398,3 +410,44 @@ void UsersTab::configureUserAppleMail(QString const &userID, QString const &addr
app().log().info(QString("Apple mail configuration was requested for user %1, address %2").arg(userID, address)); app().log().info(QString("Apple mail configuration was requested for user %1, address %2").arg(userID, address));
} }
//****************************************************************************************************************************************************
/// \param[in] checked Is the sync checkbox checked?
//****************************************************************************************************************************************************
void UsersTab::onCheckSyncToggled(bool checked) {
SPUser const user = this->selectedUser();
if ((!user) || (user->isSyncing() == checked)) {
return;
}
user->setIsSyncing(checked);
user->setSyncProgress(0.0);
GRPCService &grpc = app().grpc();
// we do not apply delay for these event.
if (checked) {
grpc.sendEvent(newSyncStartedEvent(user->id()));
grpc.sendEvent(newSyncProgressEvent(user->id(), 0.0, 1, 1));
} else {
grpc.sendEvent(newSyncFinishedEvent(user->id()));
}
this->updateGUIState();
}
//****************************************************************************************************************************************************
/// \param[in] value The value for the slider.
//****************************************************************************************************************************************************
void UsersTab::onSliderSyncValueChanged(int value) {
SPUser const user = this->selectedUser();
if ((!user) || (!user->isSyncing()) || user->syncProgress() == value) {
return;
}
double const progress = value / 100.0;
user->setSyncProgress(progress);
app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining.
this->updateGUIState();
}

View File

@ -54,7 +54,6 @@ public slots:
void logoutUser(QString const &userID); ///< slot for the logging out of a user. 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 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. void configureUserAppleMail(QString const &userID, QString const &address); ///< Slot for the configuration of Apple mail.
private slots: private slots:
void onAddUserButton(); ///< Add a user to the user list. void onAddUserButton(); ///< Add a user to the user list.
void onEditUserButton(); ///< Edit the currently selected user. void onEditUserButton(); ///< Edit the currently selected user.
@ -63,6 +62,8 @@ private slots:
void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button. void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button.
void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed Event' button. void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed Event' button.
void onSendIMAPLoginFailedEvent(); ///< Slot for the 'Send IMAP Login failure Event' button. void onSendIMAPLoginFailedEvent(); ///< Slot for the 'Send IMAP Login failure Event' button.
void onCheckSyncToggled(bool checked); ///< Slot for the 'Synchronizing' check box.
void onSliderSyncValueChanged(int value); ///< Slot for the sync 'Progress' slider.
void updateGUIState(); ///< Update the GUI state. void updateGUIState(); ///< Update the GUI state.
private: // member functions. private: // member functions.

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1225</width> <width>1221</width>
<height>717</height> <height>894</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -66,6 +66,52 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QGroupBox" name="groupboxSync">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Sync</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
<item>
<widget class="QCheckBox" name="checkSync">
<property name="text">
<string>Synchronizing</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelSync">
<property name="text">
<string>0%</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSlider" name="sliderSync">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBoxBadEvent"> <widget class="QGroupBox" name="groupBoxBadEvent">
<property name="minimumSize"> <property name="minimumSize">

View File

@ -19,6 +19,7 @@
#include "MainWindow.h" #include "MainWindow.h"
#include "AppController.h" #include "AppController.h"
#include "GRPCServerWorker.h" #include "GRPCServerWorker.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/Worker/Overseer.h> #include <bridgepp/Worker/Overseer.h>
@ -54,6 +55,8 @@ int main(int argc, char **argv) {
QApplication::setOrganizationDomain("proton.ch"); QApplication::setOrganizationDomain("proton.ch");
QApplication::setQuitOnLastWindowClosed(true); QApplication::setQuitOnLastWindowClosed(true);
bridgelib::loadLibrary();
Log &log = app().log(); Log &log = app().log();
log.setEchoInConsole(true); log.setEchoInConsole(true);
log.setLevel(Log::Level::Debug); log.setLevel(Log::Level::Debug);
@ -85,7 +88,10 @@ int main(int argc, char **argv) {
return exitCode; return exitCode;
} }
catch (Exception const &e) { catch (Exception const &e) {
QTextStream(stderr) << QString("A fatal error occurred: %1\n").arg(e.qwhat()); QString message = e.qwhat();
if (!e.details().isEmpty())
message += "\n\nDetails:\n" + e.details();
QTextStream(stderr) << QString("A fatal error occurred: %1\n").arg(message);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
} }

View File

@ -152,10 +152,9 @@ add_executable(bridge-gui
Resources.qrc Resources.qrc
AppController.cpp AppController.h AppController.cpp AppController.h
BridgeApp.cpp BridgeApp.h BridgeApp.cpp BridgeApp.h
BridgeLib.cpp BridgeLib.h
CommandLine.cpp CommandLine.h CommandLine.cpp CommandLine.h
EventStreamWorker.cpp EventStreamWorker.h EventStreamWorker.cpp EventStreamWorker.h
Log.cpp Log.h LogUtils.cpp LogUtils.h
main.cpp main.cpp
Pch.h Pch.h
QMLBackend.cpp QMLBackend.h QMLBackend.cpp QMLBackend.h

View File

@ -16,9 +16,9 @@
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#include "Log.h" #include "LogUtils.h"
#include "BridgeLib.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include <bridgepp/BridgeLib.h>
using namespace bridgepp; using namespace bridgepp;

View File

@ -16,8 +16,8 @@
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#ifndef BRIDGE_GUI_LOG_H #ifndef BRIDGE_GUI_LOG_UTILS_H
#define BRIDGE_GUI_LOG_H #define BRIDGE_GUI_LOG_UTILS_H
#include <bridgepp/Log/Log.h> #include <bridgepp/Log/Log.h>
@ -26,4 +26,4 @@
bridgepp::Log &initLog(); ///< Initialize the application log. bridgepp::Log &initLog(); ///< Initialize the application log.
#endif //BRIDGE_GUI_LOG_H #endif //BRIDGE_GUI_LOG_UTILS_H

View File

@ -18,8 +18,8 @@
#include "QMLBackend.h" #include "QMLBackend.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "BridgeLib.h"
#include "EventStreamWorker.h" #include "EventStreamWorker.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/GRPC/GRPCClient.h> #include <bridgepp/GRPC/GRPCClient.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/Worker/Overseer.h> #include <bridgepp/Worker/Overseer.h>

View File

@ -17,12 +17,8 @@
#include "SentryUtils.h" #include "SentryUtils.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "BridgeLib.h" #include <bridgepp/BridgeLib.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
#include <QByteArray>
#include <QCryptographicHash>
#include <QString>
#include <QSysInfo>
using namespace bridgepp; using namespace bridgepp;

View File

@ -39,6 +39,9 @@ void UserList::connectGRPCEvents() const {
connect(&client, &GRPCClient::userChanged, this, &UserList::onUserChanged); connect(&client, &GRPCClient::userChanged, this, &UserList::onUserChanged);
connect(&client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished); connect(&client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished);
connect(&client, &GRPCClient::usedBytesChanged, this, &UserList::onUsedBytesChanged); connect(&client, &GRPCClient::usedBytesChanged, this, &UserList::onUsedBytesChanged);
connect(&client, &GRPCClient::syncStarted, this, &UserList::onSyncStarted);
connect(&client, &GRPCClient::syncFinished, this, &UserList::onSyncFinished);
connect(&client, &GRPCClient::syncProgress, this, &UserList::onSyncProgress);
} }
@ -251,3 +254,47 @@ void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) {
} }
users_[index]->setUsedBytes(usedBytes); users_[index]->setUsedBytes(usedBytes);
} }
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
//****************************************************************************************************************************************************
void UserList::onSyncStarted(QString const &userID) {
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID));
return;
}
users_[index]->setIsSyncing(true);
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
//****************************************************************************************************************************************************
void UserList::onSyncFinished(QString const &userID) {
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
return;
}
users_[index]->setIsSyncing(false);
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
/// \param[in] progress The sync progress ratio.
/// \param[in] elapsedMs The elapsed sync time in milliseconds.
/// \param[in] remainingMs The remaining sync time in milliseconds.
//****************************************************************************************************************************************************
void UserList::onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs) {
Q_UNUSED(elapsedMs)
Q_UNUSED(remainingMs)
int const index = this->rowOfUserID(userID);
if (index < 0) {
app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID));
return;
}
users_[index]->setSyncProgress(progress);
}

View File

@ -61,6 +61,9 @@ public slots: ///< handler for signals coming from the gRPC service
void onUserChanged(QString const &userID); void onUserChanged(QString const &userID);
void onToggleSplitModeFinished(QString const &userID); void onToggleSplitModeFinished(QString const &userID);
void onUsedBytesChanged(QString const &userID, qint64 usedBytes); ///< Slot for usedBytesChanged events. void onUsedBytesChanged(QString const &userID, qint64 usedBytes); ///< Slot for usedBytesChanged events.
void onSyncStarted(QString const &userID); ///< Slot for syncStarted events.
void onSyncFinished(QString const &userID); ///< Slot for syncFinished events.
void onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs); ///< Slot for syncFinished events.
private: // data members private: // data members
QList<bridgepp::SPUser> users_; ///< The user list. QList<bridgepp::SPUser> users_; ///< The user list.

View File

@ -18,12 +18,12 @@
#include "Pch.h" #include "Pch.h"
#include "BridgeApp.h" #include "BridgeApp.h"
#include "BridgeLib.h"
#include "CommandLine.h" #include "CommandLine.h"
#include "Log.h" #include "LogUtils.h"
#include "QMLBackend.h" #include "QMLBackend.h"
#include "SentryUtils.h" #include "SentryUtils.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/BridgeUtils.h> #include <bridgepp/BridgeUtils.h>
#include <bridgepp/Exception/Exception.h> #include <bridgepp/Exception/Exception.h>
#include <bridgepp/FocusGRPC/FocusGRPCClient.h> #include <bridgepp/FocusGRPC/FocusGRPCClient.h>

View File

@ -29,14 +29,19 @@ Item {
property var _spacing: 12 * ProtonStyle.px property var _spacing: 12 * ProtonStyle.px
property color usedSpaceColor : { property color progressColor : {
if (!root.enabled) return root.colorScheme.text_weak if (!root.enabled) return root.colorScheme.text_weak
if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak
if (root.usedFraction < .50) return root.colorScheme.signal_success if (root.user && root.user.isSyncing) return root.colorScheme.text_weak
if (root.usedFraction < .75) return root.colorScheme.signal_warning if (root.progressRatio < .50) return root.colorScheme.signal_success
if (root.progressRatio < .75) return root.colorScheme.signal_warning
return root.colorScheme.signal_danger return root.colorScheme.signal_danger
} }
property real usedFraction: root.user ? reasonableFraction(root.user.usedBytes, root.user.totalBytes) : 0 property real progressRatio: {
if (!root.user)
return 0
return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes)
}
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0) property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0) property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
@ -171,18 +176,21 @@ Item {
case EUserState.Locked: case EUserState.Locked:
return qsTr("Connecting") + dotsTimer.dots return qsTr("Connecting") + dotsTimer.dots
case EUserState.Connected: case EUserState.Connected:
return root.usedSpace if (root.user.isSyncing)
return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots
else
return root.usedSpace
} }
} }
Timer { // dots animation while connecting. 1 sec cycle, roughly similar to the webmail loading page. Timer { // dots animation while connecting & syncing.
id:dotsTimer id:dotsTimer
property string dots: "" property string dots: ""
interval: 250; interval: 500;
repeat: true; repeat: true;
running: (root.user != null) && (root.user.state === EUserState.Locked) running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
onTriggered: { onTriggered: {
dots = dots + "." dots += "."
if (dots.length > 3) if (dots.length > 3)
dots = "" dots = ""
} }
@ -191,7 +199,7 @@ Item {
} }
} }
color: root.usedSpaceColor color: root.progressColor
type: { type: {
switch (root.type) { switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption case AccountDelegate.SmallView: return Label.Caption
@ -202,7 +210,7 @@ Item {
Label { Label {
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: root.user && root.user.state == EUserState.Connected ? " / " + root.totalSpace : "" text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : ""
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
type: { type: {
switch (root.type) { switch (root.type) {
@ -213,26 +221,27 @@ Item {
} }
} }
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 }
Rectangle { Rectangle {
id: storage_bar id: progress_bar
visible: root.user ? root.type == AccountDelegate.LargeView : false visible: root.user ? root.type == AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px width: 140 * ProtonStyle.px
height: 4 * ProtonStyle.px height: 4 * ProtonStyle.px
radius: ProtonStyle.storage_bar_radius radius: ProtonStyle.progress_bar_radius
color: root.colorScheme.border_weak color: root.colorScheme.border_weak
Rectangle { Rectangle {
id: storage_bar_filled id: progress_bar_filled
radius: ProtonStyle.storage_bar_radius radius: ProtonStyle.progress_bar_radius
color: root.usedSpaceColor color: root.progressColor
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected) : false visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false
anchors { anchors {
top : parent.top top : parent.top
bottom : parent.bottom bottom : parent.bottom
left : parent.left left : parent.left
} }
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width
} }
} }
} }

View File

@ -362,7 +362,7 @@ QtObject {
property real banner_radius : 12 * root.px // px property real banner_radius : 12 * root.px // px
property real dialog_radius : 12 * root.px // px property real dialog_radius : 12 * root.px // px
property real card_radius : 12 * root.px // px property real card_radius : 12 * root.px // px
property real storage_bar_radius : 3 * root.px // px property real progress_bar_radius : 3 * root.px // px
property real tooltip_radius : 8 * root.px // px property real tooltip_radius : 8 * root.px // px
property int heading_font_size: 28 property int heading_font_size: 28

View File

@ -135,6 +135,7 @@ add_custom_command(
add_library(bridgepp add_library(bridgepp
bridgepp/BridgeLib.cpp bridgepp/BridgeLib.h
bridgepp/BridgeUtils.cpp bridgepp/BridgeUtils.h bridgepp/BridgeUtils.cpp bridgepp/BridgeUtils.h
bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp
bridgepp/GRPC/GRPCClient.cpp bridgepp/GRPC/GRPCClient.h bridgepp/GRPC/GRPCClient.cpp bridgepp/GRPC/GRPCClient.h

View File

@ -601,6 +601,51 @@ SPStreamEvent newIMAPLoginFailedEvent(QString const &username) {
} }
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newSyncStartedEvent(QString const &userID) {
auto event = new grpc::SyncStartedEvent;
event->set_userid(userID.toStdString());
auto userEvent = new grpc::UserEvent;
userEvent->set_allocated_syncstartedevent(event);
return wrapUserEvent(userEvent);
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newSyncFinishedEvent(QString const &userID) {
auto event = new grpc::SyncFinishedEvent;
event->set_userid(userID.toStdString());
auto userEvent = new grpc::UserEvent;
userEvent->set_allocated_syncfinishedevent(event);
return wrapUserEvent(userEvent);
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
/// \param[in] progress The progress ratio.
/// \param[in] elapsedMs The elapsed time in milliseconds.
/// \param[in] remainingMs The remaining time in milliseconds.
/// \return The event.
//****************************************************************************************************************************************************
SPStreamEvent newSyncProgressEvent(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs) {
auto event = new grpc::SyncProgressEvent;
event->set_userid(userID.toStdString());
event->set_progress(progress);
event->set_elapsedms(elapsedMs);
event->set_remainingms(remainingMs);
auto userEvent = new grpc::UserEvent;
userEvent->set_allocated_syncprogressevent(event);
return wrapUserEvent(userEvent);
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] errorCode The error errorCode. /// \param[in] errorCode The error errorCode.
/// \return The event. /// \return The event.

View File

@ -80,6 +80,9 @@ SPStreamEvent newUserChangedEvent(QString const &userID); ///< Create a new User
SPStreamEvent newUserBadEvent(QString const &userID, QString const& errorMessage); ///< Create a new UserBadEvent event. SPStreamEvent newUserBadEvent(QString const &userID, QString const& errorMessage); ///< Create a new UserBadEvent event.
SPStreamEvent newUsedBytesChangedEvent(QString const &userID, qint64 usedBytes); ///< Create a new UsedBytesChangedEvent event. SPStreamEvent newUsedBytesChangedEvent(QString const &userID, qint64 usedBytes); ///< Create a new UsedBytesChangedEvent event.
SPStreamEvent newIMAPLoginFailedEvent(QString const &username); ///< Create a new ImapLoginFailedEvent event. SPStreamEvent newIMAPLoginFailedEvent(QString const &username); ///< Create a new ImapLoginFailedEvent event.
SPStreamEvent newSyncStartedEvent(QString const &userID); ///< Create a new SyncStarted event.
SPStreamEvent newSyncFinishedEvent(QString const &userID); ///< Create a new SyncFinished event.
SPStreamEvent newSyncProgressEvent(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs); ///< Create a new SyncFinished event.
// Generic error event // Generic error event
SPStreamEvent newGenericErrorEvent(grpc::ErrorCode errorCode); ///< Create a new GenericErrrorEvent event. SPStreamEvent newGenericErrorEvent(grpc::ErrorCode errorCode); ///< Create a new GenericErrrorEvent event.

View File

@ -1380,6 +1380,28 @@ void GRPCClient::processUserEvent(UserEvent const &event) {
emit imapLoginFailed(username); emit imapLoginFailed(username);
break; break;
} }
case UserEvent::kSyncStartedEvent: {
SyncStartedEvent const &e = event.syncstartedevent();
QString const &userID = QString::fromStdString(e.userid());
this->logTrace(QString("User event received: SyncStarted (userID = %1).:").arg(userID));
emit syncStarted(userID);
break;
}
case UserEvent::kSyncFinishedEvent: {
SyncFinishedEvent const &e = event.syncfinishedevent();
QString const &userID = QString::fromStdString(e.userid());
this->logTrace(QString("User event received: SyncFinished (userID = %1).:").arg(userID));
emit syncFinished(userID);
break;
}
case UserEvent::kSyncProgressEvent: {
SyncProgressEvent const &e = event.syncprogressevent();
QString const &userID = QString::fromStdString(e.userid());
this->logTrace(QString("User event received SyncProgress (userID = %1, progress = %2, elapsedMs = %3, remainingMs = %4).").arg(userID)
.arg(e.progress()).arg(e.elapsedms()).arg(e.remainingms()));
emit syncProgress(userID, e.progress(), e.elapsedms(), e.remainingms());
break;
}
default: default:
this->logError("Unknown User event received."); this->logError("Unknown User event received.");
} }

View File

@ -181,6 +181,9 @@ signals:
void userBadEvent(QString const &userID, QString const& errorMessage); void userBadEvent(QString const &userID, QString const& errorMessage);
void usedBytesChanged(QString const &userID, qint64 usedBytes); void usedBytesChanged(QString const &userID, qint64 usedBytes);
void imapLoginFailed(QString const& username); void imapLoginFailed(QString const& username);
void syncStarted(QString const &userID);
void syncFinished(QString const &userID);
void syncProgress(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs);
public: // keychain related calls public: // keychain related calls
grpc::Status availableKeychains(QStringList &outKeychains); grpc::Status availableKeychains(QStringList &outKeychains);

File diff suppressed because it is too large Load Diff

View File

@ -294,6 +294,48 @@ void User::setTotalBytes(float totalBytes) {
} }
//****************************************************************************************************************************************************
/// \return true iff a sync is in progress.
//****************************************************************************************************************************************************
bool User::isSyncing() const {
return isSyncing_;
}
//****************************************************************************************************************************************************
/// \param[in] syncing The new value for the sync state.
//****************************************************************************************************************************************************
void User::setIsSyncing(bool syncing) {
if (isSyncing_ == syncing) {
return;
}
isSyncing_ = syncing;
emit isSyncingChanged(syncing);
}
//****************************************************************************************************************************************************
/// \return The sync progress ratio
//****************************************************************************************************************************************************
float User::syncProgress() const {
return syncProgress_;
}
//****************************************************************************************************************************************************
/// \param[in] progress The progress ratio.
//****************************************************************************************************************************************************
void User::setSyncProgress(float progress) {
if (qAbs(syncProgress_ - progress) < 0.00001) {
return;
}
syncProgress_ = progress;
emit syncProgressChanged(progress);
}
//**************************************************************************************************************************************************** //****************************************************************************************************************************************************
/// \param[in] state The user state. /// \param[in] state The user state.
/// \return A string describing the state. /// \return A string describing the state.

View File

@ -101,6 +101,8 @@ public:
Q_PROPERTY(bool splitMode READ splitMode WRITE setSplitMode NOTIFY splitModeChanged) Q_PROPERTY(bool splitMode READ splitMode WRITE setSplitMode NOTIFY splitModeChanged)
Q_PROPERTY(float usedBytes READ usedBytes WRITE setUsedBytes NOTIFY usedBytesChanged) Q_PROPERTY(float usedBytes READ usedBytes WRITE setUsedBytes NOTIFY usedBytesChanged)
Q_PROPERTY(float totalBytes READ totalBytes WRITE setTotalBytes NOTIFY totalBytesChanged) Q_PROPERTY(float totalBytes READ totalBytes WRITE setTotalBytes NOTIFY totalBytesChanged)
Q_PROPERTY(bool isSyncing READ isSyncing WRITE setIsSyncing NOTIFY isSyncingChanged)
Q_PROPERTY(float syncProgress READ syncProgress WRITE setSyncProgress NOTIFY syncProgressChanged)
QString id() const; QString id() const;
void setID(QString const &id); void setID(QString const &id);
@ -120,6 +122,10 @@ public:
void setUsedBytes(float usedBytes); void setUsedBytes(float usedBytes);
float totalBytes() const; float totalBytes() const;
void setTotalBytes(float totalBytes); void setTotalBytes(float totalBytes);
bool isSyncing() const;
void setIsSyncing(bool syncing);
float syncProgress() const;
void setSyncProgress(float progress);
signals: signals:
// signals used for Qt properties // signals used for Qt properties
@ -134,6 +140,8 @@ signals:
void usedBytesChanged(float byteCount); void usedBytesChanged(float byteCount);
void totalBytesChanged(float byteCount); void totalBytesChanged(float byteCount);
void toggleSplitModeFinished(); void toggleSplitModeFinished();
void isSyncingChanged(bool syncing);
void syncProgressChanged(float syncProgress);
private: // member functions. private: // member functions.
User(QObject *parent); ///< Default constructor. User(QObject *parent); ///< Default constructor.
@ -149,6 +157,8 @@ private: // data members.
bool splitMode_ { false }; ///< Is split mode active. bool splitMode_ { false }; ///< Is split mode active.
float usedBytes_ { 0.0f }; ///< The storage used by the user. float usedBytes_ { 0.0f }; ///< The storage used by the user.
float totalBytes_ { 1.0f }; ///< The storage quota of the user. float totalBytes_ { 1.0f }; ///< The storage quota of the user.
bool isSyncing_ { false }; ///< Is a sync in progress for the user.
float syncProgress_ { 0.0f }; ///< The sync progress.
}; };

File diff suppressed because it is too large Load Diff

View File

@ -449,6 +449,9 @@ message UserEvent {
UserBadEvent userBadEvent = 4; UserBadEvent userBadEvent = 4;
UsedBytesChangedEvent usedBytesChangedEvent = 5; UsedBytesChangedEvent usedBytesChangedEvent = 5;
ImapLoginFailedEvent imapLoginFailedEvent = 6; ImapLoginFailedEvent imapLoginFailedEvent = 6;
SyncStartedEvent syncStartedEvent = 7;
SyncFinishedEvent syncFinishedEvent = 8;
SyncProgressEvent syncProgressEvent = 9;
} }
} }
@ -478,6 +481,21 @@ message ImapLoginFailedEvent {
string username = 1; string username = 1;
} }
message SyncStartedEvent {
string userID = 1;
}
message SyncFinishedEvent {
string userID = 1;
}
message SyncProgressEvent {
string userID = 1;
double progress = 2;
int64 elapsedMs = 3;
int64 remainingMs = 4;
}
//********************************************************** //**********************************************************
// Generic errors // Generic errors
//********************************************************** //**********************************************************

View File

@ -185,6 +185,23 @@ func newIMAPLoginFailedEvent(username string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_ImapLoginFailedEvent{ImapLoginFailedEvent: &ImapLoginFailedEvent{Username: username}}}) return userEvent(&UserEvent{Event: &UserEvent_ImapLoginFailedEvent{ImapLoginFailedEvent: &ImapLoginFailedEvent{Username: username}}})
} }
func NewSyncStartedEvent(userID string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_SyncStartedEvent{SyncStartedEvent: &SyncStartedEvent{UserID: userID}}})
}
func NewSyncFinishedEvent(userID string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_SyncFinishedEvent{SyncFinishedEvent: &SyncFinishedEvent{UserID: userID}}})
}
func NewSyncProgressEvent(userID string, progress float64, elapsedMs, remainingMs int64) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_SyncProgressEvent{SyncProgressEvent: &SyncProgressEvent{
UserID: userID,
Progress: progress,
ElapsedMs: elapsedMs,
RemainingMs: remainingMs,
}}})
}
func NewGenericErrorEvent(errorCode ErrorCode) *StreamEvent { func NewGenericErrorEvent(errorCode ErrorCode) *StreamEvent {
return genericErrorEvent(&GenericErrorEvent{Code: errorCode}) return genericErrorEvent(&GenericErrorEvent{Code: errorCode})
} }

View File

@ -323,6 +323,15 @@ func (s *Service) watchEvents() {
case events.UserBadEvent: case events.UserBadEvent:
_ = s.SendEvent(NewUserBadEvent(event.UserID, event.Error.Error())) _ = s.SendEvent(NewUserBadEvent(event.UserID, event.Error.Error()))
case events.SyncStarted:
_ = s.SendEvent(NewSyncStartedEvent(event.UserID))
case events.SyncFinished:
_ = s.SendEvent(NewSyncFinishedEvent(event.UserID))
case events.SyncProgress:
_ = s.SendEvent(NewSyncProgressEvent(event.UserID, event.Progress, event.Elapsed.Milliseconds(), event.Remaining.Milliseconds()))
case events.UpdateLatest: case events.UpdateLatest:
safe.RLock(func() { safe.RLock(func() {
s.latest = event.Version s.latest = event.Version