Skip to content

Commit

Permalink
[buteo-sync-plugins-social] Fix Google contact avatar updates and rem…
Browse files Browse the repository at this point in the history
…oval. Fixes JB#51229

- Update the contact avatar when it is modified or removed on the
server, rather than only when it is added.
- Remove avatar files for deleted accounts.
  • Loading branch information
Bea Lam committed Sep 25, 2020
1 parent 6dd0ed5 commit 7bfecf3
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 56 deletions.
13 changes: 4 additions & 9 deletions src/google/google-contacts/googlecontactstream.cpp
Expand Up @@ -344,13 +344,7 @@ void GoogleContactStream::handleAtomEntry()
// Batch link etc.

// If it's an avatar, we grab it as a QContactAvatar detail
QContactAvatar avatar;
Q_FOREACH (const QContactAvatar &av, entryContact.details<QContactAvatar>()) {
if (av.value(QContactAvatar::FieldMetaData).toString() == QStringLiteral("picture")) {
avatar = av;
break;
}
}
QContactAvatar avatar = entryContact.detail<QContactAvatar>();
bool isAvatar = false;
QString unsupportedElement = handleEntryLink(&avatar, &isAvatar);
if (isAvatar) {
Expand Down Expand Up @@ -418,11 +412,12 @@ QString GoogleContactStream::handleEntryLink(QContactAvatar *avatar, bool *isAva
{
Q_ASSERT(mXmlReader->isStartElement() && mXmlReader->name() == "link");

if (mXmlReader->attributes().hasAttribute("gd:etag")
const QString etag = mXmlReader->attributes().value("gd:etag").toString();
if (!etag.isEmpty()
&& (mXmlReader->attributes().value("rel") == "http://schemas.google.com/contacts/2008/rel#photo")) {
// this is an avatar photo for the contact entry
avatar->setImageUrl(mXmlReader->attributes().value("href").toString());
avatar->setValue(QContactAvatar::FieldMetaData, QVariant::fromValue<QString>(QStringLiteral("picture")));
avatar->setValue(QContactAvatar::FieldMetaData, etag);
*isAvatar = true;
}

Expand Down
103 changes: 56 additions & 47 deletions src/google/google-contacts/googletwowaycontactsyncadaptor.cpp
Expand Up @@ -566,7 +566,7 @@ void GoogleTwoWayContactSyncAdaptor::continueSync(int accountId, const QString &
return;
}

// for each of the addmods, we need to fixup the contact avatars.
// download avatars for new and modified contacts
transformContactAvatars(m_remoteAdds[accountId], accountId, accessToken);
transformContactAvatars(m_remoteMods[accountId], accountId, accessToken);

Expand Down Expand Up @@ -781,7 +781,7 @@ void GoogleTwoWayContactSyncAdaptor::queueOutstandingAvatars(int accountId, cons
}
}

SOCIALD_LOG_DEBUG("queued" << queuedCount << "avatars for download for account" << accountId);
SOCIALD_LOG_DEBUG("queued" << queuedCount << "outstanding avatars for download for account" << accountId);
}

bool GoogleTwoWayContactSyncAdaptor::queueAvatarForDownload(int accountId, const QString &accessToken, const QString &contactGuid, const QString &imageUrl)
Expand Down Expand Up @@ -819,45 +819,49 @@ void GoogleTwoWayContactSyncAdaptor::transformContactAvatars(QList<QContact> &re
// then later avatars will not be transformed. TODO: fix this.
// We also only bother to do this for contacts with a GUID, as we don't
// store locally any contact without one.
QString contactGuid = curr.detail<QContactGuid>().guid();
if (curr.details<QContactAvatar>().size() && !contactGuid.isEmpty()) {
// we have a remote avatar which we need to transform.
QContactAvatar avatar = curr.detail<QContactAvatar>();
Q_FOREACH (const QContactAvatar &av, curr.details<QContactAvatar>()) {
if (av.value(QContactAvatar::FieldMetaData).toString() == QStringLiteral("picture")) {
avatar = av;
break;
}
const QString contactGuid = curr.detail<QContactGuid>().guid();
if (contactGuid.isEmpty()) {
continue;
}

QContactAvatar avatar = curr.detail<QContactAvatar>();
const QString remoteImageUrl = avatar.imageUrl().toString();

if (remoteImageUrl.isEmpty()) {
// If the contact previously had an avatar, remove it.
const QString prevRemoteImageUrl = m_avatarImageUrls[accountId].value(contactGuid);

if (!prevRemoteImageUrl.isEmpty()) {
const QString savedLocalFile = GoogleContactImageDownloader::staticOutputFile(
contactGuid, prevRemoteImageUrl);
QFile::remove(savedLocalFile);
}
QString remoteImageUrl = avatar.imageUrl().toString();
if (!remoteImageUrl.isEmpty() && !avatar.imageUrl().isLocalFile()) {

} else {
// We have a remote avatar which we need to download.
const QString prevAvatarEtag = m_avatarEtags[accountId].value(contactGuid);
const QString newAvatarEtag = avatar.value(QContactAvatar::FieldMetaData).toString();
const bool isNewAvatar = prevAvatarEtag.isEmpty();
const bool isModifiedAvatar = !isNewAvatar && prevAvatarEtag != newAvatarEtag;

if (!isNewAvatar && !isModifiedAvatar) {
// Shouldn't happen as we won't get an avatar in the atom if it didn't change.
continue;
}

if (!avatar.imageUrl().isLocalFile()) {
// transform to a local file name.
QString localFileName = GoogleContactImageDownloader::staticOutputFile(
const QString localFileName = GoogleContactImageDownloader::staticOutputFile(
contactGuid, remoteImageUrl);
QFile::remove(localFileName);

// and trigger downloading the image, if it doesn't already exist.
// this means that we shouldn't download images needlessly after
// first sync, but it also means that if it updates/changes on the
// server side, we also won't retrieve any updated image.
if (QFile::exists(localFileName)) {
QImageReader reader(localFileName);
if (reader.canRead()) {
// avatar image already exists, update the detail in the contact.
avatar.setImageUrl(localFileName);
curr.saveDetail(&avatar);
} else {
// not a valid image file. Could be artifact from an error.
QFile::remove(localFileName);
}
}
// temporarily remove the avatar from the contact
m_contactAvatars[accountId].insert(contactGuid, remoteImageUrl);
curr.removeDetail(&avatar);
m_avatarEtags[accountId][contactGuid] = newAvatarEtag;

if (!QFile::exists(localFileName)) {
// temporarily remove the avatar from the contact
m_contactAvatars[accountId].insert(contactGuid, remoteImageUrl);
curr.removeDetail(&avatar);
// then trigger the download
queueAvatarForDownload(accountId, accessToken, contactGuid, remoteImageUrl);
}
// then trigger the download
queueAvatarForDownload(accountId, accessToken, contactGuid, remoteImageUrl);
}
}
}
Expand Down Expand Up @@ -901,7 +905,7 @@ void GoogleTwoWayContactSyncAdaptor::purgeAccount(int pid)
<< QContactDetail::TypeAvatar);
const QList<QContact> savedContacts = m_contactManager->contacts(collectionFilter, QList<QContactSortOrder>(), fetchHint);
for (const QContact &contact : savedContacts) {
const QContactAvatar avatar = pictureAvatar(contact);
const QContactAvatar avatar = contact.detail<QContactAvatar>();
const QString imageUrl = avatar.imageUrl().toString();
if (!imageUrl.isEmpty()) {
if (!QFile::remove(imageUrl)) {
Expand Down Expand Up @@ -951,19 +955,14 @@ void GoogleTwoWayContactSyncAdaptor::finalize(int accountId)
QMap<QString, QContact> contactsToSave;
for (auto it = m_downloadedContactAvatars[accountId].constBegin();
it != m_downloadedContactAvatars[accountId].constEnd(); ++it) {
QContact c = findContact(savedContacts, it.key());
const QString &guid = it.key();
QContact c = findContact(savedContacts, guid);
if (c.isEmpty()) {
SOCIALD_LOG_ERROR("Not saving avatar, cannot find contact with guid" << it.key());
SOCIALD_LOG_ERROR("Not saving avatar, cannot find contact with guid" << guid);
} else {
// we have downloaded the avatar for this contact, and need to update it.
QContactAvatar a;
Q_FOREACH (const QContactAvatar &av, c.details<QContactAvatar>()) {
if (av.value(QContactAvatar::FieldMetaData).toString() == QStringLiteral("picture")) {
a = av;
break;
}
}
a.setValue(QContactAvatar::FieldMetaData, QVariant::fromValue<QString>(QStringLiteral("picture")));
QContactAvatar a = c.detail<QContactAvatar>();
a.setValue(QContactAvatar::FieldMetaData, m_avatarEtags[accountId].value(guid));
a.setImageUrl(it.value());
if (c.saveDetail(&a)) {
contactsToSave[c.detail<QContactGuid>().guid()] = c;
Expand Down Expand Up @@ -1069,6 +1068,8 @@ void GoogleTwoWayContactSyncAdaptor::readSyncStateData(const QContactCollection

m_contactEtags[accountId].clear();
m_contactIds[accountId].clear();
m_avatarEtags[accountId].clear();

for (const QContact &contact : savedContacts) {
const QString contactGuid = contact.detail<QContactGuid>().guid();
if (contactGuid.isEmpty()) {
Expand All @@ -1084,6 +1085,14 @@ void GoogleTwoWayContactSyncAdaptor::readSyncStateData(const QContactCollection

// m_contactIds
m_contactIds[accountId][contactGuid] = contact.id().toString();

// m_avatarEtags
// m_avatarImageUrls
QContactAvatar avatar = contact.detail<QContactAvatar>();
if (!avatar.isEmpty()) {
m_avatarEtags[accountId][contactGuid] = avatar.value(QContactAvatar::FieldMetaData).toString();
m_avatarImageUrls[accountId][contactGuid] = avatar.imageUrl().toString();
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/google/google-contacts/googletwowaycontactsyncadaptor.h
Expand Up @@ -171,6 +171,8 @@ class GoogleTwoWayContactSyncAdaptor : public GoogleDataTypeSyncAdaptor
QMap<int, QMap<QString, QString> > m_contactEtags; // contact guid -> contact etag
QMap<int, QMap<QString, QString> > m_contactIds; // contact guid -> contact id
QMap<int, QMap<QString, QString> > m_contactAvatars; // contact guid -> remote avatar path
QMap<int, QMap<QString, QString> > m_avatarEtags;
QMap<int, QMap<QString, QString> > m_avatarImageUrls;

QMap<int, int> m_apiRequestsRemaining;
QMap<int, QMap<QString, QString> > m_queuedAvatarsForDownload; // contact guid -> remote avatar path
Expand Down

0 comments on commit 7bfecf3

Please sign in to comment.