1 /*
2 Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com>
3 Copyright (C) 2011 Dario Freddi <dario.freddi@collabora.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20
21 #include "contact-request-handler.h"
22 #include "ktp_kded_debug.h"
23
24 #include <KTp/error-dictionary.h>
25 #include <KTp/core.h>
26 #include <KTp/Widgets/contact-info-dialog.h>
27
28 #include <TelepathyQt/Account>
29 #include <TelepathyQt/AccountManager>
30 #include <TelepathyQt/Connection>
31 #include <TelepathyQt/Contact>
32 #include <TelepathyQt/ContactManager>
33 #include <TelepathyQt/PendingComposite>
34 #include <TelepathyQt/PendingOperation>
35
36 #include <KStatusNotifierItem>
37 #include <KLocalizedString>
38
39 #include <QAction>
40 #include <QIcon>
41 #include <QFutureWatcher>
42 #include <QtConcurrentFilter>
43 #include <QMenu>
44
Q_DECLARE_METATYPE(Tp::ContactPtr)45 Q_DECLARE_METATYPE(Tp::ContactPtr)
46
47 static bool kde_tp_filter_contacts_by_publication_status(const Tp::ContactPtr &contact)
48 {
49 return contact->publishState() == Tp::Contact::PresenceStateAsk && !contact->isBlocked();
50 }
51
ContactRequestHandler(QObject * parent)52 ContactRequestHandler::ContactRequestHandler(QObject *parent)
53 : QObject(parent)
54 {
55 connect(KTp::accountManager().data(), SIGNAL(newAccount(Tp::AccountPtr)),
56 this, SLOT(onNewAccountAdded(Tp::AccountPtr)));
57
58 QList<Tp::AccountPtr> accounts = KTp::accountManager()->allAccounts();
59
60 Q_FOREACH(const Tp::AccountPtr &account, accounts) {
61 onNewAccountAdded(account);
62 }
63
64 }
65
~ContactRequestHandler()66 ContactRequestHandler::~ContactRequestHandler()
67 {
68
69 }
70
onNewAccountAdded(const Tp::AccountPtr & account)71 void ContactRequestHandler::onNewAccountAdded(const Tp::AccountPtr &account)
72 {
73 qCWarning(KTP_KDED_MODULE);
74 Q_ASSERT(account->isReady(Tp::Account::FeatureCore));
75
76 if (account->connection()) {
77 handleNewConnection(account->connection());
78 }
79
80 connect(account.data(),
81 SIGNAL(connectionChanged(Tp::ConnectionPtr)),
82 this, SLOT(onConnectionChanged(Tp::ConnectionPtr)));
83 }
84
onConnectionChanged(const Tp::ConnectionPtr & connection)85 void ContactRequestHandler::onConnectionChanged(const Tp::ConnectionPtr &connection)
86 {
87 if (!connection.isNull()) {
88 handleNewConnection(connection);
89 }
90 }
91
handleNewConnection(const Tp::ConnectionPtr & connection)92 void ContactRequestHandler::handleNewConnection(const Tp::ConnectionPtr &connection)
93 {
94 qCDebug(KTP_KDED_MODULE);
95 connect(connection->contactManager().data(), SIGNAL(presencePublicationRequested(Tp::Contacts)),
96 this, SLOT(onPresencePublicationRequested(Tp::Contacts)));
97
98 connect(connection->contactManager().data(),
99 SIGNAL(stateChanged(Tp::ContactListState)),
100 this, SLOT(onContactManagerStateChanged(Tp::ContactListState)));
101
102 onContactManagerStateChanged(connection->contactManager(),
103 connection->contactManager()->state());
104 }
105
onContactManagerStateChanged(Tp::ContactListState state)106 void ContactRequestHandler::onContactManagerStateChanged(Tp::ContactListState state)
107 {
108 onContactManagerStateChanged(Tp::ContactManagerPtr(qobject_cast< Tp::ContactManager* >(sender())), state);
109 }
110
onContactManagerStateChanged(const Tp::ContactManagerPtr & contactManager,Tp::ContactListState state)111 void ContactRequestHandler::onContactManagerStateChanged(const Tp::ContactManagerPtr &contactManager,
112 Tp::ContactListState state)
113 {
114 if (state == Tp::ContactListStateSuccess) {
115 QFutureWatcher< Tp::ContactPtr > *watcher = new QFutureWatcher< Tp::ContactPtr >(this);
116 connect(watcher, SIGNAL(finished()), this, SLOT(onAccountsPresenceStatusFiltered()));
117 watcher->setFuture(QtConcurrent::filtered(contactManager->allKnownContacts(),
118 kde_tp_filter_contacts_by_publication_status));
119
120 qCDebug(KTP_KDED_MODULE) << "Watcher is on";
121 } else {
122 qCDebug(KTP_KDED_MODULE) << "Watcher still off, state is" << state << "contactManager is" << contactManager.isNull();
123 }
124 }
125
onAccountsPresenceStatusFiltered()126 void ContactRequestHandler::onAccountsPresenceStatusFiltered()
127 {
128 qCDebug(KTP_KDED_MODULE) << "Watcher is here";
129 QFutureWatcher< Tp::ContactPtr > *watcher = dynamic_cast< QFutureWatcher< Tp::ContactPtr > * >(sender());
130 qCDebug(KTP_KDED_MODULE) << "Watcher is casted";
131 Tp::Contacts contacts = watcher->future().results().toSet();
132 qCDebug(KTP_KDED_MODULE) << "Watcher is used";
133 if (!contacts.isEmpty()) {
134 onPresencePublicationRequested(contacts);
135 }
136 watcher->deleteLater();
137 }
138
onPresencePublicationRequested(const Tp::Contacts & contacts)139 void ContactRequestHandler::onPresencePublicationRequested(const Tp::Contacts &contacts)
140 {
141 qCDebug(KTP_KDED_MODULE) << "New contact requested";
142
143 Q_FOREACH (const Tp::ContactPtr &contact, contacts) {
144 Tp::ContactManagerPtr manager = contact->manager();
145
146 if (contact->subscriptionState() == Tp::Contact::PresenceStateYes) {
147 Tp::PendingOperation *op = manager->authorizePresencePublication(QList< Tp::ContactPtr >() << contact);
148 op->setProperty("__contact", QVariant::fromValue(contact));
149
150 connect(op, SIGNAL(finished(Tp::PendingOperation*)),
151 this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*)));
152 } else {
153 // Handle multiaccount requests properly
154 if (m_pendingContacts.contains(contact->id())) {
155 // It's likely we have a simultaneous request
156 bool newReq = true;
157 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contact->id());
158 while (i != m_pendingContacts.constEnd() && i.key() == contact->id()) {
159 if (i.value().data() == contact.data()) {
160 newReq = false;
161 break;
162 }
163 ++i;
164 }
165
166 if (newReq) {
167 // Insert multi
168 m_pendingContacts.insertMulti(contact->id(), contact);
169 }
170 } else {
171 // Simple insertion
172 m_pendingContacts.insert(contact->id(), contact);
173 }
174
175 connect(contact.data(), SIGNAL(invalidated()), this, SLOT(onContactInvalidated()));
176
177 updateMenus();
178
179 if (!m_notifierItem.isNull()) {
180 m_notifierItem.data()->showMessage(i18n("New contact request"), //krazy:exclude=qmethods
181 i18n("The contact %1 wants to be able to chat with you.",
182 contact->id()),
183 QLatin1String("list-add-user"));
184 }
185 }
186 }
187 }
188
onFinalizeSubscriptionFinished(Tp::PendingOperation * op)189 void ContactRequestHandler::onFinalizeSubscriptionFinished(Tp::PendingOperation *op)
190 {
191 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
192
193 Q_ASSERT(!contact.isNull());
194
195 if (op->isError()) {
196 // ARGH
197 if (!m_notifierItem.isNull()) {
198 m_notifierItem.data()->showMessage(i18n("Error adding contact"),
199 i18n("%1 has been added successfully to your contact list, "
200 "but might be unable to see when you are online. Error details: %2",
201 contact->alias(), KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
202 QLatin1String("dialog-error"));
203 }
204 } else {
205 // Update the menu
206 m_pendingContacts.remove(contact->id());
207 updateMenus();
208 }
209 }
210
onContactInvalidated()211 void ContactRequestHandler::onContactInvalidated()
212 {
213 Tp::ContactPtr contact = Tp::ContactPtr(qobject_cast<Tp::Contact*>(sender()));
214
215 m_pendingContacts.remove(contact->id());
216 updateMenus();
217 }
218
onNotifierActivated(bool active,const QPoint & pos)219 void ContactRequestHandler::onNotifierActivated(bool active, const QPoint &pos)
220 {
221 if (active) {
222 if (m_notifierItem) {
223 m_notifierItem.data()->contextMenu()->popup(pos);
224 }
225 }
226 }
227
onContactRequestApproved()228 void ContactRequestHandler::onContactRequestApproved()
229 {
230 QString contactId = qobject_cast<QAction*>(sender())->data().toString();
231
232 // Disable the action in the meanwhile
233 m_menuItems.value(contactId)->setEnabled(false);
234
235 if (!contactId.isEmpty()) {
236 QList<Tp::PendingOperation*> operations;
237 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId);
238 while (i != m_pendingContacts.constEnd() && i.key() == contactId) {
239 if (!i.value()->manager().isNull()) {
240 Tp::PendingOperation *op = i.value()->manager()->authorizePresencePublication(QList< Tp::ContactPtr >() << i.value());
241 op->setProperty("__contact", QVariant::fromValue(i.value()));
242 operations.append(op);
243 }
244 ++i;
245 }
246
247 // Take the first value, if any
248 if (!operations.isEmpty()) {
249 Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
250
251 Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact);
252 op->setProperty("__contact", QVariant::fromValue(contact));
253
254 connect(op, SIGNAL(finished(Tp::PendingOperation*)),
255 this, SLOT(onAuthorizePresencePublicationFinished(Tp::PendingOperation*)));
256 }
257 }
258
259 }
260
onShowContactDetails()261 void ContactRequestHandler::onShowContactDetails()
262 {
263 QString contactId = qobject_cast<QAction*>(sender())->data().toString();
264
265 if (!contactId.isEmpty()) {
266 const Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
267 const Tp::ContactManagerPtr manager = contact->manager();
268 Q_FOREACH (const Tp::AccountPtr &account, KTp::accountManager()->allAccounts()) {
269 if (account->connection() == manager->connection()) {
270 KTp::ContactInfoDialog *dialog = new KTp::ContactInfoDialog(account, contact);
271 connect(dialog, SIGNAL(closeClicked()), dialog, SLOT(deleteLater()));
272 dialog->show();
273 break;
274 }
275 }
276 }
277 }
278
onAuthorizePresencePublicationFinished(Tp::PendingOperation * op)279 void ContactRequestHandler::onAuthorizePresencePublicationFinished(Tp::PendingOperation *op)
280 {
281 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
282
283 if (op->isError()) {
284 if (!m_notifierItem.isNull()) {
285 m_notifierItem.data()->showMessage(i18n("Error granting contact authorization"),
286 i18n("There was an error while accepting the request: %1",
287 KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
288 QLatin1String("dialog-error"));
289 }
290
291 // Re-enable the action
292 m_menuItems.value(contact->id())->setEnabled(true);
293 } else {
294 // op succeeded
295 if (!m_notifierItem.isNull()) {
296 m_notifierItem.data()->showMessage(i18n("Contact request accepted"),
297 i18n("%1 will now be able to see when you are online",
298 contact->alias()), QLatin1String("dialog-ok-apply"));
299 }
300
301 // If needed, reiterate the request on the other end
302 if (contact->manager()->canRequestPresenceSubscription() &&
303 contact->subscriptionState() == Tp::Contact::PresenceStateNo) {
304
305 Tp::PendingOperation *op = contact->manager()->requestPresenceSubscription(QList< Tp::ContactPtr >() << contact);
306 op->setProperty("__contact", QVariant::fromValue(contact));
307
308 connect(op,
309 SIGNAL(finished(Tp::PendingOperation*)),
310 this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*)));
311 } else {
312 // Update the menu
313 m_pendingContacts.remove(contact->id());
314 updateMenus();
315 }
316 }
317 }
318
onContactRequestDenied()319 void ContactRequestHandler::onContactRequestDenied()
320 {
321 QString contactId = qobject_cast<QAction*>(sender())->data().toString();
322
323 // Disable the action in the meanwhile
324 m_menuItems.value(contactId)->setEnabled(false);
325
326 if (!contactId.isEmpty()) {
327 QList<Tp::PendingOperation*> operations;
328 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId);
329 while (i != m_pendingContacts.constEnd() && i.key() == contactId) {
330 if (!i.value()->manager().isNull()) {
331 //don't publish our presence to that user
332 Tp::PendingOperation *op = i.value()->manager()->removePresencePublication(QList< Tp::ContactPtr >() << i.value());
333 op->setProperty("__contact", QVariant::fromValue(i.value()));
334 operations.append(op);
335
336 //and block that contact
337 if (i.value()->manager()->canBlockContacts()) {
338 Tp::PendingOperation *blockOp = i.value()->manager()->blockContacts(QList<Tp::ContactPtr>() << i.value());
339 operations.append(blockOp);
340 }
341 }
342 ++i;
343 }
344
345 // Wait until all operations complete
346 if (!operations.isEmpty()) {
347 Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
348
349 Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact);
350 op->setProperty("__contact", QVariant::fromValue(contact));
351
352 connect(op, SIGNAL(finished(Tp::PendingOperation*)),
353 this, SLOT(onRemovePresencePublicationFinished(Tp::PendingOperation*)));
354 }
355 }
356 }
357
onRemovePresencePublicationFinished(Tp::PendingOperation * op)358 void ContactRequestHandler::onRemovePresencePublicationFinished(Tp::PendingOperation *op)
359 {
360 Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
361
362 if (op->isError()) {
363 // ARGH
364 m_notifierItem.data()->showMessage(i18n("Error denying contact request"),
365 i18n("There was an error while denying the request: %1",
366 KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
367 QLatin1String("dialog-error"));
368
369 // Re-enable the action
370 m_menuItems.value(contact->id())->setEnabled(true);
371 } else {
372 // Yeah
373 if (!m_notifierItem.isNull()) {
374 m_notifierItem.data()->showMessage(i18n("Contact request denied"),
375 i18n("%1 will not be able to see when you are online",
376 contact->alias()), QLatin1String("dialog-information"));
377 }
378 // Update the menu
379 m_pendingContacts.remove(contact->id());
380 updateMenus();
381 }
382 }
383
updateMenus()384 void ContactRequestHandler::updateMenus()
385 {
386 if (m_notifierItem.isNull()) {
387 m_notifierItem = new KStatusNotifierItem(QLatin1String("telepathy_kde_contact_requests"), this);
388 m_notifierItem.data()->setCategory(KStatusNotifierItem::Communications);
389 m_notifierItem.data()->setIconByName(QLatin1String("user-identity"));
390 m_notifierItem.data()->setAttentionIconByName(QLatin1String("list-add-user"));
391 m_notifierItem.data()->setStandardActionsEnabled(false);
392 m_notifierItem.data()->setTitle(i18nc("Menu title", "Pending contact requests"));
393 m_notifierItem.data()->setStatus(KStatusNotifierItem::Active);
394
395 QMenu *notifierMenu = new QMenu(0);
396 notifierMenu->setTitle(i18nc("Context menu title", "Received contact requests"));
397
398 connect(m_notifierItem.data(), SIGNAL(activateRequested(bool,QPoint)), SLOT(onNotifierActivated(bool,QPoint)));
399
400 m_notifierItem.data()->setContextMenu(notifierMenu);
401 }
402
403 qCDebug(KTP_KDED_MODULE) << m_pendingContacts.keys();
404
405 //add members in pending contacts not in the menu to the menu.
406 QHash<QString, Tp::ContactPtr>::const_iterator i;
407 for (i = m_pendingContacts.constBegin(); i != m_pendingContacts.constEnd(); ++i) {
408 if (m_menuItems.contains(i.key())) {
409 // Skip
410 continue;
411 }
412
413 qCDebug(KTP_KDED_MODULE);
414 Tp::ContactPtr contact = i.value();
415
416 QMenu *contactMenu = new QMenu(m_notifierItem.data()->contextMenu());
417 contactMenu->setTitle(i18n("Request from %1", contact->alias()));
418 contactMenu->setObjectName(contact->id());
419
420 QAction *menuAction;
421
422 menuAction = new QAction(QIcon(QLatin1String("user-identity")), i18n("Contact Details"), contactMenu);
423 menuAction->setData(i.key());
424 connect(menuAction, SIGNAL(triggered()),
425 this, SLOT(onShowContactDetails()));
426 contactMenu->addAction(menuAction);
427
428 if (!contact->publishStateMessage().isEmpty()) {
429 contactMenu->insertSection(menuAction, contact->publishStateMessage());
430 } else {
431 contactMenu->insertSection(menuAction, contact->alias());
432 }
433
434 contactMenu->addSeparator();
435
436 menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")), i18n("Approve"), contactMenu);
437 menuAction->setData(i.key());
438 connect(menuAction, SIGNAL(triggered()),
439 this, SLOT(onContactRequestApproved()));
440 contactMenu->addAction(menuAction);
441
442 menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-close")), i18n("Deny"), contactMenu);
443 menuAction->setData(i.key());
444 connect(menuAction, SIGNAL(triggered()),
445 this, SLOT(onContactRequestDenied()));
446 contactMenu->addAction(menuAction);
447
448 m_notifierItem.data()->contextMenu()->addMenu(contactMenu);
449 m_menuItems.insert(i.key(), contactMenu);
450 }
451
452 //remove items that are still in the menu, but not in pending contacts
453 QHash<QString, QMenu*>::iterator j = m_menuItems.begin();
454 while (j != m_menuItems.end()) {
455 if (m_pendingContacts.contains(j.key())) {
456 // Skip
457 ++j;
458 continue;
459 }
460
461 // Remove
462 m_notifierItem.data()->contextMenu()->removeAction(j.value()->menuAction());
463 j = m_menuItems.erase(j);
464 }
465
466 if (m_menuItems.size() > 0) {
467 //if menu still contains items, update the tooltip to have the correct number
468 m_notifierItem.data()->setToolTip(QLatin1String("list-add-user"),
469 i18np("You have 1 contact wanting to chat with you",
470 "You have %1 contacts wanting to chat with you",
471 m_menuItems.size()),
472 QString());
473 } else {
474 //if empty delete the status notifier item
475 m_notifierItem.data()->deleteLater();
476 }
477
478 }
479
480 #include "contact-request-handler.moc"
481