mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-20 00:56:47 +00:00
feat(GODT-2261): sync progress in GUI.
This commit is contained in:
@ -1,225 +0,0 @@
|
||||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "BridgeLib.h"
|
||||
#include <bridgepp/Exception/Exception.h>
|
||||
#include <bridgepp/BridgeUtils.h>
|
||||
|
||||
|
||||
using namespace bridgepp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef char *(*FuncReturningCString)();
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
FuncReturningCString goosFunc = nullptr; ///< A pointer to the dynamically loaded GoOS function.
|
||||
FuncReturningCString userCacheDirFunc = nullptr; ///< A pointer to the dynamically loaded UserCache function.
|
||||
FuncReturningCString userConfigDirFunc = nullptr; ///< A pointer to the dynamically loaded UserConfig function.
|
||||
FuncReturningCString userDataDirFunc = nullptr; ///< A pointer to the dynamically loaded UserData function.
|
||||
void (*deleteCStringFunc)(char *) = nullptr; ///< A pointer to the deleteCString function.
|
||||
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
typedef HINSTANCE LibHandle;
|
||||
#else
|
||||
typedef void *LibHandle;
|
||||
#endif
|
||||
|
||||
|
||||
LibHandle loadDynamicLibrary(QString const &path); ///< Load a dynamic library.
|
||||
void *getFuncPointer(LibHandle lib, QString const &funcName); ///< Retrieve a function pointer from a dynamic library.
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path to the bridgelib library file.
|
||||
//****************************************************************************************************************************************************
|
||||
QString bridgelibPath() {
|
||||
QString const path = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("bridgelib.");
|
||||
switch (os()) {
|
||||
case OS::Windows:
|
||||
return path + "dll";
|
||||
case OS::MacOS:
|
||||
return path + "dylib";
|
||||
case OS::Linux:
|
||||
default:
|
||||
return path + "so";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] path The path of the library file.
|
||||
/// \return A pointer to the library object
|
||||
//****************************************************************************************************************************************************
|
||||
LibHandle loadDynamicLibrary(QString const &path) {
|
||||
if (!QFileInfo::exists(path)) {
|
||||
throw Exception(QString("The dynamic library file bridgelib.dylib could not be found at '%1'.").arg(path));
|
||||
}
|
||||
|
||||
LibHandle handle = LoadLibrary(reinterpret_cast<LPCWSTR>(path.toStdWString().c_str()));
|
||||
if (!handle) {
|
||||
throw Exception(QString("The bridgelib dynamic library file '%1' could not be opened.").arg(path));
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] lib A handle to the library
|
||||
/// \param[in] funcName The name of the function.
|
||||
/// \return A pointer to the function
|
||||
//****************************************************************************************************************************************************
|
||||
void *getFuncPointer(LibHandle lib, QString const &funcName) {
|
||||
void *pointer = reinterpret_cast<void*>(GetProcAddress(lib, funcName.toLocal8Bit()));
|
||||
if (!pointer)
|
||||
throw Exception(QString("Could not locate function %1 in bridgelib dynamic library").arg(funcName));
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] path The path of the library file.
|
||||
/// \return A pointer to the library object
|
||||
//****************************************************************************************************************************************************
|
||||
void *loadDynamicLibrary(QString const &path) {
|
||||
if (!QFileInfo::exists(path)) {
|
||||
throw Exception(QString("The dynamic library file bridgelib.dylib could not be found at '%1'.").arg(path));
|
||||
}
|
||||
|
||||
void *lib = dlopen(path.toLocal8Bit().data(), RTLD_LAZY);
|
||||
if (!lib) {
|
||||
throw Exception(QString("The bridgelib dynamic library file '%1' could not be opened.").arg(path));
|
||||
}
|
||||
|
||||
return lib;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] lib A handle to the library
|
||||
/// \param[in] funcName The name of the function.
|
||||
/// \return A pointer to the function
|
||||
//****************************************************************************************************************************************************
|
||||
void *getFuncPointer(LibHandle lib, QString const &funcName) {
|
||||
void *pointer = dlsym(lib, funcName.toLocal8Bit());
|
||||
if (!pointer) {
|
||||
throw Exception(QString("Could not locate function %1 in bridgelib dynamic library").arg(funcName));
|
||||
}
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
||||
|
||||
#endif // defined(Q_OS_WINDOWS)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace bridgelib {
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
//
|
||||
//****************************************************************************************************************************************************
|
||||
void loadLibrary() {
|
||||
try {
|
||||
LibHandle lib = loadDynamicLibrary(bridgelibPath());
|
||||
goosFunc = reinterpret_cast<FuncReturningCString>(getFuncPointer(lib, "GoOS"));
|
||||
userCacheDirFunc = reinterpret_cast<FuncReturningCString>(getFuncPointer(lib, "UserCacheDir"));
|
||||
userConfigDirFunc = reinterpret_cast<FuncReturningCString>(getFuncPointer(lib, "UserConfigDir"));
|
||||
userDataDirFunc = reinterpret_cast<FuncReturningCString>(getFuncPointer(lib, "UserDataDir"));
|
||||
deleteCStringFunc = reinterpret_cast<void (*)(char*)>(getFuncPointer(lib, "DeleteCString"));
|
||||
|
||||
} catch (Exception const &e) {
|
||||
throw Exception("Error loading the bridgelib dynamic library file.", e.qwhat());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \brief Converts a C-style string returned by a go library function to a QString, and release the memory allocated for the C-style string.
|
||||
/// \param[in] cString The C-style string, in UTF-8 format.
|
||||
/// \return A QString.
|
||||
//****************************************************************************************************************************************************
|
||||
QString goToQString(char *const cString) {
|
||||
if (!cString) {
|
||||
return QString();
|
||||
}
|
||||
QString const result = QString::fromUtf8(cString);
|
||||
deleteCStringFunc(cString);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value of the Go runtime.GOOS constant.
|
||||
//****************************************************************************************************************************************************
|
||||
QString goos() {
|
||||
return goToQString(goosFunc());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path to the user cache folder.
|
||||
//****************************************************************************************************************************************************
|
||||
QString userCacheDir() {
|
||||
return goToQString(userCacheDirFunc());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path to the user cache folder.
|
||||
//****************************************************************************************************************************************************
|
||||
QString userConfigDir() {
|
||||
return goToQString(userConfigDirFunc());
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The path to the user data folder.
|
||||
//****************************************************************************************************************************************************
|
||||
QString userDataDir() {
|
||||
return goToQString(userDataDirFunc());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#ifndef BRIDGE_GUI_BRIDGELIB_H
|
||||
#define BRIDGE_GUI_BRIDGELIB_H
|
||||
|
||||
|
||||
namespace bridgelib {
|
||||
|
||||
|
||||
void loadLibrary();
|
||||
QString goos();
|
||||
QString userCacheDir();
|
||||
QString userConfigDir();
|
||||
QString userDataDir();
|
||||
|
||||
|
||||
} // namespace bridgelib
|
||||
|
||||
|
||||
#endif //BRIDGE_GUI_BRIDGELIB_H
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -61,7 +61,10 @@ 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.
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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:
|
||||
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
|
||||
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
|
||||
visible: root.user ? parent.visible && (root.user.state == EUserState.Connected) : false
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user