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