File indexing completed on 2024-04-21 15:02:48

0001 /*
0002     Persons Model
0003     SPDX-FileCopyrightText: 2012 Martin Klapetek <martin.klapetek@gmail.com>
0004     SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0005     SPDX-FileCopyrightText: 2013 David Edmundson <davidedmundson@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-or-later
0008 */
0009 
0010 #include "personsmodel.h"
0011 
0012 #include "backends/abstractcontact.h"
0013 #include "backends/basepersonsdatasource.h"
0014 #include "imageprovideruri_p.h"
0015 #include "metacontact_p.h"
0016 #include "personmanager_p.h"
0017 #include "personpluginmanager.h"
0018 
0019 #include "kpeople_debug.h"
0020 
0021 #include <QPixmap>
0022 #include <QStandardPaths>
0023 #include <QUrl>
0024 
0025 namespace KPeople
0026 {
0027 class PersonsModelPrivate : public QObject
0028 {
0029     Q_OBJECT
0030 public:
0031     PersonsModelPrivate(PersonsModel *qq)
0032         : q(qq)
0033     {
0034     }
0035     PersonsModel *const q;
0036 
0037     // NOTE This is the opposite way round to the return value from contactMapping() for easier lookups
0038     QHash<QString /*contactUri*/, QString /*PersonUri*/> contactToPersons;
0039 
0040     // hash of person objects indexed by ID
0041     QHash<QString /*Person ID*/, QPersistentModelIndex /*Row*/> personIndex;
0042 
0043     // a list so we have an order in the model
0044     QVector<MetaContact> metacontacts;
0045 
0046     QVector<AllContactsMonitorPtr> m_sourceMonitors;
0047 
0048     int initialFetchesDoneCount = 0;
0049 
0050     bool isInitialized = false;
0051     bool hasError = false;
0052 
0053     // methods that manipulate the model
0054     void addPerson(const MetaContact &mc);
0055     void removePerson(const QString &id);
0056     void personChanged(const QString &personUri);
0057     QString personUriForContact(const QString &contactUri) const;
0058     QVariant dataForContact(const QString &personUri, const AbstractContact::Ptr &contact, int role) const;
0059 
0060     //     SLOTS
0061     void onContactsFetched();
0062     // update when a resource signals a contact has changed
0063     void onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact);
0064     void onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact);
0065     void onContactRemoved(const QString &contactUri);
0066 
0067     // update on metadata changes
0068     void onAddContactToPerson(const QString &contactUri, const QString &newPersonUri);
0069     void onRemoveContactsFromPerson(const QString &contactUri);
0070 
0071 public Q_SLOTS:
0072     void onMonitorInitialFetchComplete(bool success = true);
0073 };
0074 }
0075 
0076 using namespace KPeople;
0077 
0078 PersonsModel::PersonsModel(QObject *parent)
0079     : QAbstractItemModel(parent)
0080     , d_ptr(new PersonsModelPrivate(this))
0081 {
0082     Q_D(PersonsModel);
0083     const auto listPlugins = PersonPluginManager::dataSourcePlugins();
0084     for (BasePersonsDataSource *dataSource : listPlugins) {
0085         const AllContactsMonitorPtr monitor = dataSource->allContactsMonitor();
0086         if (monitor->isInitialFetchComplete()) {
0087             QMetaObject::invokeMethod(d, "onMonitorInitialFetchComplete", Qt::QueuedConnection, Q_ARG(bool, monitor->initialFetchSuccess()));
0088         } else {
0089             connect(monitor.data(), &AllContactsMonitor::initialFetchComplete, d, &PersonsModelPrivate::onMonitorInitialFetchComplete);
0090         }
0091         d->m_sourceMonitors << monitor;
0092     }
0093     d->onContactsFetched();
0094 
0095     connect(PersonManager::instance(), &PersonManager::contactAddedToPerson, d, &PersonsModelPrivate::onAddContactToPerson);
0096     connect(PersonManager::instance(), &PersonManager::contactRemovedFromPerson, d, &PersonsModelPrivate::onRemoveContactsFromPerson);
0097 
0098     initResources();
0099 }
0100 
0101 PersonsModel::~PersonsModel()
0102 {
0103 }
0104 
0105 QHash<int, QByteArray> PersonsModel::roleNames() const
0106 {
0107     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0108     roles.insert(PersonUriRole, "personUri");
0109     roles.insert(PersonVCardRole, "personVCard");
0110     roles.insert(ContactsVCardRole, "contactsVCard");
0111     roles.insert(PhoneNumberRole, "phoneNumber");
0112     roles.insert(PhotoImageProviderUri, "photoImageProviderUri");
0113     return roles;
0114 }
0115 
0116 QVariant PersonsModel::data(const QModelIndex &index, int role) const
0117 {
0118     Q_D(const PersonsModel);
0119 
0120     // optimization - if we don't cover this role, ignore it
0121     if (role < Qt::UserRole && role != Qt::DisplayRole && role != Qt::DecorationRole) {
0122         return QVariant();
0123     }
0124 
0125     if (index.row() < 0 || index.row() >= rowCount(index.parent())) {
0126         return QVariant();
0127     }
0128 
0129     if (index.parent().isValid()) {
0130         if (role == ContactsVCardRole) {
0131             return QVariant::fromValue<AbstractContact::List>(AbstractContact::List());
0132         }
0133         const MetaContact &mc = d->metacontacts.at(index.parent().row());
0134 
0135         return d->dataForContact(mc.id(), mc.contacts().at(index.row()), role);
0136     } else {
0137         const MetaContact &mc = d->metacontacts.at(index.row());
0138         return d->dataForContact(mc.id(), mc.personAddressee(), role);
0139     }
0140 }
0141 
0142 QVariant PersonsModelPrivate::dataForContact(const QString &personUri, const AbstractContact::Ptr &person, int role) const
0143 {
0144     switch (role) {
0145     case PersonsModel::FormattedNameRole:
0146         return person->customProperty(AbstractContact::NameProperty);
0147     case PersonsModel::PhotoRole: {
0148         QVariant pic = person->customProperty(AbstractContact::PictureProperty);
0149         if (pic.canConvert<QImage>()) {
0150             QImage avatar = pic.value<QImage>();
0151             if (!avatar.isNull()) {
0152                 return avatar;
0153             }
0154         } else if (pic.canConvert<QPixmap>()) {
0155             QPixmap avatar = pic.value<QPixmap>();
0156             if (!avatar.isNull()) {
0157                 return avatar;
0158             }
0159         } else if (pic.canConvert<QUrl>() && pic.toUrl().isLocalFile()) {
0160             QPixmap avatar = QPixmap(pic.toUrl().toLocalFile());
0161             if (!avatar.isNull()) {
0162                 return avatar;
0163             }
0164         }
0165 
0166         // If none of the above were valid images,
0167         // return the generic one
0168         return QPixmap(QStringLiteral(":/org.kde.kpeople/pixmaps/dummy_avatar.png"));
0169     }
0170     case PersonsModel::PersonUriRole:
0171         return personUri;
0172     case PersonsModel::PersonVCardRole:
0173         return QVariant::fromValue<AbstractContact::Ptr>(person);
0174     case PersonsModel::ContactsVCardRole:
0175         return QVariant::fromValue<AbstractContact::List>(metacontacts[personIndex[personUri].row()].contacts());
0176     case PersonsModel::GroupsRole:
0177         return person->customProperty(QStringLiteral("all-groups"));
0178     case PersonsModel::PhoneNumberRole:
0179         return person->customProperty(AbstractContact::PhoneNumberProperty);
0180     case PersonsModel::PhotoImageProviderUri:
0181         return ::photoImageProviderUri(personUri);
0182     }
0183     return QVariant();
0184 }
0185 
0186 int PersonsModel::columnCount(const QModelIndex &parent) const
0187 {
0188     Q_UNUSED(parent);
0189 
0190     return 1;
0191 }
0192 
0193 int PersonsModel::rowCount(const QModelIndex &parent) const
0194 {
0195     Q_D(const PersonsModel);
0196 
0197     if (!parent.isValid()) {
0198         return d->metacontacts.size();
0199     }
0200 
0201     if (parent.isValid() && !parent.parent().isValid()) {
0202         return d->metacontacts.at(parent.row()).contacts().count();
0203     }
0204 
0205     return 0;
0206 }
0207 
0208 bool PersonsModel::isInitialized() const
0209 {
0210     Q_D(const PersonsModel);
0211 
0212     return d->isInitialized;
0213 }
0214 
0215 QModelIndex PersonsModel::index(int row, int column, const QModelIndex &parent) const
0216 {
0217     if (row < 0 || column < 0 || row >= rowCount(parent)) {
0218         return QModelIndex();
0219     }
0220     // top level items have internalId -1. Anything >=0 is the row of the top level item
0221     if (!parent.isValid()) {
0222         return createIndex(row, column, -1);
0223     }
0224 
0225     return createIndex(row, column, parent.row());
0226 }
0227 
0228 QModelIndex PersonsModel::parent(const QModelIndex &childIndex) const
0229 {
0230     if (childIndex.internalId() == -1 || !childIndex.isValid()) {
0231         return QModelIndex();
0232     }
0233 
0234     return index(childIndex.internalId(), 0, QModelIndex());
0235 }
0236 
0237 void PersonsModelPrivate::onMonitorInitialFetchComplete(bool success)
0238 {
0239     initialFetchesDoneCount++;
0240     if (!success) {
0241         hasError = true;
0242     }
0243     Q_ASSERT(initialFetchesDoneCount <= m_sourceMonitors.count());
0244     if (initialFetchesDoneCount == m_sourceMonitors.count()) {
0245         isInitialized = true;
0246         Q_EMIT q->modelInitialized(!hasError);
0247     }
0248 }
0249 
0250 void PersonsModelPrivate::onContactsFetched()
0251 {
0252     QMap<QString, AbstractContact::Ptr> addresseeMap;
0253 
0254     // fetch all already loaded contacts from plugins
0255     for (const AllContactsMonitorPtr &contactWatcher : std::as_const(m_sourceMonitors)) {
0256         addresseeMap.insert(contactWatcher->contacts());
0257     }
0258 
0259     // add metacontacts
0260     const QMultiHash<QString, QString> contactMapping = PersonManager::instance()->allPersons();
0261 
0262     for (const QString &key : contactMapping.uniqueKeys()) {
0263         QMap<QString, AbstractContact::Ptr> contacts;
0264         for (const QString &contact : contactMapping.values(key)) {
0265             contactToPersons[contact] = key;
0266             AbstractContact::Ptr ptr = addresseeMap.take(contact);
0267             if (ptr) {
0268                 contacts[contact] = ptr;
0269             }
0270         }
0271         if (!contacts.isEmpty()) {
0272             addPerson(MetaContact(key, contacts));
0273         }
0274     }
0275 
0276     // add remaining contacts
0277     QMap<QString, AbstractContact::Ptr>::const_iterator i;
0278     for (i = addresseeMap.constBegin(); i != addresseeMap.constEnd(); ++i) {
0279         addPerson(MetaContact(i.key(), i.value()));
0280     }
0281 
0282     for (const AllContactsMonitorPtr &monitor : std::as_const(m_sourceMonitors)) {
0283         connect(monitor.data(), &AllContactsMonitor::contactAdded, this, &PersonsModelPrivate::onContactAdded);
0284         connect(monitor.data(), &AllContactsMonitor::contactChanged, this, &PersonsModelPrivate::onContactChanged);
0285         connect(monitor.data(), &AllContactsMonitor::contactRemoved, this, &PersonsModelPrivate::onContactRemoved);
0286     }
0287 }
0288 
0289 void PersonsModelPrivate::onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact)
0290 {
0291     const QString &personUri = personUriForContact(contactUri);
0292 
0293     QHash<QString, QPersistentModelIndex>::const_iterator pidx = personIndex.constFind(personUri);
0294     if (pidx != personIndex.constEnd()) {
0295         int personRow = pidx->row();
0296         MetaContact &mc = metacontacts[personRow];
0297 
0298         // if the MC object already contains this object, we want to update the row, not do an insert
0299         if (mc.contactUris().contains(contactUri)) {
0300             qCWarning(KPEOPLE_LOG) << "Source emitted contactAdded for a contact we already know about " << contactUri;
0301             onContactChanged(contactUri, contact);
0302         } else {
0303             int newContactPos = mc.contacts().size();
0304             q->beginInsertRows(q->index(personRow), newContactPos, newContactPos);
0305             mc.insertContact(contactUri, contact);
0306             q->endInsertRows();
0307             personChanged(personUri);
0308         }
0309     } else { // new contact -> new person
0310         QMap<QString, AbstractContact::Ptr> map;
0311         map[contactUri] = contact;
0312         addPerson(MetaContact(personUri, map));
0313     }
0314 }
0315 
0316 void PersonsModelPrivate::onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact)
0317 {
0318     const QString &personUri = personUriForContact(contactUri);
0319     int personRow = personIndex[personUri].row();
0320     int contactRow = metacontacts[personRow].updateContact(contactUri, contact);
0321 
0322     const QModelIndex contactIndex = q->index(contactRow, 0, q->index(personRow));
0323 
0324     Q_EMIT q->dataChanged(contactIndex, contactIndex);
0325 
0326     personChanged(personUri);
0327 }
0328 
0329 void PersonsModelPrivate::onContactRemoved(const QString &contactUri)
0330 {
0331     const QString &personUri = personUriForContact(contactUri);
0332 
0333     int personRow = personIndex[personUri].row();
0334 
0335     MetaContact &mc = metacontacts[personRow];
0336     int contactPosition = mc.contactUris().indexOf(contactUri);
0337     q->beginRemoveRows(q->index(personRow, 0), contactPosition, contactPosition);
0338     mc.removeContact(contactUri);
0339     q->endRemoveRows();
0340 
0341     // if MC object is now invalid remove the person from the list
0342     if (!mc.isValid()) {
0343         removePerson(personUri);
0344     } else {
0345         personChanged(personUri);
0346     }
0347 }
0348 
0349 void PersonsModelPrivate::onAddContactToPerson(const QString &contactUri, const QString &newPersonUri)
0350 {
0351     const QString oldPersonUri = personUriForContact(contactUri);
0352 
0353     contactToPersons.insert(contactUri, newPersonUri);
0354 
0355     int oldPersonRow = personIndex[oldPersonUri].row();
0356 
0357     if (oldPersonRow < 0) {
0358         return;
0359     }
0360 
0361     MetaContact &oldMc = metacontacts[oldPersonRow];
0362 
0363     // get contact already in the model, remove it from the previous contact
0364     int contactPosition = oldMc.contactUris().indexOf(contactUri);
0365     const AbstractContact::Ptr contact = oldMc.contacts().at(contactPosition);
0366 
0367     q->beginRemoveRows(q->index(oldPersonRow), contactPosition, contactPosition);
0368     oldMc.removeContact(contactUri);
0369     q->endRemoveRows();
0370 
0371     if (!oldMc.isValid()) {
0372         removePerson(oldPersonUri);
0373     } else {
0374         personChanged(oldPersonUri);
0375     }
0376 
0377     // if the new person is already in the model, add the contact to it
0378     QHash<QString, QPersistentModelIndex>::const_iterator pidx = personIndex.constFind(newPersonUri);
0379     if (pidx != personIndex.constEnd()) {
0380         int newPersonRow = pidx->row();
0381         MetaContact &newMc = metacontacts[newPersonRow];
0382         int newContactPos = newMc.contacts().size();
0383         q->beginInsertRows(q->index(newPersonRow), newContactPos, newContactPos);
0384         newMc.insertContact(contactUri, contact);
0385         q->endInsertRows();
0386         personChanged(newPersonUri);
0387     } else { // if the person is not in the model, create a new person and insert it
0388         QMap<QString, AbstractContact::Ptr> contacts;
0389         contacts[contactUri] = contact;
0390         addPerson(MetaContact(newPersonUri, contacts));
0391     }
0392 }
0393 
0394 void PersonsModelPrivate::onRemoveContactsFromPerson(const QString &contactUri)
0395 {
0396     const QString personUri = personUriForContact(contactUri);
0397     int personRow = personIndex[personUri].row();
0398     MetaContact &mc = metacontacts[personRow];
0399 
0400     const AbstractContact::Ptr &contact = mc.contact(contactUri);
0401     const int index = mc.contactUris().indexOf(contactUri);
0402 
0403     q->beginRemoveRows(personIndex[personUri], index, index);
0404     mc.removeContact(contactUri);
0405     q->endRemoveRows();
0406     contactToPersons.remove(contactUri);
0407 
0408     // if we don't want the person object anymore
0409     if (!mc.isValid()) {
0410         removePerson(personUri);
0411     } else {
0412         personChanged(personUri);
0413     }
0414 
0415     // now re-insert as a new contact
0416     // we know it's not part of a metacontact anymore so reinsert as a contact
0417     addPerson(MetaContact(contactUri, contact));
0418 }
0419 
0420 void PersonsModelPrivate::addPerson(const KPeople::MetaContact &mc)
0421 {
0422     const QString &id = mc.id();
0423 
0424     int row = metacontacts.size();
0425     q->beginInsertRows(QModelIndex(), row, row);
0426     metacontacts.append(mc);
0427     personIndex[id] = q->index(row);
0428     q->endInsertRows();
0429 }
0430 
0431 void PersonsModelPrivate::removePerson(const QString &id)
0432 {
0433     QPersistentModelIndex index = personIndex.value(id);
0434     if (!index.isValid()) { // item not found
0435         return;
0436     }
0437 
0438     q->beginRemoveRows(QModelIndex(), index.row(), index.row());
0439     personIndex.remove(id);
0440     metacontacts.removeAt(index.row());
0441     q->endRemoveRows();
0442 }
0443 
0444 void PersonsModelPrivate::personChanged(const QString &personUri)
0445 {
0446     int row = personIndex[personUri].row();
0447     if (row >= 0) {
0448         const QModelIndex personIndex = q->index(row);
0449         Q_EMIT q->dataChanged(personIndex, personIndex);
0450     }
0451 }
0452 
0453 QString PersonsModelPrivate::personUriForContact(const QString &contactUri) const
0454 {
0455     QHash<QString, QString>::const_iterator it = contactToPersons.constFind(contactUri);
0456     if (it != contactToPersons.constEnd()) {
0457         return *it;
0458     } else {
0459         return contactUri;
0460     }
0461 }
0462 
0463 QModelIndex PersonsModel::indexForPersonUri(const QString &personUri) const
0464 {
0465     Q_D(const PersonsModel);
0466     return d->personIndex.value(personUri);
0467 }
0468 
0469 QVariant PersonsModel::get(int row, int role)
0470 {
0471     return index(row, 0).data(role);
0472 }
0473 
0474 QVariant PersonsModel::contactCustomProperty(const QModelIndex &index, const QString &key) const
0475 {
0476     Q_D(const PersonsModel);
0477     if (index.parent().isValid()) {
0478         const MetaContact &mc = d->metacontacts.at(index.parent().row());
0479 
0480         return mc.contacts().at(index.row())->customProperty(key);
0481     } else {
0482         const MetaContact &mc = d->metacontacts.at(index.row());
0483         return mc.personAddressee()->customProperty(key);
0484     }
0485 }
0486 
0487 #include "moc_personsmodel.cpp"
0488 #include "personsmodel.moc"