diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d777e5cc..e9e73db9 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -22,6 +22,8 @@ public final class Config { public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb + public static final long MAX_HISTORY_AGE = 7 * 24 * 60 * 60 * 1000; + private Config() { } diff --git a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java index 92b8a729..957b0a14 100644 --- a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java +++ b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java @@ -17,5 +17,4 @@ public abstract class AbstractEntity { public boolean equals(AbstractEntity entity) { return this.getUuid().equals(entity.getUuid()); } - } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 725ed27b..63f341e7 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -16,6 +16,8 @@ import org.json.JSONObject; import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -471,12 +473,25 @@ public class Conversation extends AbstractEntity { } } - public void setLastMessageReceived(long value) { + public boolean setLastMessageReceived(long value) { + long before = getLastMessageReceived(); this.setAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED, String.valueOf(value)); + return (value - before > 1000); } public long getLastMessageReceived() { - return getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + if (timestamp == 0) { + synchronized (this.messages) { + for(int i = this.messages.size() - 1; i >= 0; --i) { + Message message = this.messages.get(i); + if (message.getStatus() == Message.STATUS_RECEIVED) { + return message.getTimeSent(); + } + } + } + } + return timestamp; } public void setMutedTill(long value) { @@ -544,6 +559,26 @@ public class Conversation extends AbstractEntity { } } + public void sort() { + synchronized (this.messages) { + for(Message message : this.messages) { + message.untie(); + } + Collections.sort(this.messages,new Comparator() { + @Override + public int compare(Message left, Message right) { + if (left.getTimeSent() < right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() > right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + }); + } + } + public class Smp { public static final int STATUS_NONE = 0; public static final int STATUS_CONTACT_REQUESTED = 1; diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 47861d06..1213f66a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -493,6 +493,11 @@ public class Message extends AbstractEntity { } } + public void untie() { + this.mNextMessage = null; + this.mPreviousMessage = null; + } + public class ImageParams { public URL url; public long size = 0; diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index b902db51..3714ac90 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -549,8 +549,11 @@ public class MessageParser extends AbstractParser implements } Conversation conversation = message.getConversation(); conversation.add(message); - conversation.setLastMessageReceived(System.currentTimeMillis()); - mXmppConnectionService.updateConversation(conversation); + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { + if (conversation.setLastMessageReceived(System.currentTimeMillis())) { + mXmppConnectionService.updateConversation(conversation); + } + } if (message.getStatus() == Message.STATUS_RECEIVED && conversation.getOtrSession() != null diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 4f47cdbe..c77262cb 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -4,16 +4,18 @@ import android.util.Log; import java.math.BigInteger; import java.util.HashSet; +import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class MessageArchiveService { +public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private final XmppConnectionService mXmppConnectionService; @@ -28,18 +30,31 @@ public class MessageArchiveService { final Account account = conversation.getAccount(); long start = conversation.getLastMessageReceived(); long end = account.getXmppConnection().getLastSessionEstablished(); + if (end - start >= Config.MAX_HISTORY_AGE) { + start = end - Config.MAX_HISTORY_AGE; + } final Query query = new Query(conversation, start, end); this.queries.add(query); IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, packet.toString()); + if (packet.getType() == IqPacket.TYPE_ERROR) { + finalizeQuery(query); + } } }); } } + private void finalizeQuery(Query query) { + synchronized (this.queries) { + this.queries.remove(query); + } + query.getConversation().sort(); + this.mXmppConnectionService.updateConversationUi(); + } + public void processFin(Element fin) { if (fin == null) { return; @@ -48,27 +63,26 @@ public class MessageArchiveService { if (query == null) { return; } - Log.d(Config.LOGTAG,"fin "+fin.toString()); boolean complete = fin.getAttributeAsBoolean("complete"); Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); Element last = set == null ? null : set.findChild("last"); if (complete || last == null) { - Log.d(Config.LOGTAG,"completed mam query for "+query.getWith().toString()); - synchronized (this.queries) { - this.queries.remove(query); - } + final Account account = query.getConversation().getAccount(); + Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": completed mam query for "+query.getWith().toString()); + this.finalizeQuery(query); } else { - Query nextQuery = query.next(last == null ? null : last.getContent()); + final Query nextQuery = query.next(last == null ? null : last.getContent()); IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery); synchronized (this.queries) { this.queries.remove(query); this.queries.add(nextQuery); } - Log.d(Config.LOGTAG,packet.toString()); this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG,packet.toString()); + if (packet.getType() == IqPacket.TYPE_ERROR) { + finalizeQuery(nextQuery); + } } }); } @@ -88,6 +102,20 @@ public class MessageArchiveService { } } + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + List conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { + this.query(conversation); + } + } + } else { + Log.d(Config.LOGTAG,"no mam available"); + } + } + public class Query { private long start; private long end; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 9d73868c..3e2c1b8b 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -73,7 +73,7 @@ import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesAvailable; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -205,12 +205,7 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); } }; - private OnAdvancedStreamFeaturesAvailable onAdvancedStreamFeaturesAvailable = new OnAdvancedStreamFeaturesAvailable() { - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - queryMessagesFromArchive(account); - } - }; + private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; private int rosterChangedListenerCount = 0; @@ -592,7 +587,7 @@ public class XmppConnectionService extends Service { connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); - connection.setOnAdvancedStreamFeaturesAvailableListener(this.onAdvancedStreamFeaturesAvailable); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); return connection; } @@ -1027,6 +1022,7 @@ public class XmppConnectionService extends Service { } this.databaseBackend.createConversation(conversation); } + this.mMessageArchiveService.query(conversation); this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1239,19 +1235,6 @@ public class XmppConnectionService extends Service { } } - private void queryMessagesFromArchive(final Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { - this.mMessageArchiveService.query(conversation); - } - } - } else { - Log.d(Config.LOGTAG,"no mam available"); - } - } - public void joinMuc(Conversation conversation) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java similarity index 75% rename from src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java rename to src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java index a41bce86..e45eba73 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java @@ -2,6 +2,6 @@ package eu.siacs.conversations.xmpp; import eu.siacs.conversations.entities.Account; -public interface OnAdvancedStreamFeaturesAvailable { +public interface OnAdvancedStreamFeaturesLoaded { public void onAdvancedStreamFeaturesAvailable(final Account account); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 0e5d26ed..af0499c6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -107,7 +107,7 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; - private OnAdvancedStreamFeaturesAvailable advancedStreamFeaturesAvailableListener = null; + private ArrayList advancedStreamFeaturesLoadedListeners = new ArrayList<>(); private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; @@ -772,8 +772,8 @@ public class XmppConnection implements Runnable { if (account.getServer().equals(server.toDomainJid())) { enableAdvancedStreamFeatures(); - if (advancedStreamFeaturesAvailableListener != null) { - advancedStreamFeaturesAvailableListener.onAdvancedStreamFeaturesAvailable(account); + for(OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); } } } @@ -947,8 +947,10 @@ public class XmppConnection implements Runnable { this.acknowledgedListener = listener; } - public void setOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesAvailable listener) { - this.advancedStreamFeaturesAvailableListener = listener; + public void addOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesLoaded listener) { + if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) { + this.advancedStreamFeaturesLoadedListeners.add(listener); + } } public void disconnect(boolean force) { @@ -1095,6 +1097,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); } + public boolean advancedStreamFeaturesLoaded() { + return disco.containsKey(account.getServer().toString()); + } + public boolean rosterVersioning() { return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); }