add DB columns for occupant id and reactions

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Bookmark.java              |  8 
src/main/java/eu/siacs/conversations/entities/IndividualMessage.java     | 51 
src/main/java/eu/siacs/conversations/entities/Message.java               | 73 
src/main/java/eu/siacs/conversations/entities/MucOptions.java            |  6 
src/main/java/eu/siacs/conversations/entities/Reaction.java              | 43 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           | 11 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java    |  8 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  3 
8 files changed, 144 insertions(+), 59 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Bookmark.java 🔗

@@ -9,7 +9,6 @@ import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 
 import java.lang.ref.WeakReference;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -61,11 +60,11 @@ public class Bookmark extends Element implements ListItem {
 		return bookmarks;
 	}
 
-	public static Map<Jid, Bookmark> parseFromPubsub(Element pubsub, Account account) {
-		if (pubsub == null) {
+	public static Map<Jid, Bookmark> parseFromPubSub(final Element pubSub, final Account account) {
+		if (pubSub == null) {
 			return Collections.emptyMap();
 		}
-		final Element items = pubsub.findChild("items");
+		final Element items = pubSub.findChild("items");
 		if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) {
 			final Map<Jid, Bookmark> bookmarks = new HashMap<>();
 			for(Element item : items.getChildren()) {
@@ -99,6 +98,7 @@ public class Bookmark extends Element implements ListItem {
 		}
 		final Bookmark bookmark = new Bookmark(account);
 		bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
+		// TODO verify that we only use bare jids and ignore full jids
 		if (bookmark.jid == null) {
 			return null;
 		}

src/main/java/eu/siacs/conversations/entities/IndividualMessage.java 🔗

@@ -31,6 +31,7 @@ package eu.siacs.conversations.entities;
 
 import android.database.Cursor;
 
+import java.util.Collection;
 import java.util.Set;
 
 import eu.siacs.conversations.ui.adapter.MessageAdapter;
@@ -43,8 +44,8 @@ public class IndividualMessage extends Message {
 		super(conversation);
 	}
 
-	private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean deleted, String bodyLanguage) {
-		super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage);
+	private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean deleted, String bodyLanguage, String occupantId, Collection<Reaction> reactions) {
+		super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, edited, oob, errorMessage, readByMarkers, markable, deleted, bodyLanguage, occupantId, reactions);
 	}
 
 	@Override
@@ -73,7 +74,7 @@ public class IndividualMessage extends Message {
 	public static Message fromCursor(Cursor cursor, Conversational conversation) {
 		Jid jid;
 		try {
-			String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
+			String value = cursor.getString(cursor.getColumnIndexOrThrow(COUNTERPART));
 			if (value != null) {
 				jid = Jid.of(value);
 			} else {
@@ -86,7 +87,7 @@ public class IndividualMessage extends Message {
 		}
 		Jid trueCounterpart;
 		try {
-			String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
+			String value = cursor.getString(cursor.getColumnIndexOrThrow(TRUE_COUNTERPART));
 			if (value != null) {
 				trueCounterpart = Jid.of(value);
 			} else {
@@ -96,28 +97,30 @@ public class IndividualMessage extends Message {
 			trueCounterpart = null;
 		}
 		return new IndividualMessage(conversation,
-				cursor.getString(cursor.getColumnIndex(UUID)),
-				cursor.getString(cursor.getColumnIndex(CONVERSATION)),
+				cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
+				cursor.getString(cursor.getColumnIndexOrThrow(CONVERSATION)),
 				jid,
 				trueCounterpart,
-				cursor.getString(cursor.getColumnIndex(BODY)),
-				cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
-				cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
-				cursor.getInt(cursor.getColumnIndex(STATUS)),
-				cursor.getInt(cursor.getColumnIndex(TYPE)),
-				cursor.getInt(cursor.getColumnIndex(CARBON)) > 0,
-				cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
-				cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
-				cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
-				cursor.getString(cursor.getColumnIndex(FINGERPRINT)),
-				cursor.getInt(cursor.getColumnIndex(READ)) > 0,
-				cursor.getString(cursor.getColumnIndex(EDITED)),
-				cursor.getInt(cursor.getColumnIndex(OOB)) > 0,
-				cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
-				ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
-				cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
-				cursor.getInt(cursor.getColumnIndex(DELETED)) > 0,
-				cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
+				cursor.getString(cursor.getColumnIndexOrThrow(BODY)),
+				cursor.getLong(cursor.getColumnIndexOrThrow(TIME_SENT)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTION)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(CARBON)) > 0,
+				cursor.getString(cursor.getColumnIndexOrThrow(REMOTE_MSG_ID)),
+				cursor.getString(cursor.getColumnIndexOrThrow(RELATIVE_FILE_PATH)),
+				cursor.getString(cursor.getColumnIndexOrThrow(SERVER_MSG_ID)),
+				cursor.getString(cursor.getColumnIndexOrThrow(FINGERPRINT)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(READ)) > 0,
+				cursor.getString(cursor.getColumnIndexOrThrow(EDITED)),
+				cursor.getInt(cursor.getColumnIndexOrThrow(OOB)) > 0,
+				cursor.getString(cursor.getColumnIndexOrThrow(ERROR_MESSAGE)),
+				ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndexOrThrow(READ_BY_MARKERS))),
+				cursor.getInt(cursor.getColumnIndexOrThrow(MARKABLE)) > 0,
+				cursor.getInt(cursor.getColumnIndexOrThrow(DELETED)) > 0,
+				cursor.getString(cursor.getColumnIndexOrThrow(BODY_LANGUAGE)),
+				cursor.getString(cursor.getColumnIndexOrThrow(OCCUPANT_ID)),
+				Reaction.fromString(cursor.getString(cursor.getColumnIndexOrThrow(REACTIONS)))
 		);
 	}
 }

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -16,6 +16,8 @@ import org.json.JSONException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -86,6 +88,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     public static final String READ_BY_MARKERS = "readByMarkers";
     public static final String MARKABLE = "markable";
     public static final String DELETED = "deleted";
+    public static final String OCCUPANT_ID = "occupantId";
+    public static final String REACTIONS = "reactions";
     public static final String ME_COMMAND = "/me ";
 
     public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled";
@@ -117,6 +121,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
     private String axolotlFingerprint = null;
     private String errorMessage = null;
     private Set<ReadByMarker> readByMarkers = new CopyOnWriteArraySet<>();
+    private String occupantId;
+    private Collection<Reaction> reactions = Collections.emptyList();
 
     private Boolean isGeoUri = null;
     private Boolean isEmojisOnly = null;
@@ -155,7 +161,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 null,
                 false,
                 false,
-                null);
+                null,
+                null,
+                Collections.emptyList());
     }
 
     public Message(Conversation conversation, int status, int type, final String remoteMsgId) {
@@ -180,7 +188,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                 null,
                 false,
                 false,
-                null);
+                null,
+                null,
+                Collections.emptyList());
     }
 
     protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
