store messages in database
This commit is contained in:
parent
dc371d7017
commit
9b62861a64
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "2972255ca35c75ece48909471313d20a",
|
"identityHash": "03075d3509cc0d79cf5e733cff6b71fd",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "account",
|
"tableName": "account",
|
||||||
|
@ -1420,7 +1420,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "message",
|
"tableName": "message",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `chatId` INTEGER NOT NULL, `receivedAt` INTEGER, `sentAt` INTEGER, `bareTo` TEXT, `toResource` TEXT, `bareFrom` TEXT, `fromResource` TEXT, `occupantId` TEXT, `messageId` TEXT, `stanzaId` TEXT, `acknowledged` INTEGER NOT NULL, FOREIGN KEY(`chatId`) REFERENCES `chat`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `chatId` INTEGER NOT NULL, `receivedAt` INTEGER, `sentAt` INTEGER, `outgoing` INTEGER NOT NULL, `toBare` TEXT, `toResource` TEXT, `fromBare` TEXT, `fromResource` TEXT, `occupantId` TEXT, `messageId` TEXT, `stanzaId` TEXT, `stanzaIdVerified` INTEGER NOT NULL, `latestVersion` INTEGER, `acknowledged` INTEGER NOT NULL, FOREIGN KEY(`chatId`) REFERENCES `chat`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`latestVersion`) REFERENCES `message_version`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
@ -1447,8 +1447,14 @@
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "bareTo",
|
"fieldPath": "outgoing",
|
||||||
"columnName": "bareTo",
|
"columnName": "outgoing",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toBare",
|
||||||
|
"columnName": "toBare",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
|
@ -1459,8 +1465,8 @@
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "bareFrom",
|
"fieldPath": "fromBare",
|
||||||
"columnName": "bareFrom",
|
"columnName": "fromBare",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
|
@ -1488,6 +1494,18 @@
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "stanzaIdVerified",
|
||||||
|
"columnName": "stanzaIdVerified",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "latestVersion",
|
||||||
|
"columnName": "latestVersion",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "acknowledged",
|
"fieldPath": "acknowledged",
|
||||||
"columnName": "acknowledged",
|
"columnName": "acknowledged",
|
||||||
|
@ -1510,6 +1528,15 @@
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_chatId` ON `${TABLE_NAME}` (`chatId`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_chatId` ON `${TABLE_NAME}` (`chatId`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_message_latestVersion",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"latestVersion"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_latestVersion` ON `${TABLE_NAME}` (`latestVersion`)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
|
@ -1523,11 +1550,22 @@
|
||||||
"referencedColumns": [
|
"referencedColumns": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "message_version",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"latestVersion"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "message_part",
|
"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 )",
|
"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 )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
@ -1575,13 +1613,13 @@
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
"name": "index_message_part_messageVersionId",
|
"name": "index_message_content_messageVersionId",
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"messageVersionId"
|
"messageVersionId"
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_part_messageVersionId` ON `${TABLE_NAME}` (`messageVersionId`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_content_messageVersionId` ON `${TABLE_NAME}` (`messageVersionId`)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
|
@ -1600,7 +1638,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "message_version",
|
"tableName": "message_version",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `messageEntityId` INTEGER NOT NULL, `messageId` TEXT, `stanzaId` TEXT, `modification` TEXT, `modifiedBy` TEXT, `modifiedByResource` TEXT, `occupantId` TEXT, `receivedAt` INTEGER, FOREIGN KEY(`messageId`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `messageEntityId` INTEGER NOT NULL, `messageId` TEXT, `stanzaId` TEXT, `modification` TEXT, `modifiedBy` TEXT, `modifiedByResource` TEXT, `occupantId` TEXT, `receivedAt` INTEGER, FOREIGN KEY(`messageEntityId`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
@ -1665,13 +1703,13 @@
|
||||||
},
|
},
|
||||||
"indices": [
|
"indices": [
|
||||||
{
|
{
|
||||||
"name": "index_message_version_messageId",
|
"name": "index_message_version_messageEntityId",
|
||||||
"unique": false,
|
"unique": false,
|
||||||
"columnNames": [
|
"columnNames": [
|
||||||
"messageId"
|
"messageEntityId"
|
||||||
],
|
],
|
||||||
"orders": [],
|
"orders": [],
|
||||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_version_messageId` ON `${TABLE_NAME}` (`messageId`)"
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_message_version_messageEntityId` ON `${TABLE_NAME}` (`messageEntityId`)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"foreignKeys": [
|
"foreignKeys": [
|
||||||
|
@ -1680,7 +1718,7 @@
|
||||||
"onDelete": "CASCADE",
|
"onDelete": "CASCADE",
|
||||||
"onUpdate": "NO ACTION",
|
"onUpdate": "NO ACTION",
|
||||||
"columns": [
|
"columns": [
|
||||||
"messageId"
|
"messageEntityId"
|
||||||
],
|
],
|
||||||
"referencedColumns": [
|
"referencedColumns": [
|
||||||
"id"
|
"id"
|
||||||
|
@ -2112,7 +2150,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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, '2972255ca35c75ece48909471313d20a')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '03075d3509cc0d79cf5e733cff6b71fd')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,6 +24,7 @@ public final class Namespace {
|
||||||
public static final String CONFERENCE = "jabber:x:conference";
|
public static final String CONFERENCE = "jabber:x:conference";
|
||||||
public static final String CSI = "urn:xmpp:csi:0";
|
public static final String CSI = "urn:xmpp:csi:0";
|
||||||
public static final String DATA = "jabber:x:data";
|
public static final String DATA = "jabber:x:data";
|
||||||
|
public static final String DELAY = "urn:xmpp:delay";
|
||||||
public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts";
|
public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts";
|
||||||
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
|
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
|
||||||
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
|
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
|
||||||
|
@ -65,6 +66,7 @@ public final class Namespace {
|
||||||
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
|
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
|
||||||
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
|
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
|
||||||
public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0";
|
public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0";
|
||||||
|
public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2";
|
||||||
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";
|
||||||
|
@ -74,9 +76,9 @@ public final class Namespace {
|
||||||
public static final String PARS = "urn:xmpp:pars:0";
|
public static final String PARS = "urn:xmpp:pars:0";
|
||||||
public static final String PING = "urn:xmpp:ping";
|
public static final String PING = "urn:xmpp:ping";
|
||||||
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
|
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
|
||||||
|
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
||||||
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
|
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
|
||||||
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
||||||
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
|
||||||
public static final String PUB_SUB = "http://jabber.org/protocol/pubsub";
|
public static final String PUB_SUB = "http://jabber.org/protocol/pubsub";
|
||||||
public static final String PUB_SUB_ERRORS = PUB_SUB + "#errors";
|
public static final String PUB_SUB_ERRORS = PUB_SUB + "#errors";
|
||||||
public static final String PUB_SUB_EVENT = PUB_SUB + "#event";
|
public static final String PUB_SUB_EVENT = PUB_SUB + "#event";
|
||||||
|
@ -91,7 +93,6 @@ public final class Namespace {
|
||||||
public static final String SASL_2 = "urn:xmpp:sasl:2";
|
public static final String SASL_2 = "urn:xmpp:sasl:2";
|
||||||
public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||||
public static final String STANZA_IDS = "urn:xmpp:sid:0";
|
public static final String STANZA_IDS = "urn:xmpp:sid:0";
|
||||||
public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2";
|
|
||||||
public static final String STREAMS = "http://etherx.jabber.org/streams";
|
public static final String STREAMS = "http://etherx.jabber.org/streams";
|
||||||
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
|
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
|
||||||
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
||||||
|
|
|
@ -10,6 +10,7 @@ import im.conversations.android.database.dao.AvatarDao;
|
||||||
import im.conversations.android.database.dao.AxolotlDao;
|
import im.conversations.android.database.dao.AxolotlDao;
|
||||||
import im.conversations.android.database.dao.BlockingDao;
|
import im.conversations.android.database.dao.BlockingDao;
|
||||||
import im.conversations.android.database.dao.BookmarkDao;
|
import im.conversations.android.database.dao.BookmarkDao;
|
||||||
|
import im.conversations.android.database.dao.ChatDao;
|
||||||
import im.conversations.android.database.dao.DiscoDao;
|
import im.conversations.android.database.dao.DiscoDao;
|
||||||
import im.conversations.android.database.dao.MessageDao;
|
import im.conversations.android.database.dao.MessageDao;
|
||||||
import im.conversations.android.database.dao.NickDao;
|
import im.conversations.android.database.dao.NickDao;
|
||||||
|
@ -35,8 +36,8 @@ import im.conversations.android.database.entity.DiscoExtensionFieldValueEntity;
|
||||||
import im.conversations.android.database.entity.DiscoFeatureEntity;
|
import im.conversations.android.database.entity.DiscoFeatureEntity;
|
||||||
import im.conversations.android.database.entity.DiscoIdentityEntity;
|
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.MessageEntity;
|
import im.conversations.android.database.entity.MessageEntity;
|
||||||
import im.conversations.android.database.entity.MessagePartEntity;
|
|
||||||
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;
|
||||||
|
@ -67,7 +68,7 @@ import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
DiscoIdentityEntity.class,
|
DiscoIdentityEntity.class,
|
||||||
DiscoItemEntity.class,
|
DiscoItemEntity.class,
|
||||||
MessageEntity.class,
|
MessageEntity.class,
|
||||||
MessagePartEntity.class,
|
MessageContentEntity.class,
|
||||||
MessageVersionEntity.class,
|
MessageVersionEntity.class,
|
||||||
NickEntity.class,
|
NickEntity.class,
|
||||||
PresenceEntity.class,
|
PresenceEntity.class,
|
||||||
|
@ -107,6 +108,8 @@ public abstract class ConversationsDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public abstract BookmarkDao bookmarkDao();
|
public abstract BookmarkDao bookmarkDao();
|
||||||
|
|
||||||
|
public abstract ChatDao chatDao();
|
||||||
|
|
||||||
public abstract DiscoDao discoDao();
|
public abstract DiscoDao discoDao();
|
||||||
|
|
||||||
public abstract MessageDao messageDao();
|
public abstract MessageDao messageDao();
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package im.conversations.android.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Transaction;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import im.conversations.android.database.entity.ChatEntity;
|
||||||
|
import im.conversations.android.database.model.Account;
|
||||||
|
import im.conversations.android.database.model.ChatIdentifier;
|
||||||
|
import im.conversations.android.database.model.ChatType;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class ChatDao {
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
public ChatIdentifier getOrCreateChat(
|
||||||
|
final Account account,
|
||||||
|
final Jid remote,
|
||||||
|
final Message.Type messageType,
|
||||||
|
final boolean multiUserChat) {
|
||||||
|
final ChatType chatType;
|
||||||
|
if (multiUserChat
|
||||||
|
&& Arrays.asList(Message.Type.CHAT, Message.Type.NORMAL).contains(messageType)) {
|
||||||
|
chatType = ChatType.MUC_PM;
|
||||||
|
} else if (messageType == Message.Type.GROUPCHAT) {
|
||||||
|
chatType = ChatType.MUC;
|
||||||
|
} else {
|
||||||
|
chatType = ChatType.INDIVIDUAL;
|
||||||
|
}
|
||||||
|
final Jid address = chatType == ChatType.MUC_PM ? remote : remote.asBareJid();
|
||||||
|
final ChatIdentifier existing = get(account.id, address);
|
||||||
|
if (existing != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final var entity = new ChatEntity();
|
||||||
|
entity.accountId = account.id;
|
||||||
|
entity.address = address.toEscapedString();
|
||||||
|
entity.type = chatType;
|
||||||
|
entity.archived = true;
|
||||||
|
final long id = insert(entity);
|
||||||
|
return new ChatIdentifier(id, address, chatType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT id,address,type,archived FROM chat WHERE accountId=:accountId AND"
|
||||||
|
+ " address=:address")
|
||||||
|
protected abstract ChatIdentifier get(final long accountId, final Jid address);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract long insert(ChatEntity chatEntity);
|
||||||
|
}
|
|
@ -2,24 +2,38 @@ package im.conversations.android.database.dao;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Insert;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Transaction;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
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.MessageEntity;
|
||||||
|
import im.conversations.android.database.entity.MessageVersionEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
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.Modification;
|
||||||
|
import im.conversations.android.transformer.Transformation;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class MessageDao {
|
public abstract class MessageDao {
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE message SET acknowledged=1 WHERE messageId=:messageId AND bareTo=:bareTo AND"
|
"UPDATE message SET acknowledged=1 WHERE messageId=:messageId AND toBare=:toBare AND"
|
||||||
+ " toResource=NULL AND chatId IN (SELECT id FROM chat WHERE accountId=:account)")
|
+ " toResource=NULL AND chatId IN (SELECT id FROM chat WHERE accountId=:account)")
|
||||||
abstract int acknowledge(long account, String messageId, final String bareTo);
|
abstract int acknowledge(long account, String messageId, final String toBare);
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE message SET acknowledged=1 WHERE messageId=:messageId AND bareTo=:bareTo AND"
|
"UPDATE message SET acknowledged=1 WHERE messageId=:messageId AND toBare=:toBare AND"
|
||||||
+ " toResource=:toResource AND chatId IN (SELECT id FROM chat WHERE"
|
+ " toResource=:toResource AND chatId IN (SELECT id FROM chat WHERE"
|
||||||
+ " accountId=:account)")
|
+ " accountId=:account)")
|
||||||
abstract int acknowledge(
|
abstract int acknowledge(
|
||||||
long account, final String messageId, final String bareTo, final String toResource);
|
long account, final String messageId, final String toBare, final String toResource);
|
||||||
|
|
||||||
public boolean acknowledge(
|
public boolean acknowledge(
|
||||||
final Account account, @NonNull final String messageId, @NonNull final Jid to) {
|
final Account account, @NonNull final String messageId, @NonNull final Jid to) {
|
||||||
|
@ -36,4 +50,87 @@ public abstract class MessageDao {
|
||||||
> 0;
|
> 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
public MessageIdentifier getOrCreateMessage(
|
||||||
|
ChatIdentifier chatIdentifier, final Transformation transformation) {
|
||||||
|
final MessageIdentifier messageIdentifier =
|
||||||
|
get(
|
||||||
|
chatIdentifier.id,
|
||||||
|
transformation.fromBare(),
|
||||||
|
transformation.stanzaId,
|
||||||
|
transformation.messageId);
|
||||||
|
if (messageIdentifier != null) {
|
||||||
|
if (messageIdentifier.isStub()) {
|
||||||
|
// TODO create version
|
||||||
|
// TODO fill up information
|
||||||
|
return messageIdentifier;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"A message with stanzaId '%s' and messageId '%s' from %s already"
|
||||||
|
+ " exists",
|
||||||
|
transformation.stanzaId,
|
||||||
|
transformation.messageId,
|
||||||
|
transformation.from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final MessageEntity entity = MessageEntity.of(chatIdentifier.id, transformation);
|
||||||
|
final long messageEntityId = insert(entity);
|
||||||
|
final long messageVersionId =
|
||||||
|
insert(
|
||||||
|
MessageVersionEntity.of(
|
||||||
|
messageEntityId, Modification.ORIGINAl, transformation));
|
||||||
|
setLatestMessageId(messageEntityId, messageVersionId);
|
||||||
|
return new MessageIdentifier(
|
||||||
|
messageEntityId,
|
||||||
|
transformation.stanzaId,
|
||||||
|
transformation.messageId,
|
||||||
|
transformation.fromBare(),
|
||||||
|
messageVersionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract long insert(MessageEntity messageEntity);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract long insert(MessageVersionEntity messageVersionEntity);
|
||||||
|
|
||||||
|
@Query("UPDATE message SET latestVersion=:messageVersionId WHERE id=:messageEntityId")
|
||||||
|
protected abstract void setLatestMessageId(
|
||||||
|
final long messageEntityId, final long messageVersionId);
|
||||||
|
|
||||||
|
public Long getOrCreateStub(final Transformation transformation) {
|
||||||
|
// TODO look up where parentId matches messageId (or stanzaId for group chats)
|
||||||
|
|
||||||
|
// when creating stub either set from (correction) or don’t (other attachment)
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this gets either a message or a stub.
|
||||||
|
// stubs are recognized by latestVersion=NULL
|
||||||
|
// 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
|
||||||
|
// 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"
|
||||||
|
+ " (stanzaId = NULL AND messageId=:messageId AND latestVersion = NULL))")
|
||||||
|
abstract MessageIdentifier get(long chatId, Jid fromBare, String stanzaId, String messageId);
|
||||||
|
|
||||||
|
public void insertMessageContent(Long latestVersion, List<MessageContent> contents) {
|
||||||
|
Preconditions.checkNotNull(
|
||||||
|
latestVersion, "Contents can only be inserted for a specific version");
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
contents.size() > 0,
|
||||||
|
"If you are trying to insert empty contents something went wrong");
|
||||||
|
insertMessageContent(
|
||||||
|
Lists.transform(contents, c -> MessageContentEntity.of(latestVersion, c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
protected abstract void insertMessageContent(Collection<MessageContentEntity> contentEntities);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ 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 im.conversations.android.database.model.MessageContent;
|
||||||
import im.conversations.android.database.model.PartType;
|
import im.conversations.android.database.model.PartType;
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "message_part",
|
tableName = "message_content",
|
||||||
foreignKeys =
|
foreignKeys =
|
||||||
@ForeignKey(
|
@ForeignKey(
|
||||||
entity = MessageVersionEntity.class,
|
entity = MessageVersionEntity.class,
|
||||||
|
@ -16,7 +17,7 @@ import im.conversations.android.database.model.PartType;
|
||||||
childColumns = {"messageVersionId"},
|
childColumns = {"messageVersionId"},
|
||||||
onDelete = ForeignKey.CASCADE),
|
onDelete = ForeignKey.CASCADE),
|
||||||
indices = {@Index(value = "messageVersionId")})
|
indices = {@Index(value = "messageVersionId")})
|
||||||
public class MessagePartEntity {
|
public class MessageContentEntity {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
public Long id;
|
public Long id;
|
||||||
|
@ -30,4 +31,15 @@ public class MessagePartEntity {
|
||||||
public String body;
|
public String body;
|
||||||
|
|
||||||
public String url;
|
public String url;
|
||||||
|
|
||||||
|
public static MessageContentEntity of(
|
||||||
|
final long messageVersionId, final MessageContent content) {
|
||||||
|
final var entity = new MessageContentEntity();
|
||||||
|
entity.messageVersionId = messageVersionId;
|
||||||
|
entity.language = content.language;
|
||||||
|
entity.type = content.type;
|
||||||
|
entity.body = content.body;
|
||||||
|
entity.url = content.url;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,21 +1,31 @@
|
||||||
package im.conversations.android.database.entity;
|
package im.conversations.android.database.entity;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Entity;
|
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;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "message",
|
tableName = "message",
|
||||||
foreignKeys =
|
foreignKeys = {
|
||||||
@ForeignKey(
|
@ForeignKey(
|
||||||
entity = ChatEntity.class,
|
entity = ChatEntity.class,
|
||||||
parentColumns = {"id"},
|
parentColumns = {"id"},
|
||||||
childColumns = {"chatId"},
|
childColumns = {"chatId"},
|
||||||
onDelete = ForeignKey.CASCADE),
|
onDelete = ForeignKey.CASCADE),
|
||||||
indices = {@Index(value = "chatId")})
|
@ForeignKey(
|
||||||
|
entity = MessageVersionEntity.class,
|
||||||
|
parentColumns = {"id"},
|
||||||
|
childColumns = {"latestVersion"},
|
||||||
|
onDelete = ForeignKey.CASCADE),
|
||||||
|
},
|
||||||
|
indices = {@Index(value = "chatId"), @Index(value = "latestVersion")})
|
||||||
public class MessageEntity {
|
public class MessageEntity {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ -28,17 +38,36 @@ public class MessageEntity {
|
||||||
|
|
||||||
public boolean outgoing;
|
public boolean outgoing;
|
||||||
|
|
||||||
public String bareTo;
|
public Jid toBare;
|
||||||
public String toResource;
|
public String toResource;
|
||||||
public String bareFrom;
|
public Jid fromBare;
|
||||||
public String fromResource;
|
public String fromResource;
|
||||||
|
|
||||||
public String occupantId;
|
public String occupantId;
|
||||||
|
|
||||||
public String messageId;
|
public String messageId;
|
||||||
public String stanzaId;
|
public String stanzaId;
|
||||||
// the stanza id might not be verified if this MessageEntity was created as a stub parent to attach reactions to or new versions (created by LMC etc)
|
// the stanza id might not be verified if this MessageEntity was created as a stub parent to
|
||||||
public String stanzaIdVerified;
|
// attach reactions to or new versions (created by LMC etc)
|
||||||
|
public boolean stanzaIdVerified;
|
||||||
|
|
||||||
|
@Nullable public Long latestVersion;
|
||||||
|
|
||||||
public boolean acknowledged = false;
|
public boolean acknowledged = false;
|
||||||
|
|
||||||
|
public static MessageEntity of(final long chatId, final Transformation transformation) {
|
||||||
|
final var entity = new MessageEntity();
|
||||||
|
entity.chatId = chatId;
|
||||||
|
entity.receivedAt = transformation.receivedAt;
|
||||||
|
entity.sentAt = transformation.sentAt();
|
||||||
|
entity.outgoing = transformation.outgoing();
|
||||||
|
entity.toBare = transformation.toBare();
|
||||||
|
entity.toResource = transformation.toResource();
|
||||||
|
entity.fromBare = transformation.fromBare();
|
||||||
|
entity.fromResource = transformation.fromResource();
|
||||||
|
entity.messageId = transformation.messageId;
|
||||||
|
entity.stanzaId = transformation.stanzaId;
|
||||||
|
entity.stanzaIdVerified = Objects.nonNull(transformation.stanzaId);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,21 @@ 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.database.model.Modification;
|
import im.conversations.android.database.model.Modification;
|
||||||
|
import im.conversations.android.transformer.Transformation;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = "message_version",
|
tableName = "message_version",
|
||||||
foreignKeys =
|
foreignKeys = {
|
||||||
@ForeignKey(
|
@ForeignKey(
|
||||||
entity = MessageEntity.class,
|
entity = MessageEntity.class,
|
||||||
parentColumns = {"id"},
|
parentColumns = {"id"},
|
||||||
childColumns = {"messageId"},
|
childColumns = {"messageEntityId"},
|
||||||
onDelete = ForeignKey.CASCADE),
|
onDelete = ForeignKey.CASCADE),
|
||||||
indices = {@Index(value = "messageId")})
|
},
|
||||||
|
indices = {@Index(value = "messageEntityId")})
|
||||||
public class MessageVersionEntity {
|
public class MessageVersionEntity {
|
||||||
|
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ -26,14 +29,28 @@ public class MessageVersionEntity {
|
||||||
public String messageId;
|
public String messageId;
|
||||||
public String stanzaId;
|
public String stanzaId;
|
||||||
public Modification modification;
|
public Modification modification;
|
||||||
public String modifiedBy;
|
public Jid modifiedBy;
|
||||||
public String modifiedByResource;
|
public String modifiedByResource;
|
||||||
public String occupantId;
|
public String occupantId;
|
||||||
Instant receivedAt;
|
public Instant receivedAt;
|
||||||
|
|
||||||
// the version order is determined by the receivedAt
|
// the version order is determined by the receivedAt
|
||||||
// the actual display time and display order comes from the parent MessageEntity
|
// the actual display time and display order comes from the parent MessageEntity
|
||||||
// the original has a receivedAt = null and stanzaId = null and inherits it's timestamp from
|
// the original has a receivedAt = null and stanzaId = null and inherits it's timestamp from
|
||||||
// it's parent
|
// it's parent
|
||||||
|
|
||||||
|
public static MessageVersionEntity of(
|
||||||
|
long messageEntityId,
|
||||||
|
final Modification modification,
|
||||||
|
final Transformation transformation) {
|
||||||
|
final var entity = new MessageVersionEntity();
|
||||||
|
entity.messageEntityId = messageEntityId;
|
||||||
|
entity.messageId = transformation.messageId;
|
||||||
|
entity.stanzaId = transformation.stanzaId;
|
||||||
|
entity.modification = modification;
|
||||||
|
entity.modifiedBy = transformation.fromBare();
|
||||||
|
entity.modifiedByResource = transformation.fromResource();
|
||||||
|
entity.receivedAt = transformation.receivedAt;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
|
public class ChatIdentifier {
|
||||||
|
|
||||||
|
public final long id;
|
||||||
|
public final Jid address;
|
||||||
|
public final ChatType type;
|
||||||
|
public final boolean archived;
|
||||||
|
|
||||||
|
public ChatIdentifier(long id, Jid address, ChatType type, final boolean archived) {
|
||||||
|
this.id = id;
|
||||||
|
this.address = address;
|
||||||
|
this.type = type;
|
||||||
|
this.archived = archived;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
public class MessageContent {
|
||||||
|
|
||||||
|
public final String language;
|
||||||
|
|
||||||
|
public final PartType type;
|
||||||
|
|
||||||
|
public final String body;
|
||||||
|
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
public MessageContent(String language, PartType type, String body, String url) {
|
||||||
|
this.language = language;
|
||||||
|
this.type = type;
|
||||||
|
this.body = body;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageContent text(final String body, final String language) {
|
||||||
|
return new MessageContent(language, PartType.TEXT, body, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageContent file(final String url) {
|
||||||
|
return new MessageContent(null, PartType.FILE, null, url);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
|
public class MessageIdentifier {
|
||||||
|
|
||||||
|
public final long id;
|
||||||
|
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 MessageIdentifier(
|
||||||
|
long id, String stanzaId, String messageId, Jid fromBare, Long latestVersion) {
|
||||||
|
this.id = id;
|
||||||
|
this.stanzaId = stanzaId;
|
||||||
|
this.messageId = messageId;
|
||||||
|
this.fromBare = fromBare;
|
||||||
|
this.latestVersion = latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStub() {
|
||||||
|
return this.latestVersion == null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,20 @@
|
||||||
package im.conversations.android.transformer;
|
package im.conversations.android.transformer;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
|
import im.conversations.android.xmpp.model.DeliveryReceiptRequest;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||||
import im.conversations.android.xmpp.model.jabber.Body;
|
import im.conversations.android.xmpp.model.jabber.Body;
|
||||||
import im.conversations.android.xmpp.model.jabber.Thread;
|
import im.conversations.android.xmpp.model.jabber.Thread;
|
||||||
|
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.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -18,10 +22,18 @@ import java.util.List;
|
||||||
public class Transformation {
|
public class Transformation {
|
||||||
|
|
||||||
private static final List<Class<? extends Extension>> EXTENSION_FOR_TRANSFORMATION =
|
private static final List<Class<? extends Extension>> EXTENSION_FOR_TRANSFORMATION =
|
||||||
Arrays.asList(Body.class, Thread.class, Encrypted.class, OutOfBandData.class);
|
Arrays.asList(
|
||||||
|
Body.class,
|
||||||
|
Thread.class,
|
||||||
|
Encrypted.class,
|
||||||
|
OutOfBandData.class,
|
||||||
|
DeliveryReceipt.class,
|
||||||
|
MultiUserChat.class);
|
||||||
|
|
||||||
|
public final Instant receivedAt;
|
||||||
public final Jid to;
|
public final Jid to;
|
||||||
public final Jid from;
|
public final Jid from;
|
||||||
|
public final Jid remote;
|
||||||
public final Message.Type type;
|
public final Message.Type type;
|
||||||
public final String messageId;
|
public final String messageId;
|
||||||
public final String stanzaId;
|
public final String stanzaId;
|
||||||
|
@ -31,15 +43,19 @@ public class Transformation {
|
||||||
public final Collection<DeliveryReceiptRequest> deliveryReceiptRequests;
|
public final Collection<DeliveryReceiptRequest> deliveryReceiptRequests;
|
||||||
|
|
||||||
private Transformation(
|
private Transformation(
|
||||||
|
final Instant receivedAt,
|
||||||
final Jid to,
|
final Jid to,
|
||||||
final Jid from,
|
final Jid from,
|
||||||
|
final Jid remote,
|
||||||
final Message.Type type,
|
final Message.Type type,
|
||||||
final String messageId,
|
final String messageId,
|
||||||
final String stanzaId,
|
final String stanzaId,
|
||||||
final List<Extension> extensions,
|
final List<Extension> extensions,
|
||||||
final Collection<DeliveryReceiptRequest> deliveryReceiptRequests) {
|
final Collection<DeliveryReceiptRequest> deliveryReceiptRequests) {
|
||||||
|
this.receivedAt = receivedAt;
|
||||||
this.to = to;
|
this.to = to;
|
||||||
this.from = from;
|
this.from = from;
|
||||||
|
this.remote = remote;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
this.stanzaId = stanzaId;
|
this.stanzaId = stanzaId;
|
||||||
|
@ -51,6 +67,32 @@ public class Transformation {
|
||||||
return this.extensions.size() > 0;
|
return this.extensions.size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Jid fromBare() {
|
||||||
|
return from == null ? null : from.asBareJid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fromResource() {
|
||||||
|
return from == null ? null : from.getResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid toBare() {
|
||||||
|
return to == null ? null : to.asBareJid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toResource() {
|
||||||
|
return to == null ? null : to.getResource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant sentAt() {
|
||||||
|
// TODO get Delay that matches sender; return receivedAt if not found
|
||||||
|
return receivedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean outgoing() {
|
||||||
|
// TODO handle case for self addressed (to == from)
|
||||||
|
return remote.asBareJid().equals(toBare());
|
||||||
|
}
|
||||||
|
|
||||||
public <E extends Extension> E getExtension(final Class<E> clazz) {
|
public <E extends Extension> E getExtension(final Class<E> clazz) {
|
||||||
final var extension = Iterables.find(this.extensions, clazz::isInstance, null);
|
final var extension = Iterables.find(this.extensions, clazz::isInstance, null);
|
||||||
return extension == null ? null : clazz.cast(extension);
|
return extension == null ? null : clazz.cast(extension);
|
||||||
|
@ -61,7 +103,11 @@ public class Transformation {
|
||||||
Collections2.filter(this.extensions, clazz::isInstance), clazz::cast);
|
Collections2.filter(this.extensions, clazz::isInstance), clazz::cast);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Transformation of(final Message message, final String stanzaId) {
|
public static Transformation of(
|
||||||
|
@NonNull final Message message,
|
||||||
|
@NonNull final Instant receivedAt,
|
||||||
|
@NonNull final Jid remote,
|
||||||
|
final String stanzaId) {
|
||||||
final var to = message.getTo();
|
final var to = message.getTo();
|
||||||
final var from = message.getFrom();
|
final var from = message.getFrom();
|
||||||
final var type = message.getType();
|
final var type = message.getType();
|
||||||
|
@ -72,6 +118,14 @@ public class Transformation {
|
||||||
}
|
}
|
||||||
final var requests = message.getExtensions(DeliveryReceiptRequest.class);
|
final var requests = message.getExtensions(DeliveryReceiptRequest.class);
|
||||||
return new Transformation(
|
return new Transformation(
|
||||||
to, from, type, messageId, stanzaId, extensionListBuilder.build(), requests);
|
receivedAt,
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
remote,
|
||||||
|
type,
|
||||||
|
messageId,
|
||||||
|
stanzaId,
|
||||||
|
extensionListBuilder.build(),
|
||||||
|
requests);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package im.conversations.android.transformer;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class TransformationFactory extends XmppConnection.Delegate {
|
||||||
|
|
||||||
|
public TransformationFactory(Context context, XmppConnection connection) {
|
||||||
|
super(context, connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformation create(final Message message, final String stanzaId) {
|
||||||
|
return create(message, stanzaId, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformation create(
|
||||||
|
final Message message, final String stanzaId, final Instant receivedAt) {
|
||||||
|
final var boundAddress = connection.getBoundAddress().asBareJid();
|
||||||
|
final var from = message.getFrom();
|
||||||
|
final var to = message.getTo();
|
||||||
|
final Jid remote;
|
||||||
|
if (from == null || from.asBareJid().equals(boundAddress)) {
|
||||||
|
remote = to == null ? boundAddress : to;
|
||||||
|
} else {
|
||||||
|
remote = from;
|
||||||
|
}
|
||||||
|
// TODO parse occupant on group chats
|
||||||
|
return Transformation.of(message, receivedAt, remote, stanzaId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,29 @@
|
||||||
package im.conversations.android.transformer;
|
package im.conversations.android.transformer;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.database.ConversationsDatabase;
|
||||||
import im.conversations.android.database.model.Account;
|
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.xmpp.model.DeliveryReceipt;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
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.jabber.Body;
|
||||||
|
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 java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class Transformer {
|
public class Transformer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(Transformer.class);
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final Account account;
|
private final Account account;
|
||||||
|
|
||||||
|
@ -16,23 +32,93 @@ public class Transformer {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean transform(final Transformation transformation) {
|
||||||
|
final var database = ConversationsDatabase.getInstance(context);
|
||||||
|
return database.runInTransaction(() -> transform(database, transformation));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param transformation
|
* @param transformation
|
||||||
* @return returns true if there is something we want to send a delivery receipt for. Basically
|
* @return returns true if there is something we want to send a delivery receipt for. Basically
|
||||||
* anything that created a new message in the database. Notably not something that only
|
* anything that created a new message in the database. Notably not something that only
|
||||||
* updated a status somewhere
|
* updated a status somewhere
|
||||||
*/
|
*/
|
||||||
public boolean transform(final Transformation transformation) {
|
private boolean transform(
|
||||||
final var encrypted = transformation.getExtension(Encrypted.class);
|
final ConversationsDatabase database, final Transformation transformation) {
|
||||||
final var bodies = transformation.getExtensions(Body.class);
|
final var remote = transformation.remote;
|
||||||
final var outOfBandData = transformation.getExtensions(OutOfBandData.class);
|
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);
|
||||||
|
|
||||||
// TODO get or create Chat
|
|
||||||
// TODO create MessageEntity or get existing entity
|
final List<MessageContent> contents = parseContent(transformation);
|
||||||
// TODO for replaced message create a new version; re-target latestVersion
|
|
||||||
|
// 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 (contents.isEmpty()) {
|
||||||
|
LOGGER.info("Received message from {} w/o contents", transformation.from);
|
||||||
// TODO apply errors, displayed, received etc
|
// TODO apply errors, displayed, received etc
|
||||||
// TODO apply reactions
|
// TODO apply reactions
|
||||||
|
} else {
|
||||||
|
if (versionModification) {
|
||||||
|
// TODO use getOrStub
|
||||||
|
// TODO check if versionModification has already been applied
|
||||||
|
|
||||||
|
// TODO for replaced message create a new version; re-target latestVersion
|
||||||
|
|
||||||
|
} else {
|
||||||
|
final var messageIdentifier =
|
||||||
|
database.messageDao().getOrCreateMessage(chat, transformation);
|
||||||
|
database.messageDao()
|
||||||
|
.insertMessageContent(messageIdentifier.latestVersion, contents);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<MessageContent> parseContent(final Transformation transformation) {
|
||||||
|
final var encrypted = transformation.getExtension(Encrypted.class);
|
||||||
|
final var encryptedWithPayload = encrypted != null && encrypted.hasPayload();
|
||||||
|
final Collection<Body> bodies = transformation.getExtensions(Body.class);
|
||||||
|
final Collection<OutOfBandData> outOfBandData =
|
||||||
|
transformation.getExtensions(OutOfBandData.class);
|
||||||
|
final ImmutableList.Builder<MessageContent> messageContentBuilder = ImmutableList.builder();
|
||||||
|
|
||||||
|
// TODO decrypt
|
||||||
|
|
||||||
|
if (bodies.size() == 1 && outOfBandData.size() == 1) {
|
||||||
|
final String text = Iterables.getOnlyElement(bodies).getContent();
|
||||||
|
final String url = Iterables.getOnlyElement(outOfBandData).getURL();
|
||||||
|
if (!Strings.isNullOrEmpty(url) && url.equals(text)) {
|
||||||
|
return ImmutableList.of(MessageContent.file(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO verify that body is not fallback
|
||||||
|
for (final Body body : bodies) {
|
||||||
|
final String text = body.getContent();
|
||||||
|
if (Strings.isNullOrEmpty(text)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messageContentBuilder.add(MessageContent.text(text, body.getLang()));
|
||||||
|
}
|
||||||
|
for (final OutOfBandData data : outOfBandData) {
|
||||||
|
final String url = data.getURL();
|
||||||
|
if (Strings.isNullOrEmpty(url)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messageContentBuilder.add(MessageContent.file(url));
|
||||||
|
}
|
||||||
|
return messageContentBuilder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
44
src/main/java/im/conversations/android/xmpp/Timestamps.java
Normal file
44
src/main/java/im/conversations/android/xmpp/Timestamps.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public final class Timestamps {
|
||||||
|
|
||||||
|
private Timestamps() {
|
||||||
|
throw new IllegalStateException("Do not instantiate me");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long parse(final String input) throws ParseException {
|
||||||
|
if (input == null) {
|
||||||
|
throw new IllegalArgumentException("timestamp should not be null");
|
||||||
|
}
|
||||||
|
final String timestamp = input.replace("Z", "+0000");
|
||||||
|
final SimpleDateFormat simpleDateFormat =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
|
||||||
|
final long milliseconds = getMilliseconds(timestamp);
|
||||||
|
final String formatted =
|
||||||
|
timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5);
|
||||||
|
final Date date = simpleDateFormat.parse(formatted);
|
||||||
|
if (date == null) {
|
||||||
|
throw new IllegalArgumentException("Date was null");
|
||||||
|
}
|
||||||
|
return date.getTime() + milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getMilliseconds(final String timestamp) {
|
||||||
|
if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') {
|
||||||
|
final String millis = timestamp.substring(19, timestamp.length() - 5);
|
||||||
|
try {
|
||||||
|
double fractions = Double.parseDouble("0" + millis);
|
||||||
|
return Math.round(1000 * fractions);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,10 @@ package im.conversations.android.xmpp.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import im.conversations.android.transformer.Transformation;
|
import im.conversations.android.transformer.TransformationFactory;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.mam.Result;
|
import im.conversations.android.xmpp.model.delay.Delay;
|
||||||
|
import im.conversations.android.xmpp.model.mam.Result;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -13,8 +14,11 @@ public class ArchiveManager extends AbstractManager {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ArchiveManager.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ArchiveManager.class);
|
||||||
|
|
||||||
|
private final TransformationFactory transformationFactory;
|
||||||
|
|
||||||
public ArchiveManager(Context context, XmppConnection connection) {
|
public ArchiveManager(Context context, XmppConnection connection) {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
|
this.transformationFactory = new TransformationFactory(context, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(final Message message) {
|
public void handle(final Message message) {
|
||||||
|
@ -24,14 +28,20 @@ public class ArchiveManager extends AbstractManager {
|
||||||
final var stanzaId = result.getId();
|
final var stanzaId = result.getId();
|
||||||
final var queryId = result.getQueryId();
|
final var queryId = result.getQueryId();
|
||||||
final var forwarded = result.getForwarded();
|
final var forwarded = result.getForwarded();
|
||||||
final var forwardedMessage = forwarded == null ? null : forwarded.getMessage();
|
if (forwarded == null || queryId == null || stanzaId == null) {
|
||||||
if (forwardedMessage == null || queryId == null || stanzaId == null) {
|
|
||||||
LOGGER.info("Received invalid MAM result from {} ", from);
|
LOGGER.info("Received invalid MAM result from {} ", from);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final var forwardedMessage = forwarded.getMessage();
|
||||||
|
final var delay = forwarded.getExtension(Delay.class);
|
||||||
|
final var receivedAt = delay == null ? null : delay.getStamp();
|
||||||
|
if (forwardedMessage == null || receivedAt == null) {
|
||||||
|
LOGGER.info("MAM result from {} is missing message or receivedAt (delay)", from);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// TODO get query based on queryId and from
|
// TODO get query based on queryId and from
|
||||||
|
|
||||||
final var transformation = Transformation.of(forwardedMessage, stanzaId);
|
final var transformation = this.transformationFactory.create(message, stanzaId, receivedAt);
|
||||||
|
|
||||||
// TODO create transformation; add transformation to Query.Transformer
|
// TODO create transformation; add transformation to Query.Transformer
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
public abstract class DeliveryReceipt extends Extension {
|
||||||
|
|
||||||
|
protected DeliveryReceipt(Class<? extends Extension> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getId();
|
||||||
|
}
|
|
@ -9,4 +9,8 @@ public class Encrypted extends Extension {
|
||||||
public Encrypted() {
|
public Encrypted() {
|
||||||
super(Encrypted.class);
|
super(Encrypted.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasPayload() {
|
||||||
|
return hasExtension(Payload.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package im.conversations.android.xmpp.model.delay;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.Timestamps;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.DELAY)
|
||||||
|
public class Delay extends Extension {
|
||||||
|
|
||||||
|
public Delay() {
|
||||||
|
super(Delay.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getStamp() {
|
||||||
|
final var stamp = this.getAttribute("stamp");
|
||||||
|
if (Strings.isNullOrEmpty(stamp)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Instant.ofEpochMilli(Timestamps.parse(stamp));
|
||||||
|
} catch (final IllegalArgumentException | ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,4 +9,8 @@ public class Body extends Extension {
|
||||||
public Body() {
|
public Body() {
|
||||||
super(Body.class);
|
super(Body.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLang() {
|
||||||
|
return this.getAttribute("xml:lang");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package im.conversations.android.xmpp.mam;
|
package im.conversations.android.xmpp.model.mam;
|
||||||
|
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.Extension;
|
|
@ -1,5 +1,5 @@
|
||||||
@XmlPackage(namespace = Namespace.MESSAGE_ARCHIVE_MANAGEMENT)
|
@XmlPackage(namespace = Namespace.MESSAGE_ARCHIVE_MANAGEMENT)
|
||||||
package im.conversations.android.xmpp.mam;
|
package im.conversations.android.xmpp.model.mam;
|
||||||
|
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import im.conversations.android.annotation.XmlPackage;
|
import im.conversations.android.annotation.XmlPackage;
|
|
@ -1,10 +1,10 @@
|
||||||
package im.conversations.android.xmpp.model.markers;
|
package im.conversations.android.xmpp.model.markers;
|
||||||
|
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
|
|
||||||
@XmlElement
|
@XmlElement
|
||||||
public class Received extends Extension {
|
public class Received extends DeliveryReceipt {
|
||||||
|
|
||||||
public Received() {
|
public Received() {
|
||||||
super(Received.class);
|
super(Received.class);
|
||||||
|
@ -13,4 +13,8 @@ public class Received extends Extension {
|
||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
this.setAttribute("id", id);
|
this.setAttribute("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.getAttribute("id");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.muc.user;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "x")
|
||||||
|
public class MultiUserChat extends Extension {
|
||||||
|
|
||||||
|
public MultiUserChat() {
|
||||||
|
super(MultiUserChat.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.MUC_USER)
|
||||||
|
package im.conversations.android.xmpp.model.muc.user;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
|
@ -1,10 +1,10 @@
|
||||||
package im.conversations.android.xmpp.model.receipts;
|
package im.conversations.android.xmpp.model.receipts;
|
||||||
|
|
||||||
import im.conversations.android.annotation.XmlElement;
|
import im.conversations.android.annotation.XmlElement;
|
||||||
import im.conversations.android.xmpp.model.Extension;
|
import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
|
|
||||||
@XmlElement
|
@XmlElement
|
||||||
public class Received extends Extension {
|
public class Received extends DeliveryReceipt {
|
||||||
|
|
||||||
public Received() {
|
public Received() {
|
||||||
super(Received.class);
|
super(Received.class);
|
||||||
|
@ -13,4 +13,8 @@ public class Received extends Extension {
|
||||||
public void setId(String id) {
|
public void setId(String id) {
|
||||||
this.setAttribute("id", id);
|
this.setAttribute("id", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.getAttribute("id");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package im.conversations.android.xmpp.processor;
|
package im.conversations.android.xmpp.processor;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import im.conversations.android.transformer.Transformation;
|
import im.conversations.android.transformer.TransformationFactory;
|
||||||
import im.conversations.android.transformer.Transformer;
|
import im.conversations.android.transformer.Transformer;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.mam.Result;
|
|
||||||
import im.conversations.android.xmpp.manager.ArchiveManager;
|
import im.conversations.android.xmpp.manager.ArchiveManager;
|
||||||
import im.conversations.android.xmpp.manager.CarbonsManager;
|
import im.conversations.android.xmpp.manager.CarbonsManager;
|
||||||
import im.conversations.android.xmpp.manager.ChatStateManager;
|
import im.conversations.android.xmpp.manager.ChatStateManager;
|
||||||
|
@ -13,6 +12,7 @@ import im.conversations.android.xmpp.manager.ReceiptManager;
|
||||||
import im.conversations.android.xmpp.manager.StanzaIdManager;
|
import im.conversations.android.xmpp.manager.StanzaIdManager;
|
||||||
import im.conversations.android.xmpp.model.carbons.Received;
|
import im.conversations.android.xmpp.model.carbons.Received;
|
||||||
import im.conversations.android.xmpp.model.carbons.Sent;
|
import im.conversations.android.xmpp.model.carbons.Sent;
|
||||||
|
import im.conversations.android.xmpp.model.mam.Result;
|
||||||
import im.conversations.android.xmpp.model.pubsub.event.Event;
|
import im.conversations.android.xmpp.model.pubsub.event.Event;
|
||||||
import im.conversations.android.xmpp.model.stanza.Message;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
import im.conversations.android.xmpp.model.state.ChatStateNotification;
|
import im.conversations.android.xmpp.model.state.ChatStateNotification;
|
||||||
|
@ -25,6 +25,7 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);
|
||||||
|
|
||||||
private final Level level;
|
private final Level level;
|
||||||
|
private final TransformationFactory transformationFactory;
|
||||||
|
|
||||||
public MessageProcessor(final Context context, final XmppConnection connection) {
|
public MessageProcessor(final Context context, final XmppConnection connection) {
|
||||||
this(context, connection, Level.ROOT);
|
this(context, connection, Level.ROOT);
|
||||||
|
@ -34,6 +35,7 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
|
||||||
final Context context, final XmppConnection connection, final Level level) {
|
final Context context, final XmppConnection connection, final Level level) {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
this.level = level;
|
this.level = level;
|
||||||
|
this.transformationFactory = new TransformationFactory(context, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -59,10 +61,13 @@ public class MessageProcessor extends XmppConnection.Delegate implements Consume
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LOGGER.info("Message from {} with {}", message.getFrom(), message.getExtensionIds());
|
||||||
|
|
||||||
final var from = message.getFrom();
|
final var from = message.getFrom();
|
||||||
|
|
||||||
final var id = message.getId();
|
final var id = message.getId();
|
||||||
final var stanzaId = getManager(StanzaIdManager.class).getStanzaId(message);
|
final var stanzaId = getManager(StanzaIdManager.class).getStanzaId(message);
|
||||||
final var transformation = Transformation.of(message, stanzaId);
|
final var transformation = transformationFactory.create(message, stanzaId);
|
||||||
final boolean sendReceipts;
|
final boolean sendReceipts;
|
||||||
if (transformation.isAnythingToTransform()) {
|
if (transformation.isAnythingToTransform()) {
|
||||||
final var transformer = new Transformer(context, getAccount());
|
final var transformer = new Transformer(context, getAccount());
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
import eu.siacs.conversations.xml.XmlElementReader;
|
||||||
|
import im.conversations.android.xmpp.model.delay.Delay;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.ConscryptMode;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@ConscryptMode(ConscryptMode.Mode.OFF)
|
||||||
|
public class TimestampTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZuluNoMillis() throws IOException {
|
||||||
|
final String xml =
|
||||||
|
"<delay xmlns='urn:xmpp:delay'\n"
|
||||||
|
+ " from='capulet.com'\n"
|
||||||
|
+ " stamp='2002-09-10T23:08:25Z'/>";
|
||||||
|
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
|
||||||
|
assertThat(element, instanceOf(Delay.class));
|
||||||
|
final Delay delay = (Delay) element;
|
||||||
|
assertEquals(1031699305000L, delay.getStamp().toEpochMilli());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testZuluWithMillis() throws IOException {
|
||||||
|
final String xml =
|
||||||
|
"<delay xmlns='urn:xmpp:delay'\n"
|
||||||
|
+ " from='capulet.com'\n"
|
||||||
|
+ " stamp='2002-09-10T23:08:25.023Z'/>";
|
||||||
|
final Element element = XmlElementReader.read(xml.getBytes(StandardCharsets.UTF_8));
|
||||||
|
assertThat(element, instanceOf(Delay.class));
|
||||||
|
final Delay delay = (Delay) element;
|
||||||
|
assertEquals(1031699305023L, delay.getStamp().toEpochMilli());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue