store disco features after fetching them
This commit is contained in:
parent
1b438117a3
commit
6458c6e9f9
|
@ -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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue