1 #include "notificationhandler.h"
2 
3 #include "accountstate.h"
4 #include "capabilities.h"
5 #include "networkjobs.h"
6 
7 #include "iconjob.h"
8 
9 #include <QJsonDocument>
10 #include <QJsonObject>
11 
12 namespace OCC {
13 
14 Q_LOGGING_CATEGORY(lcServerNotification, "nextcloud.gui.servernotification", QtInfoMsg)
15 
16 const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v2/notifications");
17 const char propertyAccountStateC[] = "oc_account_state";
18 const int successStatusCode = 200;
19 const int notModifiedStatusCode = 304;
20 
ServerNotificationHandler(AccountState * accountState,QObject * parent)21 ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent)
22     : QObject(parent)
23     , _accountState(accountState)
24 {
25 }
26 
slotFetchNotifications()27 void ServerNotificationHandler::slotFetchNotifications()
28 {
29     // check connectivity and credentials
30     if (!(_accountState && _accountState->isConnected() && _accountState->account() && _accountState->account()->credentials() && _accountState->account()->credentials()->ready())) {
31         deleteLater();
32         return;
33     }
34     // check if the account has notifications enabled. If the capabilities are
35     // not yet valid, its assumed that notifications are available.
36     if (_accountState->account()->capabilities().isValid()) {
37         if (!_accountState->account()->capabilities().notificationsAvailable()) {
38             qCInfo(lcServerNotification) << "Account" << _accountState->account()->displayName() << "does not have notifications enabled.";
39             deleteLater();
40             return;
41         }
42     }
43 
44     // if the previous notification job has finished, start next.
45     _notificationJob = new JsonApiJob(_accountState->account(), notificationsPath, this);
46     QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived,
47         this, &ServerNotificationHandler::slotNotificationsReceived);
48     QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived,
49         this, &ServerNotificationHandler::slotEtagResponseHeaderReceived);
50     QObject::connect(_notificationJob.data(), &JsonApiJob::allowDesktopNotificationsChanged,
51             this, &ServerNotificationHandler::slotAllowDesktopNotificationsChanged);
52     _notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue<AccountState *>(_accountState));
53     _notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader());
54     _notificationJob->start();
55 }
56 
slotEtagResponseHeaderReceived(const QByteArray & value,int statusCode)57 void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode)
58 {
59     if (statusCode == successStatusCode) {
60         qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value;
61         auto *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
62         account->setNotificationsEtagResponseHeader(value);
63     }
64 }
65 
slotAllowDesktopNotificationsChanged(bool isAllowed)66 void ServerNotificationHandler::slotAllowDesktopNotificationsChanged(bool isAllowed)
67 {
68     auto *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
69     if (account != nullptr) {
70        account->setDesktopNotificationsAllowed(isAllowed);
71     }
72 }
73 
slotNotificationsReceived(const QJsonDocument & json,int statusCode)74 void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode)
75 {
76     if (statusCode != successStatusCode && statusCode != notModifiedStatusCode) {
77         qCWarning(lcServerNotification) << "Notifications failed with status code " << statusCode;
78         deleteLater();
79         return;
80     }
81 
82     if (statusCode == notModifiedStatusCode) {
83         qCWarning(lcServerNotification) << "Status code " << statusCode << " Not Modified - No new notifications.";
84         deleteLater();
85         return;
86     }
87 
88     auto notifies = json.object().value("ocs").toObject().value("data").toArray();
89 
90     auto *ai = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
91 
92     ActivityList list;
93 
94     foreach (auto element, notifies) {
95         Activity a;
96         auto json = element.toObject();
97         a._type = Activity::NotificationType;
98         a._accName = ai->account()->displayName();
99         a._id = json.value("notification_id").toInt();
100 
101         //need to know, specially for remote_share
102         a._objectType = json.value("object_type").toString();
103         a._status = 0;
104 
105         a._subject = json.value("subject").toString();
106         a._message = json.value("message").toString();
107         a._icon = json.value("icon").toString();
108 
109         QUrl link(json.value("link").toString());
110         if (!link.isEmpty()) {
111             if (link.host().isEmpty()) {
112                 link.setScheme(ai->account()->url().scheme());
113                 link.setHost(ai->account()->url().host());
114             }
115             if (link.port() == -1) {
116                 link.setPort(ai->account()->url().port());
117             }
118         }
119         a._link = link;
120         a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
121 
122         auto actions = json.value("actions").toArray();
123         foreach (auto action, actions) {
124             auto actionJson = action.toObject();
125             ActivityLink al;
126             al._label = QUrl::fromPercentEncoding(actionJson.value("label").toString().toUtf8());
127             al._link = actionJson.value("link").toString();
128             al._verb = actionJson.value("type").toString().toUtf8();
129             al._primary = actionJson.value("primary").toBool();
130 
131             a._links.append(al);
132         }
133 
134         // Add another action to dismiss notification on server
135         // https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
136         ActivityLink al;
137         al._label = tr("Dismiss");
138         al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
139         al._verb = "DELETE";
140         al._primary = false;
141         a._links.append(al);
142 
143         list.append(a);
144     }
145     emit newNotificationList(list);
146 
147     deleteLater();
148 }
149 }
150