@@ -189,7 +199,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
                       final String remoteMsgId, final String relativeFilePath,
                       final String serverMsgId, final String fingerprint, final boolean read,
                       final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
-                      final boolean markable, final boolean deleted, final String bodyLanguage) {
+                      final boolean markable, final boolean deleted, final String bodyLanguage, final String occupantId, final Collection<Reaction> reactions) {
         this.conversation = conversation;
         this.uuid = uuid;
         this.conversationUuid = conversationUUid;
@@ -213,32 +223,37 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         this.markable = markable;
         this.deleted = deleted;
         this.bodyLanguage = bodyLanguage;
+        this.occupantId = occupantId;
+        this.reactions = reactions;
     }
 
     public static Message fromCursor(Cursor cursor, Conversation conversation) {
         return new Message(conversation,
-                cursor.getString(cursor.getColumnIndex(UUID)),
-                cursor.getString(cursor.getColumnIndex(CONVERSATION)),
-                fromString(cursor.getString(cursor.getColumnIndex(COUNTERPART))),
-                fromString(cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART))),
-                cursor.getString(cursor.getColumnIndex(BODY)),
-                cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
-                cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
-                cursor.getInt(cursor.getColumnIndex(STATUS)),
-                cursor.getInt(cursor.getColumnIndex(TYPE)),
-                cursor.getInt(cursor.getColumnIndex(CARBON)) > 0,
-                cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
-                cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
-                cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
-                cursor.getString(cursor.getColumnIndex(FINGERPRINT)),
-                cursor.getInt(cursor.getColumnIndex(READ)) > 0,
-                cursor.getString(cursor.getColumnIndex(EDITED)),
-                cursor.getInt(cursor.getColumnIndex(OOB)) > 0,
-                cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
-                ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
-                cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
-                cursor.getInt(cursor.getColumnIndex(DELETED)) > 0,
-                cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
+                cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CONVERSATION)),
+                fromString(cursor.getString(cursor.getColumnIndexOrThrow(COUNTERPART))),
+                fromString(cursor.getString(cursor.getColumnIndexOrThrow(TRUE_COUNTERPART))),
+                cursor.getString(cursor.getColumnIndexOrThrow(BODY)),
+                cursor.getLong(cursor.getColumnIndexOrThrow(TIME_SENT)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTION)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(STATUS)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(CARBON)) > 0,
+                cursor.getString(cursor.getColumnIndexOrThrow(REMOTE_MSG_ID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(RELATIVE_FILE_PATH)),
+                cursor.getString(cursor.getColumnIndexOrThrow(SERVER_MSG_ID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(FINGERPRINT)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(READ)) > 0,
+                cursor.getString(cursor.getColumnIndexOrThrow(EDITED)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(OOB)) > 0,
+                cursor.getString(cursor.getColumnIndexOrThrow(ERROR_MESSAGE)),
+                ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndexOrThrow(READ_BY_MARKERS))),
+                cursor.getInt(cursor.getColumnIndexOrThrow(MARKABLE)) > 0,
+                cursor.getInt(cursor.getColumnIndexOrThrow(DELETED)) > 0,
+                cursor.getString(cursor.getColumnIndexOrThrow(BODY_LANGUAGE)),
+                cursor.getString(cursor.getColumnIndexOrThrow(OCCUPANT_ID)),
+                Reaction.fromString(cursor.getString(cursor.getColumnIndexOrThrow(REACTIONS)))
+
         );
     }
 
