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}")
#*****************************************************************************************************************************************************
# 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
#*****************************************************************************************************************************************************
@ -72,6 +114,8 @@ add_executable(bridge-gui-tester
UserTable.cpp UserTable.h
)
add_dependencies(bridge-gui-tester bridgelib)
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}\")

View File

@ -19,6 +19,7 @@
#include "GRPCServerWorker.h"
#include "Cert.h"
#include "GRPCService.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/Exception/Exception.h>
#include <bridgepp/GRPC/GRPCUtils.h>
#include <bridgepp/GRPC/GRPCConfig.h>
@ -33,7 +34,6 @@ using namespace grpc;
//****************************************************************************************************************************************************
GRPCServerWorker::GRPCServerWorker(QObject *parent)
: Worker(parent) {
}
@ -81,7 +81,7 @@ void GRPCServerWorker::run() {
config.port = port;
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));
}

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.
//****************************************************************************************************************************************************

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 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 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 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;

View File

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

View File

@ -55,6 +55,8 @@ UsersTab::UsersTab(QWidget *parent)
connect(ui_.buttonImapLoginFailed, &QPushButton::clicked, this, &UsersTab::onSendIMAPLoginFailedEvent);
connect(ui_.buttonUsedBytesChanged, &QPushButton::clicked, this, &UsersTab::onSendUsedBytesChangedEvent);
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());
@ -217,9 +219,19 @@ void UsersTab::updateGUIState() {
ui_.groupBoxUsedSpace->setEnabled(hasSelectedUser && (UserState::Connected == state));
ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked());
ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0);
ui_.groupboxSync->setEnabled(user.get());
if (user)
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));
}
//****************************************************************************************************************************************************
/// \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 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.
@ -63,6 +62,8 @@ private slots:
void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button.
void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed 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.
private: // member functions.

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>1225</width>
<height>717</height>
<width>1221</width>
<height>894</height>
</rect>
</property>
<property name="windowTitle">
@ -66,6 +66,52 @@
</property>
</spacer>
</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>
<widget class="QGroupBox" name="groupBoxBadEvent">
<property name="minimumSize">

View File

@ -19,6 +19,7 @@
#include "MainWindow.h"
#include "AppController.h"
#include "GRPCServerWorker.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/Exception/Exception.h>
#include <bridgepp/Worker/Overseer.h>
@ -54,6 +55,8 @@ int main(int argc, char **argv) {
QApplication::setOrganizationDomain("proton.ch");
QApplication::setQuitOnLastWindowClosed(true);
bridgelib::loadLibrary();
Log &log = app().log();
log.setEchoInConsole(true);
log.setLevel(Log::Level::Debug);
@ -85,7 +88,10 @@ int main(int argc, char **argv) {
return exitCode;
}
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;
}
}

View File

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

View File

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

View File

@ -16,8 +16,8 @@
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#ifndef BRIDGE_GUI_LOG_H
#define BRIDGE_GUI_LOG_H
#ifndef BRIDGE_GUI_LOG_UTILS_H
#define BRIDGE_GUI_LOG_UTILS_H
#include <bridgepp/Log/Log.h>
@ -26,4 +26,4 @@
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 "BuildConfig.h"
#include "BridgeLib.h"
#include "EventStreamWorker.h"
#include <bridgepp/BridgeLib.h>
#include <bridgepp/GRPC/GRPCClient.h>
#include <bridgepp/Exception/Exception.h>
#include <bridgepp/Worker/Overseer.h>

View File

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

View File

@ -39,6 +39,9 @@ void UserList::connectGRPCEvents() const {
connect(&client, &GRPCClient::userChanged, this, &UserList::onUserChanged);
connect(&client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished);
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);
}
//****************************************************************************************************************************************************
/// \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 onToggleSplitModeFinished(QString const &userID);
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
QList<bridgepp::SPUser> users_; ///< The user list.

View File

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

View File

