diff --git a/resources/google-new/contacthandler.cpp b/resources/google-new/contacthandler.cpp index c4f696657..ea64fc357 100644 --- a/resources/google-new/contacthandler.cpp +++ b/resources/google-new/contacthandler.cpp @@ -1,497 +1,494 @@ /* Copyright (C) 2011-2013 Daniel Vrátil 2020 Igor Poboiko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "contacthandler.h" #include "googleresource.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "googlecontacts_debug.h" #define OTHERCONTACTS_REMOTEID QStringLiteral("OtherContacts") using namespace KGAPI2; using namespace Akonadi; QString ContactHandler::mimetype() { return KContacts::Addressee::mimeType(); } bool ContactHandler::canPerformTask(const Item &item) { return m_resource->canPerformTask(item, mimetype()); } QString ContactHandler::myContactsRemoteId() const { return QStringLiteral("http://www.google.com/m8/feeds/groups/%1/base/6").arg(QString::fromLatin1(QUrl::toPercentEncoding(m_settings->accountPtr()->accountName()))); } void ContactHandler::setupCollection(Collection &collection, const ContactsGroupPtr &group) { collection.setContentMimeTypes({ KContacts::Addressee::mimeType() }); collection.setName(group->id()); collection.setRemoteId(group->id()); collection.setParentCollection(m_resource->rootCollection()); QString realName = group->title(); if (group->isSystemGroup()) { if (group->title().contains(QLatin1String("Coworkers"))) { realName = i18nc("Name of a group of contacts", "Coworkers"); } else if (group->title().contains(QLatin1String("Friends"))) { realName = i18nc("Name of a group of contacts", "Friends"); } else if (group->title().contains(QLatin1String("Family"))) { realName = i18nc("Name of a group of contacts", "Family"); } else if (group->title().contains(QLatin1String("My Contacts"))) { realName = i18nc("Name of a group of contacts", "My Contacts"); } } // "My Contacts" is the only one not virtual if (group->id() == myContactsRemoteId()) { collection.setRights(Collection::CanCreateItem |Collection::CanChangeItem |Collection::CanDeleteItem); } else { collection.setRights(Collection::CanLinkItem |Collection::CanUnlinkItem |Collection::CanChangeItem); collection.setVirtual(true); if (!group->isSystemGroup()) { collection.setRights(collection.rights() |Collection::CanChangeCollection |Collection::CanDeleteCollection); } } auto attr = collection.attribute(Collection::AddIfMissing); attr->setDisplayName(realName); attr->setIconName(QStringLiteral("view-pim-contacts")); } void ContactHandler::retrieveCollections() { Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Retrieving contacts groups")); qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieving contacts groups..."; m_collections.clear(); Collection otherCollection; otherCollection.setContentMimeTypes({ KContacts::Addressee::mimeType() }); otherCollection.setName(i18n("Other Contacts")); otherCollection.setParentCollection(m_resource->rootCollection()); otherCollection.setRights(Collection::CanCreateItem |Collection::CanChangeItem |Collection::CanDeleteItem); otherCollection.setRemoteId(OTHERCONTACTS_REMOTEID); auto attr = otherCollection.attribute(Collection::AddIfMissing); attr->setDisplayName(i18n("Other Contacts")); attr->setIconName(QStringLiteral("view-pim-contacts")); m_resource->collectionsRetrieved({ otherCollection }); m_collections[ OTHERCONTACTS_REMOTEID ] = otherCollection; auto job = new ContactsGroupFetchJob(m_settings->accountPtr(), this); connect(job, &ContactFetchJob::finished, this, &ContactHandler::slotCollectionsRetrieved); } void ContactHandler::slotCollectionsRetrieved(KGAPI2::Job* job) { if (!m_resource->handleError(job)) { return; } qCDebug(GOOGLE_CONTACTS_LOG) << "Contacts groups retrieved"; const ObjectsList objects = qobject_cast(job)->items(); Collection::List collections; collections.reserve(objects.count()); std::transform(objects.cbegin(), objects.cend(), std::back_inserter(collections), [this](const ObjectPtr &object){ const ContactsGroupPtr group = object.dynamicCast(); qCDebug(GOOGLE_CONTACTS_LOG) << " -" << group->title() << "(" << group->id() << ")"; Collection collection; setupCollection(collection, group); m_collections[ collection.remoteId() ] = collection; return collection; }); m_resource->collectionsRetrievedFromHandler(collections); } void ContactHandler::retrieveItems(const Collection &collection) { // Contacts are stored inside "My Contacts" and "Other Contacts" only if ((collection.remoteId() != OTHERCONTACTS_REMOTEID) && (collection.remoteId() != myContactsRemoteId())) { m_resource->itemsRetrievalDone(); return; } Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Retrieving contacts for group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Retreiving contacts for group" << collection.remoteId() << "..."; auto job = new ContactFetchJob(m_settings->accountPtr(), this); if (!collection.remoteRevision().isEmpty()) { job->setFetchOnlyUpdated(collection.remoteRevision().toLongLong()); job->setFetchDeleted(true); } else { // No need to fetch deleted items for a non-incremental update job->setFetchDeleted(false); } connect(job, &ContactFetchJob::finished, this, &ContactHandler::slotItemsRetrieved); } void ContactHandler::slotItemsRetrieved(KGAPI2::Job *job) { if (!m_resource->handleError(job)) { return; } Collection collection = m_resource->currentCollection(); Item::List changedItems, removedItems; QHash groupsMap; QStringList changedPhotos; auto fetchJob = qobject_cast(job); bool isIncremental = (fetchJob->fetchOnlyUpdated() > 0); const ObjectsList objects = fetchJob->items(); qCDebug(GOOGLE_CONTACTS_LOG) << "Retrieved" << objects.count() << "contacts"; for (const ObjectPtr &object : objects) { const ContactPtr contact = object.dynamicCast(); - // Items inside "My Contacts" should have at least 1 group added, - // otherwise contact belongs to "Other Contacts" - if (((collection.remoteId() == myContactsRemoteId()) && contact->groups().isEmpty()) - || ((collection.remoteId() == OTHERCONTACTS_REMOTEID) && !contact->groups().isEmpty())) { - continue; - } Item item; - item.setMimeType(KContacts::Addressee::mimeType()); + item.setMimeType( mimetype() ); item.setParentCollection(collection); item.setRemoteId(contact->uid()); item.setRemoteRevision(contact->etag()); item.setPayload(*contact.dynamicCast()); - if (contact->deleted()) { + if (contact->deleted() + || (collection.remoteId() == OTHERCONTACTS_REMOTEID && !contact->groups().isEmpty()) + || (collection.remoteId() == myContactsRemoteId() && contact->groups().isEmpty())) { qCDebug(GOOGLE_CONTACTS_LOG) << " - removed" << contact->uid(); removedItems << item; } else { qCDebug(GOOGLE_CONTACTS_LOG) << " - changed" << contact->uid(); changedItems << item; changedPhotos << contact->uid(); } const QStringList groups = contact->groups(); for (const QString &group : groups) { // We don't link contacts to "My Contacts" if (group != myContactsRemoteId()) { groupsMap[group] << item; } } } if (isIncremental) { m_resource->itemsRetrievedIncremental(changedItems, removedItems); } else { m_resource->itemsRetrieved(changedItems); } for (auto iter = groupsMap.constBegin(), iterEnd = groupsMap.constEnd(); iter != iterEnd; ++iter) { new LinkJob(m_collections[iter.key()], iter.value(), this); } + // TODO: unlink if the group was removed! if (!changedPhotos.isEmpty()) { QVariantMap map; map[QStringLiteral("collection")] = QVariant::fromValue(collection); map[QStringLiteral("modified")] = QVariant::fromValue(changedPhotos); m_resource->scheduleCustomTask(this, "retrieveContactsPhotos", map); } const QDateTime local(QDateTime::currentDateTime()); const QDateTime UTC(local.toUTC()); collection.setRemoteRevision(QString::number(UTC.toSecsSinceEpoch())); new CollectionModifyJob(collection, this); m_resource->emitReadyStatus(); } void ContactHandler::retrieveContactsPhotos(const QVariant &argument) { if (!m_resource->canPerformTask()) { return; } const auto map = argument.value(); const auto collection = map[QStringLiteral("collection")].value(); const auto changedPhotos = map[QStringLiteral("modified")].toStringList(); Q_EMIT m_resource->status(AgentBase::Running, i18ncp("@info:status", "Retrieving %1 contacts photos for group '%2'", "Retrieving %1 contact photo for group '%2'", changedPhotos.count(), collection.displayName())); Item::List items; items.reserve(changedPhotos.size()); std::transform(changedPhotos.cbegin(), changedPhotos.cend(), std::back_inserter(items), [](const QString &contact){ Item item; item.setRemoteId(contact); return item; }); auto job = new ItemFetchJob(items, this); job->setCollection(collection); job->fetchScope().fetchFullPayload(true); connect(job, &ItemFetchJob::finished, this, &ContactHandler::slotUpdatePhotosItemsRetrieved); } void ContactHandler::slotUpdatePhotosItemsRetrieved(KJob *job) { // Make sure account is still valid if (!m_resource->canPerformTask()) { return; } const Item::List items = qobject_cast(job)->items(); ContactsList contacts; qCDebug(GOOGLE_CONTACTS_LOG) << "Fetched" << items.count() << "contacts for photo update"; contacts.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), [](const Item &item){ const KContacts::Addressee addressee = item.payload(); const ContactPtr contact(new Contact(addressee)); return contact; }); qCDebug(GOOGLE_CONTACTS_LOG) << "Starting fetching photos..."; auto photoJob = new ContactFetchPhotoJob(contacts, m_settings->accountPtr(), this); photoJob->setProperty("processedItems", 0); connect(photoJob, &ContactFetchPhotoJob::photoFetched, this, [this, items](KGAPI2::Job *job, const ContactPtr &contact){ qCDebug(GOOGLE_CONTACTS_LOG) << " - fetched photo for contact" << contact->uid(); int processedItems = job->property("processedItems").toInt(); processedItems++; job->setProperty("processedItems", processedItems); Q_EMIT m_resource->percent(100.0f*processedItems / items.count()); auto it = std::find_if(items.cbegin(), items.cend(), [&contact](const Item &item){ return item.remoteId() == contact->uid(); }); if (it != items.cend()) { Item newItem(*it); newItem.setPayload(*contact.dynamicCast()); new ItemModifyJob(newItem, this); } }); connect(photoJob, &ContactFetchPhotoJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemAdded(const Item &item, const Collection &collection) { Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Adding contact to group '%1'", collection.displayName())); auto addressee = item.payload< KContacts::Addressee >(); ContactPtr contact(new Contact(addressee)); qCDebug(GOOGLE_CONTACTS_LOG) << "Creating contact"; if (collection.remoteId() == myContactsRemoteId()) { contact->addGroup(myContactsRemoteId()); } auto job = new ContactCreateJob(contact, m_settings->accountPtr(), this); connect(job, &ContactCreateJob::finished, this, [this, item](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } ContactPtr contact = qobject_cast(job)->items().first().dynamicCast(); Item newItem = item; qCDebug(GOOGLE_CONTACTS_LOG) << "Contact" << contact->uid() << "created"; newItem.setRemoteId(contact->uid()); newItem.setRemoteRevision(contact->etag()); m_resource->changeCommitted(newItem); newItem.setPayload(*contact.dynamicCast()); new ItemModifyJob(newItem, this); m_resource->emitReadyStatus(); }); } void ContactHandler::itemChanged(const Item &item, const QSet< QByteArray > &partIdentifiers) { Q_UNUSED(partIdentifiers); Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Changing contact")); qCDebug(GOOGLE_CONTACTS_LOG) << "Changing contact" << item.remoteId(); KContacts::Addressee addressee = item.payload< KContacts::Addressee >(); ContactPtr contact(new Contact(addressee)); auto job = new ContactModifyJob(contact, m_settings->accountPtr(), this); job->setProperty(ITEM_PROPERTY, QVariant::fromValue(item)); connect(job, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemsRemoved(const Item::List &items) { Q_EMIT m_resource->status(AgentBase::Running, i18ncp("@info:status", "Removing %1 contacts", "Removing %1 contact", items.count())); QStringList contactIds; contactIds.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(contactIds), [](const Item &item){ return item.remoteId(); }); qCDebug(GOOGLE_CONTACTS_LOG) << "Removing contacts" << contactIds; auto job = new ContactDeleteJob(contactIds, m_settings->accountPtr(), this); job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); connect(job, &ContactDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemsMoved(const Item::List &items, const Collection &collectionSource, const Collection &collectionDestination) { qCDebug(GOOGLE_CONTACTS_LOG) << "Moving contacts from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); if (!(((collectionSource.remoteId() == myContactsRemoteId()) && (collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID)) || ((collectionSource.remoteId() == OTHERCONTACTS_REMOTEID) && (collectionDestination.remoteId() == myContactsRemoteId())))) { m_resource->cancelTask(i18n("Invalid source or destination collection")); } Q_EMIT m_resource->status(AgentBase::Running, i18ncp("@info:status", "Moving %1 contacts from group '%2' to '%3'", "Moving %1 contact from group '%2' to '%3'", items.count(), collectionSource.remoteId(), collectionDestination.remoteId())); ContactsList contacts; contacts.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), [this, &collectionSource, &collectionDestination](const Item &item){ KContacts::Addressee addressee = item.payload(); ContactPtr contact(new Contact(addressee)); // MyContacts -> OtherContacts if (collectionSource.remoteId() == myContactsRemoteId() && collectionDestination.remoteId() == OTHERCONTACTS_REMOTEID) { contact->clearGroups(); // OtherContacts -> MyContacts } else if (collectionSource.remoteId() == OTHERCONTACTS_REMOTEID && collectionDestination.remoteId() == myContactsRemoteId()) { contact->addGroup(myContactsRemoteId()); } return contact; }); qCDebug(GOOGLE_CONTACTS_LOG) << "Moving contacts from" << collectionSource.remoteId() << "to" << collectionDestination.remoteId(); auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); connect(job, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemsLinked(const Item::List &items, const Collection &collection) { Q_EMIT m_resource->status(AgentBase::Running, i18ncp("@info:status", "Linking %1 contact", "Linking %1 contacts", items.count())); qCDebug(GOOGLE_CONTACTS_LOG) << "Linking" << items.count() << "contacts to group" << collection.remoteId(); ContactsList contacts; contacts.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), [this, &collection](const Item &item){ KContacts::Addressee addressee = item.payload(); ContactPtr contact(new Contact(addressee)); contact->addGroup(collection.remoteId()); return contact; }); auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); connect(job, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::itemsUnlinked(const Item::List &items, const Collection &collection) { Q_EMIT m_resource->status(AgentBase::Running, i18ncp("@info:status", "Unlinking %1 contact", "Unlinking %1 contacts", items.count())); qCDebug(GOOGLE_CONTACTS_LOG) << "Unlinking" << items.count() << "contacts from group" << collection.remoteId(); ContactsList contacts; contacts.reserve(items.count()); std::transform(items.cbegin(), items.cend(), std::back_inserter(contacts), [this, &collection](const Item &item){ KContacts::Addressee addressee = item.payload(); ContactPtr contact(new Contact(addressee)); contact->removeGroup(collection.remoteId()); return contact; }); auto job = new ContactModifyJob(contacts, m_settings->accountPtr(), this); job->setProperty(ITEMS_PROPERTY, QVariant::fromValue(items)); connect(job, &ContactModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::collectionAdded(const Collection &collection, const Collection & /*parent*/) { Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Creating new contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Adding contact group" << collection.displayName(); ContactsGroupPtr group(new ContactsGroup); group->setTitle(collection.name()); group->setIsSystemGroup(false); auto job = new ContactsGroupCreateJob(group, m_settings->accountPtr(), this); connect(job, &ContactsGroupCreateJob::finished, this, [this, collection](KGAPI2::Job* job){ if (!m_resource->handleError(job)) { return; } ContactsGroupPtr group = qobject_cast(job)->items().first().dynamicCast(); qCDebug(GOOGLE_CONTACTS_LOG) << "Contact group created:" << group->id(); Collection newCollection(collection); setupCollection(newCollection, group); m_collections[ newCollection.remoteId() ] = newCollection; m_resource->changeCommitted(newCollection); m_resource->emitReadyStatus(); }); } void ContactHandler::collectionChanged(const Collection &collection) { Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Changing contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Changing contact group" << collection.remoteId(); ContactsGroupPtr group(new ContactsGroup()); group->setId(collection.remoteId()); group->setTitle(collection.displayName()); auto job = new ContactsGroupModifyJob(group, m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &ContactsGroupModifyJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); } void ContactHandler::collectionRemoved(const Collection &collection) { Q_EMIT m_resource->status(AgentBase::Running, i18nc("@info:status", "Removing contact group '%1'", collection.displayName())); qCDebug(GOOGLE_CONTACTS_LOG) << "Removing contact group" << collection.remoteId(); auto job = new ContactsGroupDeleteJob(collection.remoteId(), m_settings->accountPtr(), this); job->setProperty(COLLECTION_PROPERTY, QVariant::fromValue(collection)); connect(job, &ContactsGroupDeleteJob::finished, m_resource, &GoogleResource::slotGenericJobFinished); }