File indexing completed on 2023-05-30 11:40:28

0001 /*
0002     Copyright (C) 2011  Martin Klapetek <martin.klapetek@gmail.com>
0003     Copyright (C) 2011  Dario Freddi <dario.freddi@collabora.com>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Lesser General Public
0007     License as published by the Free Software Foundation; either
0008     version 2.1 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Lesser General Public License for more details.
0014 
0015     You should have received a copy of the GNU Lesser General Public
0016     License along with this library; if not, write to the Free Software
0017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 */
0019 
0020 
0021 #include "contact-request-handler.h"
0022 #include "ktp_kded_debug.h"
0023 
0024 #include <KTp/error-dictionary.h>
0025 #include <KTp/core.h>
0026 #include <KTp/Widgets/contact-info-dialog.h>
0027 
0028 #include <TelepathyQt/Account>
0029 #include <TelepathyQt/AccountManager>
0030 #include <TelepathyQt/Connection>
0031 #include <TelepathyQt/Contact>
0032 #include <TelepathyQt/ContactManager>
0033 #include <TelepathyQt/PendingComposite>
0034 #include <TelepathyQt/PendingOperation>
0035 
0036 #include <KStatusNotifierItem>
0037 #include <KLocalizedString>
0038 
0039 #include <QAction>
0040 #include <QIcon>
0041 #include <QFutureWatcher>
0042 #include <QtConcurrentFilter>
0043 #include <QMenu>
0044 
0045 Q_DECLARE_METATYPE(Tp::ContactPtr)
0046 
0047 static bool kde_tp_filter_contacts_by_publication_status(const Tp::ContactPtr &contact)
0048 {
0049     return contact->publishState() == Tp::Contact::PresenceStateAsk && !contact->isBlocked();
0050 }
0051 
0052 ContactRequestHandler::ContactRequestHandler(QObject *parent)
0053     : QObject(parent)
0054 {
0055     connect(KTp::accountManager().data(), SIGNAL(newAccount(Tp::AccountPtr)),
0056             this, SLOT(onNewAccountAdded(Tp::AccountPtr)));
0057 
0058     QList<Tp::AccountPtr> accounts = KTp::accountManager()->allAccounts();
0059 
0060     Q_FOREACH(const Tp::AccountPtr &account, accounts) {
0061         onNewAccountAdded(account);
0062     }
0063 
0064 }
0065 
0066 ContactRequestHandler::~ContactRequestHandler()
0067 {
0068 
0069 }
0070 
0071 void ContactRequestHandler::onNewAccountAdded(const Tp::AccountPtr &account)
0072 {
0073     qCWarning(KTP_KDED_MODULE);
0074     Q_ASSERT(account->isReady(Tp::Account::FeatureCore));
0075 
0076     if (account->connection()) {
0077         handleNewConnection(account->connection());
0078     }
0079 
0080     connect(account.data(),
0081             SIGNAL(connectionChanged(Tp::ConnectionPtr)),
0082             this, SLOT(onConnectionChanged(Tp::ConnectionPtr)));
0083 }
0084 
0085 void ContactRequestHandler::onConnectionChanged(const Tp::ConnectionPtr &connection)
0086 {
0087     if (!connection.isNull()) {
0088         handleNewConnection(connection);
0089     }
0090 }
0091 
0092 void ContactRequestHandler::handleNewConnection(const Tp::ConnectionPtr &connection)
0093 {
0094     qCDebug(KTP_KDED_MODULE);
0095     connect(connection->contactManager().data(), SIGNAL(presencePublicationRequested(Tp::Contacts)),
0096             this, SLOT(onPresencePublicationRequested(Tp::Contacts)));
0097 
0098     connect(connection->contactManager().data(),
0099             SIGNAL(stateChanged(Tp::ContactListState)),
0100             this, SLOT(onContactManagerStateChanged(Tp::ContactListState)));
0101 
0102     onContactManagerStateChanged(connection->contactManager(),
0103                                  connection->contactManager()->state());
0104 }
0105 
0106 void ContactRequestHandler::onContactManagerStateChanged(Tp::ContactListState state)
0107 {
0108     onContactManagerStateChanged(Tp::ContactManagerPtr(qobject_cast< Tp::ContactManager* >(sender())), state);
0109 }
0110 
0111 void ContactRequestHandler::onContactManagerStateChanged(const Tp::ContactManagerPtr &contactManager,
0112                                                          Tp::ContactListState state)
0113 {
0114     if (state == Tp::ContactListStateSuccess) {
0115         QFutureWatcher< Tp::ContactPtr > *watcher = new QFutureWatcher< Tp::ContactPtr >(this);
0116         connect(watcher, SIGNAL(finished()), this, SLOT(onAccountsPresenceStatusFiltered()));
0117         watcher->setFuture(QtConcurrent::filtered(contactManager->allKnownContacts(),
0118                                                   kde_tp_filter_contacts_by_publication_status));
0119 
0120         qCDebug(KTP_KDED_MODULE) << "Watcher is on";
0121     } else {
0122         qCDebug(KTP_KDED_MODULE) << "Watcher still off, state is" << state << "contactManager is" << contactManager.isNull();
0123     }
0124 }
0125 
0126 void ContactRequestHandler::onAccountsPresenceStatusFiltered()
0127 {
0128     qCDebug(KTP_KDED_MODULE) << "Watcher is here";
0129     QFutureWatcher< Tp::ContactPtr > *watcher = dynamic_cast< QFutureWatcher< Tp::ContactPtr > * >(sender());
0130     qCDebug(KTP_KDED_MODULE) << "Watcher is casted";
0131     Tp::Contacts contacts = watcher->future().results().toSet();
0132     qCDebug(KTP_KDED_MODULE) << "Watcher is used";
0133     if (!contacts.isEmpty()) {
0134         onPresencePublicationRequested(contacts);
0135     }
0136     watcher->deleteLater();
0137 }
0138 
0139 void ContactRequestHandler::onPresencePublicationRequested(const Tp::Contacts &contacts)
0140 {
0141     qCDebug(KTP_KDED_MODULE) << "New contact requested";
0142 
0143     Q_FOREACH (const Tp::ContactPtr &contact, contacts) {
0144         Tp::ContactManagerPtr manager = contact->manager();
0145 
0146         if (contact->subscriptionState() == Tp::Contact::PresenceStateYes) {
0147             Tp::PendingOperation *op = manager->authorizePresencePublication(QList< Tp::ContactPtr >() << contact);
0148             op->setProperty("__contact", QVariant::fromValue(contact));
0149 
0150             connect(op, SIGNAL(finished(Tp::PendingOperation*)),
0151                     this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*)));
0152         } else {
0153             // Handle multiaccount requests properly
0154             if (m_pendingContacts.contains(contact->id())) {
0155                 // It's likely we have a simultaneous request
0156                 bool newReq = true;
0157                 QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contact->id());
0158                 while (i != m_pendingContacts.constEnd() && i.key() == contact->id()) {
0159                     if (i.value().data() == contact.data()) {
0160                         newReq = false;
0161                         break;
0162                     }
0163                     ++i;
0164                 }
0165 
0166                 if (newReq) {
0167                     // Insert multi
0168                     m_pendingContacts.insertMulti(contact->id(), contact);
0169                 }
0170             } else {
0171                 // Simple insertion
0172                 m_pendingContacts.insert(contact->id(), contact);
0173             }
0174 
0175             connect(contact.data(), SIGNAL(invalidated()), this, SLOT(onContactInvalidated()));
0176 
0177             updateMenus();
0178 
0179             if (!m_notifierItem.isNull()) {
0180                 m_notifierItem.data()->showMessage(i18n("New contact request"),    //krazy:exclude=qmethods
0181                                                    i18n("The contact %1 wants to be able to chat with you.",
0182                                                         contact->id()),
0183                                                    QLatin1String("list-add-user"));
0184             }
0185         }
0186     }
0187 }
0188 
0189 void ContactRequestHandler::onFinalizeSubscriptionFinished(Tp::PendingOperation *op)
0190 {
0191     Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
0192 
0193     Q_ASSERT(!contact.isNull());
0194 
0195     if (op->isError()) {
0196         // ARGH
0197         if (!m_notifierItem.isNull()) {
0198             m_notifierItem.data()->showMessage(i18n("Error adding contact"),
0199                                                i18n("%1 has been added successfully to your contact list, "
0200                                                     "but might be unable to see when you are online. Error details: %2",
0201                                                     contact->alias(), KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
0202                                                QLatin1String("dialog-error"));
0203         }
0204     } else {
0205         // Update the menu
0206         m_pendingContacts.remove(contact->id());
0207         updateMenus();
0208     }
0209 }
0210 
0211 void ContactRequestHandler::onContactInvalidated()
0212 {
0213     Tp::ContactPtr contact = Tp::ContactPtr(qobject_cast<Tp::Contact*>(sender()));
0214 
0215     m_pendingContacts.remove(contact->id());
0216     updateMenus();
0217 }
0218 
0219 void ContactRequestHandler::onNotifierActivated(bool active, const QPoint &pos)
0220 {
0221     if (active) {
0222         if (m_notifierItem) {
0223             m_notifierItem.data()->contextMenu()->popup(pos);
0224         }
0225     }
0226 }
0227 
0228 void ContactRequestHandler::onContactRequestApproved()
0229 {
0230     QString contactId = qobject_cast<QAction*>(sender())->data().toString();
0231 
0232     // Disable the action in the meanwhile
0233     m_menuItems.value(contactId)->setEnabled(false);
0234 
0235     if (!contactId.isEmpty()) {
0236         QList<Tp::PendingOperation*> operations;
0237         QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId);
0238         while (i != m_pendingContacts.constEnd() && i.key() == contactId) {
0239             if (!i.value()->manager().isNull()) {
0240                 Tp::PendingOperation *op = i.value()->manager()->authorizePresencePublication(QList< Tp::ContactPtr >() << i.value());
0241                 op->setProperty("__contact", QVariant::fromValue(i.value()));
0242                 operations.append(op);
0243             }
0244             ++i;
0245         }
0246 
0247         // Take the first value, if any
0248         if (!operations.isEmpty()) {
0249             Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
0250 
0251             Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact);
0252             op->setProperty("__contact", QVariant::fromValue(contact));
0253 
0254             connect(op, SIGNAL(finished(Tp::PendingOperation*)),
0255                     this, SLOT(onAuthorizePresencePublicationFinished(Tp::PendingOperation*)));
0256         }
0257     }
0258 
0259 }
0260 
0261 void ContactRequestHandler::onShowContactDetails()
0262 {
0263     QString contactId = qobject_cast<QAction*>(sender())->data().toString();
0264 
0265     if (!contactId.isEmpty()) {
0266         const Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
0267         const Tp::ContactManagerPtr manager = contact->manager();
0268         Q_FOREACH (const Tp::AccountPtr &account, KTp::accountManager()->allAccounts()) {
0269             if (account->connection() == manager->connection()) {
0270                 KTp::ContactInfoDialog *dialog = new KTp::ContactInfoDialog(account, contact);
0271                 connect(dialog, SIGNAL(closeClicked()), dialog, SLOT(deleteLater()));
0272                 dialog->show();
0273                 break;
0274             }
0275         }
0276     }
0277 }
0278 
0279 void ContactRequestHandler::onAuthorizePresencePublicationFinished(Tp::PendingOperation *op)
0280 {
0281     Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
0282 
0283     if (op->isError()) {
0284         if (!m_notifierItem.isNull()) {
0285             m_notifierItem.data()->showMessage(i18n("Error granting contact authorization"),
0286                                                i18n("There was an error while accepting the request: %1",
0287                                                     KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
0288                                                QLatin1String("dialog-error"));
0289         }
0290 
0291         // Re-enable the action
0292         m_menuItems.value(contact->id())->setEnabled(true);
0293     } else {
0294         // op succeeded
0295         if (!m_notifierItem.isNull()) {
0296             m_notifierItem.data()->showMessage(i18n("Contact request accepted"),
0297                                                i18n("%1 will now be able to see when you are online",
0298                                                     contact->alias()), QLatin1String("dialog-ok-apply"));
0299         }
0300 
0301         // If needed, reiterate the request on the other end
0302         if (contact->manager()->canRequestPresenceSubscription() &&
0303                 contact->subscriptionState() == Tp::Contact::PresenceStateNo) {
0304 
0305             Tp::PendingOperation *op = contact->manager()->requestPresenceSubscription(QList< Tp::ContactPtr >() << contact);
0306             op->setProperty("__contact", QVariant::fromValue(contact));
0307 
0308             connect(op,
0309                     SIGNAL(finished(Tp::PendingOperation*)),
0310                     this, SLOT(onFinalizeSubscriptionFinished(Tp::PendingOperation*)));
0311         } else {
0312             // Update the menu
0313             m_pendingContacts.remove(contact->id());
0314             updateMenus();
0315         }
0316     }
0317 }
0318 
0319 void ContactRequestHandler::onContactRequestDenied()
0320 {
0321     QString contactId = qobject_cast<QAction*>(sender())->data().toString();
0322 
0323     // Disable the action in the meanwhile
0324     m_menuItems.value(contactId)->setEnabled(false);
0325 
0326     if (!contactId.isEmpty()) {
0327         QList<Tp::PendingOperation*> operations;
0328         QHash<QString, Tp::ContactPtr>::const_iterator i = m_pendingContacts.constFind(contactId);
0329         while (i != m_pendingContacts.constEnd() && i.key() == contactId) {
0330             if (!i.value()->manager().isNull()) {
0331                 //don't publish our presence to that user
0332                 Tp::PendingOperation *op = i.value()->manager()->removePresencePublication(QList< Tp::ContactPtr >() << i.value());
0333                 op->setProperty("__contact", QVariant::fromValue(i.value()));
0334                 operations.append(op);
0335 
0336                 //and block that contact
0337                 if (i.value()->manager()->canBlockContacts()) {
0338                     Tp::PendingOperation *blockOp = i.value()->manager()->blockContacts(QList<Tp::ContactPtr>() << i.value());
0339                     operations.append(blockOp);
0340                 }
0341             }
0342             ++i;
0343         }
0344 
0345         // Wait until all operations complete
0346         if (!operations.isEmpty()) {
0347             Tp::ContactPtr contact = m_pendingContacts.find(contactId).value();
0348 
0349             Tp::PendingComposite *op = new Tp::PendingComposite(operations, true, contact);
0350             op->setProperty("__contact", QVariant::fromValue(contact));
0351 
0352             connect(op, SIGNAL(finished(Tp::PendingOperation*)),
0353                     this, SLOT(onRemovePresencePublicationFinished(Tp::PendingOperation*)));
0354         }
0355     }
0356 }
0357 
0358 void ContactRequestHandler::onRemovePresencePublicationFinished(Tp::PendingOperation *op)
0359 {
0360     Tp::ContactPtr contact = op->property("__contact").value< Tp::ContactPtr >();
0361 
0362     if (op->isError()) {
0363         // ARGH
0364         m_notifierItem.data()->showMessage(i18n("Error denying contact request"),
0365                                            i18n("There was an error while denying the request: %1",
0366                                                 KTp::ErrorDictionary::displayVerboseErrorMessage(op->errorName())),
0367                                            QLatin1String("dialog-error"));
0368 
0369         // Re-enable the action
0370         m_menuItems.value(contact->id())->setEnabled(true);
0371     } else {
0372         // Yeah
0373         if (!m_notifierItem.isNull()) {
0374             m_notifierItem.data()->showMessage(i18n("Contact request denied"),
0375                                                i18n("%1 will not be able to see when you are online",
0376                                                     contact->alias()), QLatin1String("dialog-information"));
0377         }
0378         // Update the menu
0379         m_pendingContacts.remove(contact->id());
0380         updateMenus();
0381     }
0382 }
0383 
0384 void ContactRequestHandler::updateMenus()
0385 {
0386     if (m_notifierItem.isNull()) {
0387         m_notifierItem = new KStatusNotifierItem(QLatin1String("telepathy_kde_contact_requests"), this);
0388         m_notifierItem.data()->setCategory(KStatusNotifierItem::Communications);
0389         m_notifierItem.data()->setIconByName(QLatin1String("user-identity"));
0390         m_notifierItem.data()->setAttentionIconByName(QLatin1String("list-add-user"));
0391         m_notifierItem.data()->setStandardActionsEnabled(false);
0392         m_notifierItem.data()->setTitle(i18nc("Menu title", "Pending contact requests"));
0393         m_notifierItem.data()->setStatus(KStatusNotifierItem::Active);
0394 
0395         QMenu *notifierMenu = new QMenu(0);
0396         notifierMenu->setTitle(i18nc("Context menu title", "Received contact requests"));
0397 
0398         connect(m_notifierItem.data(), SIGNAL(activateRequested(bool,QPoint)), SLOT(onNotifierActivated(bool,QPoint)));
0399 
0400         m_notifierItem.data()->setContextMenu(notifierMenu);
0401     }
0402 
0403     qCDebug(KTP_KDED_MODULE) << m_pendingContacts.keys();
0404 
0405     //add members in pending contacts not in the menu to the menu.
0406     QHash<QString, Tp::ContactPtr>::const_iterator i;
0407     for (i = m_pendingContacts.constBegin(); i != m_pendingContacts.constEnd(); ++i) {
0408         if (m_menuItems.contains(i.key())) {
0409             // Skip
0410             continue;
0411         }
0412 
0413         qCDebug(KTP_KDED_MODULE);
0414         Tp::ContactPtr contact = i.value();
0415 
0416         QMenu *contactMenu = new QMenu(m_notifierItem.data()->contextMenu());
0417         contactMenu->setTitle(i18n("Request from %1", contact->alias()));
0418         contactMenu->setObjectName(contact->id());
0419 
0420         QAction *menuAction;
0421 
0422         menuAction = new QAction(QIcon(QLatin1String("user-identity")), i18n("Contact Details"), contactMenu);
0423         menuAction->setData(i.key());
0424         connect(menuAction, SIGNAL(triggered()),
0425                 this, SLOT(onShowContactDetails()));
0426         contactMenu->addAction(menuAction);
0427 
0428         if (!contact->publishStateMessage().isEmpty()) {
0429             contactMenu->insertSection(menuAction, contact->publishStateMessage());
0430         } else {
0431             contactMenu->insertSection(menuAction, contact->alias());
0432         }
0433 
0434         contactMenu->addSeparator();
0435 
0436         menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")), i18n("Approve"), contactMenu);
0437         menuAction->setData(i.key());
0438         connect(menuAction, SIGNAL(triggered()),
0439                 this, SLOT(onContactRequestApproved()));
0440         contactMenu->addAction(menuAction);
0441 
0442         menuAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-close")), i18n("Deny"), contactMenu);
0443         menuAction->setData(i.key());
0444         connect(menuAction, SIGNAL(triggered()),
0445                 this, SLOT(onContactRequestDenied()));
0446         contactMenu->addAction(menuAction);
0447 
0448         m_notifierItem.data()->contextMenu()->addMenu(contactMenu);
0449         m_menuItems.insert(i.key(), contactMenu);
0450     }
0451 
0452     //remove items that are still in the menu, but not in pending contacts
0453     QHash<QString, QMenu*>::iterator j = m_menuItems.begin();
0454     while (j != m_menuItems.end()) {
0455         if (m_pendingContacts.contains(j.key())) {
0456             // Skip
0457             ++j;
0458             continue;
0459         }
0460 
0461         // Remove
0462         m_notifierItem.data()->contextMenu()->removeAction(j.value()->menuAction());
0463         j = m_menuItems.erase(j);
0464     }
0465 
0466     if (m_menuItems.size() > 0) {
0467         //if menu still contains items, update the tooltip to have the correct number
0468         m_notifierItem.data()->setToolTip(QLatin1String("list-add-user"),
0469                                           i18np("You have 1 contact wanting to chat with you",
0470                                                 "You have %1 contacts wanting to chat with you",
0471                                                 m_menuItems.size()),
0472                                           QString());
0473     } else {
0474         //if empty delete the status notifier item
0475         m_notifierItem.data()->deleteLater();
0476     }
0477 
0478 }
0479 
0480 #include "contact-request-handler.moc"