use mam reference instead of timestamp

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Conversation.java          | 28 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  4 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java    | 15 
src/main/java/eu/siacs/conversations/services/MessageArchiveService.java | 88 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 17 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        |  2 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java      |  7 
src/main/java/eu/siacs/conversations/xmpp/mam/MamReference.java          | 69 
8 files changed, 155 insertions(+), 75 deletions(-)

Detailed changes

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<Conversation> {
@@ -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<Jid> 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) {

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);

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<Long, String> 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<Long,String> 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) {

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<Long,String> lastMessageReceived = mXmppConnectionService.databaseBackend.getLastMessageReceived(account);
-		final Pair<Long,String> 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<Conversation> 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());

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() {

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) {

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<Message> implements CopyTextView.CopyHandler {
 
@@ -561,16 +562,16 @@ public class MessageAdapter extends ArrayAdapter<Message> 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 {

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);
+    }
+}