diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index f48e2023d436a35b3a05476913bf89b408f85dbe..512248c06734289970afb04b1b006fefc00b8680 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -87,13 +87,6 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq retrieveVcardAvatar(final Jid to) { - final Iq packet = new Iq(Iq.Type.GET); - packet.setTo(to); - packet.addChild("vCard", "vcard-temp"); - return packet; - } - public Iq retrieveAvatarMetaData(final Jid to) { final Iq packet = retrieve("urn:xmpp:avatar:metadata", null); if (to != null) { diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 95f232d2f244ef0aa1305665de484fc58b210543..d5ebbf1deacaaf2f5040b2075851dc4674bd3a74 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -491,7 +491,6 @@ public class PresenceParser extends AbstractParser @Override public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) { - // Log.d(Config.LOGTAG,"<--"+packet); if (packet.hasChild("x", Namespace.MUC_USER)) { this.parseConferencePresence(packet); } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 2b95a1cf8de62a2661cfd72015f32eb8e65d4ac9..a261afb7fd6f63c99f7242132bf3ede67fbc73d8 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -1220,7 +1220,7 @@ public class FileBackend { try { ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); Base64OutputStream mBase64OutputStream = - new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT); + new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT | Base64.NO_WRAP); MessageDigest digest = MessageDigest.getInstance("SHA-1"); DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputStream, digest); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 4f2def45574c7d051be8c70237240d4f4a55ca2e..94a19b172791f1c1e259f3c2b9823b1f520ce5bd 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -4366,53 +4366,39 @@ public class XmppConnectionService extends Service { } private void publishMucAvatar( - Conversation conversation, Avatar avatar, OnAvatarPublication callback) { + final Conversation conversation, + final Avatar avatar, + final OnAvatarPublication callback) { final var account = conversation.getAccount(); - final Iq retrieve = mIqGenerator.retrieveVcardAvatar(avatar); - sendIqPacket( - account, - retrieve, - (response) -> { - boolean itemNotFound = - response.getType() == Iq.Type.ERROR - && response.hasChild("error") - && response.findChild("error").hasChild("item-not-found"); - if (response.getType() == Iq.Type.RESULT || itemNotFound) { - Element vcard = response.findChild("vCard", "vcard-temp"); - if (vcard == null) { - vcard = new Element("vCard", "vcard-temp"); - } - Element photo = vcard.findChild("PHOTO"); - if (photo == null) { - photo = vcard.addChild("PHOTO"); - } - photo.clearChildren(); - photo.addChild("TYPE").setContent(avatar.type); - photo.addChild("BINVAL").setContent(avatar.image); - final Iq publication = new Iq(Iq.Type.SET); - publication.setTo(conversation.getJid().asBareJid()); - publication.addChild(vcard); - sendIqPacket( - account, - publication, - (publicationResponse) -> { - if (publicationResponse.getType() == Iq.Type.RESULT) { - callback.onAvatarPublicationSucceeded(); - } else { - Log.d( - Config.LOGTAG, - "failed to publish vcard " - + publicationResponse.getErrorCondition()); - callback.onAvatarPublicationFailed( - R.string.error_publish_avatar_server_reject); - } - }); - } else { - Log.d(Config.LOGTAG, "failed to request vcard " + response); + final var connection = account.getXmppConnection(); + final var future = + connection + .getManager(VCardManager.class) + .publishPhoto( + avatar.owner, + avatar.type, + BaseEncoding.base64().decode(avatar.image)); + Futures.addCallback( + future, + new FutureCallback<>() { + @Override + public void onSuccess(Void result) { + Log.d(Config.LOGTAG, "published muc avatar"); + final var c = account.getRoster().getContact(avatar.owner); + c.setAvatar(avatar); + getAvatarService().clear(c); + getAvatarService().clear(conversation.getMucOptions()); + callback.onAvatarPublicationSucceeded(); + } + + @Override + public void onFailure(@NonNull Throwable t) { + Log.d(Config.LOGTAG, "could not publish muc avatar", t); callback.onAvatarPublicationFailed( - R.string.error_publish_avatar_no_server_support); + R.string.error_publish_avatar_server_reject); } - }); + }, + MoreExecutors.directExecutor()); } public void cancelAvatarFetches(final Account account) { @@ -4563,6 +4549,8 @@ public class XmppConnectionService extends Service { getAvatarService().clear(account); updateAccountUi(); } else { + // TODO if this is a MUC clear MucOptions too + // TODO do the same clearing for when setting a cached version final Contact contact = account.getRoster().getContact(avatar.owner); contact.setAvatar(avatar); account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java index 76f8c6fdb724d1ca4e1a75c7a80e304dbf5a3705..05ab21a94fad9d9cb3d192c11e826d7d120426cd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/VCardManager.java @@ -8,7 +8,11 @@ import com.google.common.util.concurrent.MoreExecutors; import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; +import im.conversations.android.xmpp.IqErrorException; +import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.vcard.BinaryValue; +import im.conversations.android.xmpp.model.vcard.Photo; import im.conversations.android.xmpp.model.vcard.VCard; import java.util.Objects; @@ -54,8 +58,12 @@ public class VCardManager extends AbstractManager { } public ListenableFuture publish(final VCard vCard) { + return publish(getAccount().getJid().asBareJid(), vCard); + } + + public ListenableFuture publish(final Jid address, final VCard vCard) { final var iq = new Iq(Iq.Type.SET, vCard); - iq.setTo(getAccount().getJid().asBareJid()); + iq.setTo(address); return Futures.transform( connection.sendIqPacket(iq), result -> null, MoreExecutors.directExecutor()); } @@ -78,4 +86,42 @@ public class VCardManager extends AbstractManager { }, MoreExecutors.directExecutor()); } + + public ListenableFuture publishPhoto( + final Jid address, final String type, final byte[] image) { + final var retrieveFuture = this.retrieve(address); + + final var caughtFuture = + Futures.catchingAsync( + retrieveFuture, + IqErrorException.class, + ex -> { + final var error = ex.getError(); + if (error != null + && error.getCondition() instanceof Condition.ItemNotFound) { + return Futures.immediateFuture(null); + } else { + return Futures.immediateFailedFuture(ex); + } + }, + MoreExecutors.directExecutor()); + + return Futures.transformAsync( + caughtFuture, + existing -> { + final VCard vCard; + if (existing == null) { + Log.d(Config.LOGTAG, "item-not-found. created fresh vCard"); + vCard = new VCard(); + } else { + vCard = existing; + } + final var photo = new Photo(); + photo.setType(type); + photo.addExtension(new BinaryValue()).setContent(image); + vCard.setExtension(photo); + return publish(address, vCard); + }, + MoreExecutors.directExecutor()); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/src/main/java/im/conversations/android/xmpp/model/Extension.java index 5299b353e23ab554f87c2a4add562a26c77aeea0..8f061e47780b33c6a1b719df9d7b6cfaf10bd857 100644 --- a/src/main/java/im/conversations/android/xmpp/model/Extension.java +++ b/src/main/java/im/conversations/android/xmpp/model/Extension.java @@ -59,6 +59,16 @@ public class Extension extends Element { return child; } + public T setExtension(T child) { + final var iterator = this.children.iterator(); + while (iterator.hasNext()) { + if (iterator.next().getClass().isInstance(child)) { + iterator.remove(); + } + } + return this.addExtension(child); + } + public void addExtensions(final Collection extensions) { for (final Extension extension : extensions) { addExtension(extension); diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java index 3ca485341cea54ba7db5d892e3390a6b5d959ce7..79f8a0a1ff0c7eba8523d72797f7b6971ecda15c 100644 --- a/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/Photo.java @@ -12,4 +12,9 @@ public class Photo extends Extension { public BinaryValue getBinaryValue() { return this.getExtension(BinaryValue.class); } + + public void setType(final String value) { + final var type = this.addExtension(new Type()); + type.setContent(value); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/vcard/Type.java b/src/main/java/im/conversations/android/xmpp/model/vcard/Type.java new file mode 100644 index 0000000000000000000000000000000000000000..c1c903b7ac1343b29a1f59522faa0fe6e0311e5e --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/vcard/Type.java @@ -0,0 +1,12 @@ +package im.conversations.android.xmpp.model.vcard; + +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(name = "TYPE") +public class Type extends Extension { + + public Type() { + super(Type.class); + } +}