Improve error handling

Allow viewing request URL and response of internal
errors.
This commit is contained in:
Martchus 2017-08-20 01:20:47 +02:00
parent c8b68bc7c7
commit e52a2a6ef6
14 changed files with 324 additions and 62 deletions

View File

@ -202,10 +202,24 @@ void Application::handleResponse()
}
}
void Application::handleError(const QString &message)
void Application::handleError(
const QString &message, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
VAR_UNUSED(category)
VAR_UNUSED(networkError)
eraseLine(cout);
cerr << "\rError: " << message.toLocal8Bit().data() << endl;
cerr << '\n';
setStyle(cerr, Color::Red, ColorContext::Foreground, TextAttribute::Bold);
cerr << "\rError: ";
resetStyle(cerr);
cerr << message.toLocal8Bit().data();
const QUrl url(request.url());
if (!url.isEmpty()) {
cerr << "\nRequest: " << url.toString(QUrl::PrettyDecoded).toLocal8Bit().data();
}
if (!response.isEmpty()) {
cerr << "\nResponse:\n" << response.data() << endl;
}
QCoreApplication::exit(-3);
}

View File

@ -26,7 +26,8 @@ public:
private slots:
void handleStatusChanged(Data::SyncthingStatus newStatus);
void handleResponse();
void handleError(const QString &message);
void handleError(
const QString &message, Data::SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response);
void findRelevantDirsAndDevs();
void findRelevantDirsAndDevs(OperationType operationType);
bool findPwd();

View File

