retrieve avatar via manager

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Contact.java               |   7 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 166 
src/main/java/eu/siacs/conversations/xmpp/Managers.java                  |   2 
src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java     |  66 
src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java      |  50 
src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java       |   4 
src/main/java/im/conversations/android/xmpp/model/vcard/VCard.java       |   4 
7 files changed, 202 insertions(+), 97 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Contact.java 🔗

@@ -438,15 +438,10 @@ public class Contact implements ListItem, Blockable {
     }
 
     public boolean setAvatar(final Avatar avatar) {
-        return setAvatar(avatar, false);
-    }
-
-    public boolean setAvatar(final Avatar avatar, final boolean previouslyOmittedPepFetch) {
         if (this.avatar != null && this.avatar.equals(avatar)) {
             return false;
         }
-        if (!previouslyOmittedPepFetch
-                && this.avatar != null
+        if (this.avatar != null
                 && this.avatar.origin == Avatar.Origin.PEP
                 && avatar.origin == Avatar.Origin.VCARD) {
             return false;

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -57,6 +57,7 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.common.io.BaseEncoding;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -142,6 +143,7 @@ import eu.siacs.conversations.xmpp.manager.NickManager;
 import eu.siacs.conversations.xmpp.manager.PresenceManager;
 import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
+import eu.siacs.conversations.xmpp.manager.VCardManager;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.Entity;
 import im.conversations.android.xmpp.IqErrorException;
@@ -4462,23 +4464,18 @@ public class XmppConnectionService extends Service {
         }
     }
 
-    public void fetchAvatar(Account account, Avatar avatar) {
-        fetchAvatar(account, avatar, null);
-    }
-
-    public void fetchAvatar(
-            Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+    public void fetchAvatar(Account account, final Avatar avatar) {
         final String KEY = generateFetchKey(account, avatar);
         synchronized (this.mInProgressAvatarFetches) {
             if (mInProgressAvatarFetches.add(KEY)) {
                 switch (avatar.origin) {
                     case PEP:
                         this.mInProgressAvatarFetches.add(KEY);
-                        fetchAvatarPep(account, avatar, callback);
+                        fetchAvatarPep(account, avatar, null);
                         break;
                     case VCARD:
                         this.mInProgressAvatarFetches.add(KEY);
-                        fetchAvatarVcard(account, avatar, callback);
+                        fetchAvatarVcard(account, avatar);
                         break;
                 }
             } else if (avatar.origin == Avatar.Origin.PEP) {
@@ -4560,88 +4557,81 @@ public class XmppConnectionService extends Service {
                 });
     }
 
-    private void fetchAvatarVcard(
-            final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
-        final Iq packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
-        this.sendIqPacket(
-                account,
-                packet,
-                response -> {
-                    final boolean previouslyOmittedPepFetch;
-                    synchronized (mInProgressAvatarFetches) {
-                        final String KEY = generateFetchKey(account, avatar);
-                        mInProgressAvatarFetches.remove(KEY);
-                        previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY);
-                    }
-                    if (response.getType() == Iq.Type.RESULT) {
-                        Element vCard = response.findChild("vCard", "vcard-temp");
-                        Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
-                        String image = photo != null ? photo.findChildContent("BINVAL") : null;
-                        if (image != null) {
-                            avatar.image = image;
-                            if (getFileBackend().save(avatar)) {
-                                Log.d(
-                                        Config.LOGTAG,
-                                        account.getJid().asBareJid()
-                                                + ": successfully fetched vCard avatar for "
-                                                + avatar.owner
-                                                + " omittedPep="
-                                                + previouslyOmittedPepFetch);
-                                if (avatar.owner.isBareJid()) {
-                                    if (account.getJid().asBareJid().equals(avatar.owner)
-                                            && account.getAvatar() == null) {
-                                        Log.d(
-                                                Config.LOGTAG,
-                                                account.getJid().asBareJid()
-                                                        + ": had no avatar. replacing with vcard");
-                                        account.setAvatar(avatar.getFilename());
-                                        databaseBackend.updateAccount(account);
-                                        getAvatarService().clear(account);
-                                        updateAccountUi();
-                                    } else {
-                                        final Contact contact =
-                                                account.getRoster().getContact(avatar.owner);
-                                        contact.setAvatar(avatar, previouslyOmittedPepFetch);
-                                        account.getXmppConnection()
-                                                .getManager(RosterManager.class)
-                                                .writeToDatabaseAsync();
-                                        getAvatarService().clear(contact);
-                                        updateRosterUi();
-                                    }
-                                    updateConversationUi();
-                                } else {
-                                    Conversation conversation =
-                                            find(account, avatar.owner.asBareJid());
-                                    if (conversation != null
-                                            && conversation.getMode() == Conversation.MODE_MULTI) {
-                                        MucOptions.User user =
-                                                conversation
-                                                        .getMucOptions()
-                                                        .findUserByFullJid(avatar.owner);
-                                        if (user != null) {
-                                            if (user.setAvatar(avatar)) {
-                                                getAvatarService().clear(user);
-                                                updateConversationUi();
-                                                updateMucRosterUi();
-                                            }
-                                            if (user.getRealJid() != null) {
-                                                Contact contact =
-                                                        account.getRoster()
-                                                                .getContact(user.getRealJid());
-                                                contact.setAvatar(avatar);
-                                                account.getXmppConnection()
-                                                        .getManager(RosterManager.class)
-                                                        .writeToDatabaseAsync();
-                                                getAvatarService().clear(contact);
-                                                updateRosterUi();
-                                            }
-                                        }
-                                    }
-                                }
-                            }
+    private void fetchAvatarVcard(final Account account, final Avatar avatar) {
+        final var address = avatar.owner;
+        final var connection = account.getXmppConnection();
+        final var future = connection.getManager(VCardManager.class).retrievePhoto(address);
+        Futures.addCallback(
+                future,
+                new FutureCallback<>() {
+                    @Override
+                    public void onSuccess(byte[] result) {
+                        avatar.image = BaseEncoding.base64().encode(result);
+                        if (fileBackend.save(avatar)) {
+                            setVCardAvatar(account, avatar);
                         }
                     }
-                });
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Log.d(
+                                Config.LOGTAG,
+                                "could not retrieve avatar from "
+                                        + address
+                                        + " ("
+                                        + avatar.sha1sum
+                                        + ")",
+                                t);
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+
+    private void setVCardAvatar(final Account account, final Avatar avatar) {
+        Log.d(
+                Config.LOGTAG,
+                account.getJid().asBareJid()
+                        + ": successfully fetched vCard avatar for "
+                        + avatar.owner);
+        if (avatar.owner.isBareJid()) {
+            if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) {
+                Log.d(
+                        Config.LOGTAG,
+                        account.getJid().asBareJid() + ": had no avatar. replacing with vcard");
+                account.setAvatar(avatar.getFilename());
+                databaseBackend.updateAccount(account);
+                getAvatarService().clear(account);
+                updateAccountUi();
+            } else {
+                final Contact contact = account.getRoster().getContact(avatar.owner);
+                contact.setAvatar(avatar);
+                account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
+                getAvatarService().clear(contact);
+                updateRosterUi();
+            }
+            updateConversationUi();
+        } else {
+            Conversation conversation = find(account, avatar.owner.asBareJid());
+            if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+                MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner);
+                if (user != null) {
+                    if (user.setAvatar(avatar)) {
+                        getAvatarService().clear(user);
+                        updateConversationUi();
+                        updateMucRosterUi();
+                    }
+                    if (user.getRealJid() != null) {
+                        Contact contact = account.getRoster().getContact(user.getRealJid());
+                        contact.setAvatar(avatar);
+                        account.getXmppConnection()
+                                .getManager(RosterManager.class)
+                                .writeToDatabaseAsync();
+                        getAvatarService().clear(contact);
+                        updateRosterUi();
+                    }
+                }
+            }
+        }
     }
 
     public void checkForAvatar(final Account account, final UiCallback<Avatar> callback) {

src/main/java/eu/siacs/conversations/xmpp/Managers.java 🔗

@@ -21,6 +21,7 @@ import eu.siacs.conversations.xmpp.manager.PrivateStorageManager;
 import eu.siacs.conversations.xmpp.manager.PubSubManager;
 import eu.siacs.conversations.xmpp.manager.RosterManager;
 import eu.siacs.conversations.xmpp.manager.UnifiedPushManager;
+import eu.siacs.conversations.xmpp.manager.VCardManager;
 
 public class Managers {
 
@@ -50,6 +51,7 @@ public class Managers {
                 .put(PubSubManager.class, new PubSubManager(context, connection))
                 .put(RosterManager.class, new RosterManager(context, connection))
                 .put(UnifiedPushManager.class, new UnifiedPushManager(context, connection))
+                .put(VCardManager.class, new VCardManager(context, connection))
                 .build();
     }
 }

src/main/java/eu/siacs/conversations/xmpp/manager/AvatarManager.java 🔗

@@ -1,6 +1,9 @@
 package eu.siacs.conversations.xmpp.manager;
 
 import android.util.Log;
+import androidx.annotation.NonNull;
+import com.google.common.io.BaseEncoding;
+import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
@@ -12,6 +15,7 @@ import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.XmppConnection;
 import eu.siacs.conversations.xmpp.pep.Avatar;
 import im.conversations.android.xmpp.NodeConfiguration;
+import im.conversations.android.xmpp.model.ByteContent;
 import im.conversations.android.xmpp.model.avatar.Data;
 import im.conversations.android.xmpp.model.avatar.Info;
 import im.conversations.android.xmpp.model.avatar.Metadata;
@@ -26,11 +30,50 @@ public class AvatarManager extends AbstractManager {
         this.service = service;
     }
 
+    public ListenableFuture<byte[]> fetch(final Jid address, final String itemId) {
+        final var future = getManager(PubSubManager.class).fetchItem(address, itemId, Data.class);
+        return Futures.transform(future, ByteContent::asBytes, MoreExecutors.directExecutor());
+    }
+
+    public ListenableFuture<Void> fetchAndStore(final Avatar avatar) {
+        final var future = fetch(avatar.owner, avatar.sha1sum);
+        return Futures.transform(
+                future,
+                data -> {
+                    avatar.image = BaseEncoding.base64().encode(data);
+                    if (service.getFileBackend().save(avatar)) {
+                        setPepAvatar(avatar);
+                        return null;
+                    } else {
+                        throw new IllegalStateException("Could not store avatar");
+                    }
+                },
+                MoreExecutors.directExecutor());
+    }
+
+    private void setPepAvatar(final Avatar avatar) {
+        final var account = getAccount();
+        if (account.getJid().asBareJid().equals(avatar.owner)) {
+            if (account.setAvatar(avatar.getFilename())) {
+                getDatabase().updateAccount(account);
+            }
+            this.service.getAvatarService().clear(account);
+            this.service.updateConversationUi();
+            this.service.updateAccountUi();
+        } else {
+            final Contact contact = account.getRoster().getContact(avatar.owner);
+            contact.setAvatar(avatar);
+            account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync();
+            this.service.getAvatarService().clear(contact);
+            this.service.updateConversationUi();
+            this.service.updateRosterUi();
+        }
+    }
+
     public void handleItems(final Jid from, final Items items) {
         final var account = getAccount();
         // TODO support retract
         final var entry = items.getFirstItemWithId(Metadata.class);
-        Log.d(Config.LOGTAG, "<-- " + entry + " (" + from + ")");
         final var avatar =
                 entry == null ? null : Avatar.parseMetadata(entry.getKey(), entry.getValue());
         if (avatar == null) {
@@ -57,8 +100,25 @@ public class AvatarManager extends AbstractManager {
                 }
             }
         } else if (service.isDataSaverDisabled()) {
-            // TODO use internal mechanism to fetch PEP avatars
-            service.fetchAvatar(account, avatar);
+            final var future = this.fetchAndStore(avatar);
+            Futures.addCallback(
+                    future,
+                    new FutureCallback<Void>() {
+                        @Override
+                        public void onSuccess(Void result) {
+                            Log.d(
+                                    Config.LOGTAG,
+                                    account.getJid().asBareJid()
+                                            + ": successfully fetched pep avatar for "
+                                            + avatar.owner);
+                        }
+
+                        @Override
+                        public void onFailure(@NonNull Throwable t) {
+                            Log.d(Config.LOGTAG, "could not fetch avatar", t);
+                        }
+                    },
+                    MoreExecutors.directExecutor());
         }
     }
 

src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java 🔗

@@ -0,0 +1,50 @@
+package eu.siacs.conversations.xmpp.manager;
+
+import android.content.Context;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import eu.siacs.conversations.xmpp.Jid;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import im.conversations.android.xmpp.model.stanza.Iq;
+import im.conversations.android.xmpp.model.vcard.VCard;
+
+public class VCardManager extends AbstractManager {
+
+    public VCardManager(final Context context, final XmppConnection connection) {
+        super(context, connection);
+    }
+
+    public ListenableFuture<VCard> retrieve(final Jid address) {
+        final var iq = new Iq(Iq.Type.GET, new VCard());
+        iq.setTo(address);
+        return Futures.transform(
+                this.connection.sendIqPacket(iq),
+                result -> {
+                    final var vCard = result.getExtension(VCard.class);
+                    if (vCard == null) {
+                        throw new IllegalStateException("Result did not include vCard");
+                    }
+                    return vCard;
+                },
+                MoreExecutors.directExecutor());
+    }
+
+    public ListenableFuture<byte[]> retrievePhoto(final Jid address) {
+        final var vCardFuture = retrieve(address);
+        return Futures.transform(
+                vCardFuture,
+                vCard -> {
+                    final var photo = vCard.getPhoto();
+                    if (photo == null) {
+                        throw new IllegalStateException("No photo in vCard");
+                    }
+                    final var binaryValue = photo.getBinaryValue();
+                    if (binaryValue == null) {
+                        throw new IllegalStateException("Photo has no binary value");
+                    }
+                    return binaryValue.asBytes();
+                },
+                MoreExecutors.directExecutor());
+    }
+}