store reactions in database
This commit is contained in:
parent
a69b4b14a5
commit
6c24cb12dd
|
@ -70,6 +70,7 @@ public final class Namespace {
|
||||||
public static final String MUC = "http://jabber.org/protocol/muc";
|
public static final String MUC = "http://jabber.org/protocol/muc";
|
||||||
public static final String MUC_USER = MUC + "#user";
|
public static final String MUC_USER = MUC + "#user";
|
||||||
public static final String NICK = "http://jabber.org/protocol/nick";
|
public static final String NICK = "http://jabber.org/protocol/nick";
|
||||||
|
public static final String REACTIONS = "urn:xmpp:reactions:0";
|
||||||
public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
|
public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
|
||||||
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
||||||
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
||||||
|
|
|
@ -38,11 +38,11 @@ import im.conversations.android.database.entity.DiscoIdentityEntity;
|
||||||
import im.conversations.android.database.entity.DiscoItemEntity;
|
import im.conversations.android.database.entity.DiscoItemEntity;
|
||||||
import im.conversations.android.database.entity.MessageContentEntity;
|
import im.conversations.android.database.entity.MessageContentEntity;
|
||||||
import im.conversations.android.database.entity.MessageEntity;
|
import im.conversations.android.database.entity.MessageEntity;
|
||||||
|
import im.conversations.android.database.entity.MessageReactionEntity;
|
||||||
import im.conversations.android.database.entity.MessageStateEntity;
|
import im.conversations.android.database.entity.MessageStateEntity;
|
||||||
import im.conversations.android.database.entity.MessageVersionEntity;
|
import im.conversations.android.database.entity.MessageVersionEntity;
|
||||||
import im.conversations.android.database.entity.NickEntity;
|
import im.conversations.android.database.entity.NickEntity;
|
||||||
import im.conversations.android.database.entity.PresenceEntity;
|
import im.conversations.android.database.entity.PresenceEntity;
|
||||||
import im.conversations.android.database.entity.ReactionEntity;
|
|
||||||
import im.conversations.android.database.entity.RosterItemEntity;
|
import im.conversations.android.database.entity.RosterItemEntity;
|
||||||
import im.conversations.android.database.entity.RosterItemGroupEntity;
|
import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
MessageVersionEntity.class,
|
MessageVersionEntity.class,
|
||||||
NickEntity.class,
|
NickEntity.class,
|
||||||
PresenceEntity.class,
|
PresenceEntity.class,
|
||||||
ReactionEntity.class,
|
MessageReactionEntity.class,
|
||||||
RosterItemEntity.class,
|
RosterItemEntity.class,
|
||||||
RosterItemGroupEntity.class
|
RosterItemGroupEntity.class
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,10 +6,12 @@ import androidx.room.Insert;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.room.Transaction;
|
import androidx.room.Transaction;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import im.conversations.android.database.entity.MessageContentEntity;
|
import im.conversations.android.database.entity.MessageContentEntity;
|
||||||
import im.conversations.android.database.entity.MessageEntity;
|
import im.conversations.android.database.entity.MessageEntity;
|
||||||
|
import im.conversations.android.database.entity.MessageReactionEntity;
|
||||||
import im.conversations.android.database.entity.MessageStateEntity;
|
import im.conversations.android.database.entity.MessageStateEntity;
|
||||||
import im.conversations.android.database.entity.MessageVersionEntity;
|
import im.conversations.android.database.entity.MessageVersionEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
|
@ -19,6 +21,8 @@ import im.conversations.android.database.model.MessageIdentifier;
|
||||||
import im.conversations.android.database.model.MessageState;
|
import im.conversations.android.database.model.MessageState;
|
||||||
import im.conversations.android.database.model.Modification;
|
import im.conversations.android.database.model.Modification;
|
||||||
import im.conversations.android.transformer.Transformation;
|
import im.conversations.android.transformer.Transformation;
|
||||||
|
import im.conversations.android.xmpp.model.reactions.Reactions;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -105,12 +109,12 @@ public abstract class MessageDao {
|
||||||
// when found by stanzaId the stanzaId must either by verified or belonging to a stub
|
// when found by stanzaId the stanzaId must either by verified or belonging to a stub
|
||||||
// when found by messageId the from must either match (for corrections) or not be set (null) and
|
// when found by messageId the from must either match (for corrections) or not be set (null) and
|
||||||
// we only look up stubs
|
// we only look up stubs
|
||||||
// TODO the from matcher should be in the outer condition
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare=NULL) AND ((stanzaId !="
|
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare IS NULL) AND ((stanzaId IS"
|
||||||
+ " NULL AND stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion=NULL)) OR"
|
+ " NOT NULL AND stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion IS"
|
||||||
+ " (stanzaId = NULL AND messageId=:messageId AND latestVersion = NULL))")
|
+ " NULL)) OR (stanzaId IS NULL AND messageId=:messageId AND latestVersion IS"
|
||||||
|
+ " NULL))")
|
||||||
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
||||||
|
|
||||||
public MessageIdentifier getOrCreateVersion(
|
public MessageIdentifier getOrCreateVersion(
|
||||||
|
@ -200,14 +204,39 @@ public abstract class MessageDao {
|
||||||
protected abstract void setLatestMessageId(
|
protected abstract void setLatestMessageId(
|
||||||
final long messageEntityId, final long messageVersionId);
|
final long messageEntityId, final long messageVersionId);
|
||||||
|
|
||||||
public Long getOrCreateStub(final Transformation transformation) {
|
public MessageIdentifier getOrCreateStub(
|
||||||
// TODO look up where parentId matches messageId (or stanzaId for group chats)
|
final ChatIdentifier chat, final Message.Type messageType, final String parentId) {
|
||||||
|
final MessageIdentifier existing;
|
||||||
// when creating stub either set from (correction) or don’t (other attachment)
|
if (messageType == Message.Type.GROUPCHAT) {
|
||||||
|
existing = getByStanzaId(chat.id, parentId);
|
||||||
return null;
|
} else {
|
||||||
|
existing = getByMessageId(chat.id, parentId);
|
||||||
|
}
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final MessageEntity messageEntity;
|
||||||
|
if (messageType == Message.Type.GROUPCHAT) {
|
||||||
|
LOGGER.info("Create stub for stanza id {}", parentId);
|
||||||
|
messageEntity = MessageEntity.stubOfStanzaId(chat.id, parentId);
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Create stub for message id {}", parentId);
|
||||||
|
messageEntity = MessageEntity.stubOfMessageId(chat.id, parentId);
|
||||||
|
}
|
||||||
|
final long messageEntityId = insert(messageEntity);
|
||||||
|
return new MessageIdentifier(messageEntityId, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
|
+ " chatId=:chatId AND messageId=:messageId")
|
||||||
|
protected abstract MessageIdentifier getByMessageId(final long chatId, final String messageId);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||||
|
+ " chatId=:chatId AND stanzaId=:stanzaId")
|
||||||
|
protected abstract MessageIdentifier getByStanzaId(final long chatId, final String stanzaId);
|
||||||
|
|
||||||
public void insertMessageContent(Long latestVersion, List<MessageContent> contents) {
|
public void insertMessageContent(Long latestVersion, List<MessageContent> contents) {
|
||||||
Preconditions.checkNotNull(
|
Preconditions.checkNotNull(
|
||||||
latestVersion, "Contents can only be inserted for a specific version");
|
latestVersion, "Contents can only be inserted for a specific version");
|
||||||
|
@ -245,4 +274,19 @@ public abstract class MessageDao {
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
protected abstract void insert(MessageStateEntity messageStateEntity);
|
protected abstract void insert(MessageStateEntity messageStateEntity);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract void insertReactions(Collection<MessageReactionEntity> reactionEntities);
|
||||||
|
|
||||||
|
public void insertReactions(
|
||||||
|
ChatIdentifier chat, Reactions reactions, Transformation transformation) {
|
||||||
|
final Message.Type messageType = transformation.type;
|
||||||
|
final MessageIdentifier messageIdentifier =
|
||||||
|
getOrCreateStub(chat, messageType, reactions.getId());
|
||||||
|
// TODO delete old reactions
|
||||||
|
insertReactions(
|
||||||
|
Collections2.transform(
|
||||||
|
reactions.getReactions(),
|
||||||
|
r -> MessageReactionEntity.of(messageIdentifier.id, r, transformation)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ public abstract class RosterDao {
|
||||||
}
|
}
|
||||||
final RosterItemEntity entity = RosterItemEntity.of(account.id, item);
|
final RosterItemEntity entity = RosterItemEntity.of(account.id, item);
|
||||||
final long id = insert(entity);
|
final long id = insert(entity);
|
||||||
|
// TODO groups
|
||||||
}
|
}
|
||||||
setRosterVersion(account.id, version);
|
setRosterVersion(account.id, version);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,4 +81,18 @@ public class MessageEntity {
|
||||||
entity.stanzaIdVerified = false;
|
entity.stanzaIdVerified = false;
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MessageEntity stubOfStanzaId(final long chatId, String stanzaId) {
|
||||||
|
final var entity = new MessageEntity();
|
||||||
|
entity.stanzaIdVerified = false;
|
||||||
|
entity.stanzaId = stanzaId;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageEntity stubOfMessageId(final long chatId, String messageId) {
|
||||||
|
final var entity = new MessageEntity();
|
||||||
|
entity.stanzaIdVerified = false;
|
||||||
|
entity.messageId = messageId;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import androidx.room.Entity;
|
||||||
import androidx.room.ForeignKey;
|
import androidx.room.ForeignKey;
|
||||||
import androidx.room.Index;
|
import androidx.room.Index;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import im.conversations.android.transformer.Transformation;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
@ -16,7 +18,7 @@ import java.time.Instant;
|
||||||
childColumns = {"messageEntityId"},
|
childColumns = {"messageEntityId"},
|
||||||
onDelete = ForeignKey.CASCADE),
|
onDelete = ForeignKey.CASCADE),
|
||||||
indices = {@Index(value = "messageEntityId")})
|
indices = {@Index(value = "messageEntityId")})
|
||||||
public class ReactionEntity {
|
public class MessageReactionEntity {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
public Long id;
|
public Long id;
|
||||||
|
@ -25,11 +27,25 @@ public class ReactionEntity {
|
||||||
|
|
||||||
public String stanzaId;
|
public String stanzaId;
|
||||||
public String messageId;
|
public String messageId;
|
||||||
public String reactionBy;
|
public Jid reactionBy;
|
||||||
public String reactionByResource;
|
public String reactionByResource;
|
||||||
public String occupantId;
|
public String occupantId;
|
||||||
|
|
||||||
public Instant receivedAt;
|
public Instant receivedAt;
|
||||||
|
|
||||||
public String reaction;
|
public String reaction;
|
||||||
|
|
||||||
|
public static MessageReactionEntity of(
|
||||||
|
long messageEntityId, final String reaction, final Transformation transformation) {
|
||||||
|
final var entity = new MessageReactionEntity();
|
||||||
|
entity.messageEntityId = messageEntityId;
|
||||||
|
entity.reaction = reaction;
|
||||||
|
entity.stanzaId = transformation.stanzaId;
|
||||||
|
entity.messageId = transformation.messageId;
|
||||||
|
entity.reactionBy = transformation.fromBare();
|
||||||
|
entity.reactionByResource = transformation.fromResource();
|
||||||
|
entity.occupantId = transformation.occupantId;
|
||||||
|
entity.receivedAt = transformation.receivedAt;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ import im.conversations.android.xmpp.model.jabber.Thread;
|
||||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
||||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||||
|
import im.conversations.android.xmpp.model.reactions.Reactions;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -34,7 +35,8 @@ public class Transformation {
|
||||||
DeliveryReceipt.class,
|
DeliveryReceipt.class,
|
||||||
MultiUserChat.class,
|
MultiUserChat.class,
|
||||||
Displayed.class,
|
Displayed.class,
|
||||||
Replace.class);
|
Replace.class,
|
||||||
|
Reactions.class);
|
||||||
|
|
||||||
public final Instant receivedAt;
|
public final Instant receivedAt;
|
||||||
public final Jid to;
|
public final Jid to;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import im.conversations.android.xmpp.model.jabber.Body;
|
||||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
||||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||||
|
import im.conversations.android.xmpp.model.reactions.Reactions;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -70,11 +71,16 @@ public class Transformer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final Replace messageCorrection = transformation.getExtension(Replace.class);
|
final Replace messageCorrection = transformation.getExtension(Replace.class);
|
||||||
|
final Reactions reactions = transformation.getExtension(Reactions.class);
|
||||||
final List<MessageContent> contents = parseContent(transformation);
|
final List<MessageContent> contents = parseContent(transformation);
|
||||||
|
|
||||||
final boolean identifiableSender =
|
final boolean identifiableSender =
|
||||||
Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType)
|
Arrays.asList(Message.Type.NORMAL, Message.Type.CHAT).contains(messageType)
|
||||||
|| Objects.nonNull(transformation.occupantId);
|
|| Objects.nonNull(transformation.occupantId);
|
||||||
|
final boolean isReaction =
|
||||||
|
Objects.nonNull(reactions)
|
||||||
|
&& Objects.nonNull(reactions.getId())
|
||||||
|
&& identifiableSender;
|
||||||
final boolean isMessageCorrection =
|
final boolean isMessageCorrection =
|
||||||
Objects.nonNull(messageCorrection)
|
Objects.nonNull(messageCorrection)
|
||||||
&& messageCorrection.getId() != null
|
&& messageCorrection.getId() != null
|
||||||
|
@ -83,7 +89,9 @@ public class Transformer {
|
||||||
if (contents.isEmpty()) {
|
if (contents.isEmpty()) {
|
||||||
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
||||||
transformMessageState(chat, transformation);
|
transformMessageState(chat, transformation);
|
||||||
// TODO apply reactions
|
if (isReaction) {
|
||||||
|
database.messageDao().insertReactions(chat, reactions, transformation);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final MessageIdentifier messageIdentifier;
|
final MessageIdentifier messageIdentifier;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.reactions;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Reaction extends Extension {
|
||||||
|
|
||||||
|
public Reaction() {
|
||||||
|
super(Reaction.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package im.conversations.android.xmpp.model.reactions;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Reactions extends Extension {
|
||||||
|
|
||||||
|
public Reactions() {
|
||||||
|
super(Reactions.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<String> getReactions() {
|
||||||
|
return Collections2.filter(
|
||||||
|
Collections2.transform(getExtensions(Reaction.class), Reaction::getContent),
|
||||||
|
r -> Objects.nonNull(Strings.nullToEmpty(r)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.getAttribute("id");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.REACTIONS)
|
||||||
|
package im.conversations.android.xmpp.model.reactions;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
Loading…
Reference in a new issue