@@ -270,7 +285,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
 
     @Override
     public ContentValues getContentValues() {
-        ContentValues values = new ContentValues();
+        final var values = new ContentValues();
         values.put(UUID, uuid);
         values.put(CONVERSATION, conversationUuid);
         if (counterpart == null) {
@@ -305,6 +320,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         values.put(MARKABLE, markable ? 1 : 0);
         values.put(DELETED, deleted ? 1 : 0);
         values.put(BODY_LANGUAGE, bodyLanguage);
+        values.put(OCCUPANT_ID, occupantId);
+        values.put(REACTIONS, Reaction.toString(this.reactions));
         return values;
     }
 
@@ -708,6 +725,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
         return oob;
     }
 
+    public void setOccupantId(final String id) {
+        this.occupantId = id;
+    }
+
     public static class MergeSeparator {
     }
 

src/main/java/eu/siacs/conversations/entities/MucOptions.java 🔗

@@ -11,6 +11,7 @@ import eu.siacs.conversations.services.AvatarService;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.utils.JidHelper;
 import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.chatstate.ChatState;
 import eu.siacs.conversations.xmpp.forms.Data;
@@ -226,6 +227,11 @@ public class MucOptions {
         return getFeatures().contains("http://jabber.org/protocol/muc#stable_id");
     }
 
