Merge lp:~pete-woods/unity-notifications/handle-client-death into lp:unity-notifications

Proposed by Pete Woods
Status: Merged
Approved by: Charles Kerr
Approved revision: no longer in the source branch.
Merged at revision: 228
Proposed branch: lp:~pete-woods/unity-notifications/handle-client-death
Merge into: lp:unity-notifications
Prerequisite: lp:~pete-woods/unity-notifications/clients-own-their-notifications
Diff against target: 338 lines (+162/-11)
5 files modified
include/NotificationModel.h (+2/-1)
include/NotificationServer.h (+6/-0)
src/NotificationModel.cpp (+51/-2)
src/NotificationServer.cpp (+29/-1)
test/notificationservertest.cpp (+74/-7)
To merge this branch: bzr merge lp:~pete-woods/unity-notifications/handle-client-death
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
Review via email: mp+260749@code.launchpad.net

Commit message

Handle client death by cleaning up notifications

Description of the change

Handle client death by cleaning up notifications

To post a comment you must log in.
Revision history for this message
Charles Kerr (charlesk) wrote :

Looks good overall. Inline are one or two optional suggestions, plus one NF for how NotificationModel::removeAllNotificationsForClient() handles its loops.

review: Needs Fixing
228. By Pete Woods

Fix method argument order on GetServerInformation

229. By Pete Woods

Pass back full notification information to tests

230. By Pete Woods

Remove diff noise

Revision history for this message
Charles Kerr (charlesk) wrote :

Nice work!

review: Approve
232. By Pete Woods

Fix parallel build issues

234. By Pete Woods

Actually pass in notification server

235. By Pete Woods

Notifications are now owned by the client that opened them

236. By Pete Woods

Handle local notifications

237. By Pete Woods

