use empty string instead of null for 'no node' and 'no resource'

This commit is contained in:
Daniel Gultsch 2023-01-19 19:48:57 +01:00
parent 90e613f94e
commit 1a09b3ed05
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
12 changed files with 203 additions and 96 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "a521ff7a3e16cca9f6cbfe51241ec021",
"identityHash": "b28e01dcbb5d9774a4b36783d0db6c73",
"entities": [
{
"tableName": "account",
@ -427,7 +427,7 @@
},
{
"tableName": "disco_ext_field_value",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `fieldId` INTEGER NOT NULL, `value` TEXT, FOREIGN KEY(`fieldId`) REFERENCES `disco_ext`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `fieldId` INTEGER NOT NULL, `value` TEXT, FOREIGN KEY(`fieldId`) REFERENCES `disco_ext_field`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@ -467,7 +467,7 @@
],
"foreignKeys": [
{
"table": "disco_ext",
"table": "disco_ext_field",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
@ -601,7 +601,7 @@
},
{
"tableName": "disco_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `node` TEXT, `parent` TEXT, `discoId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`discoId`) REFERENCES `disco`(`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, `address` TEXT NOT NULL, `node` TEXT NOT NULL, `parent` TEXT, `discoId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`discoId`) REFERENCES `disco`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@ -625,7 +625,7 @@
"fieldPath": "node",
"columnName": "node",
"affinity": "TEXT",
"notNull": false
"notNull": true
},
{
"fieldPath": "parent",
@ -975,7 +975,7 @@
},
{
"tableName": "presence",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `resource` TEXT, `type` TEXT, `show` TEXT, `status` TEXT, `vCardPhoto` TEXT, `occupantId` TEXT, `mucUserAffiliation` TEXT, `mucUserRole` TEXT, `mucUserJid` TEXT, `mucUserSelf` INTEGER NOT NULL, 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, `address` TEXT NOT NULL, `resource` TEXT NOT NULL, `type` TEXT, `show` TEXT, `status` TEXT, `vCardPhoto` TEXT, `occupantId` TEXT, `mucUserAffiliation` TEXT, `mucUserRole` TEXT, `mucUserJid` TEXT, `mucUserSelf` INTEGER NOT NULL, `discoId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`discoId`) REFERENCES `disco`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@ -999,7 +999,7 @@
"fieldPath": "resource",
"columnName": "resource",
"affinity": "TEXT",
"notNull": false
"notNull": true
},
{
"fieldPath": "type",
@ -1054,6 +1054,12 @@
"columnName": "mucUserSelf",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "discoId",
"columnName": "discoId",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
@ -1073,6 +1079,15 @@
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_presence_accountId_address_resource` ON `${TABLE_NAME}` (`accountId`, `address`, `resource`)"
},
{
"name": "index_presence_discoId",
"unique": false,
"columnNames": [
"discoId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_presence_discoId` ON `${TABLE_NAME}` (`discoId`)"
}
],
"foreignKeys": [
@ -1086,6 +1101,17 @@
"referencedColumns": [
"id"
]
},
{
"table": "disco",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"discoId"
],
"referencedColumns": [
"id"
]
}
]
},
@ -1310,7 +1336,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a521ff7a3e16cca9f6cbfe51241ec021')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b28e01dcbb5d9774a4b36783d0db6c73')"
]
}
}

View file

