republish avatar if server offers non-persistent pep :-(

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/persistance/FileBackend.java        |  38 
src/main/java/eu/siacs/conversations/services/AvatarService.java         |  23 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 104 
src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java            |  16 
4 files changed, 144 insertions(+), 37 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -402,6 +403,43 @@ public class FileBackend {
 		}
 	}
 
+	public Avatar getStoredPepAvatar(String hash) {
+		if (hash == null) {
+			return null;
+		}
+		Avatar avatar = new Avatar();
+		File file = new File(getAvatarPath(hash));
+		FileInputStream is = null;
+		try {
+			BitmapFactory.Options options = new BitmapFactory.Options();
+			options.inJustDecodeBounds = true;
+			BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+			is = new FileInputStream(file);
+			ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
+			Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+			MessageDigest digest = MessageDigest.getInstance("SHA-1");
+			DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
+			byte[] buffer = new byte[4096];
+			int length;
+			while ((length = is.read(buffer)) > 0) {
+				os.write(buffer, 0, length);
+			}
+			os.flush();
+			os.close();
+			avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
+			avatar.image = new String(mByteArrayOutputStream.toByteArray());
+			avatar.height = options.outHeight;
+			avatar.width = options.outWidth;
+			return avatar;
+		} catch (IOException e) {
+			return null;
+		} catch (NoSuchAlgorithmException e) {
+			return null;
+		} finally {
+			close(is);
+		}
+	}
+
 	public boolean isAvatarCached(Avatar avatar) {
 		File file = new File(getAvatarPath(avatar.getFilename()));
 		return file.exists();

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

@@ -6,11 +6,13 @@ import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.net.Uri;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Bookmark;
 import eu.siacs.conversations.entities.Contact;
@@ -19,8 +21,10 @@ import eu.siacs.conversations.entities.ListItem;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
+import eu.siacs.conversations.xmpp.XmppConnection;
 
-public class AvatarService {
+public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private static final int FG_COLOR = 0xFFFAFAFA;
 	private static final int TRANSPARENT = 0x00000000;
@@ -227,8 +231,7 @@ public class AvatarService {
 		if (avatar != null || cachedOnly) {
 			return avatar;
 		}
-		avatar = mXmppConnectionService.getFileBackend().getAvatar(
-				account.getAvatar(), size);
+		avatar = mXmppConnectionService.getFileBackend().getAvatar(account.getAvatar(), size);
 		if (avatar == null) {
 			avatar = get(account.getJid().toBareJid().toString(), size,false);
 		}
@@ -387,10 +390,20 @@ public class AvatarService {
 		return false;
 	}
 
-	private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop,
-						  int dstright, int dstbottom) {
+	private boolean drawTile(Canvas canvas, Bitmap bm, int dstleft, int dsttop, int dstright, int dstbottom) {
 		Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom);
 		canvas.drawBitmap(bm, null, dst, null);
 		return true;
 	}
+
+	@Override
+	public void onAdvancedStreamFeaturesAvailable(Account account) {
+		XmppConnection.Features features = account.getXmppConnection().getFeatures();
+		if (features.pep() && !features.pepPersistent()) {
+			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": has pep but is not persistent");
+			if (account.getAvatar() != null) {
+				mXmppConnectionService.republishAvatarIfNeeded(account);
+			}
+		}
+	}
 }

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

@@ -840,6 +840,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		connection.setOnBindListener(this.mOnBindListener);
 		connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
 		connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
+		connection.addOnAdvancedStreamFeaturesAvailableListener(this.mAvatarService);
 		AxolotlService axolotlService = account.getAxolotlService();
 		if (axolotlService != null) {
 			connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
@@ -2338,9 +2339,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	}
 
-	public void publishAvatar(final Account account,
-							  final Uri image,
-							  final UiCallback<Avatar> callback) {
+	public void publishAvatar(Account account, Uri image, UiCallback<Avatar> callback) {
 		final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
 		final int size = Config.AVATAR_SIZE;
 		final Avatar avatar = getFileBackend().getPepAvatar(image, size, format);
@@ -2358,40 +2357,96 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 				callback.error(R.string.error_saving_avatar, avatar);
 				return;
 			}
-			final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
-			this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+			publishAvatar(account, avatar, callback);
+		} else {
+			callback.error(R.string.error_publish_avatar_converting, null);
+		}
+	}
 
