store disco features after fetching them

This commit is contained in:
Daniel Gultsch 2023-01-16 19:53:38 +01:00
parent 1b438117a3
commit 6458c6e9f9
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
11 changed files with 206 additions and 30 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "afc5b1df44123031e340e1a3db15396d", "identityHash": "adc70f7066828bb6cf1fc32aa3a24b2f",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
@ -248,7 +248,7 @@
}, },
{ {
"tableName": "disco", "tableName": "disco",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `capsHash` BLOB, `caps2Hash` BLOB, `caps2Algorithm` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `capsHash` BLOB, `caps2HashSha256` BLOB, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -269,16 +269,10 @@
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "caps2Hash", "fieldPath": "caps2HashSha256",
"columnName": "caps2Hash", "columnName": "caps2HashSha256",
"affinity": "BLOB", "affinity": "BLOB",
"notNull": false "notNull": false
},
{
"fieldPath": "caps2Algorithm",
"columnName": "caps2Algorithm",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -289,13 +283,24 @@
}, },
"indices": [ "indices": [
{ {
"name": "index_disco_accountId", "name": "index_disco_accountId_capsHash",
"unique": false, "unique": false,
"columnNames": [ "columnNames": [
"accountId" "accountId",
"capsHash"
], ],
"orders": [], "orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_disco_accountId` ON `${TABLE_NAME}` (`accountId`)" "createSql": "CREATE INDEX IF NOT EXISTS `index_disco_accountId_capsHash` ON `${TABLE_NAME}` (`accountId`, `capsHash`)"
},
{
"name": "index_disco_accountId_caps2HashSha256",
"unique": true,
"columnNames": [
"accountId",
"caps2HashSha256"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_disco_accountId_caps2HashSha256` ON `${TABLE_NAME}` (`accountId`, `caps2HashSha256`)"
} }
], ],
"foreignKeys": [ "foreignKeys": [
@ -1299,7 +1304,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, 'afc5b1df44123031e340e1a3db15396d')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'adc70f7066828bb6cf1fc32aa3a24b2f')"
] ]
} }
} }

View file

@ -1,12 +1,26 @@
package im.conversations.android.database.dao; package im.conversations.android.database.dao;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction; import androidx.room.Transaction;
import androidx.room.Upsert; import androidx.room.Upsert;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.DiscoEntity;
import im.conversations.android.database.entity.DiscoExtensionEntity;
import im.conversations.android.database.entity.DiscoExtensionFieldEntity;
import im.conversations.android.database.entity.DiscoExtensionFieldValueEntity;
import im.conversations.android.database.entity.DiscoFeatureEntity;
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.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.model.data.Data;
import im.conversations.android.xmpp.model.data.Field;
import im.conversations.android.xmpp.model.data.Value;
import im.conversations.android.xmpp.model.disco.info.Feature;
import im.conversations.android.xmpp.model.disco.info.Identity;
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.disco.items.Item; import im.conversations.android.xmpp.model.disco.items.Item;
import java.util.Collection; import java.util.Collection;
@ -14,16 +28,78 @@ import java.util.Collection;
public abstract class DiscoDao { public abstract class DiscoDao {
@Upsert(entity = DiscoItemEntity.class) @Upsert(entity = DiscoItemEntity.class)
protected abstract void setDiscoItems(Collection<DiscoItemWithParent> items); protected abstract void insertDiscoItems(Collection<DiscoItemWithParent> items);
@Insert
protected abstract void insertDiscoIdentities(Collection<DiscoIdentityEntity> identities);
@Insert
protected abstract void insertDiscoFeatures(Collection<DiscoFeatureEntity> features);
@Insert
protected abstract void insertDiscoFieldValues(
Collection<DiscoExtensionFieldValueEntity> value);
@Upsert(entity = DiscoItemEntity.class)
protected abstract void insert(DiscoItemWithDiscoId item);
@Insert
protected abstract long insert(DiscoEntity entity);
@Insert
protected abstract long insert(DiscoExtensionEntity entity);
@Insert
protected abstract long insert(DiscoExtensionFieldEntity entity);
@Transaction @Transaction
public void setDiscoItems( public void set(final Account account, final Jid parent, final Collection<Item> items) {
final Account account, final Jid parent, final Collection<Item> items) {
final var entities = final var entities =
Collections2.transform(items, i -> DiscoItemWithParent.of(account.id, parent, i)); Collections2.transform(items, i -> DiscoItemWithParent.of(account.id, parent, i));
setDiscoItems(entities); insertDiscoItems(entities);
} }
@Transaction
public void set(
final Account account,
final Jid address,
final String node,
final byte[] capsHash,
final byte[] caps2HashSha256,
final InfoQuery infoQuery) {
final Long existingDiscoId = getDiscoId(account.id, caps2HashSha256);
if (existingDiscoId != null) {
insert(DiscoItemWithDiscoId.of(account.id, address, node, existingDiscoId));
return;
}
final long discoId = insert(DiscoEntity.of(account.id, capsHash, caps2HashSha256));
insertDiscoIdentities(
Collections2.transform(
infoQuery.getExtensions(Identity.class),
i -> DiscoIdentityEntity.of(discoId, i)));
insertDiscoFeatures(
Collections2.transform(
infoQuery.getExtensions(Feature.class),
f -> DiscoFeatureEntity.of(discoId, f.getVar())));
for (final Data data : infoQuery.getExtensions(Data.class)) {
final var extensionId = insert(DiscoExtensionEntity.of(discoId));
for (final var field : data.getExtensions(Field.class)) {
final var fieldId =
insert(DiscoExtensionFieldEntity.of(extensionId, field.getFieldName()));
insertDiscoFieldValues(
Collections2.transform(
field.getExtensions(Value.class),
v -> DiscoExtensionFieldValueEntity.of(fieldId, v.getContent())));
}
}
}
@Query("SELECT id FROM disco WHERE accountId=:accountId AND caps2HashSha256=:caps2HashSha256")
protected abstract Long getDiscoId(final long accountId, final byte[] caps2HashSha256);
public static class DiscoItemWithParent { public static class DiscoItemWithParent {
public long accountId; public long accountId;
public Jid address; public Jid address;
@ -40,4 +116,21 @@ public abstract class DiscoDao {
return entity; return entity;
} }
} }
public static class DiscoItemWithDiscoId {
public long accountId;
public Jid address;
public String node;
public long discoId;
public static DiscoItemWithDiscoId of(
final long account, final Jid address, final String node, final long discoId) {
final var entity = new DiscoItemWithDiscoId();
entity.accountId = account;
entity.address = address;
entity.node = node;
entity.discoId = discoId;
return entity;
}
}
} }

View file

@ -14,15 +14,28 @@ import androidx.room.PrimaryKey;
parentColumns = {"id"}, parentColumns = {"id"},
childColumns = {"accountId"}, childColumns = {"accountId"},
onDelete = ForeignKey.CASCADE), onDelete = ForeignKey.CASCADE),
indices = {@Index(value = {"accountId"})}) indices = {
@Index(value = {"accountId", "capsHash"}),
@Index(
value = {"accountId", "caps2HashSha256"},
unique = true)
})
public class DiscoEntity { public class DiscoEntity {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
public Long id; public Long id;
@NonNull Long accountId; @NonNull public Long accountId;
public byte[] capsHash; public byte[] capsHash;
public byte[] caps2Hash; public byte[] caps2HashSha256;
public String caps2Algorithm;
public static DiscoEntity of(
final long accountId, final byte[] capsHash, final byte[] caps2HashSha256) {
final var entity = new DiscoEntity();
entity.accountId = accountId;
entity.capsHash = capsHash;
entity.caps2HashSha256 = caps2HashSha256;
return entity;
}
} }

View file

@ -21,4 +21,10 @@ public class DiscoExtensionEntity {
public Long id; public Long id;
@NonNull public Long discoId; @NonNull public Long discoId;
public static DiscoExtensionEntity of(long discoId) {
final var entity = new DiscoExtensionEntity();
entity.discoId = discoId;
return entity;
}
} }

View file

@ -23,4 +23,11 @@ public class DiscoExtensionFieldEntity {
@NonNull public Long extensionId; @NonNull public Long extensionId;
public String field; public String field;
public static DiscoExtensionFieldEntity of(final long extensionId, final String fieldName) {
final var entity = new DiscoExtensionFieldEntity();
entity.extensionId = extensionId;
entity.field = fieldName;
return entity;
}
} }

View file

@ -23,4 +23,8 @@ public class DiscoExtensionFieldValueEntity {
@NonNull public Long fieldId; @NonNull public Long fieldId;
public String value; public String value;
public static DiscoExtensionFieldValueEntity of(long fieldId, final String value) {
return null;
}
} }

View file

@ -23,4 +23,11 @@ public class DiscoFeatureEntity {
@NonNull public Long discoId; @NonNull public Long discoId;
@NonNull public String feature; @NonNull public String feature;
public static DiscoFeatureEntity of(final long discoId, final String feature) {
final var entity = new DiscoFeatureEntity();
entity.discoId = discoId;
entity.feature = feature;
return entity;
}
} }

View file

@ -5,6 +5,7 @@ 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.xmpp.model.disco.info.Identity;
@Entity( @Entity(
tableName = "disco_identity", tableName = "disco_identity",
@ -25,4 +26,13 @@ public class DiscoIdentityEntity {
public String category; public String category;
public String type; public String type;
public String name; public String name;
public static DiscoIdentityEntity of(final long discoId, final Identity i) {
final var entity = new DiscoIdentityEntity();
entity.discoId = discoId;
entity.category = i.getCategory();
entity.type = i.getType();
entity.name = i.getIdentityName();
return entity;
}
} }

View file

@ -4,6 +4,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import im.conversations.android.xmpp.model.data.Data; import im.conversations.android.xmpp.model.data.Data;
@ -25,8 +26,12 @@ public class EntityCapabilities2 {
private static final char FILE_SEPARATOR = 0x1c; private static final char FILE_SEPARATOR = 0x1c;
public static byte[] hash(final InfoQuery info) { public static byte[] hash(final InfoQuery info) {
return hash(Hashing.sha256(), info);
}
public static byte[] hash(HashFunction hashFunction, final InfoQuery info) {
final String algo = algorithm(info); final String algo = algorithm(info);
return Hashing.sha256().hashString(algo, StandardCharsets.UTF_8).asBytes(); return hashFunction.hashString(algo, StandardCharsets.UTF_8).asBytes();
} }
private static String asHex(final String message) { private static String asHex(final String message) {

View file

@ -7,6 +7,8 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.EntityCapabilities;
import im.conversations.android.xmpp.EntityCapabilities2;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.disco.items.Item; import im.conversations.android.xmpp.model.disco.items.Item;
@ -21,18 +23,34 @@ public class DiscoManager extends AbstractManager {
} }
public ListenableFuture<InfoQuery> info(final Jid entity) { public ListenableFuture<InfoQuery> info(final Jid entity) {
final var iqPacket = new IqPacket(IqPacket.TYPE.GET); return info(entity, null);
iqPacket.setTo(entity); }
iqPacket.addChild(new InfoQuery());
final var future = connection.sendIqPacket(iqPacket); public ListenableFuture<InfoQuery> info(final Jid entity, final String node) {
final var iqRequest = new IqPacket(IqPacket.TYPE.GET);
iqRequest.setTo(entity);
final var infoQueryRequest = new InfoQuery();
if (node != null) {
infoQueryRequest.setNode(node);
}
iqRequest.addChild(infoQueryRequest);
final var future = connection.sendIqPacket(iqRequest);
return Futures.transform( return Futures.transform(
future, future,
iqResult -> { iqResult -> {
final var infoQuery = iqResult.getExtension(InfoQuery.class); final var infoQuery = iqResult.getExtension(InfoQuery.class);
if (infoQuery == null) { if (infoQuery == null) {
throw new IllegalStateException(); throw new IllegalStateException("Response did not have query child");
} }
// TODO store query if (!Objects.equals(node, infoQuery.getNode())) {
throw new IllegalStateException(
"Node in response did not match node in request");
}
final byte[] caps = EntityCapabilities.hash(infoQuery);
final byte[] caps2 = EntityCapabilities2.hash(infoQuery);
getDatabase()
.discoDao()
.set(getAccount(), entity, node, caps, caps2, infoQuery);
return infoQuery; return infoQuery;
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
@ -53,7 +71,7 @@ public class DiscoManager extends AbstractManager {
final var items = itemsQuery.getExtensions(Item.class); final var items = itemsQuery.getExtensions(Item.class);
final var validItems = final var validItems =
Collections2.filter(items, i -> Objects.nonNull(i.getJid())); Collections2.filter(items, i -> Objects.nonNull(i.getJid()));
getDatabase().discoDao().setDiscoItems(getAccount(), entity, validItems); getDatabase().discoDao().set(getAccount(), entity, validItems);
return validItems; return validItems;
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());

View file

@ -9,4 +9,12 @@ public class InfoQuery extends Extension {
public InfoQuery() { public InfoQuery() {
super(InfoQuery.class); super(InfoQuery.class);
} }
public void setNode(final String node) {
this.setAttribute("node", node);
}
public String getNode() {
return this.getAttribute("node");
}
} }