@ -864,7 +864,7 @@ bool SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionS
*/
void SyncthingConnection::readConfig()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_configReply) {
m_configReply = nullptr;
@ -872,8 +872,9 @@ void SyncthingConnection::readConfig()
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
m_rawConfig = replyDoc.object();
emit newConfig(m_rawConfig);
@ -884,14 +885,14 @@ void SyncthingConnection::readConfig()
continueConnecting();
}
} else {
emit error(tr("Unable to parse Syncthing config: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse Syncthing config: "), jsonError, reply, response);
}
break;
}
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request Syncthing config: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request Syncthing config: "), SyncthingErrorCategory::OverallConnection, reply);
setStatus(SyncthingStatus::Disconnected);
if (m_autoReconnectTimer.interval()) {
m_autoReconnectTimer.start();
@ -962,7 +963,7 @@ void SyncthingConnection::readDevs(const QJsonArray &devs)
*/
void SyncthingConnection::readStatus()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_statusReply) {
m_statusReply = nullptr;
@ -970,8 +971,9 @@ void SyncthingConnection::readStatus()
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
const QString myId(replyObj.value(QStringLiteral("myID")).toString());
@ -993,14 +995,14 @@ void SyncthingConnection::readStatus()
continueConnecting();
}
} else {
emit error(tr("Unable to parse Syncthing status: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse Syncthing status: "), jsonError, reply, response);
}
break;
}
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request Syncthing status: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request Syncthing status: "), SyncthingErrorCategory::OverallConnection, reply);
}
}
@ -1009,7 +1011,7 @@ void SyncthingConnection::readStatus()
*/
void SyncthingConnection::readConnections()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_connectionsReply) {
m_connectionsReply = nullptr;
@ -1017,8 +1019,9 @@ void SyncthingConnection::readConnections()
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
const QJsonObject totalObj(replyObj.value(QStringLiteral("total")).toObject());
@ -1081,14 +1084,14 @@ void SyncthingConnection::readConnections()
m_trafficPollTimer.start();
}
} else {
emit error(tr("Unable to parse connections: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse connections: "), jsonError, reply, response);
}
break;
}
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request connections: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request connections: "), SyncthingErrorCategory::OverallConnection, reply);
}
}
@ -1097,13 +1100,18 @@ void SyncthingConnection::readConnections()
*/
void SyncthingConnection::readDirStatistics()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
QTimer::singleShot(7000, [this] {
emit this->error(QStringLiteral("some error message"), SyncthingErrorCategory::SpecificRequest, 0,
QNetworkRequest(QUrl(QString("http://tadlasjkfefj asdj<sdf jasyöldf"))), QByteArray("asdlkfja saslködfj asölf jaeöfklaj söefkla"));
});
if (jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
int index = 0;
@ -1142,15 +1150,14 @@ void SyncthingConnection::readDirStatistics()
++index;
}
} else {
emit error(
tr("Unable to parse directory statistics: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse directory statistics: "), jsonError, reply, response);
}
break;
}
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request directory statistics: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request directory statistics: "), SyncthingErrorCategory::OverallConnection, reply);
}
}
@ -1159,13 +1166,14 @@ void SyncthingConnection::readDirStatistics()
*/
void SyncthingConnection::readDeviceStatistics()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
int index = 0;
@ -1186,14 +1194,14 @@ void SyncthingConnection::readDeviceStatistics()
m_devStatsPollTimer.start();
}
} else {
emit error(tr("Unable to parse device statistics: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse device statistics: "), jsonError, reply, response);
}
break;
}
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request device statistics: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request device statistics: "), SyncthingErrorCategory::OverallConnection, reply);
}
}
@ -1202,7 +1210,7 @@ void SyncthingConnection::readDeviceStatistics()
*/
void SyncthingConnection::readErrors()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_errorsReply) {
m_errorsReply = nullptr;
@ -1215,8 +1223,9 @@ void SyncthingConnection::readErrors()
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
for (const QJsonValue &errorVal : replyDoc.object().value(QStringLiteral("errors")).toArray()) {
const QJsonObject errorObj(errorVal.toObject());
@ -1231,7 +1240,7 @@ void SyncthingConnection::readErrors()
}
}
} else {
emit error(tr("Unable to parse errors: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse errors: "), jsonError, reply, response);
}
// since there seems no event for this data, keep polling
@ -1243,7 +1252,7 @@ void SyncthingConnection::readErrors()
case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request errors: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request errors: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1252,14 +1261,14 @@ void SyncthingConnection::readErrors()
*/
void SyncthingConnection::readClearingErrors()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
break;
default:
emit error(tr("Unable to request clearing errors: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request clearing errors: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1268,7 +1277,7 @@ void SyncthingConnection::readClearingErrors()
*/
void SyncthingConnection::readEvents()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply == m_eventsReply) {
m_eventsReply = nullptr;
@ -1276,8 +1285,9 @@ void SyncthingConnection::readEvents()
switch (reply->error()) {
case QNetworkReply::NoError: {
const QByteArray response(reply->readAll());
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
const QJsonDocument replyDoc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
const QJsonArray replyArray = replyDoc.array();
emit newEvents(replyArray);
@ -1312,7 +1322,7 @@ void SyncthingConnection::readEvents()
}
}
} else {
emit error(tr("Unable to parse Syncthing events: ") + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError);
emitError(tr("Unable to parse Syncthing events: "), jsonError, reply, response);
setStatus(SyncthingStatus::Disconnected);
if (m_autoReconnectTimer.interval()) {
m_autoReconnectTimer.start();
@ -1335,7 +1345,7 @@ void SyncthingConnection::readEvents()
}
return;
default:
emit error(tr("Unable to request Syncthing events: ") + reply->errorString(), SyncthingErrorCategory::OverallConnection, reply->error());
emitError(tr("Unable to request Syncthing events: "), SyncthingErrorCategory::OverallConnection, reply);
setStatus(SyncthingStatus::Disconnected);
if (m_autoReconnectTimer.interval()) {
m_autoReconnectTimer.start();
@ -1570,7 +1580,7 @@ void SyncthingConnection::readDeviceEvent(DateTime eventTime, const QString &eve
/*!
* \brief Reads results of requestEvents().
* \remarks TODO
* \todo Implement this.
*/
void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject &eventData)
{
@ -1613,14 +1623,14 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject
*/
void SyncthingConnection::readRescan()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
emit rescanTriggered(reply->property("dirId").toString());
break;
default:
emit error(tr("Unable to request rescan: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request rescan: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1629,7 +1639,7 @@ void SyncthingConnection::readRescan()
*/
void SyncthingConnection::readDevPauseResume()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError: {
@ -1644,13 +1654,13 @@ void SyncthingConnection::readDevPauseResume()
break;
}
default:
emit error(tr("Unable to request device pause/resume: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request device pause/resume: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
void SyncthingConnection::readDirPauseResume()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError: {
@ -1665,7 +1675,7 @@ void SyncthingConnection::readDirPauseResume()
break;
}
default:
emit error(tr("Unable to request directory pause/resume: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request directory pause/resume: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1674,14 +1684,14 @@ void SyncthingConnection::readDirPauseResume()
*/
void SyncthingConnection::readRestart()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
emit restartTriggered();
break;
default:
emit error(tr("Unable to request restart: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request restart: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1690,14 +1700,14 @@ void SyncthingConnection::readRestart()
*/
void SyncthingConnection::readShutdown()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch (reply->error()) {
case QNetworkReply::NoError:
emit shutdownTriggered();
break;
default:
emit error(tr("Unable to request shutdown: ") + reply->errorString(), SyncthingErrorCategory::SpecificRequest, reply->error());
emitError(tr("Unable to request shutdown: "), SyncthingErrorCategory::SpecificRequest, reply);
}
}
@ -1780,6 +1790,23 @@ void SyncthingConnection::emitNotification(DateTime when, const QString &message
emit newNotification(when, message);
}
/*!
* \brief Internally called to emit a JSON parsing error.
* \remarks Since in this case the reply has already been read, its response must be passed as extra argument.
*/
void SyncthingConnection::emitError(const QString &message, const QJsonParseError &jsonError, QNetworkReply *reply, const QByteArray &response)
{
emit error(message + jsonError.errorString(), SyncthingErrorCategory::Parsing, QNetworkReply::NoError, reply->request(), response);
}
/*!
* \brief Internally called to emit a network error (server replied error code are connection or server could not be reached at all).
*/
void SyncthingConnection::emitError(const QString &message, SyncthingErrorCategory category, QNetworkReply *reply)
{
emit error(message + reply->errorString(), category, reply->error(), reply->request(), reply->readAll());
}
/*!
* \fn SyncthingConnection::newConfig()
* \brief Indicates new configuration (dirs, devs, ...) is available.

View File

@ -6,6 +6,7 @@
#include <QJsonObject>
#include <QList>
#include <QNetworkRequest>
#include <QObject>
#include <QSslError>
#include <QTimer>
@ -16,10 +17,10 @@
QT_FORWARD_DECLARE_CLASS(QNetworkAccessManager)
QT_FORWARD_DECLARE_CLASS(QNetworkReply)
QT_FORWARD_DECLARE_CLASS(QNetworkRequest)
QT_FORWARD_DECLARE_CLASS(QUrlQuery)
QT_FORWARD_DECLARE_CLASS(QJsonObject)
QT_FORWARD_DECLARE_CLASS(QJsonArray)
QT_FORWARD_DECLARE_CLASS(QJsonParseError)
class ConnectionTests;
class MiscTests;
@ -152,7 +153,8 @@ Q_SIGNALS:
void devStatusChanged(const SyncthingDev &dev, int index);
void downloadProgressChanged();
void newNotification(ChronoUtilities::DateTime when, const QString &message);
void error(const QString &errorMessage, SyncthingErrorCategory category, int networkError);
void error(const QString &errorMessage, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request = QNetworkRequest(),
const QByteArray &response = QByteArray());
void statusChanged(SyncthingStatus newStatus);
void configDirChanged(const QString &newConfigDir);
void myIdChanged(const QString &myNewId);
@ -204,6 +206,8 @@ private Q_SLOTS:
void autoReconnect();
void setStatus(SyncthingStatus status);
void emitNotification(ChronoUtilities::DateTime when, const QString &message);
void emitError(const QString &message, const QJsonParseError &jsonError, QNetworkReply *reply, const QByteArray &response = QByteArray());
void emitError(const QString &message, SyncthingErrorCategory category, QNetworkReply *reply);
private:
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true);

View File

@ -1,7 +1,9 @@
#include "./trayicon.h"
#include "./traywidget.h"
#include "../../widgets/misc/errorviewdialog.h"
#include "../../widgets/misc/statusinfo.h"
#include "../../widgets/misc/textviewdialog.h"
#include "../../widgets/settings/settings.h"
#include "../../model/syncthingicons.h"
@ -39,6 +41,7 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
, m_initialized(false)
, m_trayMenu(this, connectionConfig)
, m_status(SyncthingStatus::Disconnected)
, m_messageClickedAction(TrayIconMessageClickedAction::None)
{
// set context menu
connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("internet-web-browser"),
@ -74,7 +77,7 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
// connect signals and slots
SyncthingConnection *connection = &(m_trayMenu.widget()->connection());
connect(this, &TrayIcon::activated, this, &TrayIcon::handleActivated);
connect(this, &TrayIcon::messageClicked, m_trayMenu.widget(), &TrayWidget::dismissNotifications);
connect(this, &TrayIcon::messageClicked, this, &TrayIcon::handleMessageClicked);
connect(connection, &SyncthingConnection::error, this, &TrayIcon::showInternalError);
connect(connection, &SyncthingConnection::newNotification, this, &TrayIcon::showSyncthingNotification);
connect(connection, &SyncthingConnection::statusChanged, this, &TrayIcon::handleConnectionStatusChanged);
@ -83,6 +86,7 @@ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent)
static_cast<void (SyncthingConnection::*)(void)>(&SyncthingConnection::connect));
connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, m_trayMenu.widget(), &TrayWidget::dismissNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, m_trayMenu.widget(), &TrayWidget::showNotifications);
connect(&m_dbusNotifier, &DBusStatusNotifier::errorDetailsRequested, &ErrorViewDialog::showInstance);
#endif
m_initialized = true;
}
@ -121,6 +125,20 @@ void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason)
}
}
void TrayIcon::handleMessageClicked()
{
switch (m_messageClickedAction) {
case TrayIconMessageClickedAction::None:
return;
case TrayIconMessageClickedAction::DismissNotification:
m_trayMenu.widget()->dismissNotifications();
break;
case TrayIconMessageClickedAction::ShowInternalErrors:
ErrorViewDialog::instance()->show();
break;
}
}
void TrayIcon::handleConnectionStatusChanged(SyncthingStatus status)
{
if (m_initialized && m_status == status) {
@ -131,7 +149,8 @@ void TrayIcon::handleConnectionStatusChanged(SyncthingStatus status)
m_status = status;
}
void TrayIcon::showInternalError(const QString &errorMsg, SyncthingErrorCategory category, int networkError)
void TrayIcon::showInternalError(
const QString &errorMsg, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response)
{
const auto &settings = Settings::values();
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
@ -148,14 +167,17 @@ void TrayIcon::showInternalError(const QString &errorMsg, SyncthingErrorCategory
&& !service.isActiveWithoutSleepFor(settings.ignoreInavailabilityAfterStart)))
#endif
) {
InternalError error(errorMsg, request.url(), response);
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
if (settings.dbusNotifications) {
m_dbusNotifier.showInternalError(errorMsg, category, networkError);
m_dbusNotifier.showInternalError(error);
} else
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::ShowInternalErrors;
showMessage(tr("Error"), errorMsg, QSystemTrayIcon::Critical);
}
ErrorViewDialog::addError(move(error));
}
}
@ -171,6 +193,7 @@ void TrayIcon::showSyncthingNotification(ChronoUtilities::DateTime when, const Q
Q_UNUSED(when)
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::DismissNotification;
showMessage(tr("Syncthing notification - click to dismiss"), message, QSystemTrayIcon::Warning);
}
}
@ -206,6 +229,7 @@ void TrayIcon::showStatusNotification(SyncthingStatus status)
} else
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::None;
showMessage(QCoreApplication::applicationName(), tr("Disconnected from Syncthing"), QSystemTrayIcon::Warning);
}
}
@ -241,6 +265,7 @@ void TrayIcon::showStatusNotification(SyncthingStatus status)
} else
#endif
{
m_messageClickedAction = TrayIconMessageClickedAction::None;
showMessage(QCoreApplication::applicationName(), message, QSystemTrayIcon::Information);
}
}

View File

@ -11,6 +11,7 @@
#include <QSystemTrayIcon>
QT_FORWARD_DECLARE_CLASS(QPixmap)
QT_FORWARD_DECLARE_CLASS(QNetworkRequest)
namespace Data {
enum class SyncthingStatus;
@ -19,6 +20,8 @@ enum class SyncthingErrorCategory;
namespace QtGui {
enum class TrayIconMessageClickedAction { None, DismissNotification, ShowInternalErrors };
class TrayIcon : public QSystemTrayIcon {
Q_OBJECT
@ -27,13 +30,15 @@ public:
TrayMenu &trayMenu();
public slots:
void showInternalError(const QString &errorMsg, Data::SyncthingErrorCategory category, int networkError);
void showInternalError(
const QString &errorMsg, Data::SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response);
void showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message);
void showStatusNotification(Data::SyncthingStatus status);
void updateStatusIconAndText();
private slots:
void handleActivated(QSystemTrayIcon::ActivationReason reason);
void handleMessageClicked();
void handleConnectionStatusChanged(Data::SyncthingStatus status);
private:
@ -44,6 +49,7 @@ private:
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
DBusStatusNotifier m_dbusNotifier;
#endif
TrayIconMessageClickedAction m_messageClickedAction;
};
inline TrayMenu &TrayIcon::trayMenu()

View File

@ -17,8 +17,10 @@ set(WIDGETS_HEADER_FILES
webview/webpage.h
webview/webviewdialog.h
misc/textviewdialog.h
misc/errorviewdialog.h
misc/statusinfo.h
misc/dbusstatusnotifier.h
misc/internalerror.h
)
set(WIDGETS_SRC_FILES
settings/settings.cpp
@ -26,6 +28,7 @@ set(WIDGETS_SRC_FILES
webview/webpage.cpp
webview/webviewdialog.cpp
misc/textviewdialog.cpp
misc/errorviewdialog.cpp
misc/statusinfo.cpp
misc/dbusstatusnotifier.cpp
)

View File

@ -18,6 +18,8 @@ DBusStatusNotifier::DBusStatusNotifier(QObject *parent)
m_disconnectedNotification.setMessage(tr("Disconnected from Syncthing"));
m_disconnectedNotification.setActions(QStringList(tr("Try to reconnect")));
connect(&m_disconnectedNotification, &DBusNotification::actionInvoked, this, &DBusStatusNotifier::connectRequested);
m_internalErrorNotification.setActions(QStringList({ QStringLiteral("details"), tr("View details") }));
connect(&m_internalErrorNotification, &DBusNotification::actionInvoked, this, &DBusStatusNotifier::errorDetailsRequested);
m_syncthingNotification.setActions(QStringList({ QStringLiteral("show"), tr("Show"), QStringLiteral("dismiss"), tr("Dismiss") }));
connect(&m_syncthingNotification, &DBusNotification::actionInvoked, this, &DBusStatusNotifier::handleSyncthingNotificationAction);
}

View File

@ -1,7 +1,7 @@
#if !defined(SYNCTHINGWIDGETS_DBUSSTATUSNOTIFIER_H) && defined(QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS)
#define SYNCTHINGWIDGETS_DBUSSTATUSNOTIFIER_H
#include "../global.h"
#include "./internalerror.h"
#include <qtutilities/misc/dbusnotification.h>
@ -24,7 +24,7 @@ public:
public Q_SLOTS:
void showDisconnect();
void hideDisconnect();
void showInternalError(const QString &errorMsg, Data::SyncthingErrorCategory category, int networkError);
void showInternalError(const InternalError &error);
void showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message);
void showSyncComplete(const QString &message);
@ -32,6 +32,7 @@ Q_SIGNALS:
void connectRequested();
void dismissNotificationsRequested();
void showNotificationsRequested();
void errorDetailsRequested();
private Q_SLOTS:
void handleSyncthingNotificationAction(const QString &action);
@ -53,11 +54,9 @@ inline void DBusStatusNotifier::hideDisconnect()
m_disconnectedNotification.hide();
}
inline void DBusStatusNotifier::showInternalError(const QString &errorMsg, Data::SyncthingErrorCategory category, int networkError)
inline void DBusStatusNotifier::showInternalError(const InternalError &error)
{
Q_UNUSED(category)
Q_UNUSED(networkError)
m_internalErrorNotification.update(errorMsg);
m_internalErrorNotification.update(error.message);
}
inline void DBusStatusNotifier::showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message)

View File

@ -0,0 +1,100 @@
#include "./errorviewdialog.h"
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QPushButton>
#include <QStringBuilder>
#include <QTextBrowser>
#include <QVBoxLayout>
#include <algorithm>
#include <limits>
using namespace std;
using namespace ChronoUtilities;
using namespace Data;
namespace QtGui {
ErrorViewDialog *ErrorViewDialog::s_instance = nullptr;
std::vector<InternalError> ErrorViewDialog::s_internalErrors;
ErrorViewDialog::ErrorViewDialog()
: TextViewDialog(tr("Internal errors"))
, m_request(tr("Request URL:"))
, m_response(tr("Response:"))
, m_statusLabel(new QLabel(this))
{
if (!s_instance) {
s_instance = this;
}
// add layout to show status and additional buttons
auto *const buttonLayout = new QHBoxLayout(this);
buttonLayout->setMargin(0);
// add label for overall status
QFont boldFont(m_statusLabel->font());
boldFont.setBold(true);
m_statusLabel->setFont(boldFont);
buttonLayout->addWidget(m_statusLabel);
updateStatusLabel();
// add errors to text view
for (const InternalError &error : s_internalErrors) {
internalAddError(error);
}
// add a button for clearing errors
if (!s_internalErrors.empty()) {
auto *const clearButton = new QPushButton(this);
clearButton->setText(tr("Clear errors"));
clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
buttonLayout->setMargin(0);
buttonLayout->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
buttonLayout->addWidget(clearButton);
connect(clearButton, &QPushButton::clicked, &ErrorViewDialog::clearErrors);
}
layout()->addItem(buttonLayout);
}
ErrorViewDialog::~ErrorViewDialog()
{
if (s_instance == this) {
s_instance = nullptr;
}
}
void ErrorViewDialog::addError(InternalError &&newError)
{
s_internalErrors.emplace_back(newError);
if (s_instance) {
s_instance->internalAddError(s_internalErrors.back());
s_instance->updateStatusLabel();
}
}
void ErrorViewDialog::internalAddError(const InternalError &error)
{
browser()->append(QString::fromUtf8(error.when.toString(DateTimeOutputFormat::DateAndTime, true).data()) % QChar(':') % QChar(' ') % error.message
% QChar('\n') % m_request % QChar(' ') % error.url.toString(QUrl::FullyDecoded) % QChar('\n') % m_response % QChar('\n')
% QString::fromLocal8Bit(error.response) % QChar('\n'));
}
void ErrorViewDialog::updateStatusLabel()
{
m_statusLabel->setText(tr("%1 error(s) occured", nullptr, static_cast<int>(min<size_t>(s_internalErrors.size(), numeric_limits<int>::max())))
.arg(s_internalErrors.size()));
}
void ErrorViewDialog::clearErrors()
{
s_internalErrors.clear();
if (s_instance) {
s_instance->updateStatusLabel();
s_instance->browser()->clear();
}
}
}

View File

@ -0,0 +1,49 @@
#ifndef SYNCTHINGWIDGETS_ERRORVIEWDIALOG_H
#define SYNCTHINGWIDGETS_ERRORVIEWDIALOG_H
#include "./internalerror.h"
#include "./textviewdialog.h"
#include <vector>
QT_FORWARD_DECLARE_CLASS(QLabel)
namespace QtGui {
class SYNCTHINGWIDGETS_EXPORT ErrorViewDialog : public TextViewDialog {
Q_OBJECT
public:
~ErrorViewDialog();
static ErrorViewDialog *instance();
static void addError(InternalError &&newError);
public Q_SLOTS:
static void showInstance();
static void clearErrors();
private Q_SLOTS:
void internalAddError(const InternalError &error);
void updateStatusLabel();
private:
ErrorViewDialog();
const QString m_request;
const QString m_response;
QLabel *const m_statusLabel;
static ErrorViewDialog *s_instance;
static std::vector<InternalError> s_internalErrors;
};
inline ErrorViewDialog *ErrorViewDialog::instance()
{
return s_instance ? s_instance : (s_instance = new ErrorViewDialog);
}
inline void ErrorViewDialog::showInstance()
{
instance()->show();
}
}
#endif // SYNCTHINGWIDGETS_ERRORVIEWDIALOG_H

View File

@ -0,0 +1,32 @@
#ifndef SYNCTHINGWIDGETS_INTERNALERROR_H
#define SYNCTHINGWIDGETS_INTERNALERROR_H
#include "../global.h"
#include <c++utilities/chrono/datetime.h>
#include <QByteArray>
#include <QString>
#include <QUrl>
namespace QtGui {
struct SYNCTHINGWIDGETS_EXPORT InternalError {
InternalError(const QString &message = QString(), const QUrl &url = QUrl(), const QByteArray &response = QByteArray());
QString message;
QUrl url;
QByteArray response;
ChronoUtilities::DateTime when;
};
inline InternalError::InternalError(const QString &message, const QUrl &url, const QByteArray &response)
: message(message)
, url(url)
, response(response)
, when(ChronoUtilities::DateTime::now())
{
}
}
#endif // SYNCTHINGWIDGETS_INTERNALERROR_H

View File

@ -72,7 +72,7 @@ TextViewDialog *TextViewDialog::forDirectoryErrors(const Data::SyncthingDir &dir
// add errors to text view and find errors about non-empty directories to be removed
QStringList nonEmptyDirs;
for (const SyncthingItemError &error : dir.itemErrors) {
browser->append(error.path % QChar(':') % QChar(' ') % QChar('\n') % error.message % QChar('\n'));
browser->append(error.path % QChar(':') % QChar('\n') % error.message % QChar('\n'));
if (error.message.endsWith(QStringLiteral("directory not empty"))) {
nonEmptyDirs << dir.path + error.path;
}

View File

@ -1,5 +1,5 @@
#ifndef TEXTVIEWDIALOG_H
#define TEXTVIEWDIALOG_H
#ifndef SYNCTHINGWIDGETS_TEXTVIEWDIALOG_H
#define SYNCTHINGWIDGETS_TEXTVIEWDIALOG_H
#include "../global.h"
@ -21,7 +21,7 @@ public:
QTextBrowser *browser();
static TextViewDialog *forDirectoryErrors(const Data::SyncthingDir &dir);
signals:
Q_SIGNALS:
void reload();
protected:
@ -37,4 +37,4 @@ inline QTextBrowser *TextViewDialog::browser()
}
}
#endif // TEXTVIEWDIALOG_H
#endif // SYNCTHINGWIDGETS_TEXTVIEWDIALOG_H