File indexing completed on 2023-09-24 05:08:02
0001 /* 0002 Copyright (C) 2013 Martin Klapetek <mklapetek@kde.org> 0003 Copyright (C) 2014 David Edmundson <davidedmundson@kde.org> 0004 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Lesser General Public 0008 License as published by the Free Software Foundation; either 0009 version 2.1 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Lesser General Public License for more details. 0015 0016 You should have received a copy of the GNU Lesser General Public 0017 License along with this library; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0019 */ 0020 0021 0022 #include "im-persons-data-source.h" 0023 0024 #include <TelepathyQt/AccountManager> 0025 #include <TelepathyQt/AccountFactory> 0026 #include <TelepathyQt/ContactManager> 0027 #include <TelepathyQt/PendingOperation> 0028 #include <TelepathyQt/PendingReady> 0029 #include <TelepathyQt/Presence> 0030 #include <TelepathyQt/Utils> 0031 0032 #include "KTp/contact-factory.h" 0033 #include "KTp/global-contact-manager.h" 0034 #include "KTp/types.h" 0035 #include "debug.h" 0036 0037 #include <KPeopleBackend/AllContactsMonitor> 0038 0039 #include <KPluginFactory> 0040 #include <KPluginLoader> 0041 #include <KConfig> 0042 #include <KConfigGroup> 0043 0044 #include <QSqlDatabase> 0045 #include <QSqlQuery> 0046 #include <QPixmap> 0047 0048 using namespace KPeople; 0049 0050 class KTpAllContacts : public AllContactsMonitor 0051 { 0052 Q_OBJECT 0053 public: 0054 KTpAllContacts(); 0055 ~KTpAllContacts() override; 0056 QMap<QString, AbstractContact::Ptr> contacts() Q_DECL_OVERRIDE; 0057 0058 private Q_SLOTS: 0059 void loadCache(const QString &accountId = QString()); 0060 void onAccountManagerReady(Tp::PendingOperation *op); 0061 void onContactChanged(); 0062 void onContactInvalidated(); 0063 void onAllKnownContactsChanged(const Tp::Contacts &contactsAdded, const Tp::Contacts &contactsRemoved); 0064 void onAccountCurrentPresenceChanged(const Tp::Presence ¤tPresence); 0065 0066 private: 0067 //presence names indexed by ConnectionPresenceType 0068 QMap<QString, AbstractContact::Ptr> m_contactVCards; 0069 }; 0070 0071 class TelepathyContact : public KPeople::AbstractContact 0072 { 0073 public: 0074 QVariant customProperty(const QString &key) const Q_DECL_OVERRIDE 0075 { 0076 // Check if the contact is valid first 0077 if (m_contact && m_contact->manager() && m_contact->manager()->connection() && m_account) { 0078 if (key == AbstractContact::NameProperty) 0079 return m_contact->alias(); 0080 else if(key == AbstractContact::GroupsProperty) 0081 return m_contact->groups(); 0082 else if(key == S_KPEOPLE_PROPERTY_CONTACT_ID) 0083 return m_contact->id(); 0084 else if(key == S_KPEOPLE_PROPERTY_ACCOUNT_PATH) 0085 return m_account->objectPath(); 0086 else if(key == S_KPEOPLE_PROPERTY_PRESENCE) 0087 return s_presenceStrings.value(m_contact->presence().type()); 0088 else if (key == AbstractContact::PictureProperty) 0089 return m_contact->avatarPixmap(); 0090 else if (key == S_KPEOPLE_PROPERTY_ACCOUNT_DISPLAY_NAME) 0091 return m_account->displayName(); 0092 } 0093 return m_properties[key]; 0094 } 0095 0096 void insertProperty(const QString &key, const QVariant &value) 0097 { 0098 m_properties[key] = value; 0099 } 0100 0101 void setContact(const KTp::ContactPtr &contact) 0102 { 0103 m_contact = contact; 0104 } 0105 0106 void setAccount(const Tp::AccountPtr &account) 0107 { 0108 m_account = account; 0109 } 0110 0111 private: 0112 KTp::ContactPtr m_contact; 0113 Tp::AccountPtr m_account; 0114 QVariantMap m_properties; 0115 }; 0116 0117 KTpAllContacts::KTpAllContacts() 0118 { 0119 Tp::registerTypes(); 0120 0121 loadCache(); 0122 0123 //now start fetching the up-to-date information 0124 connect(KTp::accountManager()->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)), 0125 this, SLOT(onAccountManagerReady(Tp::PendingOperation*))); 0126 0127 emitInitialFetchComplete(true); 0128 } 0129 0130 KTpAllContacts::~KTpAllContacts() 0131 { 0132 } 0133 0134 void KTpAllContacts::loadCache(const QString &accountId) 0135 { 0136 QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), QLatin1String("ktpCache")); 0137 QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/ktp"); 0138 QDir().mkpath(path); 0139 db.setDatabaseName(path+QStringLiteral("/cache.db")); 0140 if (!db.open()) { 0141 qWarning() << "couldn't open database" << db.databaseName(); 0142 } 0143 0144 QSqlQuery query(db); 0145 query.exec(QLatin1String("SELECT DISTINCT groupName FROM groups ORDER BY groupId;")); 0146 0147 QStringList groupsList; 0148 while (query.next()) { 0149 groupsList.append(query.value(0).toString()); 0150 } 0151 0152 QString queryPrep = QStringLiteral("SELECT accountId, contactId, alias, avatarFileName, isBlocked"); 0153 if (!groupsList.isEmpty()) { 0154 queryPrep.append(QStringLiteral(", groupsIds")); 0155 } 0156 queryPrep.append(QStringLiteral(" FROM contacts")); 0157 0158 if (!accountId.isEmpty()) { 0159 queryPrep.append(QStringLiteral(" WHERE accountId = ?;")); 0160 query.prepare(queryPrep); 0161 query.bindValue(0, accountId); 0162 } else { 0163 queryPrep.append(QStringLiteral(";")); 0164 query.prepare(queryPrep); 0165 } 0166 0167 query.exec(); 0168 0169 KConfig config(QLatin1String("ktelepathy-avatarsrc")); 0170 QString cacheDir = QString::fromLatin1(qgetenv("XDG_CACHE_HOME")); 0171 if (cacheDir.isEmpty()) { 0172 cacheDir = QStringLiteral("%1/.cache").arg(QLatin1String(qgetenv("HOME"))); 0173 } 0174 0175 while (query.next()) { 0176 0177 const QString accountId = query.value(0).toString(); 0178 const QString contactId = query.value(1).toString(); 0179 QString avatarFileName = query.value(3).toString(); 0180 0181 const QString uri = QLatin1String("ktp://") + accountId + QLatin1Char('?') + contactId; 0182 QExplicitlySharedDataPointer<TelepathyContact> addressee; 0183 bool found = false; 0184 { 0185 QMap<QString, AbstractContact::Ptr>::const_iterator it = m_contactVCards.constFind(uri); 0186 found = it!=m_contactVCards.constEnd(); 0187 if (found) 0188 addressee = QExplicitlySharedDataPointer<TelepathyContact>(static_cast<TelepathyContact*>(it->data())); 0189 else 0190 addressee = QExplicitlySharedDataPointer<TelepathyContact>(new TelepathyContact); 0191 } 0192 0193 addressee->insertProperty(AbstractContact::NameProperty, query.value(2).toString()); 0194 0195 if (avatarFileName.isEmpty()) { 0196 KConfigGroup avatarTokenGroup = config.group(contactId); 0197 QString avatarToken = avatarTokenGroup.readEntry(QLatin1String("avatarToken")); 0198 //only bother loading the pixmap if the token is not empty 0199 if (!avatarToken.isEmpty()) { 0200 // the accountId is in form of "connection manager name / protocol / username...", 0201 // so let's look for the first / after the very first / (ie. second /) 0202 QString path = QStringLiteral("%1/telepathy/avatars/%2"). 0203 arg(cacheDir).arg(accountId.left(accountId.indexOf(QLatin1Char('/'), accountId.indexOf(QLatin1Char('/')) + 1))); 0204 0205 avatarFileName = QStringLiteral("%1/%2").arg(path).arg(Tp::escapeAsIdentifier(avatarToken)); 0206 } 0207 } 0208 0209 addressee->insertProperty(AbstractContact::PictureProperty, QUrl::fromLocalFile(avatarFileName)); 0210 addressee->insertProperty(S_KPEOPLE_PROPERTY_IS_BLOCKED, query.value(4).toBool()); 0211 0212 if (!groupsList.isEmpty()) { 0213 QVariantList contactGroups; 0214 0215 Q_FOREACH (const QString &groupIdStr, query.value(5).toString().split(QLatin1Char('/'))) { 0216 bool convSuccess; 0217 int groupId = groupIdStr.toInt(&convSuccess); 0218 if ((!convSuccess) || (groupId >= groupsList.count())) 0219 continue; 0220 0221 contactGroups.append(groupsList.at(groupId)); 0222 } 0223 0224 addressee->insertProperty(AbstractContact::GroupsProperty, contactGroups); 0225 } 0226 0227 addressee->insertProperty(S_KPEOPLE_PROPERTY_CONTACT_ID, contactId); 0228 addressee->insertProperty(S_KPEOPLE_PROPERTY_ACCOUNT_PATH, TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountId); 0229 addressee->insertProperty(S_KPEOPLE_PROPERTY_PRESENCE, s_presenceStrings[Tp::ConnectionPresenceTypeOffline]); 0230 0231 addressee->insertProperty(S_KPEOPLE_PROPERTY_CONTACT_URI, uri); 0232 0233 if (found) { 0234 Q_EMIT contactChanged(uri, addressee); 0235 } else { 0236 Q_EMIT contactAdded(uri, addressee); 0237 } 0238 0239 m_contactVCards[uri] = addressee; 0240 } 0241 } 0242 0243 void KTpAllContacts::onAccountManagerReady(Tp::PendingOperation *op) 0244 { 0245 if (op->isError()) { 0246 qCWarning(KTP_KPEOPLE) << "Failed to initialize AccountManager:" << op->errorName(); 0247 qCWarning(KTP_KPEOPLE) << op->errorMessage(); 0248 0249 return; 0250 } 0251 0252 qCDebug(KTP_KPEOPLE) << "Account manager ready"; 0253 0254 Q_FOREACH (const Tp::AccountPtr &account, KTp::accountManager()->allAccounts()) { 0255 connect(account.data(), &Tp::Account::currentPresenceChanged, this, &KTpAllContacts::onAccountCurrentPresenceChanged); 0256 } 0257 0258 connect(KTp::contactManager(), SIGNAL(allKnownContactsChanged(Tp::Contacts,Tp::Contacts)), 0259 this, SLOT(onAllKnownContactsChanged(Tp::Contacts,Tp::Contacts))); 0260 0261 onAllKnownContactsChanged(KTp::contactManager()->allKnownContacts(), Tp::Contacts()); 0262 } 0263 0264 void KTpAllContacts::onAccountCurrentPresenceChanged(const Tp::Presence ¤tPresence) 0265 { 0266 Tp::Account *account = qobject_cast<Tp::Account*>(sender()); 0267 if (!account) { 0268 return; 0269 } 0270 0271 if (currentPresence.type() == Tp::ConnectionPresenceTypeOffline) { 0272 loadCache(account->uniqueIdentifier()); 0273 } 0274 } 0275 0276 void KTpAllContacts::onAllKnownContactsChanged(const Tp::Contacts &contactsAdded, const Tp::Contacts &contactsRemoved) 0277 { 0278 if (!m_contactVCards.isEmpty()) { 0279 Q_FOREACH (const Tp::ContactPtr &c, contactsRemoved) { 0280 const KTp::ContactPtr &contact = KTp::ContactPtr::qObjectCast(c); 0281 const QString uri = contact->uri(); 0282 m_contactVCards.remove(uri); 0283 Q_EMIT contactRemoved(uri); 0284 } 0285 } 0286 0287 Q_FOREACH (const Tp::ContactPtr &contact, contactsAdded) { 0288 KTp::ContactPtr ktpContact = KTp::ContactPtr::qObjectCast(contact); 0289 const QString uri = ktpContact->uri(); 0290 0291 AbstractContact::Ptr vcard = m_contactVCards.value(uri); 0292 bool added = false; 0293 if (!vcard) { 0294 QExplicitlySharedDataPointer<TelepathyContact> tpc(new TelepathyContact); 0295 vcard = AbstractContact::Ptr(tpc); 0296 tpc->insertProperty(S_KPEOPLE_PROPERTY_CONTACT_URI, uri); 0297 m_contactVCards[uri] = vcard; 0298 added = true; 0299 } 0300 static_cast<TelepathyContact*>(vcard.data())->setContact(ktpContact); 0301 static_cast<TelepathyContact*>(vcard.data())->setAccount(KTp::contactManager()->accountForContact(ktpContact)); 0302 0303 if (added) { 0304 Q_EMIT contactAdded(uri, vcard); 0305 } else { 0306 Q_EMIT contactChanged(uri, vcard); 0307 } 0308 0309 connect(ktpContact.data(), SIGNAL(presenceChanged(Tp::Presence)), 0310 this, SLOT(onContactChanged())); 0311 0312 connect(ktpContact.data(), SIGNAL(capabilitiesChanged(Tp::ContactCapabilities)), 0313 this, SLOT(onContactChanged())); 0314 0315 connect(ktpContact.data(), SIGNAL(invalidated()), 0316 this, SLOT(onContactInvalidated())); 0317 0318 connect(ktpContact.data(), SIGNAL(avatarDataChanged(Tp::AvatarData)), 0319 this, SLOT(onContactChanged())); 0320 0321 connect(ktpContact.data(), SIGNAL(addedToGroup(QString)), 0322 this, SLOT(onContactChanged())); 0323 0324 connect(ktpContact.data(), SIGNAL(removedFromGroup(QString)), 0325 this, SLOT(onContactChanged())); 0326 } 0327 } 0328 0329 void KTpAllContacts::onContactChanged() 0330 { 0331 const KTp::ContactPtr contact(qobject_cast<KTp::Contact*>(sender())); 0332 const QString uri = contact->uri(); 0333 Q_EMIT contactChanged(uri, m_contactVCards.value(uri)); 0334 } 0335 0336 void KTpAllContacts::onContactInvalidated() 0337 { 0338 const KTp::ContactPtr contact(qobject_cast<KTp::Contact*>(sender())); 0339 const QString uri = contact->uri(); 0340 0341 //set to offline and emit changed 0342 AbstractContact::Ptr vcard = m_contactVCards.value(uri); 0343 TelepathyContact *tpContact = static_cast<TelepathyContact*>(vcard.data()); 0344 tpContact->insertProperty(S_KPEOPLE_PROPERTY_PRESENCE, QStringLiteral("offline")); 0345 Q_EMIT contactChanged(uri, vcard); 0346 } 0347 0348 QMap<QString, AbstractContact::Ptr> KTpAllContacts::contacts() 0349 { 0350 return m_contactVCards; 0351 } 0352 0353 IMPersonsDataSource::IMPersonsDataSource(QObject *parent, const QVariantList &args) 0354 : BasePersonsDataSource(parent) 0355 { 0356 Q_UNUSED(args); 0357 } 0358 0359 IMPersonsDataSource::~IMPersonsDataSource() 0360 { 0361 } 0362 0363 QString IMPersonsDataSource::sourcePluginId() const 0364 { 0365 return QStringLiteral("ktp"); 0366 } 0367 0368 AllContactsMonitor* IMPersonsDataSource::createAllContactsMonitor() 0369 { 0370 return new KTpAllContacts(); 0371 } 0372 0373 K_PLUGIN_FACTORY_WITH_JSON( IMPersonsDataSourceFactory, "im_persons_data_source_plugin.json", registerPlugin<IMPersonsDataSource>(); ) 0374 K_EXPORT_PLUGIN( IMPersonsDataSourceFactory("im_persons_data_source_plugin") ) 0375 0376 0377 #include "im-persons-data-source.moc"