@ -29,14 +29,19 @@ Item {
property var _spacing: 12 * ProtonStyle.px
property color usedSpaceColor : {
property color progressColor : {
if (!root.enabled) 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.usedFraction < .75) return root.colorScheme.signal_warning
if (root.user && root.user.isSyncing) return root.colorScheme.text_weak
if (root.progressRatio < .50) return root.colorScheme.signal_success
if (root.progressRatio < .75) return root.colorScheme.signal_warning
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 usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
@ -171,18 +176,21 @@ Item {
case EUserState.Locked:
return qsTr("Connecting") + dotsTimer.dots
case EUserState.Connected:
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
property string dots: ""
interval: 250;
interval: 500;
repeat: true;
running: (root.user != null) && (root.user.state === EUserState.Locked)
running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing))
onTriggered: {
dots = dots + "."
dots += "."
if (dots.length > 3)
dots = ""
}
@ -191,7 +199,7 @@ Item {
}
}
color: root.usedSpaceColor
color: root.progressColor
type: {
switch (root.type) {
case AccountDelegate.SmallView: return Label.Caption
@ -202,7 +210,7 @@ Item {
Label {
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
type: {
switch (root.type) {
@ -213,26 +221,27 @@ Item {
}
}
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 }
Rectangle {
id: storage_bar
id: progress_bar
visible: root.user ? root.type == AccountDelegate.LargeView : false
width: 140 * ProtonStyle.px
height: 4 * ProtonStyle.px
radius: ProtonStyle.storage_bar_radius
radius: ProtonStyle.progress_bar_radius
color: root.colorScheme.border_weak
Rectangle {
id: storage_bar_filled
radius: ProtonStyle.storage_bar_radius
color: root.usedSpaceColor
id: progress_bar_filled
radius: ProtonStyle.progress_bar_radius
color: root.progressColor
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false
anchors {
top : parent.top
bottom : parent.bottom
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 dialog_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 int heading_font_size: 28

View File

@ -135,6 +135,7 @@ add_custom_command(
add_library(bridgepp
bridgepp/BridgeLib.cpp bridgepp/BridgeLib.h
bridgepp/BridgeUtils.cpp bridgepp/BridgeUtils.h
bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp
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.
/// \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 newUsedBytesChangedEvent(QString const &userID, qint64 usedBytes); ///< Create a new UsedBytesChangedEvent 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
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);
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:
this->logError("Unknown User event received.");
}

View File

@ -181,6 +181,9 @@ signals:
void userBadEvent(QString const &userID, QString const& errorMessage);
void usedBytesChanged(QString const &userID, qint64 usedBytes);
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
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.
/// \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(float usedBytes READ usedBytes WRITE setUsedBytes NOTIFY usedBytesChanged)
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;
void setID(QString const &id);
@ -120,6 +122,10 @@ public:
void setUsedBytes(float usedBytes);
float totalBytes() const;
void setTotalBytes(float totalBytes);
bool isSyncing() const;
void setIsSyncing(bool syncing);
float syncProgress() const;
void setSyncProgress(float progress);
signals:
// signals used for Qt properties
@ -134,6 +140,8 @@ signals:
void usedBytesChanged(float byteCount);
void totalBytesChanged(float byteCount);
void toggleSplitModeFinished();
void isSyncingChanged(bool syncing);
void syncProgressChanged(float syncProgress);
private: // member functions.
User(QObject *parent); ///< Default constructor.
@ -149,6 +157,8 @@ private: // data members.
bool splitMode_ { false }; ///< Is split mode active.
float usedBytes_ { 0.0f }; ///< The storage used by 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;
UsedBytesChangedEvent usedBytesChangedEvent = 5;
ImapLoginFailedEvent imapLoginFailedEvent = 6;
SyncStartedEvent syncStartedEvent = 7;
SyncFinishedEvent syncFinishedEvent = 8;
SyncProgressEvent syncProgressEvent = 9;
}
}
@ -478,6 +481,21 @@ message ImapLoginFailedEvent {
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
//**********************************************************

View File

@ -185,6 +185,23 @@ func newIMAPLoginFailedEvent(username string) *StreamEvent {
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 {
return genericErrorEvent(&GenericErrorEvent{Code: errorCode})
}

View File

@ -323,6 +323,15 @@ func (s *Service) watchEvents() {
case events.UserBadEvent:
_ = 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:
safe.RLock(func() {
s.latest = event.Version