From 0eb104129003cb64cd8b11a20f74c42c49ede850 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 4 Mar 2016 17:34:48 +0100 Subject: [PATCH 01/44] AbstractNetworkJob: Add a delete job. It is needed to easily send delete requests which happen through the notify API. --- src/libsync/abstractnetworkjob.cpp | 5 +++++ src/libsync/abstractnetworkjob.h | 1 + src/libsync/account.cpp | 9 +++++++++ src/libsync/account.h | 1 + 4 files changed, 16 insertions(+) diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 60ac96f99..c730e14f5 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -148,6 +148,11 @@ QNetworkReply *AbstractNetworkJob::headRequest(const QUrl &url) return addTimer(_account->headRequest(url)); } +QNetworkReply *AbstractNetworkJob::deleteRequest(const QUrl &url) +{ + return addTimer(_account->deleteRequest(url)); +} + void AbstractNetworkJob::slotFinished() { _timer.stop(); diff --git a/src/libsync/abstractnetworkjob.h b/src/libsync/abstractnetworkjob.h index 039732f00..5a4728cc3 100644 --- a/src/libsync/abstractnetworkjob.h +++ b/src/libsync/abstractnetworkjob.h @@ -77,6 +77,7 @@ protected: QNetworkReply* getRequest(const QUrl &url); QNetworkReply* headRequest(const QString &relPath); QNetworkReply* headRequest(const QUrl &url); + QNetworkReply* deleteRequest(const QUrl &url); int maxRedirects() const { return 10; } virtual bool finished() = 0; diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 87208a911..75a1f2a21 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -239,6 +239,15 @@ QNetworkReply *Account::getRequest(const QUrl &url) return _am->get(request); } +QNetworkReply *Account::deleteRequest( const QUrl &url) +{ + QNetworkRequest request(url); +#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4) + request.setSslConfiguration(this->getOrCreateSslConfig()); +#endif + return _am->deleteResource(request); +} + QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data) { return davRequest(verb, concatUrlPath(davUrl(), relPath), req, data); diff --git a/src/libsync/account.h b/src/libsync/account.h index bc586c4ed..dc4240ca8 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -111,6 +111,7 @@ public: QNetworkReply* headRequest(const QUrl &url); QNetworkReply* getRequest(const QString &relPath); QNetworkReply* getRequest(const QUrl &url); + QNetworkReply* deleteRequest( const QUrl &url); QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0); QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0); From a831b7417f4f9dba5308c164cb6081346f4e0bbc Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 4 Mar 2016 17:36:15 +0100 Subject: [PATCH 02/44] Added temporar icon for notifications. --- client.qrc | 1 + resources/bell.png | Bin 0 -> 900 bytes 2 files changed, 1 insertion(+) create mode 100644 resources/bell.png diff --git a/client.qrc b/client.qrc index 5f65c91d2..07847458e 100644 --- a/client.qrc +++ b/client.qrc @@ -22,5 +22,6 @@ resources/account.png resources/more.png resources/delete.png + resources/bell.png diff --git a/resources/bell.png b/resources/bell.png new file mode 100644 index 0000000000000000000000000000000000000000..1947cde8cd2ef742bf48961b3ad75f4a43c79868 GIT binary patch literal 900 zcmV-~1AF|5P)2Q{@$D2dGlsL zOG`^jODnRf3-G-)07+np`28yi0Z9SFKo-bb>?be^902Bp4SWr7w*a)AeE<#tv%>)2 z2mC1kUbcI{0-y!x0lHO#zgQ-?Y+2xo^!utR@M#a=?QCt7;q$=GD8qjOnxhJz2PVaf zF>pHX1H5VXgEmF_32)lLD8uK0k3drto_QzRAH~Sab}OopH|={=BQKldlvwIiF$dTH zbO8T=XTWnH2ebkM!0v!ezpcPGFHY1ZV67gW64_TSV39Yi%?tRXO_3t#HUq>fgpey2$BQ#dO4JiNhf}4(Rgaj!75JxX80(z)%(8(|#ux-Ii6~kc&PsV6Y1CgSE=APFikcm*xLPpdWY)du0#4>f}{0n&2dt5_gW<(FMxB5zd+ z<2$2z;S9Ghe}NyW!^`R_Bci8Vwi{KU@{%gU0qwwui*{R8aKO?M;d3I1O1PLFSE*8e z8!N#9BT~P8McQ_$;((qKqA%C#>VR>F_DX&EHQ=I7f3!fq?GAlXs+FSqY@X{0V28tc zVbaC{n^dgF&jq#shk+v^iZLmRyn^v1S4`@2&c*>N2#Mlibi9+`#xXWin}Cf4;4`?} zyisOUoVLjTrwgnjvKZTPq0?gEa*^U`XyIigfD>Yv&q(l(q5yAI1F%wz4lOM$EiElA afAk-gT$6X!;;5Yf0000 Date: Fri, 4 Mar 2016 17:37:54 +0100 Subject: [PATCH 03/44] Minor wording fixes --- src/gui/notificationconfirmjob.cpp | 63 ++++++++++++++++++++++++++ src/gui/notificationconfirmjob.h | 73 ++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 src/gui/notificationconfirmjob.cpp create mode 100644 src/gui/notificationconfirmjob.h diff --git a/src/gui/notificationconfirmjob.cpp b/src/gui/notificationconfirmjob.cpp new file mode 100644 index 000000000..f243656fc --- /dev/null +++ b/src/gui/notificationconfirmjob.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include "notificationconfirmjob.h" +#include "networkjobs.h" +#include "account.h" +#include "json.h" + +#include + +namespace OCC { + +NotificationConfirmJob::NotificationConfirmJob(AccountPtr account) +: AbstractNetworkJob(account, "") +{ + setIgnoreCredentialFailure(true); +} + +void NotificationConfirmJob::setLinkAndVerb(const QUrl& link, const QString &verb) +{ + _link = link; + _verb = verb; +} + +void NotificationConfirmJob::start() +{ + if( !_link.isValid() ) { + qDebug() << "Attempt to trigger invalid URL: " << _link.toString(); + return; + } + QNetworkRequest req; + req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); + + QIODevice *iodevice = 0; + setReply(davRequest(_verb.toAscii(), _link, req, iodevice)); + setupConnections(reply()); + + AbstractNetworkJob::start(); +} + +bool NotificationConfirmJob::finished() +{ + int replyCode = 0; + // FIXME: check for the reply code! + const QString replyData = reply()->readAll(); + + emit jobFinished(replyData, replyCode); + + return true; + +} + +} diff --git a/src/gui/notificationconfirmjob.h b/src/gui/notificationconfirmjob.h new file mode 100644 index 000000000..e723b4233 --- /dev/null +++ b/src/gui/notificationconfirmjob.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#ifndef NOTIFICATIONCONFIRMJOB_H +#define NOTIFICATIONCONFIRMJOB_H + +#include "accountfwd.h" +#include "abstractnetworkjob.h" + +#include +#include +#include +#include + +namespace OCC { + +/** + * @brief The NotificationConfirmJob class + * @ingroup gui + * + * Class to call an action-link of a notification coming from the server. + * All the communication logic is handled in this class. + * + */ +class NotificationConfirmJob : public AbstractNetworkJob { + Q_OBJECT + +public: + + explicit NotificationConfirmJob(AccountPtr account); + + /** + * Set the verb and link for the job + * + * @param verb currently supported GET PUT POST DELETE + */ + void setLinkAndVerb(const QUrl& link, const QString &verb); + + /** + * Start the OCS request + */ + void start() Q_DECL_OVERRIDE; + +signals: + + /** + * Result of the OCS request + * + * @param reply the reply + */ + void jobFinished(QString reply, int replyCode); + +private slots: + virtual bool finished() Q_DECL_OVERRIDE; + +private: + QString _verb; + QUrl _link; +}; + +} + +#endif // NotificationConfirmJob_H From 688c5502a86e50a5ab6134a2987da17f7f741e90 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 4 Mar 2016 17:40:29 +0100 Subject: [PATCH 04/44] New GUI class NotificationWidget. It displays a server notification that can come with a dynamic set of buttons next to a message and a subject (=header) --- src/gui/notificationwidget.cpp | 83 +++++++++++++++++++++++++++ src/gui/notificationwidget.h | 51 +++++++++++++++++ src/gui/notificationwidget.ui | 101 +++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 src/gui/notificationwidget.cpp create mode 100644 src/gui/notificationwidget.h create mode 100644 src/gui/notificationwidget.ui diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp new file mode 100644 index 000000000..416096866 --- /dev/null +++ b/src/gui/notificationwidget.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include "notificationwidget.h" + +#include + +namespace OCC { + +NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) +{ + _ui.setupUi(this); +} + +void NotificationWidget::setAccountName( const QString& name ) +{ + _accountName = name; +} + +void NotificationWidget::setActivity(const Activity& activity) +{ + _myActivity = activity; + + Q_ASSERT( !activity._accName.isEmpty() ); + _accountName = activity._accName; + + // _ui._headerLabel->setText( ); + _ui._subjectLabel->setText(activity._subject); + if( activity._message.isEmpty()) { + _ui._messageLabel->hide(); + } else { + _ui._messageLabel->setText(activity._message); + } + _ui._notifIcon->setPixmap(QPixmap(":/client/resources/bell.png")); + _ui._notifIcon->setMinimumWidth(64); + _ui._notifIcon->setMinimumHeight(64); + _ui._notifIcon->show(); + + // always remove the buttons + foreach( auto button, _ui._buttonBox->buttons() ) { + _ui._buttonBox->removeButton(button); + } + + // display buttons for the links + foreach( auto link, activity._links ) { + QPushButton *b = _ui._buttonBox->addButton( link._label, QDialogButtonBox::AcceptRole); + b->setDefault(link._isPrimary); + connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); + _buttons.append(b); + } +} + +void NotificationWidget::slotButtonClicked() +{ + QObject *buttonWidget = QObject::sender(); + int index = -1; + if( buttonWidget ) { + for( int i = 0; i < _buttons.count(); i++ ) { + if( _buttons.at(i) == buttonWidget ) { + index = i; + break; + } + } + if( index > -1 && index < _myActivity._links.count() ) { + ActivityLink triggeredLink = _myActivity._links.at(index); + qDebug() << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; + + emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); + } + } +} + +} diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h new file mode 100644 index 000000000..63f4e14a3 --- /dev/null +++ b/src/gui/notificationwidget.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#ifndef NOTIFICATIONWIDGET_H +#define NOTIFICATIONWIDGET_H + +#include + +#include "activitywidget.h" + +#include "ui_notificationwidget.h" + +namespace OCC { + +class NotificationWidget : public QWidget +{ + Q_OBJECT +public: + explicit NotificationWidget(QWidget *parent = 0); + + void setAccountName( const QString& name ); + +signals: + void sendNotificationRequest( const QString&, const QString& link, const QString& verb); + +public slots: + void setActivity(const Activity& activity); + +private slots: + void slotButtonClicked(); + +private: + Ui_NotificationWidget _ui; + Activity _myActivity; + QList _buttons; + QString _accountName; +}; + +} + +#endif // NOTIFICATIONWIDGET_H diff --git a/src/gui/notificationwidget.ui b/src/gui/notificationwidget.ui new file mode 100644 index 000000000..886777278 --- /dev/null +++ b/src/gui/notificationwidget.ui @@ -0,0 +1,101 @@ + + + NotificationWidget + + + + 0 + 0 + 725 + 159 + + + + Form + + + + + + + + + 0 + 0 + + + + + + + ../../resources/bell.png + + + + + + + + + + 0 + 0 + + + + Lorem ipsum dolor sit amet + + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + + + true + + + + + + + QDialogButtonBox::Ok + + + + + + + + + + + QFrame::HLine + + + QFrame::Raised + + + 4 + + + + + + + Qt::Vertical + + + + 20 + 25 + + + + + + + + + From 32e16b323c24f4b90da8cf31831a1b875675b676 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 4 Mar 2016 17:41:57 +0100 Subject: [PATCH 05/44] Display server notifications on the client (#3733) As interaction is required, the notifications are displayed in a separate widget above the server activity list. Note that design and also where we display the notifications can still be discussed and changed. --- src/gui/CMakeLists.txt | 3 + src/gui/activitywidget.cpp | 164 +++++++++++++++++++++++++++++++++++++ src/gui/activitywidget.h | 38 ++++++++- src/gui/activitywidget.ui | 43 +++++++++- 4 files changed, 244 insertions(+), 4 deletions(-) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 2cf488614..d7c12b86e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -32,6 +32,7 @@ set(client_UI owncloudsetuppage.ui addcertificatedialog.ui proxyauthdialog.ui + notificationwidget.ui wizard/owncloudadvancedsetuppage.ui wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui @@ -84,6 +85,8 @@ set(client_SRCS proxyauthhandler.cpp proxyauthdialog.cpp synclogdialog.cpp + notificationwidget.cpp + notificationconfirmjob.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/shibbolethcredentials.cpp diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 2fa67fa26..0347b4377 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -33,6 +33,8 @@ #include "activityitemdelegate.h" #include "protocolwidget.h" #include "QProgressIndicator.h" +#include "notificationwidget.h" +#include "notificationconfirmjob.h" #include "ui_activitywidget.h" @@ -52,6 +54,20 @@ QString ActivityList::accountName() const /* ==================================================================== */ +QHash ActivityLink::toVariantHash() +{ + QHash hash; + + hash["label"] = _label; + hash["link"] = _link; + hash["verb"] = _verb; + hash["primary"] = _isPrimary; + + return hash; +} + +/* ==================================================================== */ + ActivityListModel::ActivityListModel(QWidget *parent) :QAbstractListModel(parent) { @@ -176,6 +192,7 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int stat auto json = activ.toMap(); Activity a; + a._type = Activity::ActivityType; a._accName = ast->account()->displayName(); a._id = json.value("id").toLongLong(); a._subject = json.value("subject").toString(); @@ -277,6 +294,16 @@ ActivityWidget::ActivityWidget(QWidget *parent) : _ui->_activityList->setAlternatingRowColors(true); _ui->_activityList->setModel(_model); + _ui->_notifyLabel->hide(); + _ui->_notifyScroll->hide(); + + // Create a widget container for the notifications. The ui file defines + // a scroll area that get a widget with a layout as children + QWidget *w = new QWidget(this); + _notificationsLayout = new QVBoxLayout(this); + w->setLayout(_notificationsLayout); + _ui->_notifyScroll->setWidget(w); + showLabels(); connect(_model, SIGNAL(activityJobStatusCode(AccountState*,int)), @@ -290,6 +317,9 @@ ActivityWidget::ActivityWidget(QWidget *parent) : connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this, SLOT(slotOpenFile(QModelIndex))); + + connect( this, SIGNAL(newNotificationList(ActivityList)), this, + SLOT(slotBuildNotificationDisplay(ActivityList)) ); } ActivityWidget::~ActivityWidget() @@ -300,6 +330,7 @@ ActivityWidget::~ActivityWidget() void ActivityWidget::slotRefresh(AccountState *ptr) { _model->slotRefreshActivity(ptr); + slotFetchNotifications(ptr); } void ActivityWidget::slotRemoveAccount( AccountState *ptr ) @@ -313,6 +344,8 @@ void ActivityWidget::showLabels() _ui->_headerLabel->setTextFormat(Qt::RichText); _ui->_headerLabel->setText(t); + _ui->_notifyLabel->setText(tr("Action Required: Notifications")); + t.clear(); QSetIterator i(_accountsWithoutActivities); while (i.hasNext() ) { @@ -400,6 +433,137 @@ void ActivityWidget::slotOpenFile(QModelIndex indx) } } +void ActivityWidget::slotFetchNotifications(AccountState *ptr) +{ + /* start the notification fetch job as well */ + if( !ptr) { + return; + } + + // if the previous notification job has finished, start next. + if( !_notificationJob ) { + _notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this ); + QObject::connect(_notificationJob.data(), SIGNAL(jsonReceived(QVariantMap, int)), + this, SLOT(slotNotificationsReceived(QVariantMap, int))); + _notificationJob->setProperty("AccountStatePtr", QVariant::fromValue(ptr)); + + qDebug() << "Start fetching notifications for " << ptr->account()->displayName(); + _notificationJob->start(); + } else { + qDebug() << "Notification Job still running, not starting a new one."; + } +} + + +void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int statusCode) +{ + if( statusCode != 200 ) { + qDebug() << "Failed for Notifications"; + return; + } + + auto notifies = json.value("ocs").toMap().value("data").toList(); + + AccountState* ai = qvariant_cast(sender()->property("AccountStatePtr")); + + qDebug() << "Notifications for " << ai->account()->displayName() << notifies; + + ActivityList list; + + foreach( auto element, notifies ) { + Activity a; + auto json = element.toMap(); + a._type = Activity::NotificationType; + a._accName = ai->account()->displayName(); + a._id = json.value("notification_id").toLongLong(); + a._subject = json.value("subject").toString(); + a._message = json.value("message").toString(); + QString s = json.value("link").toString(); + if( !s.isEmpty() ) { + a._link = QUrl(s); + } + a._dateTime = json.value("datetime").toDateTime(); + a._dateTime.setTimeSpec(Qt::UTC); + + auto actions = json.value("actions").toList(); + foreach( auto action, actions) { + auto actionJson = action.toMap(); + ActivityLink al; + al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray()); + al._link = actionJson.value("link").toString(); + al._verb = actionJson.value("type").toString(); + al._isPrimary = actionJson.value("primary").toBool(); + + a._links.append(al); + } + + list.append(a); + } + emit newNotificationList( list ); +} + +// GUI: Display the notifications +void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) +{ + foreach( auto activity, list ) { + NotificationWidget *widget = 0; + + if( _widgetForNotifId.contains(activity._id) ) { + widget = _widgetForNotifId[activity._id]; + } else { + widget = new NotificationWidget(this); + connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QString)), + this, SLOT(slotSendNotificationRequest(QString, QString, QString))); + _notificationsLayout->addWidget(widget); + // _ui->_notifyScroll->setMinimumHeight( widget->height()); + _ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); + _widgetForNotifId[activity._id] = widget; + } + + widget->setAccountName( activity._accName ); + widget->setActivity( activity ); + } + _ui->_notifyLabel->setHidden( list.count() == 0 ); + _ui->_notifyScroll->setHidden( list.count() == 0 ); +} + +void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb) +{ + qDebug() << "Server Notification Request " << verb << link << "on account" << accountName; + + const QStringList validVerbs = QStringList() << "GET" << "PUT" << "POST" << "DELETE"; + + if( validVerbs.contains(verb)) { + AccountStatePtr acc = AccountManager::instance()->account(accountName); + if( acc ) { + NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); + QString myLink(link); + QUrl l(myLink); + job->setLinkAndVerb(l, verb); + connect( job, SIGNAL( networkError(QNetworkReply*)), + this, SLOT(slotNotifyNetworkError(QNetworkReply*))); + connect( job, SIGNAL( jobFinished(QString, int)), + this, SLOT(slotNotifyServerFinished(QString, int)) ); + job->start(); + } + } else { + qDebug() << "Invalid verb:" << verb; + } +} + + +void ActivityWidget::slotNotifyNetworkError( QNetworkReply* ) +{ + qDebug() << "Server notify job failed."; +} + +void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCode ) +{ + // FIXME: remove the widget after a couple of seconds + qDebug() << "Server Notification reply code"<< replyCode << reply; + +} + /* ==================================================================== */ ActivitySettings::ActivitySettings(QWidget *parent) diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index c3ba5cab5..c27724cdb 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -33,12 +33,31 @@ namespace OCC { class Account; class AccountStatusPtr; class ProtocolWidget; +class JsonApiJob; +class NotificationWidget; namespace Ui { class ActivityWidget; } class Application; +/** + * @brief The ActivityLink class describes actions of an activity + * + * These are part of notifications which are mapped into activities. + */ + +class ActivityLink +{ +public: + QHash toVariantHash(); + + QString _label; + QString _link; + QString _verb; + bool _isPrimary; +}; + /** * @brief Activity Structure * @ingroup gui @@ -49,6 +68,11 @@ class Application; class Activity { public: + enum Type { + ActivityType, + NotificationType + }; + Type _type; qlonglong _id; QString _subject; QString _message; @@ -57,6 +81,7 @@ public: QDateTime _dateTime; QString _accName; + QVector _links; /** * @brief Sort operator to sort the list youngest first. * @param val @@ -146,12 +171,21 @@ public slots: void slotRefresh(AccountState* ptr); void slotRemoveAccount( AccountState *ptr ); void slotAccountActivityStatus(AccountState *ast, int statusCode); + void slotFetchNotifications(AccountState *ptr); signals: void guiLog(const QString&, const QString&); void copyToClipboard(); void rowsInserted(); void hideAcitivityTab(bool); + void newNotificationList(const ActivityList& list); + +private slots: + void slotNotificationsReceived(const QVariantMap& json, int statusCode); + void slotBuildNotificationDisplay(const ActivityList& list); + void slotSendNotificationRequest(const QString &accountName, const QString& link, const QString& verb); + void slotNotifyNetworkError( QNetworkReply* ); + void slotNotifyServerFinished( const QString& reply, int replyCode ); private: void showLabels(); @@ -160,8 +194,10 @@ private: QPushButton *_copyBtn; QSet _accountsWithoutActivities; - + QMap _widgetForNotifId; + QPointer _notificationJob; ActivityListModel *_model; + QVBoxLayout *_notificationsLayout; }; diff --git a/src/gui/activitywidget.ui b/src/gui/activitywidget.ui index eb48941a4..fefd7b3ae 100644 --- a/src/gui/activitywidget.ui +++ b/src/gui/activitywidget.ui @@ -15,23 +15,60 @@ - + TextLabel - + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 677 + 70 + + + + - + TextLabel + + + + 0 + 0 + + + + + + + + TextLabel + + + + From 4a4dac22e2611b5abd53cc27fafa1b11f8218254 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 9 Mar 2016 15:21:52 +0100 Subject: [PATCH 06/44] Notifications: Add a Progress indicator and handle job results. Parse the replyCode from the button action calls and disable buttons accordingly. --- src/gui/activitywidget.cpp | 36 +++++++++++++++++++++++++----- src/gui/activitywidget.h | 1 + src/gui/notificationconfirmjob.cpp | 24 +++++++++++++++++--- src/gui/notificationconfirmjob.h | 7 ++++++ src/gui/notificationwidget.cpp | 28 +++++++++++++++++++++-- src/gui/notificationwidget.h | 2 ++ 6 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 0347b4377..ec01dde74 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -530,6 +530,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb) { qDebug() << "Server Notification Request " << verb << link << "on account" << accountName; + NotificationWidget *theSender = qobject_cast(sender()); const QStringList validVerbs = QStringList() << "GET" << "PUT" << "POST" << "DELETE"; @@ -537,9 +538,9 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con AccountStatePtr acc = AccountManager::instance()->account(accountName); if( acc ) { NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); - QString myLink(link); - QUrl l(myLink); + QUrl l(link); job->setLinkAndVerb(l, verb); + job->setWidget(theSender); connect( job, SIGNAL( networkError(QNetworkReply*)), this, SLOT(slotNotifyNetworkError(QNetworkReply*))); connect( job, SIGNAL( jobFinished(QString, int)), @@ -547,21 +548,44 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con job->start(); } } else { - qDebug() << "Invalid verb:" << verb; + qDebug() << "Notificatio Links: Invalid verb:" << verb; } } - -void ActivityWidget::slotNotifyNetworkError( QNetworkReply* ) +void ActivityWidget::endNotificationRequest( NotificationWidget *widget, int replyCode ) { + if( widget ) { + widget->slotNotificationRequestFinished(replyCode); + } +} + +void ActivityWidget::slotNotifyNetworkError( QNetworkReply *reply) +{ + NotificationConfirmJob *job = qobject_cast(sender()); + if( !job ) { + return; + } + + int resultCode =0; + if( reply ) { + resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + } + + endNotificationRequest(job->widget(), resultCode); qDebug() << "Server notify job failed."; + } void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCode ) { + NotificationConfirmJob *job = qobject_cast(sender()); + if( !job ) { + return; + } + + endNotificationRequest(job->widget(), replyCode); // FIXME: remove the widget after a couple of seconds qDebug() << "Server Notification reply code"<< replyCode << reply; - } /* ==================================================================== */ diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index c27724cdb..7ad8c997b 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -186,6 +186,7 @@ private slots: void slotSendNotificationRequest(const QString &accountName, const QString& link, const QString& verb); void slotNotifyNetworkError( QNetworkReply* ); void slotNotifyServerFinished( const QString& reply, int replyCode ); + void endNotificationRequest(NotificationWidget *widget , int replyCode); private: void showLabels(); diff --git a/src/gui/notificationconfirmjob.cpp b/src/gui/notificationconfirmjob.cpp index f243656fc..8bcbbea3e 100644 --- a/src/gui/notificationconfirmjob.cpp +++ b/src/gui/notificationconfirmjob.cpp @@ -21,7 +21,8 @@ namespace OCC { NotificationConfirmJob::NotificationConfirmJob(AccountPtr account) -: AbstractNetworkJob(account, "") +: AbstractNetworkJob(account, ""), + _widget(0) { setIgnoreCredentialFailure(true); } @@ -32,6 +33,16 @@ void NotificationConfirmJob::setLinkAndVerb(const QUrl& link, const QString &ver _verb = verb; } +void NotificationConfirmJob::setWidget( NotificationWidget *widget ) +{ + _widget = widget; +} + +NotificationWidget *NotificationConfirmJob::widget() +{ + return _widget; +} + void NotificationConfirmJob::start() { if( !_link.isValid() ) { @@ -52,9 +63,16 @@ bool NotificationConfirmJob::finished() { int replyCode = 0; // FIXME: check for the reply code! - const QString replyData = reply()->readAll(); + const QString replyStr = reply()->readAll(); - emit jobFinished(replyData, replyCode); + if( replyStr.contains( "") ) { + QRegExp rex("(\\d+)"); + if( replyStr.contains(rex) ) { + // this is a error message coming back from ocs. + replyCode = rex.cap(1).toInt(); + } + } + emit jobFinished(replyStr, replyCode); return true; diff --git a/src/gui/notificationconfirmjob.h b/src/gui/notificationconfirmjob.h index e723b4233..3f0e8bb6b 100644 --- a/src/gui/notificationconfirmjob.h +++ b/src/gui/notificationconfirmjob.h @@ -24,6 +24,8 @@ namespace OCC { +class NotificationWidget; + /** * @brief The NotificationConfirmJob class * @ingroup gui @@ -51,6 +53,10 @@ public: */ void start() Q_DECL_OVERRIDE; + void setWidget( NotificationWidget *widget ); + + NotificationWidget *widget(); + signals: /** @@ -66,6 +72,7 @@ private slots: private: QString _verb; QUrl _link; + NotificationWidget *_widget; }; } diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 416096866..db94138fa 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -12,6 +12,7 @@ */ #include "notificationwidget.h" +#include "QProgressIndicator.h" #include @@ -20,6 +21,8 @@ namespace OCC { NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) { _ui.setupUi(this); + _progressIndi = new QProgressIndicator(this); + _ui.horizontalLayout->addWidget(_progressIndi); } void NotificationWidget::setAccountName( const QString& name ) @@ -65,19 +68,40 @@ void NotificationWidget::slotButtonClicked() QObject *buttonWidget = QObject::sender(); int index = -1; if( buttonWidget ) { + // find the button that was clicked, it has to be in the list + // of buttons that were added to the button box before. for( int i = 0; i < _buttons.count(); i++ ) { if( _buttons.at(i) == buttonWidget ) { index = i; - break; } + _buttons.at(i)->setEnabled(false); } + + // if the button was found, the link must be called if( index > -1 && index < _myActivity._links.count() ) { ActivityLink triggeredLink = _myActivity._links.at(index); qDebug() << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; - + _progressIndi->startAnimation(); emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); } } } +void NotificationWidget::slotNotificationRequestFinished(int statusCode) +{ + int i = 0; + // the ocs API returns stat code 100 if it succeeded. + if( statusCode != 100 ) { + qDebug() << "Notification Request to Server failed, leave button visible."; + for( i = 0; i < _buttons.count(); i++ ) { + _buttons.at(i)->setEnabled(true); + } + } else { + // the call to the ocs API succeeded. + _ui._buttonBox->hide(); + + } + _progressIndi->stopAnimation(); +} + } diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index 63f4e14a3..c18374220 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -35,6 +35,7 @@ signals: public slots: void setActivity(const Activity& activity); + void slotNotificationRequestFinished(int statusCode); private slots: void slotButtonClicked(); @@ -44,6 +45,7 @@ private: Activity _myActivity; QList _buttons; QString _accountName; + QProgressIndicator *_progressIndi; }; } From b97c832306f1e69690e9f4c85b5bd065aafe9696 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 9 Mar 2016 17:19:36 +0100 Subject: [PATCH 07/44] Capabilities: Add isValid check and check for notifications The isValid check should be used everywhere the capabilities are used as the loading of the capabilities is happening in parallel of the startup, so it is not guaranteed to be available always. --- src/libsync/capabilities.cpp | 10 ++++++++++ src/libsync/capabilities.h | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index a53e00f3e..d6d2eafd5 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -71,6 +71,16 @@ bool Capabilities::shareResharing() const return _capabilities["files_sharing"].toMap()["resharing"].toBool(); } +bool Capabilities::notificationsAvailable() const +{ + return _capabilities.contains("notifications"); +} + +bool Capabilities::isValid() const +{ + return !_capabilities.isEmpty(); +} + QList Capabilities::supportedChecksumTypesAdvertised() const { return QList(); diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 963c4cb53..c3297f224 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -40,6 +40,12 @@ public: int sharePublicLinkExpireDateDays() const; bool shareResharing() const; + /// returns true if the capabilities report notifications + bool notificationsAvailable() const; + + /// returns true if the capabilities are loaded already. + bool isValid() const; + /// Returns the checksum types the server explicitly advertises QList supportedChecksumTypesAdvertised() const; From 7d13a1d8e199b21b917145d52faf7cd7b4f697b3 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 9 Mar 2016 17:21:59 +0100 Subject: [PATCH 08/44] Notifications: Check capabilities if the notifications are enabled If not, do not query for them. --- src/gui/activitywidget.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index ec01dde74..07bf75d76 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -440,6 +440,15 @@ void ActivityWidget::slotFetchNotifications(AccountState *ptr) return; } + // check if the account has notifications enabled. If the capabilities are + // not yet valid, its assumed that notifications are available. + if( ptr->account() && ptr->account()->capabilities().isValid() ) { + if( ! ptr->account()->capabilities().notificationsAvailable() ) { + qDebug() << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; + return; + } + } + // if the previous notification job has finished, start next. if( !_notificationJob ) { _notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this ); From 8a0ce463da3b245c8693c4cbd54d41514babf2ad Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 9 Mar 2016 17:22:19 +0100 Subject: [PATCH 09/44] Notifications: Properly delete the notification check job. --- src/gui/activitywidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 07bf75d76..2a1fb7ee0 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -509,6 +509,9 @@ void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int stat list.append(a); } emit newNotificationList( list ); + + _notificationJob->deleteLater(); + _notificationJob = 0; } // GUI: Display the notifications From 903e79a7c4023c6f28377d5323b2401cea0cae92 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Thu, 10 Mar 2016 14:49:31 +0100 Subject: [PATCH 10/44] Notifications: Do a GUI tray notification if new notifciations arrive. Show a GUI notification once an hour if no new notifications arrive to not annoy users. --- src/gui/activitywidget.cpp | 23 +++++++++++++++++++++++ src/gui/activitywidget.h | 3 +++ 2 files changed, 26 insertions(+) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 2a1fb7ee0..191cb29c7 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -478,6 +478,7 @@ void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int stat qDebug() << "Notifications for " << ai->account()->displayName() << notifies; ActivityList list; + int newGuiLogs = 0; foreach( auto element, notifies ) { Activity a; @@ -506,10 +507,31 @@ void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int stat a._links.append(al); } + // handle gui logs. In order to NOT annoy the user with every fetching of the + // notifications the notification id is stored in a Set. Only if an id + // is not in the set, it qualifies for guiLog. + // Important: The _guiLoggedNotifications set must be wiped regularly which + // will repeat the gui log. + + // after one hour, clear the gui log notification store + if( _guiLogTimer.elapsed() > 60*1000 ) { + _guiLoggedNotifications.clear(); + } + if( !_guiLoggedNotifications.contains(a._id)) { + newGuiLogs++; + _guiLoggedNotifications.insert(a._id); + } list.append(a); } emit newNotificationList( list ); + if( newGuiLogs > 0 ) { + // restart the gui log timer now that we show a notification + _guiLogTimer.restart(); + + emit guiLog(tr("Notifications - Action Required"), + tr("You received %n new notification(s) from the server!", "", newGuiLogs)); + } _notificationJob->deleteLater(); _notificationJob = 0; } @@ -615,6 +637,7 @@ ActivitySettings::ActivitySettings(QWidget *parent) _activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); connect(_activityWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard())); connect(_activityWidget, SIGNAL(hideAcitivityTab(bool)), this, SLOT(setActivityTabHidden(bool))); + connect(_activityWidget, SIGNAL(guiLog(QString,QString)), this, SIGNAL(guiLog(QString,QString))); _protocolWidget = new ProtocolWidget(this); _tab->insertTab(1, _protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 7ad8c997b..44b4a170c 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -199,6 +199,9 @@ private: QPointer _notificationJob; ActivityListModel *_model; QVBoxLayout *_notificationsLayout; + + QElapsedTimer _guiLogTimer; + QSet _guiLoggedNotifications; }; From 2d1ab27cb52b318f30e9825091d94dc5444ff363 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Thu, 10 Mar 2016 17:09:36 +0100 Subject: [PATCH 11/44] Notifications: Refactor - create a notification handler class That cleans the ActivityWidget class --- src/gui/CMakeLists.txt | 1 + src/gui/activitywidget.cpp | 162 ++++++++------------------ src/gui/activitywidget.h | 9 +- src/gui/servernotificationhandler.cpp | 103 ++++++++++++++++ src/gui/servernotificationhandler.h | 47 ++++++++ 5 files changed, 205 insertions(+), 117 deletions(-) create mode 100644 src/gui/servernotificationhandler.cpp create mode 100644 src/gui/servernotificationhandler.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index d7c12b86e..031a1f6f5 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -87,6 +87,7 @@ set(client_SRCS synclogdialog.cpp notificationwidget.cpp notificationconfirmjob.cpp + servernotificationhandler.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/shibbolethcredentials.cpp diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 191cb29c7..01f5b4865 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -35,6 +35,7 @@ #include "QProgressIndicator.h" #include "notificationwidget.h" #include "notificationconfirmjob.h" +#include "servernotificationhandler.h" #include "ui_activitywidget.h" @@ -181,7 +182,7 @@ void ActivityListModel::startFetchJob(AccountState* s) void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) { auto activities = json.value("ocs").toMap().value("data").toList(); - qDebug() << "*** activities" << activities; + // qDebug() << "*** activities" << activities; ActivityList list; AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); @@ -278,7 +279,8 @@ void ActivityListModel::slotRemoveAccount(AccountState *ast ) ActivityWidget::ActivityWidget(QWidget *parent) : QWidget(parent), - _ui(new Ui::ActivityWidget) + _ui(new Ui::ActivityWidget), + _notificationRequests(0) { _ui->setupUi(this); @@ -317,9 +319,6 @@ ActivityWidget::ActivityWidget(QWidget *parent) : connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this, SLOT(slotOpenFile(QModelIndex))); - - connect( this, SIGNAL(newNotificationList(ActivityList)), this, - SLOT(slotBuildNotificationDisplay(ActivityList)) ); } ActivityWidget::~ActivityWidget() @@ -330,7 +329,18 @@ ActivityWidget::~ActivityWidget() void ActivityWidget::slotRefresh(AccountState *ptr) { _model->slotRefreshActivity(ptr); - slotFetchNotifications(ptr); + + // start a server notification handler if no notification requests + // are running + if( _notificationRequests == 0 ) { + ServerNotificationHandler *snh = new ServerNotificationHandler; + connect(snh, SIGNAL(newNotificationList(ActivityList)), this, + SLOT(slotBuildNotificationDisplay(ActivityList))); + + snh->slotFetchNotifications(ptr); + } else { + qDebug() << "========> notification request counter not zero."; + } } void ActivityWidget::slotRemoveAccount( AccountState *ptr ) @@ -433,112 +443,11 @@ void ActivityWidget::slotOpenFile(QModelIndex indx) } } -void ActivityWidget::slotFetchNotifications(AccountState *ptr) -{ - /* start the notification fetch job as well */ - if( !ptr) { - return; - } - - // check if the account has notifications enabled. If the capabilities are - // not yet valid, its assumed that notifications are available. - if( ptr->account() && ptr->account()->capabilities().isValid() ) { - if( ! ptr->account()->capabilities().notificationsAvailable() ) { - qDebug() << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; - return; - } - } - - // if the previous notification job has finished, start next. - if( !_notificationJob ) { - _notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this ); - QObject::connect(_notificationJob.data(), SIGNAL(jsonReceived(QVariantMap, int)), - this, SLOT(slotNotificationsReceived(QVariantMap, int))); - _notificationJob->setProperty("AccountStatePtr", QVariant::fromValue(ptr)); - - qDebug() << "Start fetching notifications for " << ptr->account()->displayName(); - _notificationJob->start(); - } else { - qDebug() << "Notification Job still running, not starting a new one."; - } -} - - -void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int statusCode) -{ - if( statusCode != 200 ) { - qDebug() << "Failed for Notifications"; - return; - } - - auto notifies = json.value("ocs").toMap().value("data").toList(); - - AccountState* ai = qvariant_cast(sender()->property("AccountStatePtr")); - - qDebug() << "Notifications for " << ai->account()->displayName() << notifies; - - ActivityList list; - int newGuiLogs = 0; - - foreach( auto element, notifies ) { - Activity a; - auto json = element.toMap(); - a._type = Activity::NotificationType; - a._accName = ai->account()->displayName(); - a._id = json.value("notification_id").toLongLong(); - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - QString s = json.value("link").toString(); - if( !s.isEmpty() ) { - a._link = QUrl(s); - } - a._dateTime = json.value("datetime").toDateTime(); - a._dateTime.setTimeSpec(Qt::UTC); - - auto actions = json.value("actions").toList(); - foreach( auto action, actions) { - auto actionJson = action.toMap(); - ActivityLink al; - al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray()); - al._link = actionJson.value("link").toString(); - al._verb = actionJson.value("type").toString(); - al._isPrimary = actionJson.value("primary").toBool(); - - a._links.append(al); - } - - // handle gui logs. In order to NOT annoy the user with every fetching of the - // notifications the notification id is stored in a Set. Only if an id - // is not in the set, it qualifies for guiLog. - // Important: The _guiLoggedNotifications set must be wiped regularly which - // will repeat the gui log. - - // after one hour, clear the gui log notification store - if( _guiLogTimer.elapsed() > 60*1000 ) { - _guiLoggedNotifications.clear(); - } - if( !_guiLoggedNotifications.contains(a._id)) { - newGuiLogs++; - _guiLoggedNotifications.insert(a._id); - } - list.append(a); - } - emit newNotificationList( list ); - - if( newGuiLogs > 0 ) { - // restart the gui log timer now that we show a notification - _guiLogTimer.restart(); - - emit guiLog(tr("Notifications - Action Required"), - tr("You received %n new notification(s) from the server!", "", newGuiLogs)); - } - _notificationJob->deleteLater(); - _notificationJob = 0; -} - // GUI: Display the notifications void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) { + int newGuiLogs = 0; + foreach( auto activity, list ) { NotificationWidget *widget = 0; @@ -556,9 +465,33 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) widget->setAccountName( activity._accName ); widget->setActivity( activity ); + + // handle gui logs. In order to NOT annoy the user with every fetching of the + // notifications the notification id is stored in a Set. Only if an id + // is not in the set, it qualifies for guiLog. + // Important: The _guiLoggedNotifications set must be wiped regularly which + // will repeat the gui log. + + // after one hour, clear the gui log notification store + if( _guiLogTimer.elapsed() > 60*60*1000 ) { + _guiLoggedNotifications.clear(); + } + if( !_guiLoggedNotifications.contains(activity._id)) { + newGuiLogs++; + _guiLoggedNotifications.insert(activity._id); + } + } + + _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); + _ui->_notifyScroll->setHidden( _widgetForNotifId.isEmpty() ); + + if( newGuiLogs > 0 ) { + // restart the gui log timer now that we show a notification + _guiLogTimer.restart(); + + emit guiLog(tr("Notifications - Action Required"), + tr("You received %n new notification(s) from the server!", "", newGuiLogs)); } - _ui->_notifyLabel->setHidden( list.count() == 0 ); - _ui->_notifyScroll->setHidden( list.count() == 0 ); } void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb) @@ -580,14 +513,19 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con connect( job, SIGNAL( jobFinished(QString, int)), this, SLOT(slotNotifyServerFinished(QString, int)) ); job->start(); + + // count the number of running notification requests. If this member var + // is larger than zero, no new fetching of notifications is started + _notificationRequests++; } } else { - qDebug() << "Notificatio Links: Invalid verb:" << verb; + qDebug() << "Notification Links: Invalid verb:" << verb; } } void ActivityWidget::endNotificationRequest( NotificationWidget *widget, int replyCode ) { + _notificationRequests--; if( widget ) { widget->slotNotificationRequestFinished(replyCode); } diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 44b4a170c..62ef46e0c 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -171,7 +171,6 @@ public slots: void slotRefresh(AccountState* ptr); void slotRemoveAccount( AccountState *ptr ); void slotAccountActivityStatus(AccountState *ast, int statusCode); - void slotFetchNotifications(AccountState *ptr); signals: void guiLog(const QString&, const QString&); @@ -181,7 +180,6 @@ signals: void newNotificationList(const ActivityList& list); private slots: - void slotNotificationsReceived(const QVariantMap& json, int statusCode); void slotBuildNotificationDisplay(const ActivityList& list); void slotSendNotificationRequest(const QString &accountName, const QString& link, const QString& verb); void slotNotifyNetworkError( QNetworkReply* ); @@ -196,12 +194,13 @@ private: QSet _accountsWithoutActivities; QMap _widgetForNotifId; - QPointer _notificationJob; + QElapsedTimer _guiLogTimer; + QSet _guiLoggedNotifications; + int _notificationRequests; + ActivityListModel *_model; QVBoxLayout *_notificationsLayout; - QElapsedTimer _guiLogTimer; - QSet _guiLoggedNotifications; }; diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp new file mode 100644 index 000000000..bfd06d7eb --- /dev/null +++ b/src/gui/servernotificationhandler.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include "servernotificationhandler.h" +#include "accountstate.h" +#include "capabilities.h" +#include "json.h" +#include "networkjobs.h" + +namespace OCC +{ + +ServerNotificationHandler::ServerNotificationHandler(QObject *parent) + : QObject(parent) +{ + +} + +void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr) +{ + /* start the notification fetch job as well */ + if( !ptr) { + return; + } + + // check if the account has notifications enabled. If the capabilities are + // not yet valid, its assumed that notifications are available. + if( ptr->account() && ptr->account()->capabilities().isValid() ) { + if( ! ptr->account()->capabilities().notificationsAvailable() ) { + qDebug() << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; + return; + } + } + + // if the previous notification job has finished, start next. + _notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this ); + QObject::connect(_notificationJob.data(), SIGNAL(jsonReceived(QVariantMap, int)), + this, SLOT(slotNotificationsReceived(QVariantMap, int))); + _notificationJob->setProperty("AccountStatePtr", QVariant::fromValue(ptr)); + + qDebug() << "Start fetching notifications for " << ptr->account()->displayName(); + _notificationJob->start(); +} + +void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& json, int statusCode) +{ + if( statusCode != 200 ) { + qDebug() << "Failed for Notifications"; + return; + } + + auto notifies = json.value("ocs").toMap().value("data").toList(); + + AccountState* ai = qvariant_cast(sender()->property("AccountStatePtr")); + + // qDebug() << "Notifications for " << ai->account()->displayName() << notifies; + + ActivityList list; + + foreach( auto element, notifies ) { + Activity a; + auto json = element.toMap(); + a._type = Activity::NotificationType; + a._accName = ai->account()->displayName(); + a._id = json.value("notification_id").toLongLong(); + a._subject = json.value("subject").toString(); + a._message = json.value("message").toString(); + QString s = json.value("link").toString(); + if( !s.isEmpty() ) { + a._link = QUrl(s); + } + a._dateTime = json.value("datetime").toDateTime(); + a._dateTime.setTimeSpec(Qt::UTC); + + auto actions = json.value("actions").toList(); + foreach( auto action, actions) { + auto actionJson = action.toMap(); + ActivityLink al; + al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray()); + al._link = actionJson.value("link").toString(); + al._verb = actionJson.value("type").toString(); + al._isPrimary = actionJson.value("primary").toBool(); + + a._links.append(al); + } + list.append(a); + } + emit newNotificationList( list ); + + deleteLater(); +} + +} diff --git a/src/gui/servernotificationhandler.h b/src/gui/servernotificationhandler.h new file mode 100644 index 000000000..280b219bc --- /dev/null +++ b/src/gui/servernotificationhandler.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#ifndef SERVERNOTIFICATIONHANDLER_H +#define SERVERNOTIFICATIONHANDLER_H + +#include + +#include "activitywidget.h" + +namespace OCC +{ + +class ServerNotificationHandler : public QObject +{ + Q_OBJECT +public: + explicit ServerNotificationHandler(QObject *parent = 0); + +signals: + void newNotificationList(ActivityList); + +public slots: + void slotFetchNotifications(AccountState *ptr); + +private slots: + void slotNotificationsReceived(const QVariantMap& json, int statusCode); + +private: + QPointer _notificationJob; + + +}; + +} + +#endif // SERVERNOTIFICATIONHANDLER_H From 2c2a18af43360f7846d2cadec46774e356bfcf17 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 11 Mar 2016 11:37:45 +0100 Subject: [PATCH 12/44] Activitiy: Refactor - move classes to their own source files. Created a activitydata.h header (only) for the basic data, plus a separate file for the model. Cleans up the widget source. --- src/gui/CMakeLists.txt | 1 + src/gui/activitydata.h | 106 +++++++++++++++ src/gui/activitylistmodel.cpp | 237 ++++++++++++++++++++++++++++++++++ src/gui/activitylistmodel.h | 67 ++++++++++ src/gui/activitywidget.cpp | 232 +-------------------------------- src/gui/activitywidget.h | 110 +--------------- src/gui/notificationwidget.h | 4 +- 7 files changed, 417 insertions(+), 340 deletions(-) create mode 100644 src/gui/activitydata.h create mode 100644 src/gui/activitylistmodel.cpp create mode 100644 src/gui/activitylistmodel.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 031a1f6f5..92b9b5008 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -63,6 +63,7 @@ set(client_SRCS owncloudgui.cpp owncloudsetupwizard.cpp protocolwidget.cpp + activitylistmodel.cpp activitywidget.cpp activityitemdelegate.cpp selectivesyncdialog.cpp diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h new file mode 100644 index 000000000..46efe2c1f --- /dev/null +++ b/src/gui/activitydata.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#ifndef ACTIVITYDATA_H +#define ACTIVITYDATA_H + +#include + +namespace OCC { +/** + * @brief The ActivityLink class describes actions of an activity + * + * These are part of notifications which are mapped into activities. + */ + +class ActivityLink +{ +public: + QHash toVariantHash() { + QHash hash; + + hash["label"] = _label; + hash["link"] = _link; + hash["verb"] = _verb; + hash["primary"] = _isPrimary; + + return hash; + } + + QString _label; + QString _link; + QString _verb; + bool _isPrimary; +}; + +/* ==================================================================== */ +/** + * @brief Activity Structure + * @ingroup gui + * + * contains all the information describing a single activity. + */ + +class Activity +{ +public: + enum Type { + ActivityType, + NotificationType + }; + Type _type; + qlonglong _id; + QString _subject; + QString _message; + QString _file; + QUrl _link; + QDateTime _dateTime; + QString _accName; + + QVector _links; + /** + * @brief Sort operator to sort the list youngest first. + * @param val + * @return + */ + bool operator<( const Activity& val ) const { + return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); + } + +}; + +/* ==================================================================== */ +/** + * @brief The ActivityList + * @ingroup gui + * + * A QList based list of Activities + */ +class ActivityList:public QList +{ +public: + void setAccountName( const QString& name ) { + _accountName = name; + } + + QString accountName() const { + return _accountName; + } + +private: + QString _accountName; +}; + +} + +#endif // ACTIVITYDATA_H diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp new file mode 100644 index 000000000..8fa3ff462 --- /dev/null +++ b/src/gui/activitylistmodel.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include +#include +#include +#include + +#include "account.h" +#include "accountstate.h" +#include "accountmanager.h" +#include "folderman.h" +#include "accessmanager.h" +#include "activityitemdelegate.h" + +#include "activitydata.h" +#include "activitylistmodel.h" + +namespace OCC { + +ActivityListModel::ActivityListModel(QWidget *parent) + :QAbstractListModel(parent) +{ +} + +QVariant ActivityListModel::data(const QModelIndex &index, int role) const +{ + Activity a; + + if (!index.isValid()) + return QVariant(); + + a = _finalList.at(index.row()); + AccountStatePtr ast = AccountManager::instance()->account(a._accName); + QStringList list; + + if (role == Qt::EditRole) + return QVariant(); + + switch (role) { + case ActivityItemDelegate::PathRole: + list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account()); + if( list.count() > 0 ) { + return QVariant(list.at(0)); + } + // File does not exist anymore? Let's try to open its path + list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account()); + if( list.count() > 0 ) { + return QVariant(list.at(0)); + } + return QVariant(); + break; + case ActivityItemDelegate::ActionIconRole: + return QVariant(); // FIXME once the action can be quantified, display on Icon + break; + case ActivityItemDelegate::UserIconRole: + return QIcon(QLatin1String(":/client/resources/account.png")); + break; + case Qt::ToolTipRole: + case ActivityItemDelegate::ActionTextRole: + return a._subject; + break; + case ActivityItemDelegate::LinkRole: + return a._link; + break; + case ActivityItemDelegate::AccountRole: + return a._accName; + break; + case ActivityItemDelegate::PointInTimeRole: + return Utility::timeAgoInWords(a._dateTime); + break; + case ActivityItemDelegate::AccountConnectedRole: + return (ast && ast->isConnected()); + break; + default: + return QVariant(); + + } + return QVariant(); + +} + +int ActivityListModel::rowCount(const QModelIndex&) const +{ + return _finalList.count(); +} + +// current strategy: Fetch 100 items per Account +// ATTENTION: This method is const and thus it is not possible to modify +// the _activityLists hash or so. Doesn't make it easier... +bool ActivityListModel::canFetchMore(const QModelIndex& ) const +{ + if( _activityLists.count() == 0 ) return true; + + QMap::const_iterator i = _activityLists.begin(); + while (i != _activityLists.end()) { + AccountState *ast = i.key(); + if( ast && ast->isConnected() ) { + ActivityList activities = i.value(); + if( activities.count() == 0 && + ! _currentlyFetching.contains(ast) ) { + return true; + } + } + ++i; + } + + return false; +} + +void ActivityListModel::startFetchJob(AccountState* s) +{ + if( !s->isConnected() ) { + return; + } + JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this); + QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), + this, SLOT(slotActivitiesReceived(QVariantMap, int))); + job->setProperty("AccountStatePtr", QVariant::fromValue(s)); + + QList< QPair > params; + params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0"))); + params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100"))); + job->addQueryParams(params); + + _currentlyFetching.insert(s); + qDebug() << "Start fetching activities for " << s->account()->displayName(); + job->start(); +} + +void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) +{ + auto activities = json.value("ocs").toMap().value("data").toList(); + // qDebug() << "*** activities" << activities; + + ActivityList list; + AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); + _currentlyFetching.remove(ast); + list.setAccountName( ast->account()->displayName()); + + foreach( auto activ, activities ) { + auto json = activ.toMap(); + + Activity a; + a._type = Activity::ActivityType; + a._accName = ast->account()->displayName(); + a._id = json.value("id").toLongLong(); + a._subject = json.value("subject").toString(); + a._message = json.value("message").toString(); + a._file = json.value("file").toString(); + a._link = json.value("link").toUrl(); + a._dateTime = json.value("date").toDateTime(); + a._dateTime.setTimeSpec(Qt::UTC); + list.append(a); + } + + _activityLists[ast] = list; + + emit activityJobStatusCode(ast, statusCode); + + combineActivityLists(); +} + + +void ActivityListModel::combineActivityLists() +{ + ActivityList resultList; + + foreach( ActivityList list, _activityLists.values() ) { + resultList.append(list); + } + + std::sort( resultList.begin(), resultList.end() ); + + beginInsertRows(QModelIndex(), 0, resultList.count()-1); + _finalList = resultList; + endInsertRows(); +} + +void ActivityListModel::fetchMore(const QModelIndex &) +{ + QList accounts = AccountManager::instance()->accounts(); + + foreach (AccountStatePtr asp, accounts) { + bool newItem = false; + + if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { + _activityLists[asp.data()] = ActivityList(); + newItem = true; + } + if( newItem ) { + startFetchJob(asp.data()); + } + } +} + +void ActivityListModel::slotRefreshActivity(AccountState *ast) +{ + if(ast && _activityLists.contains(ast)) { + qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); + _activityLists.remove(ast); + } + startFetchJob(ast); +} + +void ActivityListModel::slotRemoveAccount(AccountState *ast ) +{ + if( _activityLists.contains(ast) ) { + int i = 0; + const QString accountToRemove = ast->account()->displayName(); + + QMutableListIterator it(_finalList); + + while (it.hasNext()) { + Activity activity = it.next(); + if( activity._accName == accountToRemove ) { + beginRemoveRows(QModelIndex(), i, i+1); + it.remove(); + endRemoveRows(); + } + } + _activityLists.remove(ast); + _currentlyFetching.remove(ast); + } +} + +} diff --git a/src/gui/activitylistmodel.h b/src/gui/activitylistmodel.h new file mode 100644 index 000000000..89e5e46ad --- /dev/null +++ b/src/gui/activitylistmodel.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#ifndef ACTIVITYLISTMODEL_H +#define ACTIVITYLISTMODEL_H + +#include + +#include "activitydata.h" + +namespace OCC { + +class AccountState; + +/** + * @brief The ActivityListModel + * @ingroup gui + * + * Simple list model to provide the list view with data. + */ + +class ActivityListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit ActivityListModel(QWidget *parent=0); + + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE; + void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE; + + ActivityList activityList() { return _finalList; } + +public slots: + void slotRefreshActivity(AccountState* ast); + void slotRemoveAccount( AccountState *ast ); + +private slots: + void slotActivitiesReceived(const QVariantMap& json, int statusCode); + +signals: + void activityJobStatusCode(AccountState* ast, int statusCode); + +private: + void startFetchJob(AccountState* s); + void combineActivityLists(); + + QMap _activityLists; + ActivityList _finalList; + QSet _currentlyFetching; +}; + + +} +#endif // ACTIVITYLISTMODEL_H diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 01f5b4865..611020775 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -16,6 +16,7 @@ #include #endif +#include "activitylistmodel.h" #include "activitywidget.h" #include "configfile.h" #include "syncresult.h" @@ -43,237 +44,6 @@ namespace OCC { -void ActivityList::setAccountName( const QString& name ) -{ - _accountName = name; -} - -QString ActivityList::accountName() const -{ - return _accountName; -} - -/* ==================================================================== */ - -QHash ActivityLink::toVariantHash() -{ - QHash hash; - - hash["label"] = _label; - hash["link"] = _link; - hash["verb"] = _verb; - hash["primary"] = _isPrimary; - - return hash; -} - -/* ==================================================================== */ - -ActivityListModel::ActivityListModel(QWidget *parent) - :QAbstractListModel(parent) -{ -} - -QVariant ActivityListModel::data(const QModelIndex &index, int role) const -{ - Activity a; - - if (!index.isValid()) - return QVariant(); - - a = _finalList.at(index.row()); - AccountStatePtr ast = AccountManager::instance()->account(a._accName); - QStringList list; - - if (role == Qt::EditRole) - return QVariant(); - - switch (role) { - case ActivityItemDelegate::PathRole: - list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account()); - if( list.count() > 0 ) { - return QVariant(list.at(0)); - } - // File does not exist anymore? Let's try to open its path - list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account()); - if( list.count() > 0 ) { - return QVariant(list.at(0)); - } - return QVariant(); - break; - case ActivityItemDelegate::ActionIconRole: - return QVariant(); // FIXME once the action can be quantified, display on Icon - break; - case ActivityItemDelegate::UserIconRole: - return QIcon(QLatin1String(":/client/resources/account.png")); - break; - case Qt::ToolTipRole: - case ActivityItemDelegate::ActionTextRole: - return a._subject; - break; - case ActivityItemDelegate::LinkRole: - return a._link; - break; - case ActivityItemDelegate::AccountRole: - return a._accName; - break; - case ActivityItemDelegate::PointInTimeRole: - return Utility::timeAgoInWords(a._dateTime); - break; - case ActivityItemDelegate::AccountConnectedRole: - return (ast && ast->isConnected()); - break; - default: - return QVariant(); - - } - return QVariant(); - -} - -int ActivityListModel::rowCount(const QModelIndex&) const -{ - return _finalList.count(); -} - -// current strategy: Fetch 100 items per Account -// ATTENTION: This method is const and thus it is not possible to modify -// the _activityLists hash or so. Doesn't make it easier... -bool ActivityListModel::canFetchMore(const QModelIndex& ) const -{ - if( _activityLists.count() == 0 ) return true; - - QMap::const_iterator i = _activityLists.begin(); - while (i != _activityLists.end()) { - AccountState *ast = i.key(); - if( ast && ast->isConnected() ) { - ActivityList activities = i.value(); - if( activities.count() == 0 && - ! _currentlyFetching.contains(ast) ) { - return true; - } - } - ++i; - } - - return false; -} - -void ActivityListModel::startFetchJob(AccountState* s) -{ - if( !s->isConnected() ) { - return; - } - JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this); - QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), - this, SLOT(slotActivitiesReceived(QVariantMap, int))); - job->setProperty("AccountStatePtr", QVariant::fromValue(s)); - - QList< QPair > params; - params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0"))); - params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100"))); - job->addQueryParams(params); - - _currentlyFetching.insert(s); - qDebug() << "Start fetching activities for " << s->account()->displayName(); - job->start(); -} - -void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) -{ - auto activities = json.value("ocs").toMap().value("data").toList(); - // qDebug() << "*** activities" << activities; - - ActivityList list; - AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); - _currentlyFetching.remove(ast); - list.setAccountName( ast->account()->displayName()); - - foreach( auto activ, activities ) { - auto json = activ.toMap(); - - Activity a; - a._type = Activity::ActivityType; - a._accName = ast->account()->displayName(); - a._id = json.value("id").toLongLong(); - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - a._file = json.value("file").toString(); - a._link = json.value("link").toUrl(); - a._dateTime = json.value("date").toDateTime(); - a._dateTime.setTimeSpec(Qt::UTC); - list.append(a); - } - - _activityLists[ast] = list; - - emit activityJobStatusCode(ast, statusCode); - - combineActivityLists(); -} - - -void ActivityListModel::combineActivityLists() -{ - ActivityList resultList; - - foreach( ActivityList list, _activityLists.values() ) { - resultList.append(list); - } - - std::sort( resultList.begin(), resultList.end() ); - - beginInsertRows(QModelIndex(), 0, resultList.count()-1); - _finalList = resultList; - endInsertRows(); -} - -void ActivityListModel::fetchMore(const QModelIndex &) -{ - QList accounts = AccountManager::instance()->accounts(); - - foreach (AccountStatePtr asp, accounts) { - bool newItem = false; - - if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { - _activityLists[asp.data()] = ActivityList(); - newItem = true; - } - if( newItem ) { - startFetchJob(asp.data()); - } - } -} - -void ActivityListModel::slotRefreshActivity(AccountState *ast) -{ - if(ast && _activityLists.contains(ast)) { - qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); - _activityLists.remove(ast); - } - startFetchJob(ast); -} - -void ActivityListModel::slotRemoveAccount(AccountState *ast ) -{ - if( _activityLists.contains(ast) ) { - int i = 0; - const QString accountToRemove = ast->account()->displayName(); - - QMutableListIterator it(_finalList); - - while (it.hasNext()) { - Activity activity = it.next(); - if( activity._accName == accountToRemove ) { - beginRemoveRows(QModelIndex(), i, i+1); - it.remove(); - endRemoveRows(); - } - } - _activityLists.remove(ast); - _currentlyFetching.remove(ast); - } -} /* ==================================================================== */ diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 62ef46e0c..a2d5c8116 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -22,6 +22,7 @@ #include "progressdispatcher.h" #include "owncloudgui.h" #include "account.h" +#include "activitydata.h" #include "ui_activitywidget.h" @@ -35,120 +36,13 @@ class AccountStatusPtr; class ProtocolWidget; class JsonApiJob; class NotificationWidget; +class ActivityListModel; namespace Ui { class ActivityWidget; } class Application; -/** - * @brief The ActivityLink class describes actions of an activity - * - * These are part of notifications which are mapped into activities. - */ - -class ActivityLink -{ -public: - QHash toVariantHash(); - - QString _label; - QString _link; - QString _verb; - bool _isPrimary; -}; - -/** - * @brief Activity Structure - * @ingroup gui - * - * contains all the information describing a single activity. - */ - -class Activity -{ -public: - enum Type { - ActivityType, - NotificationType - }; - Type _type; - qlonglong _id; - QString _subject; - QString _message; - QString _file; - QUrl _link; - QDateTime _dateTime; - QString _accName; - - QVector _links; - /** - * @brief Sort operator to sort the list youngest first. - * @param val - * @return - */ - bool operator<( const Activity& val ) const { - return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); - } - -}; - -/** - * @brief The ActivityList - * @ingroup gui - * - * A QList based list of Activities - */ -class ActivityList:public QList -{ -public: - void setAccountName( const QString& name ); - QString accountName() const; - -private: - QString _accountName; -}; - - -/** - * @brief The ActivityListModel - * @ingroup gui - * - * Simple list model to provide the list view with data. - */ -class ActivityListModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit ActivityListModel(QWidget *parent=0); - - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; - - bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE; - void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE; - - ActivityList activityList() { return _finalList; } - -public slots: - void slotRefreshActivity(AccountState* ast); - void slotRemoveAccount( AccountState *ast ); - -private slots: - void slotActivitiesReceived(const QVariantMap& json, int statusCode); - -signals: - void activityJobStatusCode(AccountState* ast, int statusCode); - -private: - void startFetchJob(AccountState* s); - void combineActivityLists(); - - QMap _activityLists; - ActivityList _finalList; - QSet _currentlyFetching; -}; - /** * @brief The ActivityWidget class * @ingroup gui diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index c18374220..89f3d2043 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -16,10 +16,12 @@ #include -#include "activitywidget.h" +#include "activitydata.h" #include "ui_notificationwidget.h" +class QProgressIndicator; + namespace OCC { class NotificationWidget : public QWidget From adf9570a9271f146717a4019727151129c6ce555 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 11 Mar 2016 12:48:31 +0100 Subject: [PATCH 13/44] Notification: Enhance the tray message Add the hostname from where the notification comes, as well as the name of the application to the header. --- src/gui/activitywidget.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 611020775..e2956da4f 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -37,6 +37,7 @@ #include "notificationwidget.h" #include "notificationconfirmjob.h" #include "servernotificationhandler.h" +#include "theme.h" #include "ui_activitywidget.h" @@ -218,6 +219,8 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) { int newGuiLogs = 0; + QHash accNotified; + foreach( auto activity, list ) { NotificationWidget *widget = 0; @@ -248,6 +251,21 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) } if( !_guiLoggedNotifications.contains(activity._id)) { newGuiLogs++; + QString host = activity._accName; + // store the name of the account that sends the notification to be + // able to add it to the tray notification + // remove the user name from the account as that is not accurate here. + int indx = host.indexOf(QChar('@')); + if( indx>-1 ) { + host.remove(0, 1+indx); + } + if( !host.isEmpty() ) { + if( accNotified.contains(host)) { + accNotified[host] = accNotified[host]+1; + } else { + accNotified[host] = 1; + } + } _guiLoggedNotifications.insert(activity._id); } } @@ -259,8 +277,22 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) // restart the gui log timer now that we show a notification _guiLogTimer.restart(); - emit guiLog(tr("Notifications - Action Required"), - tr("You received %n new notification(s) from the server!", "", newGuiLogs)); + // Assemble a tray notification + QString msg = tr("You received %n new notification(s)", "", newGuiLogs); + QString addM; + foreach( QString m, accNotified.keys() ) { + if(addM.isEmpty()) { + /*: translators: from is in the context of "got a message from account goof@owncloud.foo.com" */ + addM = tr(" from "); + } else { + addM += tr(" and "); + } + addM += m; + } + msg += addM; + + emit guiLog(Theme::instance()->appNameGUI() + QLatin1String(" ") + tr("Notifications - Action Required"), + msg); } } From 73cd5a9c272db9e5072d615dabf30e381dbcd400 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 14 Mar 2016 14:41:21 +0100 Subject: [PATCH 14/44] Notifications: Cleaner notification string build --- src/gui/activitywidget.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index e2956da4f..decefdbd5 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -217,8 +217,6 @@ void ActivityWidget::slotOpenFile(QModelIndex indx) // GUI: Display the notifications void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) { - int newGuiLogs = 0; - QHash accNotified; foreach( auto activity, list ) { @@ -250,7 +248,6 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) _guiLoggedNotifications.clear(); } if( !_guiLoggedNotifications.contains(activity._id)) { - newGuiLogs++; QString host = activity._accName; // store the name of the account that sends the notification to be // able to add it to the tray notification @@ -273,23 +270,27 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); _ui->_notifyScroll->setHidden( _widgetForNotifId.isEmpty() ); - if( newGuiLogs > 0 ) { + int newGuiLogCount = accNotified.count(); + + if( newGuiLogCount > 0 ) { // restart the gui log timer now that we show a notification _guiLogTimer.restart(); // Assemble a tray notification - QString msg = tr("You received %n new notification(s)", "", newGuiLogs); - QString addM; - foreach( QString m, accNotified.keys() ) { - if(addM.isEmpty()) { - /*: translators: from is in the context of "got a message from account goof@owncloud.foo.com" */ - addM = tr(" from "); + QString msg = tr("You received %1 new notification(s) from %2."). + arg(accNotified[accNotified.keys().at(0)]). + arg(accNotified.keys().at(0)); + + if( newGuiLogCount >= 2 ) { + QString acc1 = accNotified.keys().at(0); + QString acc2 = accNotified.keys().at(1); + if( newGuiLogCount == 2 ) { + int notiCount = accNotified[ acc1 ] + accNotified[ acc2 ]; + msg = tr("You received %1 new notifications from %2 and %3.").arg(notiCount).arg(acc1).arg(acc2); } else { - addM += tr(" and "); + msg = tr("You received new notifications from %1, %2 and other accounts.").arg(acc1).arg(acc2); } - addM += m; } - msg += addM; emit guiLog(Theme::instance()->appNameGUI() + QLatin1String(" ") + tr("Notifications - Action Required"), msg); From 97f1694f7e318693b32dec2da833540a36b62e6b Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 14 Mar 2016 15:39:07 +0100 Subject: [PATCH 15/44] ActivityData: Simplified implementation. Use QVariantHash and removed ActivityList object in favour of a typedef --- src/gui/activitydata.h | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h index 46efe2c1f..68067f553 100644 --- a/src/gui/activitydata.h +++ b/src/gui/activitydata.h @@ -26,8 +26,8 @@ namespace OCC { class ActivityLink { public: - QHash toVariantHash() { - QHash hash; + QVariantHash toVariantHash() const { + QVariantHash hash; hash["label"] = _label; hash["link"] = _link; @@ -86,20 +86,9 @@ public: * * A QList based list of Activities */ -class ActivityList:public QList -{ -public: - void setAccountName( const QString& name ) { - _accountName = name; - } - QString accountName() const { - return _accountName; - } +typedef QList ActivityList; -private: - QString _accountName; -}; } From 9d219a18f36b2d1d4ab39b1a8eef8a29306708c7 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 14 Mar 2016 15:40:39 +0100 Subject: [PATCH 16/44] ActivityListModel: Code cleanups based on review feedback. --- src/gui/activitylistmodel.cpp | 18 ++++++------------ src/gui/activitywidget.cpp | 13 ++++++------- src/gui/notificationwidget.cpp | 2 +- src/gui/servernotificationhandler.cpp | 7 ++----- 4 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index 8fa3ff462..a16eafe84 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -44,9 +44,6 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const AccountStatePtr ast = AccountManager::instance()->account(a._accName); QStringList list; - if (role == Qt::EditRole) - return QVariant(); - switch (role) { case ActivityItemDelegate::PathRole: list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account()); @@ -134,19 +131,17 @@ void ActivityListModel::startFetchJob(AccountState* s) job->addQueryParams(params); _currentlyFetching.insert(s); - qDebug() << "Start fetching activities for " << s->account()->displayName(); + qDebug() << Q_FUNC_INFO << "Start fetching activities for " << s->account()->displayName(); job->start(); } void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) { auto activities = json.value("ocs").toMap().value("data").toList(); - // qDebug() << "*** activities" << activities; ActivityList list; AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); _currentlyFetching.remove(ast); - list.setAccountName( ast->account()->displayName()); foreach( auto activ, activities ) { auto json = activ.toMap(); @@ -182,7 +177,11 @@ void ActivityListModel::combineActivityLists() std::sort( resultList.begin(), resultList.end() ); - beginInsertRows(QModelIndex(), 0, resultList.count()-1); + beginRemoveRows(QModelIndex(), 0, _finalList.count() ); + _finalList.clear(); + endRemoveRows(); + + beginInsertRows(QModelIndex(), 0, resultList.count()); _finalList = resultList; endInsertRows(); } @@ -192,13 +191,9 @@ void ActivityListModel::fetchMore(const QModelIndex &) QList accounts = AccountManager::instance()->accounts(); foreach (AccountStatePtr asp, accounts) { - bool newItem = false; if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { _activityLists[asp.data()] = ActivityList(); - newItem = true; - } - if( newItem ) { startFetchJob(asp.data()); } } @@ -207,7 +202,6 @@ void ActivityListModel::fetchMore(const QModelIndex &) void ActivityListModel::slotRefreshActivity(AccountState *ast) { if(ast && _activityLists.contains(ast)) { - qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); _activityLists.remove(ast); } startFetchJob(ast); diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index decefdbd5..463d2feeb 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -110,7 +110,7 @@ void ActivityWidget::slotRefresh(AccountState *ptr) snh->slotFetchNotifications(ptr); } else { - qDebug() << "========> notification request counter not zero."; + qDebug() << Q_FUNC_INFO << "========> notification request counter not zero."; } } @@ -204,7 +204,7 @@ void ActivityWidget::storeActivityList( QTextStream& ts ) void ActivityWidget::slotOpenFile(QModelIndex indx) { - qDebug() << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString()); + qDebug() << Q_FUNC_INFO << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString()); if( indx.isValid() ) { QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString(); @@ -299,7 +299,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb) { - qDebug() << "Server Notification Request " << verb << link << "on account" << accountName; + qDebug() << Q_FUNC_INFO << "Server Notification Request " << verb << link << "on account" << accountName; NotificationWidget *theSender = qobject_cast(sender()); const QStringList validVerbs = QStringList() << "GET" << "PUT" << "POST" << "DELETE"; @@ -322,7 +322,7 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con _notificationRequests++; } } else { - qDebug() << "Notification Links: Invalid verb:" << verb; + qDebug() << Q_FUNC_INFO << "Notification Links: Invalid verb:" << verb; } } @@ -347,7 +347,7 @@ void ActivityWidget::slotNotifyNetworkError( QNetworkReply *reply) } endNotificationRequest(job->widget(), resultCode); - qDebug() << "Server notify job failed."; + qDebug() << Q_FUNC_INFO << "Server notify job failed with code " << resultCode; } @@ -360,7 +360,7 @@ void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCo endNotificationRequest(job->widget(), replyCode); // FIXME: remove the widget after a couple of seconds - qDebug() << "Server Notification reply code"<< replyCode << reply; + qDebug() << Q_FUNC_INFO << "Server Notification reply code"<< replyCode << reply; } /* ==================================================================== */ @@ -453,7 +453,6 @@ void ActivitySettings::slotRemoveAccount( AccountState *ptr ) void ActivitySettings::slotRefresh( AccountState* ptr ) { if( ptr && ptr->isConnected() && isVisible()) { - qDebug() << "Refreshing Activity list for " << ptr->account()->displayName(); _progressIndicator->startAnimation(); _activityWidget->slotRefresh(ptr); } diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index db94138fa..4b1fb77c3 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -80,7 +80,7 @@ void NotificationWidget::slotButtonClicked() // if the button was found, the link must be called if( index > -1 && index < _myActivity._links.count() ) { ActivityLink triggeredLink = _myActivity._links.at(index); - qDebug() << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; + qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; _progressIndi->startAnimation(); emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); } diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp index bfd06d7eb..94ff13144 100644 --- a/src/gui/servernotificationhandler.cpp +++ b/src/gui/servernotificationhandler.cpp @@ -37,7 +37,7 @@ void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr) // not yet valid, its assumed that notifications are available. if( ptr->account() && ptr->account()->capabilities().isValid() ) { if( ! ptr->account()->capabilities().notificationsAvailable() ) { - qDebug() << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; + qDebug() << Q_FUNC_INFO << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; return; } } @@ -48,14 +48,13 @@ void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr) this, SLOT(slotNotificationsReceived(QVariantMap, int))); _notificationJob->setProperty("AccountStatePtr", QVariant::fromValue(ptr)); - qDebug() << "Start fetching notifications for " << ptr->account()->displayName(); _notificationJob->start(); } void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& json, int statusCode) { if( statusCode != 200 ) { - qDebug() << "Failed for Notifications"; + qDebug() << Q_FUNC_INFO << "Notifications failed with status code " << statusCode; return; } @@ -63,8 +62,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& jso AccountState* ai = qvariant_cast(sender()->property("AccountStatePtr")); - // qDebug() << "Notifications for " << ai->account()->displayName() << notifies; - ActivityList list; foreach( auto element, notifies ) { From 9a2f1456c5b3163f92de0a4d234726d09e32f661 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 14 Mar 2016 15:41:20 +0100 Subject: [PATCH 17/44] ocs jobs: Add a define for OCS job success. --- src/gui/notificationwidget.cpp | 6 ++++-- src/gui/ocsjob.cpp | 2 +- src/gui/ocsjob.h | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 4b1fb77c3..3686be3b2 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -16,6 +16,8 @@ #include +#include "ocsjob.h" + namespace OCC { NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) @@ -91,8 +93,8 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) { int i = 0; // the ocs API returns stat code 100 if it succeeded. - if( statusCode != 100 ) { - qDebug() << "Notification Request to Server failed, leave button visible."; + if( statusCode != OCS_SUCCESS_STATUS_CODE ) { + qDebug() << Q_FUNC_INFO << "Notification Request to Server failed, leave button visible."; for( i = 0; i < _buttons.count(); i++ ) { _buttons.at(i)->setEnabled(true); } diff --git a/src/gui/ocsjob.cpp b/src/gui/ocsjob.cpp index 4ab1473ea..ff313e462 100644 --- a/src/gui/ocsjob.cpp +++ b/src/gui/ocsjob.cpp @@ -23,7 +23,7 @@ namespace OCC { OcsJob::OcsJob(AccountPtr account) : AbstractNetworkJob(account, "") { - _passStatusCodes.append(100); + _passStatusCodes.append(OCS_SUCCESS_STATUS_CODE); setIgnoreCredentialFailure(true); } diff --git a/src/gui/ocsjob.h b/src/gui/ocsjob.h index dcd205854..24f611442 100644 --- a/src/gui/ocsjob.h +++ b/src/gui/ocsjob.h @@ -22,6 +22,8 @@ #include #include +#define OCS_SUCCESS_STATUS_CODE 100 + namespace OCC { /** From a4dcc2784acffa398b03352fb3f4173ff55f0dd2 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 14 Mar 2016 16:21:04 +0100 Subject: [PATCH 18/44] Notification: Fix plural handling for tray message --- src/gui/activitywidget.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 463d2feeb..2d1745345 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -277,8 +277,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) _guiLogTimer.restart(); // Assemble a tray notification - QString msg = tr("You received %1 new notification(s) from %2."). - arg(accNotified[accNotified.keys().at(0)]). + QString msg = tr("You received %n new notification(s) from %2.", "", accNotified[accNotified.keys().at(0)]). arg(accNotified.keys().at(0)); if( newGuiLogCount >= 2 ) { From 45c32ec0b16afaa43977cc0e64e60858b2711e72 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 16 Mar 2016 16:21:20 +0100 Subject: [PATCH 19/44] NotificationWidget: Remove not needed method. --- src/gui/activitywidget.cpp | 1 - src/gui/notificationwidget.cpp | 5 ----- src/gui/notificationwidget.h | 2 -- 3 files changed, 8 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 2d1745345..86f259ff7 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -234,7 +234,6 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) _widgetForNotifId[activity._id] = widget; } - widget->setAccountName( activity._accName ); widget->setActivity( activity ); // handle gui logs. In order to NOT annoy the user with every fetching of the diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 3686be3b2..ac65aaaf7 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -27,11 +27,6 @@ NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) _ui.horizontalLayout->addWidget(_progressIndi); } -void NotificationWidget::setAccountName( const QString& name ) -{ - _accountName = name; -} - void NotificationWidget::setActivity(const Activity& activity) { _myActivity = activity; diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index 89f3d2043..9abcd8b6c 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -30,8 +30,6 @@ class NotificationWidget : public QWidget public: explicit NotificationWidget(QWidget *parent = 0); - void setAccountName( const QString& name ); - signals: void sendNotificationRequest( const QString&, const QString& link, const QString& verb); From f7f412007e9dd2efc47ea73052fb8237c1e4ce01 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 16 Mar 2016 16:31:52 +0100 Subject: [PATCH 20/44] Activity: Some documentation and better varialbe names --- src/gui/activitywidget.cpp | 8 ++++---- src/gui/activitywidget.h | 5 ++++- src/gui/notificationconfirmjob.h | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 86f259ff7..a8be2449a 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -51,7 +51,7 @@ namespace OCC { ActivityWidget::ActivityWidget(QWidget *parent) : QWidget(parent), _ui(new Ui::ActivityWidget), - _notificationRequests(0) + _notificationRequestsRunning(0) { _ui->setupUi(this); @@ -103,7 +103,7 @@ void ActivityWidget::slotRefresh(AccountState *ptr) // start a server notification handler if no notification requests // are running - if( _notificationRequests == 0 ) { + if( _notificationRequestsRunning == 0 ) { ServerNotificationHandler *snh = new ServerNotificationHandler; connect(snh, SIGNAL(newNotificationList(ActivityList)), this, SLOT(slotBuildNotificationDisplay(ActivityList))); @@ -317,7 +317,7 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con // count the number of running notification requests. If this member var // is larger than zero, no new fetching of notifications is started - _notificationRequests++; + _notificationRequestsRunning++; } } else { qDebug() << Q_FUNC_INFO << "Notification Links: Invalid verb:" << verb; @@ -326,7 +326,7 @@ void ActivityWidget::slotSendNotificationRequest(const QString& accountName, con void ActivityWidget::endNotificationRequest( NotificationWidget *widget, int replyCode ) { - _notificationRequests--; + _notificationRequestsRunning--; if( widget ) { widget->slotNotificationRequestFinished(replyCode); } diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index a2d5c8116..01c591433 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -90,7 +90,10 @@ private: QMap _widgetForNotifId; QElapsedTimer _guiLogTimer; QSet _guiLoggedNotifications; - int _notificationRequests; + + // number of currently running notification requests. If non zero, + // no query for notifications is started. + int _notificationRequestsRunning; ActivityListModel *_model; QVBoxLayout *_notificationsLayout; diff --git a/src/gui/notificationconfirmjob.h b/src/gui/notificationconfirmjob.h index 3f0e8bb6b..7ae57bb41 100644 --- a/src/gui/notificationconfirmjob.h +++ b/src/gui/notificationconfirmjob.h @@ -42,19 +42,29 @@ public: explicit NotificationConfirmJob(AccountPtr account); /** - * Set the verb and link for the job + * @brief Set the verb and link for the job * * @param verb currently supported GET PUT POST DELETE */ void setLinkAndVerb(const QUrl& link, const QString &verb); /** - * Start the OCS request + * @brief Start the OCS request */ void start() Q_DECL_OVERRIDE; + /** + * @brief setWidget stores the associated widget to be able to use + * it when the job has finished + * @param widget pointer to the notification widget to store + */ void setWidget( NotificationWidget *widget ); + /** + * @brief widget - get the associated notification widget as stored + * with setWidget method. + * @return widget pointer to the notification widget + */ NotificationWidget *widget(); signals: From f71fdab997fffa2cbf832f46dbaf9b1a568062e4 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 08:20:23 +0100 Subject: [PATCH 21/44] Fix timeAgoInWords --- src/libsync/utility.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index 85e42c4e3..bb3f563a6 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -434,15 +434,17 @@ QByteArray Utility::versionOfInstalledBinary( const QString& command ) QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) { - QDateTime now = QDateTime::currentDateTime(); + QDateTime now = QDateTime::currentDateTimeUtc(); if( from.isValid() ) { now = from; } + Q_ASSERT(dt.timeSpec() == Qt::UTC); + if( dt.daysTo(now)>0 ) { int dtn = dt.daysTo(now); - return QObject::tr("%1 day(s) ago", "", dtn).arg(dtn); + return QObject::tr("%n day(s) ago", "", dtn).arg(dtn); } else { qint64 secs = dt.secsTo(now); if( secs < 0 ) { @@ -450,7 +452,7 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) } if( floor(secs / 3600.0) > 0 ) { int hours = floor(secs/3600.0); - return( QObject::tr("%1 hour(s) ago", "", hours).arg(hours)); + return( QObject::tr("%n hour(s) ago", "", hours).arg(hours)); } else { int minutes = qRound(secs/60.0); if( minutes == 0 ) { @@ -460,7 +462,7 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) return QObject::tr("Less than a minute ago"); } } - return( QObject::tr("%1 minute(s) ago", "", minutes).arg(minutes)); + return( QObject::tr("%n minute(s) ago", "", minutes).arg(minutes)); } } return QObject::tr("Some time ago"); From 05de710b672f91cbc5bfe57d86ff429092b05487 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 08:21:54 +0100 Subject: [PATCH 22/44] Notifications: Display timestamp of the notification in the widget --- src/gui/notificationwidget.cpp | 6 +++++- src/gui/notificationwidget.ui | 33 ++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index ac65aaaf7..1ef0f8c48 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -13,6 +13,7 @@ #include "notificationwidget.h" #include "QProgressIndicator.h" +#include "utility.h" #include @@ -30,6 +31,7 @@ NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) void NotificationWidget::setActivity(const Activity& activity) { _myActivity = activity; + QLocale locale; Q_ASSERT( !activity._accName.isEmpty() ); _accountName = activity._accName; @@ -46,6 +48,9 @@ void NotificationWidget::setActivity(const Activity& activity) _ui._notifIcon->setMinimumHeight(64); _ui._notifIcon->show(); + QString tText = tr("Created at %1").arg(Utility::timeAgoInWords(activity._dateTime)); + _ui._timeLabel->setText(tText); + // always remove the buttons foreach( auto button, _ui._buttonBox->buttons() ) { _ui._buttonBox->removeButton(button); @@ -96,7 +101,6 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) } else { // the call to the ocs API succeeded. _ui._buttonBox->hide(); - } _progressIndi->stopAnimation(); } diff --git a/src/gui/notificationwidget.ui b/src/gui/notificationwidget.ui index 886777278..c8d34017d 100644 --- a/src/gui/notificationwidget.ui +++ b/src/gui/notificationwidget.ui @@ -15,7 +15,7 @@ - + @@ -28,7 +28,7 @@ - ../../resources/bell.png + ../../../../resources/bell.png @@ -58,11 +58,30 @@ - - - QDialogButtonBox::Ok - - + + + + + + 8 + + + + TextLabel + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + QDialogButtonBox::Ok + + + + From 0a590b7cbed90eb71a2ba7e70aed77bdb6cba884 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 10:02:11 +0100 Subject: [PATCH 23/44] Notifications: Give feedback if notifcation request succeeded. Also display a time stamp. --- src/gui/notificationwidget.cpp | 13 +++++++++++++ src/gui/notificationwidget.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 1ef0f8c48..afba08346 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -82,6 +82,7 @@ void NotificationWidget::slotButtonClicked() // if the button was found, the link must be called if( index > -1 && index < _myActivity._links.count() ) { ActivityLink triggeredLink = _myActivity._links.at(index); + _actionLabel = triggeredLink._label; qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; _progressIndi->startAnimation(); emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); @@ -92,16 +93,28 @@ void NotificationWidget::slotButtonClicked() void NotificationWidget::slotNotificationRequestFinished(int statusCode) { int i = 0; + QString doneText; + QLocale locale; + + QString timeStr = locale.toString(QTime::currentTime()); + // the ocs API returns stat code 100 if it succeeded. if( statusCode != OCS_SUCCESS_STATUS_CODE ) { qDebug() << Q_FUNC_INFO << "Notification Request to Server failed, leave button visible."; for( i = 0; i < _buttons.count(); i++ ) { _buttons.at(i)->setEnabled(true); } + //* The second parameter is a time, such as 'failed at 09:58pm' + doneText = tr("%1 request failed at %2").arg(_actionLabel).arg(timeStr); } else { // the call to the ocs API succeeded. _ui._buttonBox->hide(); + + //* The second parameter is a time, such as 'selected at 09:58pm' + doneText = tr("'%1' selected at %2").arg(_actionLabel).arg(timeStr); } + _ui._timeLabel->setText( doneText ); + _progressIndi->stopAnimation(); } diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index 9abcd8b6c..fceb2d97f 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -46,6 +46,7 @@ private: QList _buttons; QString _accountName; QProgressIndicator *_progressIndi; + QString _actionLabel; }; } From 328d254f7f93c3ac5a555bd1a8a822b1b6a3ee0e Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 11:25:14 +0100 Subject: [PATCH 24/44] Notifications: Remove "done" notification widgets after fife seconds. --- src/gui/activitywidget.cpp | 27 +++++++++++++++++++++++++++ src/gui/activitywidget.h | 1 + src/gui/notificationwidget.cpp | 12 ++++++++++++ src/gui/notificationwidget.h | 5 +++++ 4 files changed, 45 insertions(+) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index a8be2449a..a009bd72d 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -38,6 +38,7 @@ #include "notificationconfirmjob.h" #include "servernotificationhandler.h" #include "theme.h" +#include "ocsjob.h" #include "ui_activitywidget.h" @@ -359,8 +360,34 @@ void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCo endNotificationRequest(job->widget(), replyCode); // FIXME: remove the widget after a couple of seconds qDebug() << Q_FUNC_INFO << "Server Notification reply code"<< replyCode << reply; + + // if the notification was successful start a timer that triggers + // removal of the done widgets in a few seconds + // Add 200 millisecs to the predefined value to make sure that the timer in + // widget's method readyToClose() has elapsed. + if( replyCode == OCS_SUCCESS_STATUS_CODE ) { + QTimer::singleShot(NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS+200, this, SLOT(slotCleanWidgetList())); + } } +void ActivityWidget::slotCleanWidgetList() +{ + foreach( int id, _widgetForNotifId.keys() ) { + Q_ASSERT(_widgetForNotifId[id]); + if( _widgetForNotifId[id]->readyToClose() ) { + auto *widget = _widgetForNotifId[id]; + _widgetForNotifId.remove(id); + delete widget; + } + } + + if( _widgetForNotifId.isEmpty() ) { + _ui->_notifyLabel->setHidden(true); + _ui->_notifyScroll->setHidden(true); + } +} + + /* ==================================================================== */ ActivitySettings::ActivitySettings(QWidget *parent) diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 01c591433..6a07b342f 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -79,6 +79,7 @@ private slots: void slotNotifyNetworkError( QNetworkReply* ); void slotNotifyServerFinished( const QString& reply, int replyCode ); void endNotificationRequest(NotificationWidget *widget , int replyCode); + void slotCleanWidgetList(); private: void showLabels(); diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index afba08346..de28c7c6d 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -112,10 +112,22 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) //* The second parameter is a time, such as 'selected at 09:58pm' doneText = tr("'%1' selected at %2").arg(_actionLabel).arg(timeStr); + + // start a timer, so that activity widget can remove this widget after a + // certain time. It needs to be done by ActivityWidget because it also + // needs to hide the scrollview if no widget is left any more. + // method readyToClose() checks for the timer value to see if it is expired + _closeTimer.start(); } _ui._timeLabel->setText( doneText ); _progressIndi->stopAnimation(); + +} + +bool NotificationWidget::readyToClose() +{ + return _closeTimer.isValid() && _closeTimer.elapsed() > NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS; } } diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index fceb2d97f..8c143390f 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -20,6 +20,8 @@ #include "ui_notificationwidget.h" +#define NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS 4800 + class QProgressIndicator; namespace OCC { @@ -30,6 +32,8 @@ class NotificationWidget : public QWidget public: explicit NotificationWidget(QWidget *parent = 0); + bool readyToClose(); + signals: void sendNotificationRequest( const QString&, const QString& link, const QString& verb); @@ -47,6 +51,7 @@ private: QString _accountName; QProgressIndicator *_progressIndi; QString _actionLabel; + QElapsedTimer _closeTimer; }; } From 7f22a073125457e1ebf27255a4f0043714fe34fd Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 11 Mar 2016 15:16:09 +0100 Subject: [PATCH 25/44] Notifications: Check if the account is connected before querying. Also avoid memory leaks if it is not connected. --- src/gui/servernotificationhandler.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp index 94ff13144..14a9e0710 100644 --- a/src/gui/servernotificationhandler.cpp +++ b/src/gui/servernotificationhandler.cpp @@ -28,16 +28,19 @@ ServerNotificationHandler::ServerNotificationHandler(QObject *parent) void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr) { - /* start the notification fetch job as well */ - if( !ptr) { + // check connectivity and credentials + if( !( ptr && ptr->isConnected() && ptr->account() && + ptr->account()->credentials() && + ptr->account()->credentials()->ready() ) ) { + deleteLater(); return; } - // check if the account has notifications enabled. If the capabilities are // not yet valid, its assumed that notifications are available. - if( ptr->account() && ptr->account()->capabilities().isValid() ) { + if( ptr->account()->capabilities().isValid() ) { if( ! ptr->account()->capabilities().notificationsAvailable() ) { qDebug() << Q_FUNC_INFO << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; + deleteLater(); return; } } @@ -55,6 +58,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& jso { if( statusCode != 200 ) { qDebug() << Q_FUNC_INFO << "Notifications failed with status code " << statusCode; + deleteLater(); return; } From b9663456d8d4aa85e808f65c64fa6ac3def77d6f Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 16:23:21 +0100 Subject: [PATCH 26/44] Notifications: Refresh the notifications based on a config value. Pulls a timer that polls for new notifications regularly. Add Config file method for the interval value. --- src/gui/activitywidget.cpp | 46 +++++++++++++++++++++++++++++++++----- src/gui/activitywidget.h | 9 ++++++-- src/gui/settingsdialog.cpp | 4 +++- src/libsync/configfile.cpp | 17 ++++++++++++++ src/libsync/configfile.h | 3 +++ 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index a009bd72d..18418d0f6 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -18,7 +18,6 @@ #include "activitylistmodel.h" #include "activitywidget.h" -#include "configfile.h" #include "syncresult.h" #include "logger.h" #include "utility.h" @@ -44,6 +43,10 @@ #include +// time span in milliseconds which has to be between two +// refreshes of the notifications +#define NOTIFICATION_REQUEST_FREE_PERIOD 15000 + namespace OCC { @@ -98,10 +101,13 @@ ActivityWidget::~ActivityWidget() delete _ui; } -void ActivityWidget::slotRefresh(AccountState *ptr) +void ActivityWidget::slotRefreshActivities(AccountState *ptr) { _model->slotRefreshActivity(ptr); +} +void ActivityWidget::slotRefreshNotifications(AccountState *ptr) +{ // start a server notification handler if no notification requests // are running if( _notificationRequestsRunning == 0 ) { @@ -428,10 +434,19 @@ ActivitySettings::ActivitySettings(QWidget *parent) _progressIndicator = new QProgressIndicator(this); _tab->setCornerWidget(_progressIndicator); + connect(&_notificationCheckTimer, SIGNAL(timeout()), + this, SLOT(slotRegularNotificationCheck())); + // connect a model signal to stop the animation. connect(_activityWidget, SIGNAL(rowsInserted()), _progressIndicator, SLOT(stopAnimation())); } +void ActivitySettings::setNotificationRefreshInterval( quint64 interval ) +{ + qDebug() << "Starting Notification refresh timer with " << interval/1000 << " sec interval"; + _notificationCheckTimer.start(interval); +} + void ActivitySettings::setActivityTabHidden(bool hidden) { if( hidden && _activityTabId > -1 ) { @@ -477,9 +492,30 @@ void ActivitySettings::slotRemoveAccount( AccountState *ptr ) void ActivitySettings::slotRefresh( AccountState* ptr ) { - if( ptr && ptr->isConnected() && isVisible()) { - _progressIndicator->startAnimation(); - _activityWidget->slotRefresh(ptr); + // Fetch Activities only if visible and if last check is longer than 15 secs ago + if( _timeSinceLastCheck.isValid() && _timeSinceLastCheck.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD ) { + qDebug() << Q_FUNC_INFO << "do not check as last check is only secs ago: " << _timeSinceLastCheck.elapsed() / 1000; + return; + } + if( ptr && ptr->isConnected() ) { + if( isVisible() ) { + _progressIndicator->startAnimation(); + _activityWidget->slotRefreshActivities( ptr); + } + _activityWidget->slotRefreshNotifications(ptr); + if( !_timeSinceLastCheck.isValid() ) { + _timeSinceLastCheck.start(); + } else { + _timeSinceLastCheck.restart(); + } + } +} + +void ActivitySettings::slotRegularNotificationCheck() +{ + AccountManager *am = AccountManager::instance(); + foreach (AccountStatePtr a, am->accounts()) { + slotRefresh(a.data()); } } diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 6a07b342f..acbab0d8c 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -62,7 +62,8 @@ public: public slots: void slotOpenFile(QModelIndex indx); - void slotRefresh(AccountState* ptr); + void slotRefreshActivities(AccountState* ptr); + void slotRefreshNotifications(AccountState *ptr); void slotRemoveAccount( AccountState *ptr ); void slotAccountActivityStatus(AccountState *ast, int statusCode); @@ -121,9 +122,12 @@ public slots: void slotRefresh( AccountState* ptr ); void slotRemoveAccount( AccountState *ptr ); + void setNotificationRefreshInterval( quint64 interval ); + private slots: void slotCopyToClipboard(); void setActivityTabHidden(bool hidden); + void slotRegularNotificationCheck(); signals: void guiLog(const QString&, const QString&); @@ -137,7 +141,8 @@ private: ActivityWidget *_activityWidget; ProtocolWidget *_protocolWidget; QProgressIndicator *_progressIndicator; - + QTimer _notificationCheckTimer; + QElapsedTimer _timeSinceLastCheck; }; } diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index abe9c9276..3aa8df010 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -61,6 +61,8 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) : QDialog(parent) , _ui(new Ui::SettingsDialog), _gui(gui) { + ConfigFile cfg; + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); _ui->setupUi(this); _toolBar = new QToolBar; @@ -89,6 +91,7 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) : _ui->stack->addWidget(_activitySettings); connect( _activitySettings, SIGNAL(guiLog(QString,QString)), _gui, SLOT(slotShowOptionalTrayMessage(QString,QString)) ); + _activitySettings->setNotificationRefreshInterval( cfg.notificationRefreshInterval()); QAction *generalAction = createColorAwareAction(QLatin1String(":/client/resources/settings.png"), tr("General")); _actionGroup->addAction(generalAction); @@ -128,7 +131,6 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) : customizeStyle(); - ConfigFile cfg; cfg.restoreGeometry(this); } diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 32d247570..6b11c6da0 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -42,6 +42,7 @@ namespace OCC { //static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp static const char remotePollIntervalC[] = "remotePollInterval"; static const char forceSyncIntervalC[] = "forceSyncInterval"; +static const char notificationRefreshIntervalC[] = "notificationRefreshInterval"; static const char monoIconsC[] = "monoIcons"; static const char promptDeleteC[] = "promptDeleteAllFiles"; static const char crashReporterC[] = "crashReporter"; @@ -390,6 +391,22 @@ quint64 ConfigFile::forceSyncInterval(const QString& connection) const return interval; } +quint64 ConfigFile::notificationRefreshInterval(const QString& connection) const +{ + QString con( connection ); + if( connection.isEmpty() ) con = defaultConnection(); + QSettings settings(configFile(), QSettings::IniFormat); + settings.beginGroup( con ); + + quint64 defaultInterval = 5 * 60 * 1000ull; // 5 minutes + quint64 interval = settings.value( QLatin1String(notificationRefreshIntervalC), defaultInterval ).toULongLong(); + if( interval < 60*1000ull) { + qDebug() << "notification refresh interval smaller than one minute, setting to one minute"; + interval = 60*1000ull; + } + return interval; +} + int ConfigFile::updateCheckInterval( const QString& connection ) const { QString con( connection ); diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 3d000f55a..d45a9693f 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -63,6 +63,9 @@ public: /* Set poll interval. Value in milliseconds has to be larger than 5000 */ void setRemotePollInterval(int interval, const QString& connection = QString() ); + /* Interval to check for new notifications */ + quint64 notificationRefreshInterval(const QString& connection = QString()) const; + /* Force sync interval, in milliseconds */ quint64 forceSyncInterval(const QString &connection = QString()) const; From f587f35ef09b1985a96fe5a14eba1207d7d6961a Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 18 Mar 2016 15:28:00 +0100 Subject: [PATCH 27/44] Fix plural translation handling, remove the superflous arg() --- src/libsync/utility.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index bb3f563a6..b0325f553 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -444,7 +444,7 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) if( dt.daysTo(now)>0 ) { int dtn = dt.daysTo(now); - return QObject::tr("%n day(s) ago", "", dtn).arg(dtn); + return QObject::tr("%n day(s) ago", "", dtn); } else { qint64 secs = dt.secsTo(now); if( secs < 0 ) { @@ -452,7 +452,7 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) } if( floor(secs / 3600.0) > 0 ) { int hours = floor(secs/3600.0); - return( QObject::tr("%n hour(s) ago", "", hours).arg(hours)); + return( QObject::tr("%n hour(s) ago", "", hours) ); } else { int minutes = qRound(secs/60.0); if( minutes == 0 ) { @@ -462,7 +462,7 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) return QObject::tr("Less than a minute ago"); } } - return( QObject::tr("%n minute(s) ago", "", minutes).arg(minutes)); + return( QObject::tr("%n minute(s) ago", "", minutes) ); } } return QObject::tr("Some time ago"); From d407aacc4aa1b4862e2ffa3b90b393af62decdae Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Mon, 21 Mar 2016 16:26:37 +0100 Subject: [PATCH 28/44] Notifications: remove notification widgets if the notification is gone. If a notification is not longer in the list that comes from the server, the notification is removed. That is mainly for the notifications that are created by the announcement application --- src/gui/activitywidget.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 18418d0f6..ea96f1295 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -273,6 +273,30 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) } } + // check if we have widgets that have no corresponding activity from + // the server any more. Collect them in a list + QList strayCats; + foreach( auto id, _widgetForNotifId.keys() ) { + bool found = false; + foreach( auto activity, list ) { + if( activity._id == id ) { + // found an activity + found = true; + break; + } + } + if( ! found ) { + // the activity does not exist any more. + strayCats.append(id); + } + } + // .. and now delete all these stray cat widgets. + foreach( auto strayCatId, strayCats ) { + NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId]; + widgetToGo->deleteLater(); + _widgetForNotifId.remove(strayCatId); + } + _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); _ui->_notifyScroll->setHidden( _widgetForNotifId.isEmpty() ); From d03fcc95e4d50d6978509bca6e7d01085375e917 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 22 Mar 2016 09:58:30 +0100 Subject: [PATCH 29/44] Notifications: Maintain a timeSinceLastCheck for every Account. In multi-account environment every account needs the own counter. --- src/gui/activitywidget.cpp | 12 +++++++----- src/gui/activitywidget.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index ea96f1295..68e7d67ff 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -516,9 +516,11 @@ void ActivitySettings::slotRemoveAccount( AccountState *ptr ) void ActivitySettings::slotRefresh( AccountState* ptr ) { + QElapsedTimer timer = _timeSinceLastCheck[ptr]; + // Fetch Activities only if visible and if last check is longer than 15 secs ago - if( _timeSinceLastCheck.isValid() && _timeSinceLastCheck.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD ) { - qDebug() << Q_FUNC_INFO << "do not check as last check is only secs ago: " << _timeSinceLastCheck.elapsed() / 1000; + if( timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD ) { + qDebug() << Q_FUNC_INFO << "do not check as last check is only secs ago: " << timer.elapsed() / 1000; return; } if( ptr && ptr->isConnected() ) { @@ -527,10 +529,10 @@ void ActivitySettings::slotRefresh( AccountState* ptr ) _activityWidget->slotRefreshActivities( ptr); } _activityWidget->slotRefreshNotifications(ptr); - if( !_timeSinceLastCheck.isValid() ) { - _timeSinceLastCheck.start(); + if( !( _timeSinceLastCheck[ptr].isValid() ) ) { + _timeSinceLastCheck[ptr].start(); } else { - _timeSinceLastCheck.restart(); + _timeSinceLastCheck[ptr].restart(); } } } diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index acbab0d8c..cf6d73202 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -142,7 +142,7 @@ private: ProtocolWidget *_protocolWidget; QProgressIndicator *_progressIndicator; QTimer _notificationCheckTimer; - QElapsedTimer _timeSinceLastCheck; + QHash _timeSinceLastCheck; }; } From ad60e8ac8956b7d00045d9e08b75b17125ce1d61 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 22 Mar 2016 10:35:24 +0100 Subject: [PATCH 30/44] Notifications: Fix handling of notifications to remove from the list. If a notification is not longer in the list of notifications coming from the server, it needs to be removed from the widget list. --- src/gui/activitywidget.cpp | 18 ++++++++++++++++-- src/gui/notificationwidget.cpp | 5 +++++ src/gui/notificationwidget.h | 1 + 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 68e7d67ff..08b91955d 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -221,10 +221,14 @@ void ActivityWidget::slotOpenFile(QModelIndex indx) } } -// GUI: Display the notifications +// GUI: Display the notifications. +// All notifications in list are coming from the same account +// but in the _widgetForNotifId hash widgets for all accounts are +// collected. void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) { QHash accNotified; + QString listAccountName; foreach( auto activity, list ) { NotificationWidget *widget = 0; @@ -243,6 +247,9 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) widget->setActivity( activity ); + // remember the list account name for the strayCat handling below. + listAccountName = activity._accName; + // handle gui logs. In order to NOT annoy the user with every fetching of the // notifications the notification id is stored in a Set. Only if an id // is not in the set, it qualifies for guiLog. @@ -278,6 +285,12 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) QList strayCats; foreach( auto id, _widgetForNotifId.keys() ) { bool found = false; + NotificationWidget *widget = _widgetForNotifId[id]; + + // do not mark widgets of other accounts to delete. + if( widget->accountName() != listAccountName ) { + continue; + } foreach( auto activity, list ) { if( activity._id == id ) { // found an activity @@ -290,6 +303,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) strayCats.append(id); } } + // .. and now delete all these stray cat widgets. foreach( auto strayCatId, strayCats ) { NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId]; @@ -297,7 +311,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) _widgetForNotifId.remove(strayCatId); } - _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); + _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); _ui->_notifyScroll->setHidden( _widgetForNotifId.isEmpty() ); int newGuiLogCount = accNotified.count(); diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index de28c7c6d..2e398a648 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -65,6 +65,11 @@ void NotificationWidget::setActivity(const Activity& activity) } } +QString NotificationWidget::accountName() const +{ + return _myActivity._accName; +} + void NotificationWidget::slotButtonClicked() { QObject *buttonWidget = QObject::sender(); diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index 8c143390f..adafd437f 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -33,6 +33,7 @@ public: explicit NotificationWidget(QWidget *parent = 0); bool readyToClose(); + QString accountName() const; signals: void sendNotificationRequest( const QString&, const QString& link, const QString& verb); From f70c6282ca4f417f8df8b73336b56ebc035d9cbe Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 22 Mar 2016 11:38:10 +0100 Subject: [PATCH 31/44] Notifications: Remove unused variable. --- src/gui/notificationwidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 2e398a648..e3a81874f 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -31,7 +31,6 @@ NotificationWidget::NotificationWidget(QWidget *parent) : QWidget(parent) void NotificationWidget::setActivity(const Activity& activity) { _myActivity = activity; - QLocale locale; Q_ASSERT( !activity._accName.isEmpty() ); _accountName = activity._accName; From ea2f19b78abcc9909763c641afc4eb0362eaae42 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 22 Mar 2016 11:38:44 +0100 Subject: [PATCH 32/44] Docs: Add new config option for the notification sync interval. --- doc/conffile.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/conffile.rst b/doc/conffile.rst index 90f5f7a0b..ab81c9245 100644 --- a/doc/conffile.rst +++ b/doc/conffile.rst @@ -23,3 +23,5 @@ You can change the following configuration settings (must be under the ``[ownClo - ``maxLogLines`` (default: ``20000``) -- Specifies the maximum number of log lines displayed in the log window. - ``chunkSize`` (default: ``5242880``) -- Specifies the chunk size of uploaded files in bytes. + +- ``notificationRefreshInterval`` (default``300,000``) -- Specifies the default interval of checking for new server notifications. From 161d21904a6e8e8bcf31dcc9bbea6149fbde9862 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 23 Mar 2016 16:47:13 +0100 Subject: [PATCH 33/44] ActivityData: Add source file for implementation details Add the ident() method and operators. --- src/gui/CMakeLists.txt | 1 + src/gui/activitydata.cpp | 35 +++++++++++++++++++++++++++++++++++ src/gui/activitydata.h | 21 +++++++-------------- 3 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 src/gui/activitydata.cpp diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 92b9b5008..6d9a3a2df 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -63,6 +63,7 @@ set(client_SRCS owncloudgui.cpp owncloudsetupwizard.cpp protocolwidget.cpp + activitydata.cpp activitylistmodel.cpp activitywidget.cpp activityitemdelegate.cpp diff --git a/src/gui/activitydata.cpp b/src/gui/activitydata.cpp new file mode 100644 index 000000000..2d8001c9f --- /dev/null +++ b/src/gui/activitydata.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program 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; version 2 of the License. + * + * This program 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. + */ + +#include + +#include "activitydata.h" + + +namespace OCC +{ + +bool Activity::operator<( const Activity& val ) const { + return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); +} + +bool Activity::operator==( const Activity& val ) const { + return (_type == val._type && _id == val._id && _accName == val._accName); +} + +Activity::Identifier Activity::ident() const { + return Identifier( _id, _accName ); +} + + +} diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h index 68067f553..f35611ede 100644 --- a/src/gui/activitydata.h +++ b/src/gui/activitydata.h @@ -26,17 +26,6 @@ namespace OCC { class ActivityLink { public: - QVariantHash toVariantHash() const { - QVariantHash hash; - - hash["label"] = _label; - hash["link"] = _link; - hash["verb"] = _verb; - hash["primary"] = _isPrimary; - - return hash; - } - QString _label; QString _link; QString _verb; @@ -54,10 +43,13 @@ public: class Activity { public: + typedef QPair Identifier; + enum Type { ActivityType, NotificationType }; + Type _type; qlonglong _id; QString _subject; @@ -73,10 +65,11 @@ public: * @param val * @return */ - bool operator<( const Activity& val ) const { - return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); - } + bool operator<( const Activity& val ) const; + bool operator==( const Activity& val ) const; + + Identifier ident() const; }; /* ==================================================================== */ From 1bb3a4a45d7e58030845bc8592f2b8cf041ecc83 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 23 Mar 2016 16:48:38 +0100 Subject: [PATCH 34/44] NotificationWidget: Remove accountName() and add activity() method. --- src/gui/notificationwidget.cpp | 4 ++-- src/gui/notificationwidget.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index e3a81874f..88217f136 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -64,9 +64,9 @@ void NotificationWidget::setActivity(const Activity& activity) } } -QString NotificationWidget::accountName() const +Activity NotificationWidget::activity() const { - return _myActivity._accName; + return _myActivity; } void NotificationWidget::slotButtonClicked() diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index adafd437f..c0b87558a 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -33,7 +33,7 @@ public: explicit NotificationWidget(QWidget *parent = 0); bool readyToClose(); - QString accountName() const; + Activity activity() const; signals: void sendNotificationRequest( const QString&, const QString& link, const QString& verb); From 0c944a06f9ce67c8cf259f37cb8e32f29ba2f99a Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 23 Mar 2016 16:49:25 +0100 Subject: [PATCH 35/44] NotificationWidgetUI: Fix sizing and sizePolicy --- src/gui/notificationwidget.ui | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/gui/notificationwidget.ui b/src/gui/notificationwidget.ui index c8d34017d..1f20d67cf 100644 --- a/src/gui/notificationwidget.ui +++ b/src/gui/notificationwidget.ui @@ -7,9 +7,15 @@ 0 0 725 - 159 + 139 + + + 0 + 0 + + Form @@ -52,6 +58,9 @@ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam + + false + true @@ -108,7 +117,7 @@ 20 - 25 + 1 From 1fe5d6bb0cfb6d556b8eb6bf7b35a668cdcff243 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Wed, 23 Mar 2016 16:59:03 +0100 Subject: [PATCH 36/44] Notifications: Handle Notifications without an action. The policy that was said is that if a notification has no action, the client can and should display a close-button. This patch does it. In additon to that, the client needs a blacklist of closed notifcations otherwise they would re-appear next time the server notifications are fetched again. Also, changed the cleanup of not-longer-used widgets to be more robust. --- src/gui/activitywidget.cpp | 84 +++++++++++++++++++++++++++------- src/gui/activitywidget.h | 10 +++- src/gui/notificationwidget.cpp | 44 +++++++++++------- src/gui/notificationwidget.h | 2 +- 4 files changed, 103 insertions(+), 37 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 08b91955d..9b7c7cd0d 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -94,6 +94,9 @@ ActivityWidget::ActivityWidget(QWidget *parent) : connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this, SLOT(slotOpenFile(QModelIndex))); + + connect( &_removeTimer, SIGNAL(timeout()), this, SLOT(slotCheckToCleanWidgets()) ); + _removeTimer.setInterval(1000); } ActivityWidget::~ActivityWidget() @@ -231,18 +234,26 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) QString listAccountName; foreach( auto activity, list ) { + if( _blacklistedActivities.contains(activity)) { + qDebug() << Q_FUNC_INFO << "Activity in blacklist, skip"; + continue; + } + NotificationWidget *widget = 0; - if( _widgetForNotifId.contains(activity._id) ) { - widget = _widgetForNotifId[activity._id]; + if( _widgetForNotifId.contains( activity.ident()) ) { + widget = _widgetForNotifId[activity.ident()]; } else { widget = new NotificationWidget(this); connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QString)), this, SLOT(slotSendNotificationRequest(QString, QString, QString))); + connect(widget, SIGNAL(requestCleanupAndBlacklist(Activity)), + this, SLOT(slotRequestCleanupAndBlacklist(Activity))); + _notificationsLayout->addWidget(widget); // _ui->_notifyScroll->setMinimumHeight( widget->height()); _ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); - _widgetForNotifId[activity._id] = widget; + _widgetForNotifId[activity.ident()] = widget; } widget->setActivity( activity ); @@ -280,19 +291,20 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) } } - // check if we have widgets that have no corresponding activity from + // check if there are widgets that have no corresponding activity from // the server any more. Collect them in a list - QList strayCats; + QList< Activity::Identifier > strayCats; foreach( auto id, _widgetForNotifId.keys() ) { - bool found = false; NotificationWidget *widget = _widgetForNotifId[id]; + bool found = false; // do not mark widgets of other accounts to delete. - if( widget->accountName() != listAccountName ) { + if( widget->activity()._accName != listAccountName ) { continue; } + foreach( auto activity, list ) { - if( activity._id == id ) { + if( activity.ident() == id ) { // found an activity found = true; break; @@ -307,8 +319,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) // .. and now delete all these stray cat widgets. foreach( auto strayCatId, strayCats ) { NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId]; - widgetToGo->deleteLater(); - _widgetForNotifId.remove(strayCatId); + scheduleWidgetToRemove(widgetToGo, 0); } _ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() ); @@ -410,21 +421,60 @@ void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCo // Add 200 millisecs to the predefined value to make sure that the timer in // widget's method readyToClose() has elapsed. if( replyCode == OCS_SUCCESS_STATUS_CODE ) { - QTimer::singleShot(NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS+200, this, SLOT(slotCleanWidgetList())); + scheduleWidgetToRemove( job->widget() ); } } -void ActivityWidget::slotCleanWidgetList() +// blacklist the activity coming in here. +void ActivityWidget::slotRequestCleanupAndBlacklist(const Activity& blacklistActivity) { - foreach( int id, _widgetForNotifId.keys() ) { - Q_ASSERT(_widgetForNotifId[id]); - if( _widgetForNotifId[id]->readyToClose() ) { - auto *widget = _widgetForNotifId[id]; + if ( ! _blacklistedActivities.contains(blacklistActivity) ) { + _blacklistedActivities.append(blacklistActivity); + } + + NotificationWidget *widget = _widgetForNotifId[ blacklistActivity.ident() ]; + scheduleWidgetToRemove(widget); +} + +void ActivityWidget::scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds) +{ + if( !widget ) { + return; + } + // in fife seconds from now, remove the widget. + QDateTime removeTime = QDateTime::currentDateTime().addMSecs(milliseconds); + + QPair removeInfo = qMakePair(removeTime, widget); + if( !_widgetsToRemove.contains(removeInfo) ) { + _widgetsToRemove.insert( removeInfo ); + if( !_removeTimer.isActive() ) { + _removeTimer.start(); + } + } +} + +// Called every second to see if widgets need to be removed. +void ActivityWidget::slotCheckToCleanWidgets() +{ + // loop over all widgets in the to-remove queue + foreach( auto toRemove, _widgetsToRemove ) { + QDateTime t = toRemove.first; + NotificationWidget *widget = toRemove.second; + + if( QDateTime::currentDateTime() > t ) { + // found one to remove! + Activity::Identifier id = widget->activity().ident(); _widgetForNotifId.remove(id); - delete widget; + widget->deleteLater(); + _widgetsToRemove.remove(toRemove); } } + if( _widgetsToRemove.isEmpty() ) { + _removeTimer.stop(); + } + + // check to see if the whole notification pane should be hidden if( _widgetForNotifId.isEmpty() ) { _ui->_notifyLabel->setHidden(true); _ui->_notifyScroll->setHidden(true); diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index cf6d73202..a070a6ff5 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -66,6 +66,7 @@ public slots: void slotRefreshNotifications(AccountState *ptr); void slotRemoveAccount( AccountState *ptr ); void slotAccountActivityStatus(AccountState *ast, int statusCode); + void slotRequestCleanupAndBlacklist(const Activity& blacklistActivity); signals: void guiLog(const QString&, const QString&); @@ -80,7 +81,8 @@ private slots: void slotNotifyNetworkError( QNetworkReply* ); void slotNotifyServerFinished( const QString& reply, int replyCode ); void endNotificationRequest(NotificationWidget *widget , int replyCode); - void slotCleanWidgetList(); + void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 4500); + void slotCheckToCleanWidgets(); private: void showLabels(); @@ -89,9 +91,13 @@ private: QPushButton *_copyBtn; QSet _accountsWithoutActivities; - QMap _widgetForNotifId; + QMap _widgetForNotifId; QElapsedTimer _guiLogTimer; QSet _guiLoggedNotifications; + ActivityList _blacklistedActivities; + + QSet< QPair > _widgetsToRemove; + QTimer _removeTimer; // number of currently running notification requests. If non zero, // no query for notifications is started. diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 88217f136..15dd7fc90 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -54,13 +54,22 @@ void NotificationWidget::setActivity(const Activity& activity) foreach( auto button, _ui._buttonBox->buttons() ) { _ui._buttonBox->removeButton(button); } + _buttons.clear(); // display buttons for the links - foreach( auto link, activity._links ) { - QPushButton *b = _ui._buttonBox->addButton( link._label, QDialogButtonBox::AcceptRole); - b->setDefault(link._isPrimary); + if( activity._links.isEmpty() ) { + // in case there is no action defined, do a close button. + QPushButton *b = _ui._buttonBox->addButton( QDialogButtonBox::Close ); + b->setDefault(true); connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); _buttons.append(b); + } else { + foreach( auto link, activity._links ) { + QPushButton *b = _ui._buttonBox->addButton(link._label, QDialogButtonBox::AcceptRole); + b->setDefault(link._isPrimary); + connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); + _buttons.append(b); + } } } @@ -84,12 +93,24 @@ void NotificationWidget::slotButtonClicked() } // if the button was found, the link must be called + if( index > -1 && _myActivity._links.count() == 0 ) { + // no links, that means it was the close button + // empty link. Just close and remove the widget. + QString doneText = tr("Closing in a few seconds..."); + _ui._timeLabel->setText(doneText); + emit requestCleanupAndBlacklist(_myActivity); + return; + } + if( index > -1 && index < _myActivity._links.count() ) { ActivityLink triggeredLink = _myActivity._links.at(index); _actionLabel = triggeredLink._label; - qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; - _progressIndi->startAnimation(); - emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); + + if( ! triggeredLink._link.isEmpty() ) { + qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link; + _progressIndi->startAnimation(); + emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb ); + } } } } @@ -116,12 +137,6 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) //* The second parameter is a time, such as 'selected at 09:58pm' doneText = tr("'%1' selected at %2").arg(_actionLabel).arg(timeStr); - - // start a timer, so that activity widget can remove this widget after a - // certain time. It needs to be done by ActivityWidget because it also - // needs to hide the scrollview if no widget is left any more. - // method readyToClose() checks for the timer value to see if it is expired - _closeTimer.start(); } _ui._timeLabel->setText( doneText ); @@ -129,9 +144,4 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) } -bool NotificationWidget::readyToClose() -{ - return _closeTimer.isValid() && _closeTimer.elapsed() > NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS; -} - } diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index c0b87558a..6f1928487 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -37,6 +37,7 @@ public: signals: void sendNotificationRequest( const QString&, const QString& link, const QString& verb); + void requestCleanupAndBlacklist( const Activity& activity ); public slots: void setActivity(const Activity& activity); @@ -52,7 +53,6 @@ private: QString _accountName; QProgressIndicator *_progressIndi; QString _actionLabel; - QElapsedTimer _closeTimer; }; } From 69e8e158845292528d5492bc42c428ee09ddf25a Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 13:59:08 +0200 Subject: [PATCH 37/44] Remove explicit time spec specification as it is not needed. --- src/gui/activitylistmodel.cpp | 1 - src/gui/servernotificationhandler.cpp | 1 - src/libsync/utility.cpp | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index a16eafe84..07e0ba30d 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -155,7 +155,6 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int stat a._file = json.value("file").toString(); a._link = json.value("link").toUrl(); a._dateTime = json.value("date").toDateTime(); - a._dateTime.setTimeSpec(Qt::UTC); list.append(a); } diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp index 14a9e0710..560652064 100644 --- a/src/gui/servernotificationhandler.cpp +++ b/src/gui/servernotificationhandler.cpp @@ -81,7 +81,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& jso a._link = QUrl(s); } a._dateTime = json.value("datetime").toDateTime(); - a._dateTime.setTimeSpec(Qt::UTC); auto actions = json.value("actions").toList(); foreach( auto action, actions) { diff --git a/src/libsync/utility.cpp b/src/libsync/utility.cpp index b0325f553..7d6fcdc0f 100644 --- a/src/libsync/utility.cpp +++ b/src/libsync/utility.cpp @@ -440,8 +440,6 @@ QString Utility::timeAgoInWords(const QDateTime& dt, const QDateTime& from) now = from; } - Q_ASSERT(dt.timeSpec() == Qt::UTC); - if( dt.daysTo(now)>0 ) { int dtn = dt.daysTo(now); return QObject::tr("%n day(s) ago", "", dtn); From 4d59f5ec66ac7219093cca37732e2e936bb1e1f1 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 14:09:19 +0200 Subject: [PATCH 38/44] ActivityData: Declare operators outside the class --- src/gui/activitydata.cpp | 8 ++++---- src/gui/activitydata.h | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gui/activitydata.cpp b/src/gui/activitydata.cpp index 2d8001c9f..954383841 100644 --- a/src/gui/activitydata.cpp +++ b/src/gui/activitydata.cpp @@ -19,12 +19,12 @@ namespace OCC { -bool Activity::operator<( const Activity& val ) const { - return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); +bool operator<( const Activity& rhs, const Activity& lhs ) { + return rhs._dateTime.toMSecsSinceEpoch() > lhs._dateTime.toMSecsSinceEpoch(); } -bool Activity::operator==( const Activity& val ) const { - return (_type == val._type && _id == val._id && _accName == val._accName); +bool operator==( const Activity& rhs, const Activity& lhs ) { + return (rhs._type == lhs._type && rhs._id== lhs._id && rhs._accName == lhs._accName); } Activity::Identifier Activity::ident() const { diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h index f35611ede..5dada0a80 100644 --- a/src/gui/activitydata.h +++ b/src/gui/activitydata.h @@ -65,13 +65,14 @@ public: * @param val * @return */ - bool operator<( const Activity& val ) const; - bool operator==( const Activity& val ) const; Identifier ident() const; }; +bool operator==( const Activity& rhs, const Activity& lhs ); +bool operator<( const Activity& rhs, const Activity& lhs ); + /* ==================================================================== */ /** * @brief The ActivityList From cacb751ab873e05091af7650bf4d6d93bd86924a Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 14:38:11 +0200 Subject: [PATCH 39/44] Cleaups based on review feedback. --- src/gui/activitylistmodel.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index 07e0ba30d..ac27db191 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -99,8 +99,7 @@ bool ActivityListModel::canFetchMore(const QModelIndex& ) const { if( _activityLists.count() == 0 ) return true; - QMap::const_iterator i = _activityLists.begin(); - while (i != _activityLists.end()) { + for(auto i = _activityLists.begin() ; i != _activityLists.end(); ++i) { AccountState *ast = i.key(); if( ast && ast->isConnected() ) { ActivityList activities = i.value(); @@ -176,9 +175,9 @@ void ActivityListModel::combineActivityLists() std::sort( resultList.begin(), resultList.end() ); - beginRemoveRows(QModelIndex(), 0, _finalList.count() ); + beginResetModel(); _finalList.clear(); - endRemoveRows(); + endResetModel(); beginInsertRows(QModelIndex(), 0, resultList.count()); _finalList = resultList; @@ -189,7 +188,7 @@ void ActivityListModel::fetchMore(const QModelIndex &) { QList accounts = AccountManager::instance()->accounts(); - foreach (AccountStatePtr asp, accounts) { + foreach (const AccountStatePtr& asp, accounts) { if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { _activityLists[asp.data()] = ActivityList(); From 9f438cb768837ba4e4d3f89c6897cc4c39c41fc8 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 14:39:12 +0200 Subject: [PATCH 40/44] Doc: Add milliseconds unit to notificationRefreshInterval doc --- doc/conffile.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conffile.rst b/doc/conffile.rst index ab81c9245..6c2cbc440 100644 --- a/doc/conffile.rst +++ b/doc/conffile.rst @@ -24,4 +24,4 @@ You can change the following configuration settings (must be under the ``[ownClo - ``chunkSize`` (default: ``5242880``) -- Specifies the chunk size of uploaded files in bytes. -- ``notificationRefreshInterval`` (default``300,000``) -- Specifies the default interval of checking for new server notifications. +- ``notificationRefreshInterval`` (default``300,000``) -- Specifies the default interval of checking for new server notifications in milliseconds. From 2e30a0e5bc1e0bfa2a66186bb8f642c4347d4f2b Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 16:50:15 +0200 Subject: [PATCH 41/44] Remove superflous iterator increment --- src/gui/activitylistmodel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index ac27db191..26762a3fc 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -108,7 +108,6 @@ bool ActivityListModel::canFetchMore(const QModelIndex& ) const return true; } } - ++i; } return false; From cd3f612857c80843696cc7636b717403806cedf9 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 16:50:58 +0200 Subject: [PATCH 42/44] ActivityWidget: Rename blacklistActivities to blacklistNotifications. --- src/gui/activitywidget.cpp | 6 +++--- src/gui/activitywidget.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 9b7c7cd0d..a79fa68e1 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -234,7 +234,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) QString listAccountName; foreach( auto activity, list ) { - if( _blacklistedActivities.contains(activity)) { + if( _blacklistedNotifications.contains(activity)) { qDebug() << Q_FUNC_INFO << "Activity in blacklist, skip"; continue; } @@ -428,8 +428,8 @@ void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCo // blacklist the activity coming in here. void ActivityWidget::slotRequestCleanupAndBlacklist(const Activity& blacklistActivity) { - if ( ! _blacklistedActivities.contains(blacklistActivity) ) { - _blacklistedActivities.append(blacklistActivity); + if ( ! _blacklistedNotifications.contains(blacklistActivity) ) { + _blacklistedNotifications.append(blacklistActivity); } NotificationWidget *widget = _widgetForNotifId[ blacklistActivity.ident() ]; diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index a070a6ff5..acd05696a 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -94,7 +94,7 @@ private: QMap _widgetForNotifId; QElapsedTimer _guiLogTimer; QSet _guiLoggedNotifications; - ActivityList _blacklistedActivities; + ActivityList _blacklistedNotifications; QSet< QPair > _widgetsToRemove; QTimer _removeTimer; From 8166c52f4a21062f3d7a5081b7a3a4bb0fede730 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 17:18:02 +0200 Subject: [PATCH 43/44] NotificationHandling: Use QByteArray for the verb. --- src/gui/activitydata.h | 8 ++++---- src/gui/activitywidget.cpp | 4 ++-- src/gui/activitywidget.h | 2 +- src/gui/notificationconfirmjob.cpp | 4 ++-- src/gui/notificationconfirmjob.h | 4 ++-- src/gui/notificationwidget.h | 2 +- src/gui/servernotificationhandler.cpp | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h index 5dada0a80..d6ba7d770 100644 --- a/src/gui/activitydata.h +++ b/src/gui/activitydata.h @@ -26,10 +26,10 @@ namespace OCC { class ActivityLink { public: - QString _label; - QString _link; - QString _verb; - bool _isPrimary; + QString _label; + QString _link; + QByteArray _verb; + bool _isPrimary; }; /* ==================================================================== */ diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index a79fa68e1..59219e86e 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -245,8 +245,8 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) widget = _widgetForNotifId[activity.ident()]; } else { widget = new NotificationWidget(this); - connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QString)), - this, SLOT(slotSendNotificationRequest(QString, QString, QString))); + connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QByteArray)), + this, SLOT(slotSendNotificationRequest(QString, QString, QByteArray))); connect(widget, SIGNAL(requestCleanupAndBlacklist(Activity)), this, SLOT(slotRequestCleanupAndBlacklist(Activity))); diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index acd05696a..d5bed91fe 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -77,7 +77,7 @@ signals: private slots: void slotBuildNotificationDisplay(const ActivityList& list); - void slotSendNotificationRequest(const QString &accountName, const QString& link, const QString& verb); + void slotSendNotificationRequest(const QString &accountName, const QString& link, const QByteArray &verb); void slotNotifyNetworkError( QNetworkReply* ); void slotNotifyServerFinished( const QString& reply, int replyCode ); void endNotificationRequest(NotificationWidget *widget , int replyCode); diff --git a/src/gui/notificationconfirmjob.cpp b/src/gui/notificationconfirmjob.cpp index 8bcbbea3e..84959e7f8 100644 --- a/src/gui/notificationconfirmjob.cpp +++ b/src/gui/notificationconfirmjob.cpp @@ -27,7 +27,7 @@ NotificationConfirmJob::NotificationConfirmJob(AccountPtr account) setIgnoreCredentialFailure(true); } -void NotificationConfirmJob::setLinkAndVerb(const QUrl& link, const QString &verb) +void NotificationConfirmJob::setLinkAndVerb(const QUrl& link, const QByteArray &verb) { _link = link; _verb = verb; @@ -53,7 +53,7 @@ void NotificationConfirmJob::start() req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); QIODevice *iodevice = 0; - setReply(davRequest(_verb.toAscii(), _link, req, iodevice)); + setReply(davRequest(_verb, _link, req, iodevice)); setupConnections(reply()); AbstractNetworkJob::start(); diff --git a/src/gui/notificationconfirmjob.h b/src/gui/notificationconfirmjob.h index 7ae57bb41..12f7fa82b 100644 --- a/src/gui/notificationconfirmjob.h +++ b/src/gui/notificationconfirmjob.h @@ -46,7 +46,7 @@ public: * * @param verb currently supported GET PUT POST DELETE */ - void setLinkAndVerb(const QUrl& link, const QString &verb); + void setLinkAndVerb(const QUrl& link, const QByteArray &verb); /** * @brief Start the OCS request @@ -80,7 +80,7 @@ private slots: virtual bool finished() Q_DECL_OVERRIDE; private: - QString _verb; + QByteArray _verb; QUrl _link; NotificationWidget *_widget; }; diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index 6f1928487..166d5b5d5 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -36,7 +36,7 @@ public: Activity activity() const; signals: - void sendNotificationRequest( const QString&, const QString& link, const QString& verb); + void sendNotificationRequest( const QString&, const QString& link, const QByteArray& verb); void requestCleanupAndBlacklist( const Activity& activity ); public slots: diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp index 560652064..5b8cefeea 100644 --- a/src/gui/servernotificationhandler.cpp +++ b/src/gui/servernotificationhandler.cpp @@ -88,7 +88,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QVariantMap& jso ActivityLink al; al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray()); al._link = actionJson.value("link").toString(); - al._verb = actionJson.value("type").toString(); + al._verb = actionJson.value("type").toByteArray(); al._isPrimary = actionJson.value("primary").toBool(); a._links.append(al); From 885f8b382fbe99cc9c03a9bc9b67b71b2296b3f0 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Tue, 29 Mar 2016 17:18:53 +0200 Subject: [PATCH 44/44] ActivityWidget: Handle plural properly in translations. Even for the case where the number is fixed. Also fix the translators comments. --- src/gui/activitywidget.cpp | 10 +++++----- src/gui/notificationwidget.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 59219e86e..eeb4dd147 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -340,18 +340,18 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list) QString acc2 = accNotified.keys().at(1); if( newGuiLogCount == 2 ) { int notiCount = accNotified[ acc1 ] + accNotified[ acc2 ]; - msg = tr("You received %1 new notifications from %2 and %3.").arg(notiCount).arg(acc1).arg(acc2); + msg = tr("You received %n new notification(s) from %1 and %2.", "", notiCount).arg(acc1, acc2); } else { - msg = tr("You received new notifications from %1, %2 and other accounts.").arg(acc1).arg(acc2); + msg = tr("You received new notifications from %1, %2 and other accounts.").arg(acc1, acc2); } } - emit guiLog(Theme::instance()->appNameGUI() + QLatin1String(" ") + tr("Notifications - Action Required"), - msg); + const QString log = tr("%1 Notifications - Action Required").arg(Theme::instance()->appNameGUI()); + emit guiLog( log, msg); } } -void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb) +void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QByteArray& verb) { qDebug() << Q_FUNC_INFO << "Server Notification Request " << verb << link << "on account" << accountName; NotificationWidget *theSender = qobject_cast(sender()); diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp index 15dd7fc90..33961e5ad 100644 --- a/src/gui/notificationwidget.cpp +++ b/src/gui/notificationwidget.cpp @@ -129,14 +129,14 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode) for( i = 0; i < _buttons.count(); i++ ) { _buttons.at(i)->setEnabled(true); } - //* The second parameter is a time, such as 'failed at 09:58pm' - doneText = tr("%1 request failed at %2").arg(_actionLabel).arg(timeStr); + //: The second parameter is a time, such as 'failed at 09:58pm' + doneText = tr("%1 request failed at %2").arg(_actionLabel, timeStr); } else { // the call to the ocs API succeeded. _ui._buttonBox->hide(); - //* The second parameter is a time, such as 'selected at 09:58pm' - doneText = tr("'%1' selected at %2").arg(_actionLabel).arg(timeStr); + //: The second parameter is a time, such as 'selected at 09:58pm' + doneText = tr("'%1' selected at %2").arg(_actionLabel, timeStr); } _ui._timeLabel->setText( doneText );