Handle client death

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/NotificationModel.h'
2--- include/NotificationModel.h 2015-06-23 15:49:07 +0000
3+++ include/NotificationModel.h 2015-06-23 15:49:07 +0000
4@@ -50,6 +50,7 @@
5 QSharedPointer<Notification> getNotification(const QString &summary) const;
6 QSharedPointer<Notification> getDisplayedNotification(int index) const;
7 bool hasNotification(NotificationID id) const;
8+ QList<QSharedPointer<Notification>> removeAllNotificationsForClient(const QString& clientId);
9
10 // getRaw() is only meant to be used from QML, since QML cannot handle
11 // QSharedPointers... on C++-side only use getNotification()
12@@ -87,7 +88,7 @@
13 void insertSnap(const QSharedPointer<Notification> &n);
14 void insertExtSnap(const QSharedPointer<Notification> &n);
15 void insertToVisible(const QSharedPointer<Notification> &n, int location=-1);
16- void deleteFromVisible(int loc);
17+ QSharedPointer<Notification> deleteFromVisible(int loc);
18 void deleteFirst();
19 int findFirst(const Notification::Type type) const;
20 int countShowing(const Notification::Type type) const;
21
22=== modified file 'include/NotificationServer.h'
23--- include/NotificationServer.h 2015-06-23 15:49:07 +0000
24+++ include/NotificationServer.h 2015-06-23 15:49:07 +0000
25@@ -45,6 +45,7 @@
26 #include <QDBusVariant>
27 #include <QDBusConnection>
28 #include <QDBusContext>
29+#include <QDBusServiceWatcher>
30
31 class NotificationModel;
32 class Notification;
33@@ -70,6 +71,9 @@
34 void onDataChanged(unsigned int id);
35 void onCompleted(unsigned int id);
36
37+private Q_SLOTS:
38+ void serviceUnregistered(const QString &service);
39+
40 Q_SIGNALS:
41 void NotificationClosed(unsigned int id, unsigned int reason);
42 void ActionInvoked(unsigned int id, const QString &action_key);
43@@ -78,11 +82,13 @@
44 private:
45 void incrementCounter();
46 QString messageSender();
47+ bool isCmdLine();
48
49 QSharedPointer<Notification> buildNotification(NotificationID id, const QVariantMap &hints);
50 NotificationModel &model;
51 unsigned int idCounter;
52 QDBusConnection m_connection;
53+ QDBusServiceWatcher m_watcher;
54
55 };
56
57
58=== modified file 'src/NotificationModel.cpp'
59--- src/NotificationModel.cpp 2015-06-23 15:49:07 +0000
60+++ src/NotificationModel.cpp 2015-06-23 15:49:07 +0000
61@@ -220,6 +220,54 @@
62 return !(getNotification(id).isNull());
63 }
64
65+QList<QSharedPointer<Notification>> NotificationModel::removeAllNotificationsForClient(const QString& clientId)
66+{
67+ QList<QSharedPointer<Notification>> notifications;
68+
69+ for(int i=0; i<p->ephemeralQueue.size();) {
70+ if(p->ephemeralQueue[i]->getClientId() == clientId) {
71+ notifications << p->ephemeralQueue.takeAt(i);
72+ Q_EMIT queueSizeChanged(queued());
73+ } else {
74+ ++i;
75+ }
76+ }
77+
78+ for(int i=0; i<p->snapQueue.size();) {
79+ if(p->snapQueue[i]->getClientId() == clientId) {
80+ notifications << p->snapQueue.takeAt(i);
81+ Q_EMIT queueSizeChanged(queued());
82+ } else {
83+ ++i;
84+ }
85+ }
86+
87+ for(int i=0; i<p->interactiveQueue.size();) {
88+ if(p->interactiveQueue[i]->getClientId() == clientId) {
89+ notifications << p->interactiveQueue.takeAt(i);
90+ Q_EMIT queueSizeChanged(queued());
91+ } else {
92+ ++i;
93+ }
94+ }
95+
96+ bool needToTimeout = false;
97+ for(int i=0; i<p->displayedNotifications.size();) {
98+ if(p->displayedNotifications[i]->getClientId() == clientId) {
99+ notifications << deleteFromVisible(i);
100+ needToTimeout = true;
101+ } else {
102+ ++i;
103+ }
104+ }
105+
106+ if (needToTimeout) {
107+ timeout(); // Simulate a timeout so visual state is updated.
108+ }
109+
110+ return notifications;
111+}
112+
113 void NotificationModel::removeNotification(const NotificationID id) {
114 for(int i=0; i<p->ephemeralQueue.size(); i++) {
115 if(p->ephemeralQueue[i]->getID() == id) {
116@@ -261,13 +309,14 @@
117 deleteFromVisible(0);
118 }
119
120-void NotificationModel::deleteFromVisible(int loc) {
121+QSharedPointer<Notification> NotificationModel::deleteFromVisible(int loc) {
122 QModelIndex deletePoint = QModelIndex();
123 beginRemoveRows(deletePoint, loc, loc);
124 QSharedPointer<Notification> n = p->displayedNotifications[loc];
125 p->displayTimes.erase(p->displayTimes.find(n->getID()));
126- p->displayedNotifications.erase(p->displayedNotifications.begin() + loc);
127+ auto notification = p->displayedNotifications.takeAt(loc);
128 endRemoveRows();
129+ return notification;
130 }
131
132 void NotificationModel::timeout() {
133
134=== modified file 'src/NotificationServer.cpp'
135--- src/NotificationServer.cpp 2015-06-23 15:49:07 +0000
136+++ src/NotificationServer.cpp 2015-06-23 15:49:07 +0000
137@@ -41,6 +41,10 @@
138 // Memory managed by Qt
139 new NotificationsAdaptor(this);
140
141+ m_watcher.setConnection(m_connection);
142+ m_watcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
143+ connect(&m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, &NotificationServer::serviceUnregistered);
144+
145 connect(this, SIGNAL(dataChanged(unsigned int)), &m, SLOT(onDataChanged(unsigned int)));
146
147 if(!m_connection.registerObject(DBUS_PATH, this)) {
148@@ -149,9 +153,11 @@
149
150 void NotificationServer::incrementCounter() {
151 idCounter++;
152- if(idCounter == 0) // Spec forbids zero as return value.
153+ // Spec forbids zero as return value.
154+ if(idCounter == 0) {
155 idCounter = 1;
156 }
157+}
158
159 QString NotificationServer::messageSender() {
160 QString sender(LOCAL_OWNER);
161@@ -161,6 +167,24 @@
162 return sender;
163 }
164
165+bool NotificationServer::isCmdLine() {
166+ if (!calledFromDBus()) {
167+ return false;
168+ }
169+ QString sender = message().service();
170+ uint pid = connection().interface()->servicePid(sender);
171+ QString path = QDir(QString("/proc/%1/exe").arg(pid)).canonicalPath();
172+ return (path == "/usr/bin/notify-send");
173+}
174+
175+void NotificationServer::serviceUnregistered(const QString &clientId) {
176+ m_watcher.removeWatchedService(clientId);
177+ auto notifications = model.removeAllNotificationsForClient(clientId);
178+ for (auto notification: notifications) {
179+ Q_EMIT NotificationClosed(notification->getID(), 1);
180+ }
181+}
182+
183 unsigned int NotificationServer::Notify(const QString &app_name, uint replaces_id,
184 const QString &app_icon, const QString &summary,
185 const QString &body, const QStringList &actions,
186@@ -268,6 +292,10 @@
187 notification->setClientId(clientId);
188
189 if (newNotification) {
190+ // Don't clean up after the command line client closes
191+ if (!isCmdLine()) {
192+ m_watcher.addWatchedService(clientId);
193+ }
194 model.insertNotification(notification);
195 } else {
196 model.notificationUpdated(currentId);
197
198=== modified file 'test/notificationservertest.cpp'
199--- test/notificationservertest.cpp 2015-06-23 15:49:07 +0000
200+++ test/notificationservertest.cpp 2015-06-23 15:49:07 +0000
201@@ -18,7 +18,7 @@
202
203 QSharedPointer<QDBusConnection> secondConnection;
204
205- QSharedPointer<OrgFreedesktopNotificationsInterface> notificationsInterfaceSecond;
206+ QSharedPointer<OrgFreedesktopNotificationsInterface> secondNotificationsInterface;
207
208 private Q_SLOTS:
209
210@@ -48,7 +48,7 @@
211 dbus->sessionBus() + "_second")));
212
213 // Create a second connection to the server over a different dbus connection
214- notificationsInterfaceSecond.reset(
215+ secondNotificationsInterface.reset(
216 new OrgFreedesktopNotificationsInterface(
217 DBUS_SERVICE_NAME,
218 DBUS_PATH,
219@@ -56,7 +56,7 @@
220 }
221
222 void cleanup() {
223- notificationsInterfaceSecond.reset();
224+ secondNotificationsInterface.reset();
225
226 secondConnection.reset();
227
228@@ -84,7 +84,7 @@
229 }
230
231 int notifySecond(const QString& name, int replacesId = 0, bool secondInterface = false) {
232- return _notify(notificationsInterfaceSecond, name, replacesId);
233+ return _notify(secondNotificationsInterface, name, replacesId);
234 }
235
236 void expectNotification(const NotificationDataList& notifications, int index, int id, const QString& name) {
237@@ -100,7 +100,7 @@
238 );
239 }
240
241-void close(QSharedPointer<OrgFreedesktopNotificationsInterface> interface, int id) {
242+void _close(QSharedPointer<OrgFreedesktopNotificationsInterface> interface, int id) {
243 auto reply = interface->CloseNotification(id);
244 reply.waitForFinished();
245 if (reply.isError()) {
246@@ -109,11 +109,11 @@
247 }
248
249 void closeFirst(int id) {
250- close(notificationsInterface, id);
251+ _close(notificationsInterface, id);
252 }
253
254 void closeSecond(int id) {
255- close(notificationsInterfaceSecond, id);
256+ _close(secondNotificationsInterface, id);
257 }
258
259 void testNotify() {
260@@ -221,11 +221,78 @@
261 uint id1 = notifySecond("1");
262 QCOMPARE(id1, uint(1));
263
264+ qDebug() << "====== EXPECTED ERRORS BELOW ======";
265+
266 // Try and update notification from another client
267 QVERIFY_EXCEPTION_THROWN(notifyFirst("2", id1), std::domain_error);
268
269 // Try and close notification from another client
270 QVERIFY_EXCEPTION_THROWN(closeFirst(id1), std::domain_error);
271+
272+ qDebug() << "====== EXPECTED ERRORS ABOVE ======";
273+}
274+
275+void testNotifyOneClientDies() {
276+ QSignalSpy closedSpy(notificationsInterface.data(), SIGNAL(NotificationClosed(uint, uint)));
277+
278+ uint id1 = notifySecond("1");
279+ QCOMPARE(id1, uint(1));
280+
281+ uint id2 = notifySecond("2");
282+ QCOMPARE(id2, uint(2));
283+
284+ uint id3 = notifySecond("3");
285+ QCOMPARE(id3, uint(3));
286+
287+ uint id4 = notifySecond("4");
288+ QCOMPARE(id4, uint(4));
289+
290+ // Kill off the client without cleaning up
291+ QString connectionName = secondConnection->name();
292+ secondNotificationsInterface.reset();
293+ secondConnection.reset();
294+ QDBusConnection::disconnectFromBus(connectionName);
295+
296+ QVERIFY2(closedSpy.wait(), "We should receive a closed notification");
297+
298+ // We shouldn't have any notifications left behind
299+ {
300+ NotificationDataList notifications = notificationsInterface->GetNotifications("my app");
301+ QCOMPARE(notifications.size(), 0);
302+ }
303+}
304+
305+void testNotifyTwoClientsOneDies() {
306+ QSignalSpy closedSpy(notificationsInterface.data(), SIGNAL(NotificationClosed(uint, uint)));
307+
308+ uint id1 = notifyFirst("1");
309+ QCOMPARE(id1, uint(1));
310+
311+ uint id2 = notifySecond("2");
312+ QCOMPARE(id2, uint(2));
313+
314+ uint id3 = notifyFirst("3");
315+ QCOMPARE(id3, uint(3));
316+
317+ uint id4 = notifySecond("4");
318+ QCOMPARE(id4, uint(4));
319+
320+ // Kill off the client without cleaning up
321+ QString connectionName = secondConnection->name();
322+ secondNotificationsInterface.reset();
323+ secondConnection.reset();
324+ QDBusConnection::disconnectFromBus(connectionName);
325+
326+ QVERIFY2(closedSpy.wait(), "We should receive a closed notification");
327+
328+ // We should only have notifications from the first client left behind
329+ {
330+ NotificationDataList notifications = notificationsInterface->GetNotifications("my app");
331+ QCOMPARE(notifications.size(), 2);
332+
333+ expectNotification(notifications, 0, id1, "1");
334+ expectNotification(notifications, 1, id3, "3");
335+ }
336 }
337
338 void testGetCapabilities() {

Subscribers

People subscribed via source and target branches

to all changes: