summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTarun Gupta <tarun1995gupta@gmail.com>2015-06-09 10:08:25 (GMT)
committerTarun Gupta <tarun1995gupta@gmail.com>2015-06-14 13:30:22 (GMT)
commit2180f81bd45045021d98c3a2e0649f10680596f9 (patch)
tree872db3fdeb890a232b5a1dc3b823a1e6692b5aa0
parent9518f8b9d6924e95e4ad839f40866560bd7a7878 (diff)
downloadstroke-2180f81bd45045021d98c3a2e0649f10680596f9.zip
stroke-2180f81bd45045021d98c3a2e0649f10680596f9.tar.bz2
Add Avatar Functionality.
Adds AvatarManager, AvatarManagerImpl, AvatarStorage and different AvatarProviders including DummyAvatarProvider, NullAvatarProvider and OfflineAvatarProvider. Also adds VCardAvatarManager and VCardUpdateAvatarManager. Updates VCard Element, so that it does not return null on calling getPhoto() and thereby produce Null Pointer Exceptions. License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details. Test-Information: Tests are added for AvatarManagerImpl, VCardAvatarManager and VCardUpdateAvatarManager, which passes. Tests for CombinedAvatarProvider will be added soon. Change-Id: Ia6c0f82ae496427dc0cd11841487f6c53fd0fe1c
-rwxr-xr-xsrc/com/isode/stroke/avatars/AvatarManager.java24
-rwxr-xr-xsrc/com/isode/stroke/avatars/AvatarManagerImpl.java84
-rwxr-xr-xsrc/com/isode/stroke/avatars/AvatarMemoryStorage.java57
-rwxr-xr-xsrc/com/isode/stroke/avatars/AvatarProvider.java21
-rwxr-xr-xsrc/com/isode/stroke/avatars/AvatarStorage.java27
-rwxr-xr-xsrc/com/isode/stroke/avatars/CombinedAvatarProvider.java75
-rwxr-xr-xsrc/com/isode/stroke/avatars/DummyAvatarManager.java36
-rwxr-xr-xsrc/com/isode/stroke/avatars/NullAvatarManager.java28
-rwxr-xr-xsrc/com/isode/stroke/avatars/OfflineAvatarManager.java36
-rwxr-xr-xsrc/com/isode/stroke/avatars/VCardAvatarManager.java88
-rwxr-xr-xsrc/com/isode/stroke/avatars/VCardUpdateAvatarManager.java140
-rw-r--r--src/com/isode/stroke/elements/VCard.java9
-rw-r--r--test/com/isode/stroke/avatars/AvatarManagerImplTest.java148
-rwxr-xr-xtest/com/isode/stroke/avatars/VCardAvatarManagerTest.java178
-rwxr-xr-xtest/com/isode/stroke/avatars/VCardUpdateAvatarManagerTest.java218
15 files changed, 1168 insertions, 1 deletions
diff --git a/src/com/isode/stroke/avatars/AvatarManager.java b/src/com/isode/stroke/avatars/AvatarManager.java
new file mode 100755
index 0000000..8bb8e71
--- /dev/null
+++ b/src/com/isode/stroke/avatars/AvatarManager.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import java.nio.file.Path;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.Signal1;
+
+public interface AvatarManager {
+
+ public ByteArray getAvatar(JID jid);
+ public Path getAvatarPath(JID jid);
+ public Signal1<JID> onAvatarChanged = new Signal1<JID>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/AvatarManagerImpl.java b/src/com/isode/stroke/avatars/AvatarManagerImpl.java
new file mode 100755
index 0000000..b324b3e
--- /dev/null
+++ b/src/com/isode/stroke/avatars/AvatarManagerImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarManager;
+import com.isode.stroke.avatars.CombinedAvatarProvider;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.avatars.AvatarStorage;
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.avatars.VCardUpdateAvatarManager;
+import com.isode.stroke.avatars.VCardAvatarManager;
+import com.isode.stroke.avatars.OfflineAvatarManager;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.signals.Slot1;
+import java.nio.file.*;
+import com.isode.stroke.signals.SignalConnection;
+
+public class AvatarManagerImpl implements AvatarManager {
+
+ private CombinedAvatarProvider combinedAvatarProvider = new CombinedAvatarProvider();
+ private AvatarStorage avatarStorage;
+ private VCardUpdateAvatarManager vcardUpdateAvatarManager;
+ private VCardAvatarManager vcardAvatarManager;
+ private OfflineAvatarManager offlineAvatarManager;
+ private SignalConnection onAvatarChangedConnection;
+
+ public AvatarManagerImpl(VCardManager vcardManager, StanzaChannel stanzaChannel, AvatarStorage avatarStorage, CryptoProvider crypto) {
+ this(vcardManager, stanzaChannel, avatarStorage, crypto, null);
+ }
+
+ public AvatarManagerImpl(VCardManager vcardManager, StanzaChannel stanzaChannel, AvatarStorage avatarStorage, CryptoProvider crypto, MUCRegistry mucRegistry) {
+ this.avatarStorage = avatarStorage;
+ vcardUpdateAvatarManager = new VCardUpdateAvatarManager(vcardManager, stanzaChannel, avatarStorage, crypto, mucRegistry);
+ combinedAvatarProvider.addProvider(vcardUpdateAvatarManager);
+
+ vcardAvatarManager = new VCardAvatarManager(vcardManager, avatarStorage, crypto, mucRegistry);
+ combinedAvatarProvider.addProvider(vcardAvatarManager);
+
+ offlineAvatarManager = new OfflineAvatarManager(avatarStorage);
+ combinedAvatarProvider.addProvider(offlineAvatarManager);
+
+ onAvatarChangedConnection = combinedAvatarProvider.onAvatarChanged.connect(new Slot1<JID>() {
+
+ public void call(JID p1) {
+ handleCombinedAvatarChanged(p1);
+ }
+ });
+ }
+
+ public Path getAvatarPath(JID jid) {
+ String hash = combinedAvatarProvider.getAvatarHash(jid);
+ if (hash != null && hash.length() != 0) {
+ return avatarStorage.getAvatarPath(hash);
+ }
+ return Paths.get("");
+ }
+
+ public ByteArray getAvatar(JID jid) {
+ String hash = combinedAvatarProvider.getAvatarHash(jid);
+ if (hash != null && hash.length() != 0) {
+ return avatarStorage.getAvatar(hash);
+ }
+ return new ByteArray();
+ }
+
+ private void handleCombinedAvatarChanged(JID jid) {
+ String hash = combinedAvatarProvider.getAvatarHash(jid);
+ assert(hash != null);
+ offlineAvatarManager.setAvatar(jid, hash);
+ onAvatarChanged.emit(jid);
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/AvatarMemoryStorage.java b/src/com/isode/stroke/avatars/AvatarMemoryStorage.java
new file mode 100755
index 0000000..a3ad6f0
--- /dev/null
+++ b/src/com/isode/stroke/avatars/AvatarMemoryStorage.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarStorage;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.jid.JID;
+import java.nio.file.*;
+import java.io.File;
+import java.util.*;
+
+public class AvatarMemoryStorage implements AvatarStorage {
+
+ private Map<String, ByteArray> avatars = new HashMap<String, ByteArray>();
+ private Map<JID, String> jidAvatars = new HashMap<JID, String>();
+
+ public boolean hasAvatar(String hash) {
+ return avatars.containsKey(hash);
+ }
+
+ public void addAvatar(String hash, ByteArray avatar) {
+ avatars.put(hash, avatar);
+ }
+
+ public ByteArray getAvatar(String hash) {
+ if(avatars.containsKey(hash)) {
+ return avatars.get(hash);
+ } else {
+ return new ByteArray();
+ }
+ }
+
+ public Path getAvatarPath(String hash) {
+ return (Paths.get("/avatars" + File.separator + hash)).toAbsolutePath();
+ }
+
+ public void setAvatarForJID(JID jid, String hash) {
+ jidAvatars.put(jid, hash);
+ }
+
+ public String getAvatarForJID(JID jid) {
+ if(jidAvatars.containsKey(jid)) {
+ return jidAvatars.get(jid);
+ } else {
+ return "";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/AvatarProvider.java b/src/com/isode/stroke/avatars/AvatarProvider.java
new file mode 100755
index 0000000..31fabb4
--- /dev/null
+++ b/src/com/isode/stroke/avatars/AvatarProvider.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.jid.JID;
+
+public interface AvatarProvider {
+
+ public String getAvatarHash(JID jid);
+ public Signal1<JID> onAvatarChanged = new Signal1<JID>();
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/AvatarStorage.java b/src/com/isode/stroke/avatars/AvatarStorage.java
new file mode 100755
index 0000000..98f7e6f
--- /dev/null
+++ b/src/com/isode/stroke/avatars/AvatarStorage.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.jid.JID;
+import java.nio.file.Path;
+
+public interface AvatarStorage {
+
+ public boolean hasAvatar(String hash);
+ public void addAvatar(String hash, ByteArray avatar);
+ public ByteArray getAvatar(String hash);
+ public Path getAvatarPath(String hash);
+
+ public void setAvatarForJID(JID jid, String hash);
+ public String getAvatarForJID(JID jid);
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/CombinedAvatarProvider.java b/src/com/isode/stroke/avatars/CombinedAvatarProvider.java
new file mode 100755
index 0000000..0426bd6
--- /dev/null
+++ b/src/com/isode/stroke/avatars/CombinedAvatarProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarProvider;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.jid.JID;
+import java.util.logging.Logger;
+import java.util.*;
+
+public class CombinedAvatarProvider implements AvatarProvider {
+
+ private Vector<AvatarProvider> providers = new Vector<AvatarProvider>();
+ private Map<JID, String> avatars = new HashMap<JID, String>();
+ private SignalConnection onAvatarChangedConnection_;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public String getAvatarHash(JID jid) {
+ return getCombinedAvatarAndCache(jid);
+ }
+
+ public void addProvider(AvatarProvider provider) {
+ onAvatarChangedConnection_ = provider.onAvatarChanged.connect(new Slot1<JID>() {
+
+ public void call(JID p1) {
+ handleAvatarChanged(p1);
+ }
+ });
+ providers.add(provider);
+ }
+
+ public void removeProvider(AvatarProvider provider) {
+ while(providers.contains(provider)) {
+ providers.remove(provider);
+ onAvatarChangedConnection_.disconnect();
+ }
+ }
+
+ private void handleAvatarChanged(JID jid) {
+ String oldHash = new String();
+ if(avatars.containsKey(jid)) {
+ oldHash = avatars.get(jid);
+ }
+ String newHash = getCombinedAvatarAndCache(jid);
+ if (newHash != null && !newHash.equals(oldHash)) {
+ logger_.fine("Avatar changed: " + jid + ": " + oldHash + " -> " + ((newHash != null) ? newHash : "NULL") + "\n");
+ onAvatarChanged.emit(jid);
+ }
+ }
+
+ private String getCombinedAvatarAndCache(JID jid) {
+ logger_.fine("JID: " + jid + "\n");
+ String hash = null;
+ for (int i = 0; i < providers.size() && (hash==null); ++i) {
+ hash = providers.get(i).getAvatarHash(jid);
+ logger_.fine("Provider " + providers.get(i) + ": " + ((hash != null) ? hash : "NULL") + "\n");
+ }
+ if (hash != null) {
+ avatars.put(jid, hash);
+ } else {
+ avatars.put(jid, "");
+ }
+ return hash;
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/DummyAvatarManager.java b/src/com/isode/stroke/avatars/DummyAvatarManager.java
new file mode 100755
index 0000000..f1dfe41
--- /dev/null
+++ b/src/com/isode/stroke/avatars/DummyAvatarManager.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarManager;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.jid.JID;
+import java.nio.file.*;
+import java.io.File;
+import java.util.*;
+
+public class DummyAvatarManager implements AvatarManager {
+
+ private Map<JID, ByteArray> avatars = new HashMap<JID, ByteArray>();
+
+ public Path getAvatarPath(JID j) {
+ return (Paths.get("/avatars" + File.separator + j.toString())).toAbsolutePath();
+ }
+
+ public ByteArray getAvatar(JID jid) {
+ if(avatars.containsKey(jid)) {
+ return avatars.get(jid);
+ } else {
+ return new ByteArray();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/NullAvatarManager.java b/src/com/isode/stroke/avatars/NullAvatarManager.java
new file mode 100755
index 0000000..e69ed46
--- /dev/null
+++ b/src/com/isode/stroke/avatars/NullAvatarManager.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarManager;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.jid.JID;
+import java.nio.file.*;
+
+public class NullAvatarManager implements AvatarManager {
+
+ public Path getAvatarPath(JID j) {
+ return Paths.get("");
+ }
+
+ public ByteArray getAvatar(JID jid) {
+ return new ByteArray();
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/OfflineAvatarManager.java b/src/com/isode/stroke/avatars/OfflineAvatarManager.java
new file mode 100755
index 0000000..fe5de08
--- /dev/null
+++ b/src/com/isode/stroke/avatars/OfflineAvatarManager.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarProvider;
+import com.isode.stroke.avatars.AvatarStorage;
+import com.isode.stroke.jid.JID;
+
+public class OfflineAvatarManager implements AvatarProvider {
+
+ private AvatarStorage avatarStorage;
+
+ public OfflineAvatarManager(AvatarStorage avatarStorage) {
+ this.avatarStorage = avatarStorage;
+ }
+
+ public String getAvatarHash(JID jid) {
+ return avatarStorage.getAvatarForJID(jid);
+ }
+
+ public void setAvatar(JID jid, String hash) {
+ if (!getAvatarHash(jid).equals(hash)) {
+ avatarStorage.setAvatarForJID(jid, hash);
+ onAvatarChanged.emit(jid);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/com/isode/stroke/avatars/VCardAvatarManager.java b/src/com/isode/stroke/avatars/VCardAvatarManager.java
new file mode 100755
index 0000000..b2efcf2
--- /dev/null
+++ b/src/com/isode/stroke/avatars/VCardAvatarManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import com.isode.stroke.avatars.AvatarProvider;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.avatars.AvatarStorage;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.signals.Slot2;
+import java.util.logging.Logger;
+import com.isode.stroke.signals.SignalConnection;
+
+public class VCardAvatarManager implements AvatarProvider {
+
+ private VCardManager vcardManager_;
+ private AvatarStorage avatarStorage_;
+ private CryptoProvider crypto_;
+ private MUCRegistry mucRegistry_;
+ private SignalConnection onVCardChangedConnection_;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public VCardAvatarManager(VCardManager vcardManager, AvatarStorage avatarStorage, CryptoProvider crypto) {
+ this(vcardManager, avatarStorage, crypto, null);
+ }
+
+ public VCardAvatarManager(VCardManager vcardManager, AvatarStorage avatarStorage, CryptoProvider crypto, MUCRegistry mucRegistry) {
+ this.vcardManager_ = vcardManager;
+ this.avatarStorage_ = avatarStorage;
+ this.crypto_ = crypto;
+ this.mucRegistry_ = mucRegistry;
+ onVCardChangedConnection_ = vcardManager.onVCardChanged.connect(new Slot2<JID, VCard>() {
+
+ public void call(JID p1, VCard vcard) {
+ handleVCardChanged(p1);
+ }
+ });
+ }
+
+ public String getAvatarHash(JID jid) {
+ JID avatarJID = getAvatarJID(jid);
+ String hash = vcardManager_.getPhotoHash(avatarJID);
+ if(hash.length() != 0) {
+ if (!avatarStorage_.hasAvatar(hash)) {
+ VCard vCard = vcardManager_.getVCard(avatarJID);
+ if (vCard != null) {
+ String newHash = Hexify.hexify(crypto_.getSHA1Hash(vCard.getPhoto()));
+ if (!newHash.equals(hash)) {
+ // Shouldn't happen, but sometimes seem to. Might be fixed if we
+ // move to a safer backend.
+ logger_.warning("Inconsistent vCard photo hash cache");
+ hash = newHash;
+ }
+ avatarStorage_.addAvatar(hash, vCard.getPhoto());
+ }
+ else {
+ // Can happen if the cache is inconsistent.
+ hash = "";
+ }
+ }
+ }
+ return hash;
+ }
+
+ private void handleVCardChanged(JID from) {
+ // We don't check whether the avatar actually changed. Direct use of this
+ // manager could cause unnecessary updates, but in practice, this will be
+ // caught by the wrapping CombinedAvatarManager anyway.
+ onAvatarChanged.emit(from);
+ }
+
+ private JID getAvatarJID(JID jid) {
+ JID bareFrom = jid.toBare();
+ return (mucRegistry_ != null && mucRegistry_.isMUC(bareFrom)) ? jid : bareFrom;
+ }
+}
diff --git a/src/com/isode/stroke/avatars/VCardUpdateAvatarManager.java b/src/com/isode/stroke/avatars/VCardUpdateAvatarManager.java
new file mode 100755
index 0000000..ce61892
--- /dev/null
+++ b/src/com/isode/stroke/avatars/VCardUpdateAvatarManager.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2010-2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import java.util.Map;
+import java.util.HashMap;
+import com.isode.stroke.avatars.AvatarProvider;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.VCardUpdate;
+import com.isode.stroke.client.StanzaChannel;
+import com.isode.stroke.vcards.GetVCardRequest;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.avatars.AvatarStorage;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.signals.Slot2;
+import com.isode.stroke.signals.Slot1;
+import java.util.logging.Logger;
+import com.isode.stroke.signals.SignalConnection;
+
+public class VCardUpdateAvatarManager implements AvatarProvider {
+
+ private VCardManager vcardManager_;
+ private AvatarStorage avatarStorage_;
+ private CryptoProvider crypto_;
+ private MUCRegistry mucRegistry_;
+ private Map<JID, String> avatarHashes_ = new HashMap<JID, String>();
+ private SignalConnection onPresenceReceivedConnection;
+ private SignalConnection onAvailableChangedConnection;
+ private SignalConnection onVCardChangedConnection;
+ private Logger logger_ = Logger.getLogger(this.getClass().getName());
+
+ public VCardUpdateAvatarManager(VCardManager vcardManager, StanzaChannel stanzaChannel, AvatarStorage avatarStorage, CryptoProvider crypto) {
+ this(vcardManager, stanzaChannel, avatarStorage, crypto, null);
+ }
+
+ public VCardUpdateAvatarManager(VCardManager vcardManager, StanzaChannel stanzaChannel, AvatarStorage avatarStorage, CryptoProvider crypto, MUCRegistry mucRegistry) {
+ this.vcardManager_ = vcardManager;
+ this.avatarStorage_ = avatarStorage;
+ this.crypto_ = crypto;
+ this.mucRegistry_ = mucRegistry;
+ onPresenceReceivedConnection = stanzaChannel.onPresenceReceived.connect(new Slot1<Presence>() {
+
+ public void call(Presence p1) {
+ handlePresenceReceived(p1);
+ }
+ });
+ onAvailableChangedConnection = stanzaChannel.onAvailableChanged.connect(new Slot1<Boolean>() {
+
+ public void call(Boolean b) {
+ handleStanzaChannelAvailableChanged(b);
+ }
+ });
+ onVCardChangedConnection = vcardManager_.onVCardChanged.connect(new Slot2<JID, VCard>() {
+
+ public void call(JID p1, VCard vcard) {
+ handleVCardChanged(p1, vcard);
+ }
+ });
+ }
+
+ public String getAvatarHash(JID jid) {
+ if(avatarHashes_.containsKey(jid)) {
+ return avatarHashes_.get(jid);
+ } else {
+ return null;
+ }
+ }
+
+ private void handlePresenceReceived(Presence presence) {
+ VCardUpdate update = presence.getPayload(new VCardUpdate());
+ if (update == null || presence.getPayload(new ErrorPayload()) != null) {
+ return;
+ }
+ JID from = getAvatarJID(presence.getFrom());
+ if (update.getPhotoHash().equals(getAvatarHash(from))) {
+ return;
+ }
+ logger_.fine("Updated hash: " + from + "-> " + update.getPhotoHash() + "\n");
+ if (avatarStorage_.hasAvatar(update.getPhotoHash())) {
+ setAvatarHash(from, update.getPhotoHash());
+ }
+ else {
+ vcardManager_.requestVCard(from);
+ }
+ }
+
+ private void handleStanzaChannelAvailableChanged(boolean available) {
+ if (available) {
+ Map<JID, String> oldAvatarHashes = new HashMap<JID, String>();
+ oldAvatarHashes.putAll(avatarHashes_);
+ avatarHashes_.clear();
+ for (Map.Entry<JID, String> entry : oldAvatarHashes.entrySet()) {
+ onAvatarChanged.emit(entry.getKey());
+ }
+ }
+ }
+
+ private void handleVCardChanged(JID from, VCard vCard) {
+ if (vCard == null) {
+ logger_.fine("Missing element: " + from + ": null vcard payload\n");
+ return;
+ }
+
+ if (vCard.getPhoto().isEmpty()) {
+ setAvatarHash(from, "");
+ }
+ else {
+ String hash = Hexify.hexify(crypto_.getSHA1Hash(vCard.getPhoto()));
+ if (!avatarStorage_.hasAvatar(hash)) {
+ avatarStorage_.addAvatar(hash, vCard.getPhoto());
+ }
+ setAvatarHash(from, hash);
+ }
+ }
+
+ private void setAvatarHash(JID from, String hash) {
+ logger_.fine("Updating hash: " + from + " -> " + hash + "\n");
+ avatarHashes_.put(from, hash);
+ onAvatarChanged.emit(from);
+ }
+
+ private JID getAvatarJID(JID jid) {
+ JID bareFrom = jid.toBare();
+ return (mucRegistry_ != null && mucRegistry_.isMUC(bareFrom)) ? jid : bareFrom;
+ }
+}
diff --git a/src/com/isode/stroke/elements/VCard.java b/src/com/isode/stroke/elements/VCard.java
index f9b294b..91a9093 100644
--- a/src/com/isode/stroke/elements/VCard.java
+++ b/src/com/isode/stroke/elements/VCard.java
@@ -132,7 +132,14 @@ public class VCard extends Payload implements Serializable {
public final String getNickname() { return nick_; }
public void setPhoto(final ByteArray photo) { photo_ = photo; }
- public final ByteArray getPhoto() { return photo_; }
+ public final ByteArray getPhoto() {
+ if(this.photo_ != null) {
+ return photo_;
+ }
+ else {
+ return new ByteArray();
+ }
+ }
public void setPhotoType(final String photoType) { photoType_ = photoType; }
public final String getPhotoType() { return photoType_; }
diff --git a/test/com/isode/stroke/avatars/AvatarManagerImplTest.java b/test/com/isode/stroke/avatars/AvatarManagerImplTest.java
new file mode 100644
index 0000000..7ee10fd
--- /dev/null
+++ b/test/com/isode/stroke/avatars/AvatarManagerImplTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2014 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Vector;
+import com.isode.stroke.elements.VCardUpdate;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.avatars.VCardUpdateAvatarManager;
+import com.isode.stroke.avatars.VCardAvatarManager;
+import com.isode.stroke.avatars.AvatarMemoryStorage;
+import com.isode.stroke.vcards.VCardMemoryStorage;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.client.DummyStanzaChannel;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+
+public class AvatarManagerImplTest {
+
+ private JID ownerJID;
+ private DummyStanzaChannel stanzaChannel;
+ private IQRouter iqRouter;
+ private CryptoProvider crypto;
+ private VCardMemoryStorage vcardStorage;
+ private VCardManager vcardManager;
+ private AvatarMemoryStorage avatarStorage;
+ private DummyMUCRegistry mucRegistry;
+ private AvatarManagerImpl avatarManager;
+
+ public class DummyMUCRegistry extends MUCRegistry {
+
+ public boolean isMUC(JID jid) {
+ return mucs_.contains(jid);
+ }
+
+ public Vector<JID> mucs_ = new Vector<JID>();
+ }
+
+ @Before
+ public void setUp() {
+ ownerJID = new JID("owner@domain.com/theowner");
+ stanzaChannel = new DummyStanzaChannel();
+ iqRouter = new IQRouter(stanzaChannel);
+ crypto = new JavaCryptoProvider();
+ vcardStorage = new VCardMemoryStorage(crypto);
+ vcardManager = new VCardManager(ownerJID, iqRouter, vcardStorage);
+ avatarStorage = new AvatarMemoryStorage();
+ mucRegistry = new DummyMUCRegistry();
+ avatarManager = new AvatarManagerImpl(vcardManager, stanzaChannel, avatarStorage, crypto, mucRegistry);
+ }
+
+ @Test
+ public void testGetSetAvatar() {
+ /* initially we have no knowledge of the user or their avatar */
+ JID personJID = new JID("person@domain.com/theperson");
+ ByteArray avatar = avatarManager.getAvatar(personJID.toBare());
+ assertTrue(avatar.getSize() == 0);
+
+ /* notify the 'owner' JID that our avatar has changed */
+
+ ByteArray fullAvatar = new ByteArray("abcdefg");
+ VCardUpdate vcardUpdate = new VCardUpdate();
+ vcardUpdate.setPhotoHash(Hexify.hexify(crypto.getSHA1Hash(fullAvatar)));
+ Presence presence = new Presence();
+ presence.setTo(ownerJID);
+ presence.setFrom(personJID);
+ presence.setType(Presence.Type.Available);
+ presence.addPayload(vcardUpdate);
+ stanzaChannel.onPresenceReceived.emit(presence);
+
+ /* reply to the avatar request with our new avatar */
+
+ assertEquals(1, stanzaChannel.sentStanzas.size());
+ IQ request = (IQ)(stanzaChannel.sentStanzas.get(0));
+ stanzaChannel.sentStanzas.remove(stanzaChannel.sentStanzas.lastElement());
+ assertNotNull(request);
+ VCard vcard = request.getPayload(new VCard());
+ assertNotNull(vcard);
+
+ IQ reply = new IQ(IQ.Type.Result);
+ reply.setTo(request.getFrom());
+ reply.setFrom(request.getTo());
+ reply.setID(request.getID());
+ vcard.setPhoto(fullAvatar);
+ reply.addPayload(vcard);
+ stanzaChannel.onIQReceived.emit(reply);
+
+ /* check hash through avatarManager that it received the correct photo */
+
+ ByteArray reportedAvatar = avatarManager.getAvatar(personJID.toBare());
+ assertEquals(fullAvatar.toString(), reportedAvatar.toString());
+
+ /* send new presence to notify of blank avatar */
+
+ vcardUpdate = new VCardUpdate();
+ presence = new Presence();
+ presence.setTo(ownerJID);
+ presence.setFrom(personJID);
+ presence.setType(Presence.Type.Available);
+ presence.addPayload(vcardUpdate);
+ stanzaChannel.onPresenceReceived.emit(presence);
+
+ /* reply to the avatar request with our EMPTY avatar */
+
+ assertEquals(1, stanzaChannel.sentStanzas.size());
+ request = (IQ)(stanzaChannel.sentStanzas.get(0));
+ stanzaChannel.sentStanzas.remove(stanzaChannel.sentStanzas.lastElement());
+ assertNotNull(request);
+ vcard = request.getPayload(new VCard());
+ assertNotNull(vcard);
+
+ ByteArray blankAvatar = new ByteArray("");
+ reply = new IQ(IQ.Type.Result);
+ reply.setTo(request.getFrom());
+ reply.setFrom(request.getTo());
+ reply.setID(request.getID());
+ vcard.setPhoto(blankAvatar);
+ reply.addPayload(vcard);
+ stanzaChannel.onIQReceived.emit(reply);
+
+ /* check hash through avatarManager that it received the correct photo */
+
+ reportedAvatar = avatarManager.getAvatar(personJID.toBare());
+ assertEquals(blankAvatar.toString(), reportedAvatar.toString());
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/avatars/VCardAvatarManagerTest.java b/test/com/isode/stroke/avatars/VCardAvatarManagerTest.java
new file mode 100755
index 0000000..56121ac
--- /dev/null
+++ b/test/com/isode/stroke/avatars/VCardAvatarManagerTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Vector;
+import com.isode.stroke.avatars.VCardAvatarManager;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.vcards.VCardMemoryStorage;
+import com.isode.stroke.avatars.AvatarMemoryStorage;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.client.DummyStanzaChannel;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+import com.isode.stroke.elements.IQ;
+
+public class VCardAvatarManagerTest {
+
+ private class DummyMUCRegistry extends MUCRegistry {
+
+ private Vector<JID> mucs_ = new Vector<JID>();
+
+ public boolean isMUC(JID jid) {
+ if(mucs_.contains(jid))
+ return true;
+ else
+ return false;
+ }
+ }
+
+ private JID ownJID;
+ private DummyStanzaChannel stanzaChannel;
+ private IQRouter iqRouter;
+ private DummyMUCRegistry mucRegistry;
+ private AvatarMemoryStorage avatarStorage;
+ private VCardManager vcardManager;
+ private VCardMemoryStorage vcardStorage;
+ private ByteArray avatar1;
+ private String avatar1Hash;
+ private Vector<JID> changes;
+ private JID user1;
+ private JID user2;
+ private CryptoProvider crypto;
+ private SignalConnection onAvatarChangedConnection;
+
+ @Before
+ public void setUp() {
+ crypto = new JavaCryptoProvider();
+ ownJID = new JID("foo@fum.com/bum");
+ stanzaChannel = new DummyStanzaChannel();
+ stanzaChannel.setAvailable(true);
+ iqRouter = new IQRouter(stanzaChannel);
+ mucRegistry = new DummyMUCRegistry();
+ avatarStorage = new AvatarMemoryStorage();
+ vcardStorage = new VCardMemoryStorage(crypto);
+ vcardManager = new VCardManager(ownJID, iqRouter, vcardStorage);
+ avatar1 = new ByteArray("abcdefg");
+ avatar1Hash = Hexify.hexify(crypto.getSHA1Hash(avatar1));
+ changes = new Vector<JID>();
+ user1 = new JID("user1@bar.com/bla");
+ user2 = new JID("user2@foo.com/baz");
+ }
+
+ private VCardAvatarManager createManager() {
+ VCardAvatarManager result = new VCardAvatarManager(vcardManager, avatarStorage, crypto, mucRegistry);
+ onAvatarChangedConnection = result.onAvatarChanged.connect(new Slot1<JID>() {
+
+ public void call(JID j1) {
+ handleAvatarChanged(j1);
+ }
+ });
+ return result;
+ }
+
+ private void storeVCardWithPhoto(JID jid, ByteArray avatar) {
+ VCard vcard = new VCard();
+ vcard.setPhoto(avatar);
+ vcardStorage.setVCard(jid, vcard);
+ }
+
+ private void storeEmptyVCard(JID jid) {
+ VCard vcard = new VCard();
+ vcardStorage.setVCard(jid, vcard);
+ }
+
+ private void handleAvatarChanged(JID jid) {
+ changes.add(jid);
+ }
+
+ private void sendVCardResult() {
+ VCard vcard = new VCard();
+ vcard.setFullName("Foo Bar");
+ stanzaChannel.onIQReceived.emit(IQ.createResult(new JID("baz@fum.com/dum"), stanzaChannel.sentStanzas.get(0).getTo(), stanzaChannel.sentStanzas.get(0).getID(), vcard));
+ }
+
+ @Test
+ public void testGetAvatarHashKnownAvatar() {
+ VCardAvatarManager testling = createManager();
+ storeVCardWithPhoto(user1.toBare(), avatar1);
+ avatarStorage.addAvatar(avatar1Hash, avatar1);
+
+ String result = testling.getAvatarHash(user1);
+ assertNotNull(result);
+ assertEquals(avatar1Hash, result);
+ }
+
+ @Test
+ public void testGetAvatarHashEmptyAvatar() {
+ VCardAvatarManager testling = createManager();
+ storeEmptyVCard(user1.toBare());
+
+ String result = testling.getAvatarHash(user1);
+ assertNotNull(result);
+ assertEquals("", result);
+ }
+
+ @Test
+ public void testGetAvatarHashUnknownAvatarKnownVCardStoresAvatar() {
+ VCardAvatarManager testling = createManager();
+ storeVCardWithPhoto(user1.toBare(), avatar1);
+
+ String result = testling.getAvatarHash(user1);
+ assertNotNull(result);
+ assertEquals(avatar1Hash, result);
+ assertTrue(avatarStorage.hasAvatar(avatar1Hash));
+ assertEquals(avatar1, avatarStorage.getAvatar(avatar1Hash));
+ }
+
+ @Test
+ public void testGetAvatarHashUnknownAvatarUnknownVCard() {
+ VCardAvatarManager testling = createManager();
+
+ String result = testling.getAvatarHash(user1);
+
+ assertNotNull(result);
+ assertEquals("", result);
+ }
+
+ @Test
+ public void testGetAvatarHashKnownAvatarUnknownVCard() {
+ VCardAvatarManager testling = createManager();
+ avatarStorage.setAvatarForJID(user1, avatar1Hash);
+
+ String result = testling.getAvatarHash(user1);
+
+ assertNotNull(result);
+ assertEquals("", result);
+ }
+
+ @Test
+ public void testVCardUpdateTriggersUpdate() {
+ VCardAvatarManager testling = createManager();
+ vcardManager.requestVCard(user1);
+ sendVCardResult();
+
+ assertEquals(1, changes.size());
+ }
+} \ No newline at end of file
diff --git a/test/com/isode/stroke/avatars/VCardUpdateAvatarManagerTest.java b/test/com/isode/stroke/avatars/VCardUpdateAvatarManagerTest.java
new file mode 100755
index 0000000..1030d5d
--- /dev/null
+++ b/test/com/isode/stroke/avatars/VCardUpdateAvatarManagerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2010-2013 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+/*
+ * Copyright (c) 2015 Tarun Gupta.
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+package com.isode.stroke.avatars;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import org.junit.Before;
+import org.junit.Test;
+import java.util.Vector;
+import com.isode.stroke.elements.VCardUpdate;
+import com.isode.stroke.elements.Presence;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.VCard;
+import com.isode.stroke.avatars.VCardUpdateAvatarManager;
+import com.isode.stroke.avatars.VCardAvatarManager;
+import com.isode.stroke.avatars.AvatarMemoryStorage;
+import com.isode.stroke.vcards.VCardMemoryStorage;
+import com.isode.stroke.vcards.VCardManager;
+import com.isode.stroke.muc.MUCRegistry;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.client.DummyStanzaChannel;
+import com.isode.stroke.crypto.JavaCryptoProvider;
+import com.isode.stroke.crypto.CryptoProvider;
+import com.isode.stroke.stringcodecs.Hexify;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.base.ByteArray;
+import com.isode.stroke.signals.SignalConnection;
+import com.isode.stroke.signals.Slot1;
+
+public class VCardUpdateAvatarManagerTest {
+
+ private class DummyMUCRegistry extends MUCRegistry {
+
+ private Vector<JID> mucs_ = new Vector<JID>();
+
+ public boolean isMUC(JID jid) {
+ return mucs_.contains(jid);
+ }
+ }
+
+ private JID ownJID;
+ private DummyStanzaChannel stanzaChannel;
+ private IQRouter iqRouter;
+ private DummyMUCRegistry mucRegistry;
+ private AvatarMemoryStorage avatarStorage;
+ private VCardManager vcardManager;
+ private VCardMemoryStorage vcardStorage;
+ private ByteArray avatar1;
+ private String avatar1Hash;
+ private Vector<JID> changes;
+ private JID user1;
+ private JID user2;
+ private CryptoProvider crypto;
+ private SignalConnection onAvatarChangedConnection;
+
+ @Before
+ public void setUp() {
+ crypto = new JavaCryptoProvider();
+ ownJID = new JID("foo@fum.com/bum");
+ stanzaChannel = new DummyStanzaChannel();
+ stanzaChannel.setAvailable(true);
+ iqRouter = new IQRouter(stanzaChannel);
+ mucRegistry = new DummyMUCRegistry();
+ avatarStorage = new AvatarMemoryStorage();
+ vcardStorage = new VCardMemoryStorage(crypto);
+ vcardManager = new VCardManager(ownJID, iqRouter, vcardStorage);
+ avatar1 = new ByteArray("abcdefg");
+ avatar1Hash = Hexify.hexify(crypto.getSHA1Hash(avatar1));
+ changes = new Vector<JID>();
+ user1 = new JID("user1@bar.com/bla");
+ user2 = new JID("user2@foo.com/baz");
+ }
+
+ private VCardUpdateAvatarManager createManager() {
+ VCardUpdateAvatarManager result = new VCardUpdateAvatarManager(vcardManager, stanzaChannel, avatarStorage, crypto, mucRegistry);
+ onAvatarChangedConnection = result.onAvatarChanged.connect(new Slot1<JID>() {
+
+ public void call(JID j1) {
+ handleAvatarChanged(j1);
+ }
+ });
+ return result;
+ }
+
+ private Presence createPresenceWithPhotoHash(JID jid, String hash) {
+ Presence presence = new Presence();
+ presence.setFrom(jid);
+ presence.addPayload(new VCardUpdate(hash));
+ return presence;
+ }
+
+ private IQ createVCardResult(ByteArray avatar) {
+ VCard vcard = new VCard();
+ if (!avatar.isEmpty()) {
+ vcard.setPhoto(avatar);
+ }
+ return IQ.createResult(new JID("baz@fum.com"), stanzaChannel.sentStanzas.get(0).getTo(), stanzaChannel.sentStanzas.get(0).getID(), vcard);
+ }
+
+ private void handleAvatarChanged(JID jid) {
+ changes.add(jid);
+ }
+
+ @Test
+ public void testUpdate_NewHashNewVCardRequestsVCard() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+
+ assertEquals(1, stanzaChannel.sentStanzas.size());
+ assertTrue(stanzaChannel.isRequestAtIndex(0, user1.toBare(), IQ.Type.Get, new VCard()));
+ }
+
+ @Test
+ public void testUpdate_NewHashStoresAvatarAndEmitsNotificationOnVCardReceive() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+ stanzaChannel.onIQReceived.emit(createVCardResult(avatar1));
+
+ assertEquals(1, changes.size());
+ assertEquals(user1.toBare(), changes.get(0));
+ String hash = testling.getAvatarHash(user1.toBare());
+ assertNotNull(hash);
+ assertEquals(avatar1Hash, hash);
+ assertTrue(avatarStorage.hasAvatar(avatar1Hash));
+ assertEquals(avatar1, avatarStorage.getAvatar(avatar1Hash));
+ }
+
+ @Test
+ public void testUpdate_KnownHash() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+ stanzaChannel.onIQReceived.emit(createVCardResult(avatar1));
+ changes.clear();
+ stanzaChannel.sentStanzas.clear();
+
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+
+ assertEquals(0, stanzaChannel.sentStanzas.size());
+ assertEquals(0, changes.size());
+ }
+
+ @Test
+ public void testUpdate_KnownHashFromDifferentUserDoesNotRequestVCardButTriggersNotification() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+ stanzaChannel.onIQReceived.emit(createVCardResult(avatar1));
+ changes.clear();
+ stanzaChannel.sentStanzas.clear();
+
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user2, avatar1Hash));
+ assertEquals(0, stanzaChannel.sentStanzas.size());
+ assertEquals(1, changes.size());
+ assertEquals(user2.toBare(), changes.get(0));
+ String hash = testling.getAvatarHash(user2.toBare());
+ assertNotNull(hash);
+ assertEquals(avatar1Hash, hash);
+ }
+
+
+ @Test
+ public void testVCardWithEmptyPhoto() {
+ VCardUpdateAvatarManager testling = createManager();
+ vcardManager.requestVCard(new JID("foo@bar.com"));
+ stanzaChannel.onIQReceived.emit(createVCardResult(new ByteArray()));
+
+ assertTrue(!avatarStorage.hasAvatar(Hexify.hexify(crypto.getSHA1Hash(new ByteArray()))));
+ String hash = testling.getAvatarHash(new JID("foo@bar.com"));
+ assertNotNull(hash);
+ assertEquals("", hash);
+ }
+
+ @Test
+ public void testStanzaChannelReset_ClearsHash() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+ stanzaChannel.onIQReceived.emit(createVCardResult(avatar1));
+ changes.clear();
+ stanzaChannel.sentStanzas.clear();
+
+ stanzaChannel.setAvailable(false);
+ stanzaChannel.setAvailable(true);
+
+ assertEquals(1, changes.size());
+ assertEquals(user1.toBare(), changes.get(0));
+ String hash = testling.getAvatarHash(user1.toBare());
+ assertNull(hash);
+ }
+
+ @Test
+ public void testStanzaChannelReset_ReceiveHashAfterResetUpdatesHash() {
+ VCardUpdateAvatarManager testling = createManager();
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+ stanzaChannel.onIQReceived.emit(createVCardResult(avatar1));
+ changes.clear();
+ stanzaChannel.sentStanzas.clear();
+
+ stanzaChannel.setAvailable(false);
+ stanzaChannel.setAvailable(true);
+ stanzaChannel.onPresenceReceived.emit(createPresenceWithPhotoHash(user1, avatar1Hash));
+
+ assertEquals(2, changes.size());
+ assertEquals(user1.toBare(), changes.get(1));
+ String hash = testling.getAvatarHash(user1.toBare());
+ assertNotNull(hash);
+ assertEquals(avatar1Hash, hash);
+ }
+} \ No newline at end of file