insert message states (displayed, received, error) into DB
This commit is contained in:
parent
9b62861a64
commit
be3a8dc5e1
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "03075d3509cc0d79cf5e733cff6b71fd",
|
||||
"identityHash": "b6a7be8218829fd38f51dcd76cb9cccd",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "account",
|
||||
|
@ -1564,6 +1564,84 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message_state",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `messageVersionId` INTEGER NOT NULL, `fromBare` TEXT NOT NULL, `fromResource` TEXT, `type` TEXT NOT NULL, `errorCondition` TEXT, `errorText` TEXT, FOREIGN KEY(`messageVersionId`) REFERENCES `message_version`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageVersionId",
|
||||
"columnName": "messageVersionId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromBare",
|
||||
"columnName": "fromBare",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "fromResource",
|
||||
"columnName": "fromResource",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "errorCondition",
|
||||
"columnName": "errorCondition",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "errorText",
|
||||
"columnName": "errorText",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_message_state_messageVersionId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"messageVersionId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_state_messageVersionId` ON `${TABLE_NAME}` (`messageVersionId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "message_version",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"messageVersionId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "message_content",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `messageVersionId` INTEGER NOT NULL, `language` TEXT, `type` TEXT, `body` TEXT, `url` TEXT, FOREIGN KEY(`messageVersionId`) REFERENCES `message_version`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
|
@ -2150,7 +2228,7 @@
|
|||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '03075d3509cc0d79cf5e733cff6b71fd')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b6a7be8218829fd38f51dcd76cb9cccd')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import im.conversations.android.database.entity.DiscoIdentityEntity;
|
|||
import im.conversations.android.database.entity.DiscoItemEntity;
|
||||
import im.conversations.android.database.entity.MessageContentEntity;
|
||||
import im.conversations.android.database.entity.MessageEntity;
|
||||
import im.conversations.android.database.entity.MessageStateEntity;
|
||||
import im.conversations.android.database.entity.MessageVersionEntity;
|
||||
import im.conversations.android.database.entity.NickEntity;
|
||||
import im.conversations.android.database.entity.PresenceEntity;
|
||||
|
@ -68,6 +69,7 @@ import im.conversations.android.database.entity.RosterItemGroupEntity;
|
|||
DiscoIdentityEntity.class,
|
||||
DiscoItemEntity.class,
|
||||
MessageEntity.class,
|
||||
MessageStateEntity.class,
|
||||
MessageContentEntity.class,
|
||||
MessageVersionEntity.class,
|
||||
NickEntity.class,
|
||||
|
|
|
@ -35,6 +35,7 @@ public abstract class ChatDao {
|
|||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
// TODO do not create entity for 'error'
|
||||
final var entity = new ChatEntity();
|
||||
entity.accountId = account.id;
|
||||
entity.address = address.toEscapedString();
|
||||
|
|
|
@ -10,19 +10,25 @@ import com.google.common.collect.Lists;
|
|||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import im.conversations.android.database.entity.MessageContentEntity;
|
||||
import im.conversations.android.database.entity.MessageEntity;
|
||||
import im.conversations.android.database.entity.MessageStateEntity;
|
||||
import im.conversations.android.database.entity.MessageVersionEntity;
|
||||
import im.conversations.android.database.model.Account;
|
||||
import im.conversations.android.database.model.ChatIdentifier;
|
||||
import im.conversations.android.database.model.MessageContent;
|
||||
import im.conversations.android.database.model.MessageIdentifier;
|
||||
import im.conversations.android.database.model.MessageState;
|
||||
import im.conversations.android.database.model.Modification;
|
||||
import im.conversations.android.transformer.Transformation;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Dao
|
||||
public abstract class MessageDao {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MessageDao.class);
|
||||
|
||||
@Query(
|
||||
"UPDATE message SET acknowledged=1 WHERE messageId=:messageId AND toBare=:toBare AND"
|
||||
+ " toResource=NULL AND chatId IN (SELECT id FROM chat WHERE accountId=:account)")
|
||||
|
@ -115,9 +121,9 @@ public abstract class MessageDao {
|
|||
// we only look up stubs
|
||||
// TODO the from matcher should be in the outer condition
|
||||
@Query(
|
||||
"SELECT id,stanzaId,messageId,fromBare,latestVersion FROM message WHERE chatId=:chatId"
|
||||
+ " AND (fromBare=:fromBare OR fromBare=NULL) AND ((stanzaId != NULL AND"
|
||||
+ " stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion=NULL)) OR"
|
||||
"SELECT id,stanzaId,messageId,fromBare,latestVersion as version FROM message WHERE"
|
||||
+ " chatId=:chatId AND (fromBare=:fromBare OR fromBare=NULL) AND ((stanzaId !="
|
||||
+ " NULL AND stanzaId=:stanzaId AND (stanzaIdVerified=1 OR latestVersion=NULL)) OR"
|
||||
+ " (stanzaId = NULL AND messageId=:messageId AND latestVersion = NULL))")
|
||||
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
||||
|
||||
|
@ -133,4 +139,29 @@ public abstract class MessageDao {
|
|||
|
||||
@Insert
|
||||
protected abstract void insertMessageContent(Collection<MessageContentEntity> contentEntities);
|
||||
|
||||
public void insertMessageState(
|
||||
ChatIdentifier chatIdentifier,
|
||||
final String messageId,
|
||||
final MessageState messageState) {
|
||||
final Long versionId = getVersionIdForOutgoingMessage(chatIdentifier.id, messageId);
|
||||
if (versionId == null) {
|
||||
LOGGER.warn(
|
||||
"Can not find message {} in chat {} ({})",
|
||||
messageId,
|
||||
chatIdentifier.id,
|
||||
chatIdentifier.address);
|
||||
return;
|
||||
}
|
||||
insert(MessageStateEntity.of(versionId, messageState));
|
||||
}
|
||||
|
||||
@Query(
|
||||
"SELECT message_version.id FROM message_version JOIN message ON"
|
||||
+ " message.id=message_version.messageEntityId WHERE message.chatId=:chatId AND"
|
||||
+ " message_version.messageId=:messageId AND message.outgoing=1")
|
||||
protected abstract Long getVersionIdForOutgoingMessage(long chatId, final String messageId);
|
||||
|
||||
@Insert
|
||||
protected abstract void insert(MessageStateEntity messageStateEntity);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package im.conversations.android.database.entity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.ForeignKey;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import im.conversations.android.database.model.MessageState;
|
||||
import im.conversations.android.database.model.StateType;
|
||||
|
||||
@Entity(
|
||||
tableName = "message_state",
|
||||
foreignKeys =
|
||||
@ForeignKey(
|
||||
entity = MessageVersionEntity.class,
|
||||
parentColumns = {"id"},
|
||||
childColumns = {"messageVersionId"},
|
||||
onDelete = ForeignKey.CASCADE),
|
||||
indices = {@Index(value = "messageVersionId")})
|
||||
public class MessageStateEntity {
|
||||
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public Long id;
|
||||
|
||||
@NonNull public Long messageVersionId;
|
||||
|
||||
@NonNull public Jid fromBare;
|
||||
|
||||
@Nullable public String fromResource;
|
||||
|
||||
@NonNull public StateType type;
|
||||
|
||||
public String errorCondition;
|
||||
|
||||
public String errorText;
|
||||
|
||||
public static MessageStateEntity of(
|
||||
final long messageVersionId, final MessageState messageState) {
|
||||
final var entity = new MessageStateEntity();
|
||||
entity.messageVersionId = messageVersionId;
|
||||
entity.fromBare = messageState.fromBare;
|
||||
entity.fromResource = messageState.fromResource;
|
||||
;
|
||||
entity.type = messageState.type;
|
||||
entity.errorCondition = messageState.errorCondition;
|
||||
entity.errorText = messageState.errorText;
|
||||
return entity;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package im.conversations.android.database.model;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
||||
public class MessageIdentifier {
|
||||
|
@ -9,29 +8,18 @@ public class MessageIdentifier {
|
|||
public final String stanzaId;
|
||||
public final String messageId;
|
||||
public final Jid fromBare;
|
||||
public final Long latestVersion;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("stanzaId", stanzaId)
|
||||
.add("messageId", messageId)
|
||||
.add("fromBare", fromBare)
|
||||
.add("latestVersion", latestVersion)
|
||||
.toString();
|
||||
}
|
||||
public final Long version;
|
||||
|
||||
public MessageIdentifier(
|
||||
long id, String stanzaId, String messageId, Jid fromBare, Long latestVersion) {
|
||||
long id, String stanzaId, String messageId, Jid fromBare, Long version) {
|
||||
this.id = id;
|
||||
this.stanzaId = stanzaId;
|
||||
this.messageId = messageId;
|
||||
this.fromBare = fromBare;
|
||||
this.latestVersion = latestVersion;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isStub() {
|
||||
return this.latestVersion == null;
|
||||
return this.version == null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package im.conversations.android.database.model;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import im.conversations.android.transformer.Transformation;
|
||||
import im.conversations.android.xmpp.model.error.Condition;
|
||||
import im.conversations.android.xmpp.model.error.Error;
|
||||
import im.conversations.android.xmpp.model.error.Text;
|
||||
import im.conversations.android.xmpp.model.stanza.Message;
|
||||
|
||||
public class MessageState {
|
||||
|
||||
public final Jid fromBare;
|
||||
|
||||
public final String fromResource;
|
||||
|
||||
public final StateType type;
|
||||
|
||||
public final String errorCondition;
|
||||
|
||||
public final String errorText;
|
||||
|
||||
public MessageState(
|
||||
Jid fromBare,
|
||||
String fromResource,
|
||||
StateType type,
|
||||
String errorCondition,
|
||||
String errorText) {
|
||||
this.fromBare = fromBare;
|
||||
this.fromResource = fromResource;
|
||||
this.type = type;
|
||||
this.errorCondition = errorCondition;
|
||||
this.errorText = errorText;
|
||||
}
|
||||
|
||||
public static MessageState error(final Transformation transformation) {
|
||||
Preconditions.checkArgument(transformation.type == Message.Type.ERROR);
|
||||
final Error error = transformation.getExtension(Error.class);
|
||||
final Condition condition = error == null ? null : error.getCondition();
|
||||
final Text text = error == null ? null : error.getText();
|
||||
return new MessageState(
|
||||
transformation.fromBare(),
|
||||
transformation.fromResource(),
|
||||
StateType.ERROR,
|
||||
condition == null ? null : condition.getName(),
|
||||
text == null ? null : text.getContent());
|
||||
}
|
||||
|
||||
public static MessageState delivered(final Transformation transformation) {
|
||||
return new MessageState(
|
||||
transformation.fromBare(),
|
||||
transformation.fromResource(),
|
||||
StateType.DELIVERED,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static MessageState displayed(final Transformation transformation) {
|
||||
return new MessageState(
|
||||
transformation.fromBare(),
|
||||
transformation.fromResource(),
|
||||
StateType.DISPLAYED,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package im.conversations.android.database.model;
|
||||
|
||||
public enum StateType {
|
||||
DELIVERED,
|
||||
ERROR,
|
||||
DISPLAYED
|
||||
}
|
|
@ -11,12 +11,14 @@ import im.conversations.android.xmpp.model.Extension;
|
|||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||
import im.conversations.android.xmpp.model.jabber.Body;
|
||||
import im.conversations.android.xmpp.model.jabber.Thread;
|
||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||
import im.conversations.android.xmpp.model.stanza.Message;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Transformation {
|
||||
|
@ -28,7 +30,8 @@ public class Transformation {
|
|||
Encrypted.class,
|
||||
OutOfBandData.class,
|
||||
DeliveryReceipt.class,
|
||||
MultiUserChat.class);
|
||||
MultiUserChat.class,
|
||||
Displayed.class);
|
||||
|
||||
public final Instant receivedAt;
|
||||
public final Jid to;
|
||||
|
@ -113,10 +116,16 @@ public class Transformation {
|
|||
final var type = message.getType();
|
||||
final var messageId = message.getId();
|
||||
final ImmutableList.Builder<Extension> extensionListBuilder = new ImmutableList.Builder<>();
|
||||
final Collection<DeliveryReceiptRequest> requests;
|
||||
if (type == Message.Type.ERROR) {
|
||||
extensionListBuilder.add(message.getError());
|
||||
requests = Collections.emptyList();
|
||||
} else {
|
||||
for (final Class<? extends Extension> clazz : EXTENSION_FOR_TRANSFORMATION) {
|
||||
extensionListBuilder.addAll(message.getExtensions(clazz));
|
||||
}
|
||||
final var requests = message.getExtensions(DeliveryReceiptRequest.class);
|
||||
requests = message.getExtensions(DeliveryReceiptRequest.class);
|
||||
}
|
||||
return new Transformation(
|
||||
receivedAt,
|
||||
to,
|
||||
|
|
|
@ -8,12 +8,15 @@ import im.conversations.android.database.ConversationsDatabase;
|
|||
import im.conversations.android.database.model.Account;
|
||||
import im.conversations.android.database.model.ChatIdentifier;
|
||||
import im.conversations.android.database.model.MessageContent;
|
||||
import im.conversations.android.database.model.MessageState;
|
||||
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||
import im.conversations.android.xmpp.model.correction.Replace;
|
||||
import im.conversations.android.xmpp.model.jabber.Body;
|
||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||
import im.conversations.android.xmpp.model.stanza.Message;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
@ -47,26 +50,33 @@ public class Transformer {
|
|||
final ConversationsDatabase database, final Transformation transformation) {
|
||||
final var remote = transformation.remote;
|
||||
final var messageType = transformation.type;
|
||||
final var deliveryReceipt = transformation.getExtension(DeliveryReceipt.class);
|
||||
final Replace lastMessageCorrection = transformation.getExtension(Replace.class);
|
||||
final var muc = transformation.getExtension(MultiUserChat.class);
|
||||
|
||||
|
||||
final List<MessageContent> contents = parseContent(transformation);
|
||||
|
||||
// TODO this also needs to be true for retractions once we support those (anything that
|
||||
// creates a new message version
|
||||
final boolean versionModification = Objects.nonNull(lastMessageCorrection);
|
||||
|
||||
// TODO get or create Cha
|
||||
|
||||
final ChatIdentifier chat =
|
||||
database.chatDao()
|
||||
.getOrCreateChat(account, remote, messageType, Objects.nonNull(muc));
|
||||
|
||||
if (messageType == Message.Type.ERROR) {
|
||||
if (transformation.outgoing()) {
|
||||
LOGGER.info("Ignoring outgoing error to {}", transformation.to);
|
||||
return false;
|
||||
}
|
||||
database.messageDao()
|
||||
.insertMessageState(
|
||||
chat, transformation.messageId, MessageState.error(transformation));
|
||||
return false;
|
||||
}
|
||||
final Replace lastMessageCorrection = transformation.getExtension(Replace.class);
|
||||
final List<MessageContent> contents = parseContent(transformation);
|
||||
|
||||
// TODO this also needs to be true for retractions once we support those (anything that
|
||||
// creates a new message version
|
||||
// TODO a type=groupchat message correction is only valid with an occupant id
|
||||
final boolean versionModification = Objects.nonNull(lastMessageCorrection);
|
||||
|
||||
if (contents.isEmpty()) {
|
||||
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
||||
// TODO apply errors, displayed, received etc
|
||||
transformMessageState(chat, transformation);
|
||||
// TODO apply reactions
|
||||
} else {
|
||||
if (versionModification) {
|
||||
|
@ -78,8 +88,7 @@ public class Transformer {
|
|||
} else {
|
||||
final var messageIdentifier =
|
||||
database.messageDao().getOrCreateMessage(chat, transformation);
|
||||
database.messageDao()
|
||||
.insertMessageContent(messageIdentifier.latestVersion, contents);
|
||||
database.messageDao().insertMessageContent(messageIdentifier.version, contents);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -121,4 +130,31 @@ public class Transformer {
|
|||
}
|
||||
return messageContentBuilder.build();
|
||||
}
|
||||
|
||||
private void transformMessageState(
|
||||
final ChatIdentifier chat, final Transformation transformation) {
|
||||
final var database = ConversationsDatabase.getInstance(context);
|
||||
final var displayed = transformation.getExtension(Displayed.class);
|
||||
if (displayed != null) {
|
||||
if (transformation.outgoing()) {
|
||||
LOGGER.info(
|
||||
"Received outgoing displayed marker for chat with {}",
|
||||
transformation.remote);
|
||||
return;
|
||||
}
|
||||
database.messageDao()
|
||||
.insertMessageState(
|
||||
chat, displayed.getId(), MessageState.displayed(transformation));
|
||||
}
|
||||
final var deliveryReceipt = transformation.getExtension(DeliveryReceipt.class);
|
||||
if (deliveryReceipt != null) {
|
||||
if (transformation.outgoing()) {
|
||||
LOGGER.info("Ignoring outgoing delivery receipt to {}", transformation.to);
|
||||
return;
|
||||
}
|
||||
database.messageDao()
|
||||
.insertMessageState(
|
||||
chat, deliveryReceipt.getId(), MessageState.delivered(transformation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,6 @@ public class ChatStateManager extends AbstractManager {
|
|||
}
|
||||
|
||||
public void handle(final Jid from, final ChatStateNotification chatState) {
|
||||
LOGGER.info("Received {} from {}", chatState, from);
|
||||
// LOGGER.info("Received {} from {}", chatState, from);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,8 @@ public class Displayed extends Extension {
|
|||
public Displayed() {
|
||||
super(Displayed.class);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.getAttribute("id");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue