diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 3c57f5630..40a86ad44 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -30,6 +30,7 @@ import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.mam.MamReference; public class Conversation extends AbstractEntity implements Blockable, Comparable { @@ -349,12 +350,16 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl return this.mFirstMamReference; } - public void setLastClearHistory(long time) { - setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY,String.valueOf(time)); + public void setLastClearHistory(long time,String reference) { + if (reference != null) { + setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, String.valueOf(time) + ":" + reference); + } else { + setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, String.valueOf(time)); + } } - public long getLastClearHistory() { - return getLongAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, 0); + public MamReference getLastClearHistory() { + return MamReference.fromAttribute(getAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY)); } public List getAcceptedCryptoTargets() { @@ -468,11 +473,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl if (this.messages.size() == 0) { Message message = new Message(this, "", Message.ENCRYPTION_NONE); message.setType(Message.TYPE_STATUS); - message.setTime(Math.max(getCreated(),getLastClearHistory())); + message.setTime(Math.max(getCreated(),getLastClearHistory().getTimestamp())); return message; } else { - Message message = this.messages.get(this.messages.size() - 1); - return message; + return this.messages.get(this.messages.size() - 1); } } @@ -819,19 +823,19 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl } } - public long getLastMessageTransmitted() { - final long last_clear = getLastClearHistory(); - long last_received = 0; + public MamReference getLastMessageTransmitted() { + final MamReference lastClear = getLastClearHistory(); + MamReference lastReceived = new MamReference(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 || message.isCarbon()) { - last_received = message.getTimeSent(); + lastReceived = new MamReference(message.getTimeSent(),message.getServerMsgId()); break; } } } - return Math.max(last_clear,last_received); + return MamReference.max(lastClear,lastReceived); } public void setMutedTill(long value) { diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 246430c59..2f8b66df9 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -246,7 +246,9 @@ public class IqGenerator extends AbstractGenerator { } else if (mam.getWith()!=null) { data.put("with", mam.getWith().toString()); } - data.put("start", getTimestamp(mam.getStart())); + if (mam.getStart() != 0) { + data.put("start", getTimestamp(mam.getStart())); + } data.put("end", getTimestamp(mam.getEnd())); data.submit(); query.addChild(data); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 6822ab42e..005975ebf 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -52,6 +52,7 @@ import eu.siacs.conversations.entities.ServiceDiscoveryResult; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.mam.MamReference; public class DatabaseBackend extends SQLiteOpenHelper { @@ -830,7 +831,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return db.delete(Message.TABLENAME,where,whereArgs) > 0; } - public Pair getLastMessageReceived(Account account) { + public MamReference getLastMessageReceived(Account account) { Cursor cursor = null; try { SQLiteDatabase db = this.getReadableDatabase(); @@ -841,7 +842,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return null; } else { cursor.moveToFirst(); - return new Pair<>(cursor.getLong(0), cursor.getString(1)); + return new MamReference(cursor.getLong(0), cursor.getString(1)); } } catch (Exception e) { return null; @@ -866,23 +867,23 @@ public class DatabaseBackend extends SQLiteOpenHelper { return time; } - public Pair getLastClearDate(Account account) { + public MamReference getLastClearDate(Account account) { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {Conversation.ATTRIBUTES}; String selection = Conversation.ACCOUNT + "=?"; String[] args = {account.getUuid()}; Cursor cursor = db.query(Conversation.TABLENAME,columns,selection,args,null,null,null); - long maxClearDate = 0; + MamReference maxClearDate = new MamReference(0); while (cursor.moveToNext()) { try { - final JSONObject jsonObject = new JSONObject(cursor.getString(0)); - maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY)); + final JSONObject o = new JSONObject(cursor.getString(0)); + maxClearDate = MamReference.max(maxClearDate, MamReference.fromAttribute(o.getString(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY))); } catch (Exception e) { //ignored } } cursor.close(); - return new Pair<>(maxClearDate,null); + return maxClearDate; } private Cursor getCursorForSession(Account account, AxolotlAddress contact) { diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index d9577ea70..f861b23e0 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -19,6 +19,7 @@ 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.mam.MamReference; import eu.siacs.conversations.xmpp.stanzas.IqPacket; public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @@ -46,34 +47,26 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } } - final Pair lastMessageReceived = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); - final Pair lastClearDate = mXmppConnectionService.databaseBackend.getLastClearDate(account); - long startCatchup; - final String reference; - if (lastMessageReceived != null && lastMessageReceived.first >= lastClearDate.first) { - startCatchup = lastMessageReceived.first; - reference = lastMessageReceived.second; - } else { - startCatchup = lastClearDate.first; - reference = null; - } - startCatchup = Math.max(startCatchup,mXmppConnectionService.getAutomaticMessageDeletionDate()); + MamReference mamReference = MamReference.max( + mXmppConnectionService.databaseBackend.getLastMessageReceived(account), + mXmppConnectionService.databaseBackend.getLastClearDate(account) + ); + mamReference = MamReference.max(mamReference,mXmppConnectionService.getAutomaticMessageDeletionDate()); long endCatchup = account.getXmppConnection().getLastSessionEstablished(); final Query query; - if (startCatchup == 0) { + if (mamReference.getTimestamp() == 0) { return; - } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { - startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; + } else if (endCatchup - mamReference.getTimestamp() >= Config.MAM_MAX_CATCHUP) { + long startCatchup = endCatchup - Config.MAM_MAX_CATCHUP; List conversations = mXmppConnectionService.getConversations(); for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted().getTimestamp()) { this.query(conversation,startCatchup,true); } } - query = new Query(account, startCatchup, endCatchup); + query = new Query(account, new MamReference(startCatchup), endCatchup); } else { - query = new Query(account, startCatchup, endCatchup); - query.reference = reference; + query = new Query(account, mamReference, endCatchup); } synchronized (this.queries) { this.queries.add(query); @@ -82,9 +75,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public void catchupMUC(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { query(conversation, - 0, + new MamReference(0), System.currentTimeMillis(), true); } else { @@ -96,9 +89,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } public Query query(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + if (conversation.getLastMessageTransmitted().getTimestamp() < 0 && conversation.countMessages() == 0) { return query(conversation, - 0, + new MamReference(0), System.currentTimeMillis(), false); } else { @@ -113,23 +106,27 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return this.query(conversation,conversation.getLastMessageTransmitted(),end, allowCatchup); } - public Query query(Conversation conversation, long start, long end, boolean allowCatchup) { + public Query query(Conversation conversation, MamReference start, long end, boolean allowCatchup) { synchronized (this.queries) { final Query query; - final long startActual = Math.max(start,mXmppConnectionService.getAutomaticMessageDeletionDate()); - if (start==0) { + final MamReference startActual = MamReference.max(start,mXmppConnectionService.getAutomaticMessageDeletionDate()); + if (start.getTimestamp() == 0) { query = new Query(conversation, startActual, end, false); query.reference = conversation.getFirstMamReference(); } else { - long maxCatchup = Math.max(startActual,System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); - if (maxCatchup > startActual) { - Query reverseCatchup = new Query(conversation,startActual,maxCatchup,false); - this.queries.add(reverseCatchup); - this.execute(reverseCatchup); + if (allowCatchup) { + MamReference maxCatchup = MamReference.max(startActual, System.currentTimeMillis() - Config.MAM_MAX_CATCHUP); + if (maxCatchup.greaterThan(startActual)) { + Query reverseCatchup = new Query(conversation, startActual, maxCatchup.getTimestamp(), false); + this.queries.add(reverseCatchup); + this.execute(reverseCatchup); + } + query = new Query(conversation, maxCatchup, end, allowCatchup); + } else { + query = new Query(conversation, startActual, end, false); } - query = new Query(conversation, maxCatchup, end, allowCatchup); } - if (start > end) { + if (start.greaterThan(end)) { return null; } this.queries.add(query); @@ -305,27 +302,26 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private boolean catchup = true; - public Query(Conversation conversation, long start, long end) { - this(conversation.getAccount(), start, end); + public Query(Conversation conversation, MamReference start, long end, boolean catchup) { + this(conversation.getAccount(),catchup ? start : start.timeOnly(),end); this.conversation = conversation; - } - - public Query(Conversation conversation, long start, long end, boolean catchup) { - this(conversation,start,end); this.pagingOrder = catchup ? PagingOrder.NORMAL : PagingOrder.REVERSE; this.catchup = catchup; } - public Query(Account account, long start, long end) { + public Query(Account account, MamReference start, long end) { this.account = account; - this.start = start; + if (start.getReference() != null) { + this.reference = start.getReference(); + } else { + this.start = start.getTimestamp(); + } this.end = end; this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } private Query page(String reference) { - Query query = new Query(this.account,this.start,this.end); - query.reference = reference; + Query query = new Query(this.account,new MamReference(this.start,reference),this.end); query.conversation = conversation; query.totalCount = totalCount; query.actualCount = actualCount; @@ -445,8 +441,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { builder.append(getWith().toString()); } } - builder.append(", start="); - builder.append(AbstractGenerator.getTimestamp(this.start)); + if (this.start != 0) { + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + } builder.append(", end="); builder.append(AbstractGenerator.getTimestamp(this.end)); builder.append(", order="+pagingOrder.toString()); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2f3318ff3..41a974974 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -135,6 +135,7 @@ import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived; import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket; +import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -1644,10 +1645,10 @@ public class XmppConnectionService extends Service { callback.onMoreMessagesLoaded(messages.size(), conversation); } else if (conversation.hasMessagesLeftOnServer() && account.isOnlineAndConnected() - && conversation.getLastClearHistory() == 0) { + && conversation.getLastClearHistory().getTimestamp() == 0) { if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam()) || (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) { - MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp, false); + MessageArchiveService.Query query = getMessageArchiveService().query(conversation, new MamReference(0), timestamp, false); if (query != null) { query.setCallback(callback); callback.informUser(R.string.fetching_history_from_server); @@ -2253,7 +2254,7 @@ public class XmppConnectionService extends Service { x.addChild("history").setAttribute("maxchars", "0"); } else { // Fallback to muc history - x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); + x.addChild("history").setAttribute("since", PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted().getTimestamp())); } sendPresencePacket(account, packet); if (onConferenceJoined != null) { @@ -3664,15 +3665,19 @@ public class XmppConnectionService extends Service { } public void clearConversationHistory(final Conversation conversation) { - long clearDate; + final long clearDate; + final String reference; if (conversation.countMessages() > 0) { - clearDate = conversation.getLatestMessage().getTimeSent() + 1000; + Message latestMessage = conversation.getLatestMessage(); + clearDate = latestMessage.getTimeSent() + 1000; + reference = latestMessage.getServerMsgId(); } else { clearDate = System.currentTimeMillis(); + reference = null; } conversation.clearMessages(); conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam - conversation.setLastClearHistory(clearDate); + conversation.setLastClearHistory(clearDate,reference); Runnable runnable = new Runnable() { @Override public void run() { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index d5a765d81..adb097c0d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1350,7 +1350,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa private boolean showLoadMoreMessages(final Conversation c) { final boolean mam = hasMamSupport(c); final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService(); - return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.messagesLoaded.get() && c.hasMessagesLeftOnServer() && !service.queryInProgress(c))); + return mam && (c.getLastClearHistory().getTimestamp() != 0 || (c.countMessages() == 0 && c.messagesLoaded.get() && c.hasMessagesLeftOnServer() && !service.queryInProgress(c))); } private boolean hasMamSupport(final Conversation c) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index adc78aeb8..1a203e9e9 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -65,6 +65,7 @@ import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.Patterns; import eu.siacs.conversations.utils.UIHelper; +import eu.siacs.conversations.xmpp.mam.MamReference; public class MessageAdapter extends ArrayAdapter implements CopyTextView.CopyHandler { @@ -561,16 +562,16 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } private void loadMoreMessages(Conversation conversation) { - conversation.setLastClearHistory(0); + conversation.setLastClearHistory(0,null); activity.xmppConnectionService.updateConversation(conversation); conversation.setHasMessagesLeftOnServer(true); conversation.setFirstMamReference(null); - long timestamp = conversation.getLastMessageTransmitted(); + long timestamp = conversation.getLastMessageTransmitted().getTimestamp(); if (timestamp == 0) { timestamp = System.currentTimeMillis(); } conversation.messagesLoaded.set(true); - MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp, false); + MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(conversation, new MamReference(0), timestamp, false); if (query != null) { Toast.makeText(activity, R.string.fetching_history_from_server, Toast.LENGTH_LONG).show(); } else { diff --git a/src/main/java/eu/siacs/conversations/xmpp/mam/MamReference.java b/src/main/java/eu/siacs/conversations/xmpp/mam/MamReference.java new file mode 100644 index 000000000..02937025d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/mam/MamReference.java @@ -0,0 +1,69 @@ +package eu.siacs.conversations.xmpp.mam; + +public class MamReference { + + private final long timestamp; + private final String reference; + + public MamReference(long timestamp) { + this.timestamp = timestamp; + this.reference = null; + } + + public MamReference(long timestamp, String reference) { + this.timestamp = timestamp; + this.reference = reference; + } + + public long getTimestamp() { + return timestamp; + } + + public String getReference() { + return reference; + } + + public boolean greaterThan(MamReference b) { + return timestamp > b.getTimestamp(); + } + + public boolean greaterThan(long b) { + return timestamp > b; + } + + public static MamReference max(MamReference a, MamReference b) { + if (a != null && b != null) { + return a.timestamp > b.timestamp ? a : b; + } else if (a != null) { + return a; + } else { + return b; + } + } + + public static MamReference max(MamReference a, long b) { + return max(a,new MamReference(b)); + } + + public static MamReference fromAttribute(String attr) { + if (attr == null) { + return new MamReference(0); + } else { + String[] attrs = attr.split(":"); + try { + long timestamp = Long.parseLong(attrs[0]); + if (attrs.length >= 2) { + return new MamReference(timestamp,attrs[1]); + } else { + return new MamReference(timestamp); + } + } catch (Exception e) { + return new MamReference(0); + } + } + } + + public MamReference timeOnly() { + return reference == null ? this : new MamReference(timestamp); + } +}