From 7ff890e513212f1cf03667e4ea7e429431d811c4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 11 Apr 2016 22:20:32 +0200 Subject: republish avatar if server offers non-persistent pep :-( --- .../conversations/persistance/FileBackend.java | 38 ++++++++ .../conversations/services/AvatarService.java | 23 ++++- .../services/XmppConnectionService.java | 104 ++++++++++++++++----- .../siacs/conversations/xmpp/XmppConnection.java | 16 ++-- 4 files changed, 144 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 0861a3a8..0d770fef 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/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(); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 276be10d..bb8ec5f2 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/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); + } + } + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2aaf9b62..117ab861 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/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 callback) { + public void publishAvatar(Account account, Uri image, UiCallback 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 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) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7bc08e9c..4dc5492e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/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"); } } -- cgit v1.2.3