1package eu.siacs.conversations.xmpp.manager;
2
3import android.content.Context;
4import android.util.Log;
5import com.google.common.util.concurrent.Futures;
6import com.google.common.util.concurrent.ListenableFuture;
7import com.google.common.util.concurrent.MoreExecutors;
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.xmpp.Jid;
10import eu.siacs.conversations.xmpp.XmppConnection;
11import im.conversations.android.xmpp.IqErrorException;
12import im.conversations.android.xmpp.model.error.Condition;
13import im.conversations.android.xmpp.model.stanza.Iq;
14import im.conversations.android.xmpp.model.vcard.BinaryValue;
15import im.conversations.android.xmpp.model.vcard.Photo;
16import im.conversations.android.xmpp.model.vcard.VCard;
17import java.util.Objects;
18
19public class VCardManager extends AbstractManager {
20
21 public VCardManager(final Context context, final XmppConnection connection) {
22 super(context, connection);
23 }
24
25 public ListenableFuture<VCard> retrieve(final Jid address) {
26 final var iq = new Iq(Iq.Type.GET, new VCard());
27 iq.setTo(address);
28 return Futures.transform(
29 this.connection.sendIqPacket(iq),
30 result -> {
31 final var vCard = result.getExtension(VCard.class);
32 if (vCard == null) {
33 throw new IllegalStateException("Result did not include vCard");
34 }
35 return vCard;
36 },
37 MoreExecutors.directExecutor());
38 }
39
40 public ListenableFuture<byte[]> retrievePhoto(final Jid address) {
41 final var vCardFuture = retrieve(address);
42 return Futures.transform(
43 vCardFuture,
44 vCard -> {
45 final var photo = vCard.getPhoto();
46 if (photo == null) {
47 throw new IllegalStateException(
48 String.format("No photo in vCard of %s", address));
49 }
50 final var binaryValue = photo.getBinaryValue();
51 if (binaryValue == null) {
52 throw new IllegalStateException(
53 String.format("Photo has no binary value in vCard of %s", address));
54 }
55 return binaryValue.asBytes();
56 },
57 MoreExecutors.directExecutor());
58 }
59
60 public ListenableFuture<Void> publish(final VCard vCard) {
61 return publish(getAccount().getJid().asBareJid(), vCard);
62 }
63
64 public ListenableFuture<Void> publish(final Jid address, final VCard vCard) {
65 final var iq = new Iq(Iq.Type.SET, vCard);
66 iq.setTo(address);
67 return Futures.transform(
68 connection.sendIqPacket(iq), result -> null, MoreExecutors.directExecutor());
69 }
70
71 public ListenableFuture<Void> deletePhoto() {
72 final var vCardFuture = retrieve(getAccount().getJid().asBareJid());
73 return Futures.transformAsync(
74 vCardFuture,
75 vCard -> {
76 final var photo = vCard.getPhoto();
77 if (photo == null) {
78 return Futures.immediateFuture(null);
79 }
80 Log.d(
81 Config.LOGTAG,
82 "deleting photo from vCard. binaryValue="
83 + Objects.nonNull(photo.getBinaryValue()));
84 photo.clearChildren();
85 return publish(vCard);
86 },
87 MoreExecutors.directExecutor());
88 }
89
90 public ListenableFuture<Void> publishPhoto(
91 final Jid address, final String type, final byte[] image) {
92 final var retrieveFuture = this.retrieve(address);
93
94 final var caughtFuture =
95 Futures.catchingAsync(
96 retrieveFuture,
97 IqErrorException.class,
98 ex -> {
99 final var error = ex.getError();
100 if (error != null
101 && error.getCondition() instanceof Condition.ItemNotFound) {
102 return Futures.immediateFuture(null);
103 } else {
104 return Futures.immediateFailedFuture(ex);
105 }
106 },
107 MoreExecutors.directExecutor());
108
109 return Futures.transformAsync(
110 caughtFuture,
111 existing -> {
112 final VCard vCard;
113 if (existing == null) {
114 Log.d(Config.LOGTAG, "item-not-found. created fresh vCard");
115 vCard = new VCard();
116 } else {
117 vCard = existing;
118 }
119 final var photo = new Photo();
120 photo.setType(type);
121 photo.addExtension(new BinaryValue()).setContent(image);
122 vCard.setExtension(photo);
123 return publish(address, vCard);
124 },
125 MoreExecutors.directExecutor());
126 }
127}