-				@Override
-				public void onIqPacketReceived(Account account, IqPacket result) {
-					if (result.getType() == IqPacket.TYPE.RESULT) {
-						final IqPacket packet = XmppConnectionService.this.mIqGenerator
-								.publishAvatarMetadata(avatar);
-						sendIqPacket(account, packet, new OnIqPacketReceived() {
-							@Override
-							public void onIqPacketReceived(Account account, IqPacket result) {
-								if (result.getType() == IqPacket.TYPE.RESULT) {
-									if (account.setAvatar(avatar.getFilename())) {
-										getAvatarService().clear(account);
-										databaseBackend.updateAccount(account);
-									}
+	public void publishAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+		final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
+		this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+			@Override
+			public void onIqPacketReceived(Account account, IqPacket result) {
+				if (result.getType() == IqPacket.TYPE.RESULT) {
+					final IqPacket packet = XmppConnectionService.this.mIqGenerator
+							.publishAvatarMetadata(avatar);
+					sendIqPacket(account, packet, new OnIqPacketReceived() {
+						@Override
+						public void onIqPacketReceived(Account account, IqPacket result) {
+							if (result.getType() == IqPacket.TYPE.RESULT) {
+								if (account.setAvatar(avatar.getFilename())) {
+									getAvatarService().clear(account);
+									databaseBackend.updateAccount(account);
+								}
+								if (callback != null) {
 									callback.success(avatar);
 								} else {
+									Log.d(Config.LOGTAG,account.getJid().toBareJid()+": published avatar");
+								}
+							} else {
+								if (callback != null) {
 									callback.error(
 											R.string.error_publish_avatar_server_reject,
 											avatar);
 								}
 							}
-						});
-					} else {
+						}
+					});
+				} else {
+					if (callback != null) {
 						callback.error(
 								R.string.error_publish_avatar_server_reject,
 								avatar);
 					}
 				}
-			});
-		} else {
-			callback.error(R.string.error_publish_avatar_converting, null);
+			}
+		});
+	}
+
+	public void republishAvatarIfNeeded(Account account) {
+		if (account.getAxolotlService().isPepBroken()) {
+			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping republication of avatar because pep is broken");
+			return;
 		}
+		IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
+		this.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+			private Avatar parseAvatar(IqPacket packet) {
+				Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub");
+				if (pubsub != null) {
+					Element items = pubsub.findChild("items");
+					if (items != null) {
+						return Avatar.parseMetadata(items);
+					}
+				}
+				return null;
+			}
+
+			private boolean errorIsItemNotFound(IqPacket packet) {
+				Element error = packet.findChild("error");
+				return packet.getType() == IqPacket.TYPE.ERROR
+						&& error != null
+						&& error.hasChild("item-not-found");
+			}
+
+			@Override
+			public void onIqPacketReceived(Account account, IqPacket packet) {
+				if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) {
+					Avatar serverAvatar = parseAvatar(packet);
+					if (serverAvatar == null && account.getAvatar() != null) {
+						Avatar avatar = fileBackend.getStoredPepAvatar(account.getAvatar());
+						if (avatar != null) {
+							Log.d(Config.LOGTAG,account.getJid().toBareJid()+": avatar on server was null. republishing");
+							publishAvatar(account, fileBackend.getStoredPepAvatar(account.getAvatar()), null);
+						} else {
+							Log.e(Config.LOGTAG, account.getJid().toBareJid()+": error rereading avatar");
+						}
+					}
+				}
+			}
+		});
 	}
 
 	public void fetchAvatar(Account account, Avatar avatar) {
@@ -2526,8 +2581,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
 				if (packet.getType() == IqPacket.TYPE.RESULT) {
-					Element pubsub = packet.findChild("pubsub",
-							"http://jabber.org/protocol/pubsub");
+					Element pubsub = packet.findChild("pubsub","http://jabber.org/protocol/pubsub");
 					if (pubsub != null) {
 						Element items = pubsub.findChild("items");
 						if (items != null) {

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

@@ -1530,13 +1530,15 @@ public class XmppConnection implements Runnable {
 
 		public boolean pep() {
 			synchronized (XmppConnection.this.disco) {
-				ServiceDiscoveryResult info = disco.get(account.getServer());
-				if (info != null && info.hasIdentity("pubsub", "pep")) {
-					return true;
-				} else {
-					info = disco.get(account.getJid().toBareJid());
-					return info != null && info.hasIdentity("pubsub", "pep");
-				}
+				ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
+				return info != null && info.hasIdentity("pubsub", "pep");
+			}
+		}
+
+		public boolean pepPersistent() {
+			synchronized (XmppConnection.this.disco) {
+				ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid());
+				return info != null && info.getFeatures().contains("http://jabber.org/protocol/pubsub#persistent-items");
 			}
 		}