@ -72,7 +72,7 @@ public class Element {
}
public <E extends Extension> E getExtension(final Class<E> clazz) {
final var extension = Iterables.find(this.children, clazz::isInstance);
final var extension = Iterables.find(this.children, clazz::isInstance, null);
if (extension == null) {
return null;
}

View file

@ -1,10 +1,13 @@
package im.conversations.android.database.dao;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Upsert;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.DiscoEntity;
@ -15,6 +18,7 @@ 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.model.Account;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.EntityCapabilities;
import im.conversations.android.xmpp.EntityCapabilities2;
import im.conversations.android.xmpp.model.data.Data;
@ -43,12 +47,18 @@ public abstract class DiscoDao {
protected abstract void deleteNonExistentDiscoItems(
final long account, final Jid parent, final Collection<Jid> existent);
@Query(
"UPDATE presence SET discoId=:discoId WHERE accountId=:account AND address=:address"
+ " AND resource=:resource")
protected abstract void updateDiscoIdInPresence(
long account, Jid address, String resource, long discoId);
@Insert
protected abstract void insertDiscoFieldValues(
Collection<DiscoExtensionFieldValueEntity> value);
@Upsert(entity = DiscoItemEntity.class)
protected abstract void insert(DiscoItemWithDiscoId item);
protected abstract void updateDiscoIdInDiscoItem(DiscoItemWithDiscoId item);
@Insert
protected abstract long insert(DiscoEntity entity);
@ -60,19 +70,20 @@ public abstract class DiscoDao {
protected abstract long insert(DiscoExtensionFieldEntity entity);
@Transaction
public void set(final Account account, final Jid parent, final Collection<Item> items) {
public void set(
final Account account, final Entity.DiscoItem parent, final Collection<Item> items) {
final var entities =
Collections2.transform(items, i -> DiscoItemWithParent.of(account.id, parent, i));
insertDiscoItems(entities);
deleteNonExistentDiscoItems(
account.id, parent, Collections2.transform(items, Item::getJid));
account.id, parent.address, Collections2.transform(items, Item::getJid));
}
@Transaction
public boolean set(
final Account account,
final Jid address,
final String node,
final Entity entity,
@Nullable final String node,
final EntityCapabilities.Hash capsHash) {
final Long existingDiscoId;
if (capsHash instanceof EntityCapabilities2.EntityCaps2Hash) {
@ -85,14 +96,31 @@ public abstract class DiscoDao {
if (existingDiscoId == null) {
return false;
}
insert(DiscoItemWithDiscoId.of(account.id, address, node, existingDiscoId));
updateDiscoId(account.id, entity, node, existingDiscoId);
return true;
}
protected void updateDiscoId(
final long account,
final Entity entity,
@Nullable final String node,
final long discoId) {
if (entity instanceof Entity.DiscoItem) {
updateDiscoIdInDiscoItem(
DiscoItemWithDiscoId.of(account, (Entity.DiscoItem) entity, node, discoId));
} else if (entity instanceof Entity.Presence) {
updateDiscoIdInPresence(
account,
entity.address.asBareJid(),
Strings.nullToEmpty(entity.address.getResource()),
discoId);
}
}
@Transaction
public void set(
final Account account,
final Jid address,
final Entity entity,
final String node,
final byte[] capsHash,
final byte[] caps2HashSha256,
@ -100,7 +128,7 @@ public abstract class DiscoDao {
final Long existingDiscoId = getDiscoId(account.id, caps2HashSha256);
if (existingDiscoId != null) {
insert(DiscoItemWithDiscoId.of(account.id, address, node, existingDiscoId));
updateDiscoId(account.id, entity, node, existingDiscoId);
return;
}
final long discoId = insert(DiscoEntity.of(account.id, capsHash, caps2HashSha256));
@ -125,6 +153,7 @@ public abstract class DiscoDao {
v -> DiscoExtensionFieldValueEntity.of(fieldId, v.getContent())));
}
}
updateDiscoId(account.id, entity, node, discoId);
}
@Query("SELECT id FROM disco WHERE accountId=:accountId AND caps2HashSha256=:caps2HashSha256")
@ -141,32 +170,35 @@ public abstract class DiscoDao {
public static class DiscoItemWithParent {
public long accountId;
public Jid address;
public String node;
public Jid parent;
public @NonNull Jid address;
public @NonNull String node;
public @Nullable Jid parent;
public static DiscoItemWithParent of(
final long account, final Jid parent, final Item item) {
final long account, Entity.DiscoItem parent, final Item item) {
final var entity = new DiscoItemWithParent();
entity.accountId = account;
entity.address = item.getJid();
entity.node = item.getNode();
entity.parent = parent;
entity.node = Strings.nullToEmpty(item.getNode());
entity.parent = parent.address;
return entity;
}
}
public static class DiscoItemWithDiscoId {
public long accountId;
public Jid address;
public String node;
public @NonNull Jid address;
public @NonNull String node;
public long discoId;
public static DiscoItemWithDiscoId of(
final long account, final Jid address, final String node, final long discoId) {
final long account,
final Entity.DiscoItem discoItem,
@NonNull final String node,
final long discoId) {
final var entity = new DiscoItemWithDiscoId();
entity.accountId = account;
entity.address = address;
entity.address = discoItem.address;
entity.node = node;
entity.discoId = discoId;
return entity;

View file

@ -1,5 +1,7 @@
package im.conversations.android.database.dao;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.Upsert;
@ -28,12 +30,12 @@ public abstract class PresenceDao {
abstract void insert(PresenceEntity entity);
public void set(
Account account,
Jid address,
String resource,
PresenceType type,
PresenceShow show,
String status) {
@NonNull final Account account,
@NonNull final Jid address,
@Nullable final String resource,
@Nullable final PresenceType type,
@Nullable final PresenceShow show,
@Nullable final String status) {
if (resource == null
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
deletePresences(account.id, address);

View file

@ -10,7 +10,7 @@ import androidx.room.PrimaryKey;
tableName = "disco_ext_field_value",
foreignKeys =
@ForeignKey(
entity = DiscoExtensionEntity.class,
entity = DiscoExtensionFieldEntity.class,
parentColumns = {"id"},
childColumns = {"fieldId"},
onDelete = ForeignKey.CASCADE),
@ -25,6 +25,9 @@ public class DiscoExtensionFieldValueEntity {
public String value;
public static DiscoExtensionFieldValueEntity of(long fieldId, final String value) {
return null;
final var entity = new DiscoExtensionFieldValueEntity();
entity.fieldId = fieldId;
entity.value = value;
return entity;
}
}

View file

@ -40,7 +40,7 @@ public class DiscoItemEntity {
@NonNull Jid address;
@Nullable public String node;
@NonNull public String node;
@Nullable public Jid parent;

View file

@ -6,6 +6,7 @@ import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.google.common.base.Strings;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.model.PresenceShow;
@ -13,16 +14,23 @@ import im.conversations.android.database.model.PresenceType;
@Entity(
tableName = "presence",
foreignKeys =
foreignKeys = {
@ForeignKey(
entity = AccountEntity.class,
parentColumns = {"id"},
childColumns = {"accountId"},
onDelete = ForeignKey.CASCADE),
@ForeignKey(
entity = DiscoEntity.class,
parentColumns = {"id"},
childColumns = {"discoId"},
onDelete = ForeignKey.CASCADE)
},
indices = {
@Index(
value = {"accountId", "address", "resource"},
unique = true)
unique = true),
@Index(value = {"discoId"})
})
public class PresenceEntity {
@ -33,7 +41,7 @@ public class PresenceEntity {
@NonNull public Jid address;
@Nullable public String resource;
@NonNull public String resource;
@Nullable public PresenceType type;
@ -54,17 +62,19 @@ public class PresenceEntity {
// set to true if presence has status code 110 (this means we are online)
public boolean mucUserSelf;
public Long discoId;
public static PresenceEntity of(
long account,
Jid address,
String resource,
@Nullable String resource,
PresenceType type,
PresenceShow show,
String status) {
final var entity = new PresenceEntity();
entity.accountId = account;
entity.address = address;
entity.resource = resource;
entity.resource = Strings.nullToEmpty(resource);
entity.type = type;
entity.show = show;
entity.status = status;

View file

@ -1,4 +1,34 @@
package im.conversations.android.xmpp;
public class Entity {
import eu.siacs.conversations.xmpp.Jid;
public abstract class Entity {
public final Jid address;
private Entity(final Jid address) {
this.address = address;
}
public static class DiscoItem extends Entity {
private DiscoItem(Jid address) {
super(address);
}
}
public static class Presence extends Entity {
private Presence(Jid address) {
super(address);
}
}
public static Presence presence(final Jid address) {
return new Presence(address);
}
public static DiscoItem discoItem(final Jid address) {
return new DiscoItem(address);
}
}

View file

@ -1735,10 +1735,23 @@ public class XmppConnection implements Runnable {
final Element bind = packet.findChild("bind");
if (bind != null && packet.getType() == IqPacket.TYPE.RESULT) {
isBound = true;
final Element jid = bind.findChild("jid");
if (jid != null && jid.getContent() != null) {
final String jid = bind.findChildContent("jid");
if (Strings.isNullOrEmpty(jid)) {
throw new StateChangingError(ConnectionState.BIND_FAILURE);
}
final Jid assignedJid;
try {
final Jid assignedJid = Jid.ofEscaped(jid.getContent());
assignedJid = Jid.ofEscaped(jid);
} catch (final IllegalArgumentException e) {
Log.d(
Config.LOGTAG,
account.address
+ ": server reported invalid jid ("
+ jid
+ ") on bind");
throw new StateChangingError(ConnectionState.BIND_FAILURE);
}
if (!account.address.getDomain().equals(assignedJid.getDomain())) {
Log.d(
Config.LOGTAG,
@ -1749,29 +1762,13 @@ public class XmppConnection implements Runnable {
}
setConnectionAddress(assignedJid);
if (streamFeatures.hasChild("session")
&& !streamFeatures
.findChild("session")
.hasChild("optional")) {
&& !streamFeatures.findChild("session").hasChild("optional")) {
sendStartSession();
} else {
enableStreamManagement();
sendPostBindInitialization(false);
}
return;
} catch (final IllegalArgumentException e) {
Log.d(
Config.LOGTAG,
account.address
+ ": server reported invalid jid ("
+ jid.getContent()
+ ") on bind");
}
} else {
Log.d(
Config.LOGTAG,
account.address
+ ": disconnecting because of bind failure. (no jid)");
}
} else {
Log.d(
Config.LOGTAG,
@ -1885,14 +1882,14 @@ public class XmppConnection implements Runnable {
final var discoManager = getManager(DiscoManager.class);
final var nodeHash = this.streamFeatures.getCapabilities();
final var domainDiscoItem = Entity.discoItem(account.address.getDomain());
if (nodeHash != null) {
discoFutures.add(
discoManager.info(account.address.getDomain(), nodeHash.node, nodeHash.hash));
discoFutures.add(discoManager.info(domainDiscoItem, nodeHash.node, nodeHash.hash));
} else {
discoFutures.add(discoManager.info(account.address.getDomain()));
discoFutures.add(discoManager.info(domainDiscoItem));
}
discoFutures.add(discoManager.info(account.address));
discoFutures.add(discoManager.itemsWithInfo(account.address.getDomain()));
discoFutures.add(discoManager.info(Entity.discoItem(account.address)));
discoFutures.add(discoManager.itemsWithInfo(domainDiscoItem));
final var discoFuture =
Futures.withTimeout(
@ -1912,6 +1909,7 @@ public class XmppConnection implements Runnable {
@Override
public void onFailure(@NonNull Throwable t) {
Log.d(Config.LOGTAG, "unable to fetch disco foo " + t);
// TODO reset stream ID so we get a proper connect next time
finalizeBind();
}

View file

@ -8,6 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.EntityCapabilities;
import im.conversations.android.xmpp.EntityCapabilities2;
import im.conversations.android.xmpp.XmppConnection;
@ -24,12 +25,12 @@ public class DiscoManager extends AbstractManager {
super(context, connection);
}
public ListenableFuture<InfoQuery> info(final Jid entity) {
public ListenableFuture<InfoQuery> info(final Entity entity) {
return info(entity, null);
}
public ListenableFuture<Void> info(
final Jid entity, @Nullable final String node, final EntityCapabilities.Hash hash) {
final Entity entity, @Nullable final String node, final EntityCapabilities.Hash hash) {
final String capabilityNode = hash.capabilityNode(node);
if (getDatabase().discoDao().set(getAccount(), entity, capabilityNode, hash)) {
return Futures.immediateFuture(null);
@ -38,9 +39,9 @@ public class DiscoManager extends AbstractManager {
info(entity, capabilityNode), f -> null, MoreExecutors.directExecutor());
}
public ListenableFuture<InfoQuery> info(final Jid entity, final String node) {
public ListenableFuture<InfoQuery> info(final Entity entity, final String node) {
final var iqRequest = new IqPacket(IqPacket.TYPE.GET);
iqRequest.setTo(entity);
iqRequest.setTo(entity.address);
final var infoQueryRequest = new InfoQuery();
if (node != null) {
infoQueryRequest.setNode(node);
@ -70,9 +71,9 @@ public class DiscoManager extends AbstractManager {
MoreExecutors.directExecutor());
}
public ListenableFuture<Collection<Item>> items(final Jid entity) {
public ListenableFuture<Collection<Item>> items(final Entity.DiscoItem entity) {
final var iqPacket = new IqPacket(IqPacket.TYPE.GET);
iqPacket.setTo(entity);
iqPacket.setTo(entity.address);
iqPacket.addChild(new ItemsQuery());
final var future = connection.sendIqPacket(iqPacket);
return Futures.transform(
@ -91,14 +92,16 @@ public class DiscoManager extends AbstractManager {
MoreExecutors.directExecutor());
}
public ListenableFuture<List<InfoQuery>> itemsWithInfo(final Jid entity) {
public ListenableFuture<List<InfoQuery>> itemsWithInfo(final Entity.DiscoItem entity) {
final var itemsFutures = items(entity);
return Futures.transformAsync(
itemsFutures,
items -> {
// TODO filter out items with empty jid
final var filtered =
Collections2.filter(items, i -> Objects.nonNull(i.getJid()));
Collection<ListenableFuture<InfoQuery>> infoFutures =
Collections2.transform(items, i -> info(i.getJid(), i.getNode()));
Collections2.transform(
filtered, i -> info(Entity.discoItem(i.getJid()), i.getNode()));
return Futures.allAsList(infoFutures);
},
MoreExecutors.directExecutor());

View file

@ -1,5 +1,6 @@
package im.conversations.android.xmpp.model.disco.items;
import androidx.annotation.Nullable;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@ -14,7 +15,7 @@ public class Item extends Extension {
return getAttributeAsJid("jid");
}
public String getNode() {
public @Nullable String getNode() {
return this.getAttribute("node");
}
}

View file

@ -4,6 +4,7 @@ import android.content.Context;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.DiscoManager;
import java.util.function.Consumer;
@ -41,7 +42,8 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
final var entity = presencePacket.getFrom();
final var nodeHash = presencePacket.getCapabilities();
if (nodeHash != null) {
getManager(DiscoManager.class).info(entity, nodeHash.node, nodeHash.hash);
getManager(DiscoManager.class)
.info(Entity.presence(entity), nodeHash.node, nodeHash.hash);
}
}
}