+    public boolean occupantId() {
+        final var features = getFeatures();
+        return features.contains(Namespace.OCCUPANT_ID);
+    }
+
     public User deleteUser(Jid jid) {
         User user = findUserByFullJid(jid);
         if (user != null) {

src/main/java/eu/siacs/conversations/entities/Reaction.java 🔗

@@ -0,0 +1,43 @@
+package eu.siacs.conversations.entities;
+
+import com.google.common.base.Strings;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+import eu.siacs.conversations.xmpp.Jid;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+public class Reaction {
+
+    private static final Gson GSON = new Gson();
+
+    public final String reaction;
+    public final Jid jid;
+    public final String occupantId;
+
+    public Reaction(final String reaction, final Jid jid, final String occupantId) {
+        this.reaction = reaction;
+        this.jid = jid;
+        this.occupantId = occupantId;
+    }
+
+
+    public static String toString(final Collection<Reaction> reactions) {
+        return (reactions == null || reactions.isEmpty()) ? null : GSON.toJson(reactions);
+    }
+
+    public static Collection<Reaction> fromString(final String asString) {
+        if ( Strings.isNullOrEmpty(asString)) {
+            return Collections.emptyList();
+        }
+        try {
+            return GSON.fromJson(asString,new TypeToken<ArrayList<Reaction>>(){}.getType());
+        } catch (final JsonSyntaxException e) {
+            return Collections.emptyList();
+        }
+    }
+}

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -54,6 +54,7 @@ import im.conversations.android.xmpp.model.Extension;
 import im.conversations.android.xmpp.model.carbons.Received;
 import im.conversations.android.xmpp.model.carbons.Sent;
 import im.conversations.android.xmpp.model.forward.Forwarded;
+import im.conversations.android.xmpp.model.occupant.OccupantId;
 
 public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
 
@@ -631,8 +632,14 @@ public class MessageParser extends AbstractParser implements Consumer<im.convers
             }
             message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
             if (conversationMultiMode) {
-                message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
-                final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
+                final var mucOptions = conversation.getMucOptions();
+                final var occupantId =
+                        mucOptions.occupantId() ? packet.getExtension(OccupantId.class) : null;
+                if (occupantId != null) {
+                    message.setOccupantId(occupantId.getId());
+                }
+                message.setMucUser(mucOptions.findUserByFullJid(counterpart));
+                final Jid fallback = mucOptions.getTrueCounterpart(counterpart);
                 Jid trueCounterpart;
                 if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
                     trueCounterpart = message.getTrueCounterpart();

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

@@ -64,7 +64,7 @@ import eu.siacs.conversations.xmpp.mam.MamReference;
 public class DatabaseBackend extends SQLiteOpenHelper {
 
     private static final String DATABASE_NAME = "history";
-    private static final int DATABASE_VERSION = 51;
+    private static final int DATABASE_VERSION = 52;
 
     private static boolean requiresMessageIndexRebuild = false;
     private static DatabaseBackend instance = null;
@@ -262,6 +262,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
                 + Message.MARKABLE + " NUMBER DEFAULT 0,"
                 + Message.DELETED + " NUMBER DEFAULT 0,"
                 + Message.BODY_LANGUAGE + " TEXT,"
+                + Message.OCCUPANT_ID + " TEXT,"
+                + Message.REACTIONS + " TEXT,"
                 + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
                 + Message.CONVERSATION + ") REFERENCES "
                 + Conversation.TABLENAME + "(" + Conversation.UUID
@@ -601,6 +603,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
             db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_MECHANISM + " TEXT");
             db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_TOKEN + " TEXT");
         }
+        if (oldVersion < 52 && newVersion >= 52) {
+            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OCCUPANT_ID + " TEXT");
+            db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.REACTIONS + " TEXT");
+        }
     }
 
     private void canonicalizeJids(SQLiteDatabase db) {

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

@@ -155,7 +155,6 @@ import eu.siacs.conversations.xml.LocalizedContent;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.InvalidJid;
 import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnBindListener;
 import eu.siacs.conversations.xmpp.OnContactStatusChanged;
 import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnMessageAcknowledged;
@@ -1836,7 +1835,7 @@ public class XmppConnectionService extends Service {
         sendIqPacket(account, retrieve, (response) -> {
             if (response.getType() == Iq.Type.RESULT) {
                 final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
-                final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromPubsub(pubsub, account);
+                final Map<Jid, Bookmark> bookmarks = Bookmark.parseFromPubSub(pubsub, account);
                 processBookmarksInitial(account, bookmarks, true);
             }
         });