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 &currentPresence);
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 &currentPresence)
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"