store presence in DB

This commit is contained in:
Daniel Gultsch 2023-01-19 09:17:42 +01:00
parent 26bff8028a
commit 944c48e00b
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
9 changed files with 127 additions and 12 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "adc70f7066828bb6cf1fc32aa3a24b2f", "identityHash": "a521ff7a3e16cca9f6cbfe51241ec021",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
@ -319,7 +319,7 @@
}, },
{ {
"tableName": "disco_ext", "tableName": "disco_ext",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `discoId` INTEGER NOT NULL, 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, `discoId` INTEGER NOT NULL, `type` TEXT, FOREIGN KEY(`discoId`) REFERENCES `disco`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -332,6 +332,12 @@
"columnName": "discoId", "columnName": "discoId",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -1304,7 +1310,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, 'adc70f7066828bb6cf1fc32aa3a24b2f')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a521ff7a3e16cca9f6cbfe51241ec021')"
] ]
} }
} }

View file

@ -18,7 +18,6 @@ import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.EntityCapabilities; import im.conversations.android.xmpp.EntityCapabilities;
import im.conversations.android.xmpp.EntityCapabilities2; import im.conversations.android.xmpp.EntityCapabilities2;
import im.conversations.android.xmpp.model.data.Data; 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.data.Value;
import im.conversations.android.xmpp.model.disco.info.Feature; 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.Identity;
@ -38,6 +37,12 @@ public abstract class DiscoDao {
@Insert @Insert
protected abstract void insertDiscoFeatures(Collection<DiscoFeatureEntity> features); protected abstract void insertDiscoFeatures(Collection<DiscoFeatureEntity> features);
@Query(
"DELETE FROM disco_item WHERE accountId=:account AND parent=:parent AND address NOT"
+ " IN(:existent)")
protected abstract void deleteNonExistentDiscoItems(
final long account, final Jid parent, final Collection<Jid> existent);
@Insert @Insert
protected abstract void insertDiscoFieldValues( protected abstract void insertDiscoFieldValues(
Collection<DiscoExtensionFieldValueEntity> value); Collection<DiscoExtensionFieldValueEntity> value);
@ -59,6 +64,8 @@ public abstract class DiscoDao {
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));
insertDiscoItems(entities); insertDiscoItems(entities);
deleteNonExistentDiscoItems(
account.id, parent, Collections2.transform(items, Item::getJid));
} }
@Transaction @Transaction
@ -108,8 +115,8 @@ public abstract class DiscoDao {
infoQuery.getExtensions(Feature.class), infoQuery.getExtensions(Feature.class),
f -> DiscoFeatureEntity.of(discoId, f.getVar()))); f -> DiscoFeatureEntity.of(discoId, f.getVar())));
for (final Data data : infoQuery.getExtensions(Data.class)) { for (final Data data : infoQuery.getExtensions(Data.class)) {
final var extensionId = insert(DiscoExtensionEntity.of(discoId)); final var extensionId = insert(DiscoExtensionEntity.of(discoId, data.getFormType()));
for (final var field : data.getExtensions(Field.class)) { for (final var field : data.getFields()) {
final var fieldId = final var fieldId =
insert(DiscoExtensionFieldEntity.of(extensionId, field.getFieldName())); insert(DiscoExtensionFieldEntity.of(extensionId, field.getFieldName()));
insertDiscoFieldValues( insertDiscoFieldValues(

View file

@ -2,10 +2,50 @@ package im.conversations.android.database.dao;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Query; import androidx.room.Query;
import androidx.room.Upsert;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.PresenceEntity;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.PresenceShow;
import im.conversations.android.database.model.PresenceType;
import java.util.Arrays;
@Dao @Dao
public interface PresenceDao { public abstract class PresenceDao {
@Query("DELETE FROM presence WHERE accountId=:account") @Query("DELETE FROM presence WHERE accountId=:account")
void deletePresences(long account); public abstract void deletePresences(long account);
@Query("DELETE FROM presence WHERE accountId=:account AND address=:address")
abstract void deletePresences(long account, Jid address);
@Query(
"DELETE FROM presence WHERE accountId=:account AND address=:address AND"
+ " resource=:resource")
abstract void deletePresence(long account, Jid address, String resource);
@Upsert
abstract void insert(PresenceEntity entity);
public void set(
Account account,
Jid address,
String resource,
PresenceType type,
PresenceShow show,
String status) {
if (resource == null
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
deletePresences(account.id, address);
}
if (type == PresenceType.UNAVAILABLE) {
if (resource != null) {
deletePresence(account.id, address, resource);
}
// unavailable presence only delete previous nothing left to do
return;
}
final var entity = PresenceEntity.of(account.id, address, resource, type, show, status);
insert(entity);
}
} }

View file

@ -1,6 +1,7 @@
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;
@ -22,9 +23,12 @@ public class DiscoExtensionEntity {
@NonNull public Long discoId; @NonNull public Long discoId;
public static DiscoExtensionEntity of(long discoId) { @Nullable public String type;
public static DiscoExtensionEntity of(long discoId, final String type) {
final var entity = new DiscoExtensionEntity(); final var entity = new DiscoExtensionEntity();
entity.discoId = discoId; entity.discoId = discoId;
entity.type = type;
return entity; return entity;
} }
} }

View file

@ -31,7 +31,7 @@ public class PresenceEntity {
@NonNull public Long accountId; @NonNull public Long accountId;
@NonNull public String address; @NonNull public Jid address;
@Nullable public String resource; @Nullable public String resource;
@ -53,4 +53,21 @@ public class PresenceEntity {
// set to true if presence has status code 110 (this means we are online) // set to true if presence has status code 110 (this means we are online)
public boolean mucUserSelf; public boolean mucUserSelf;
public static PresenceEntity of(
long account,
Jid address,
String resource,
PresenceType type,
PresenceShow show,
String status) {
final var entity = new PresenceEntity();
entity.accountId = account;
entity.address = address;
entity.resource = resource;
entity.type = type;
entity.show = show;
entity.status = status;
return entity;
}
} }

View file

@ -1,8 +1,18 @@
package im.conversations.android.database.model; package im.conversations.android.database.model;
import java.util.Locale;
public enum PresenceShow { public enum PresenceShow {
CHAT, CHAT,
AWAY, AWAY,
XA, XA,
DND DND;
public static PresenceShow of(final String value) {
try {
return value == null ? null : valueOf(value.toUpperCase(Locale.ROOT));
} catch (final IllegalArgumentException e) {
return null;
}
}
} }

View file

@ -1,7 +1,17 @@
package im.conversations.android.database.model; package im.conversations.android.database.model;
import androidx.annotation.Nullable;
import java.util.Locale;
public enum PresenceType { public enum PresenceType {
UNAVAILABLE, UNAVAILABLE,
ERROR, ERROR,
SUBSCRIBE SUBSCRIBE;
public static PresenceType of(@Nullable String typeAttribute) {
if (typeAttribute == null) {
return null;
}
return of(typeAttribute.toUpperCase(Locale.ROOT));
}
} }

View file

@ -47,6 +47,8 @@ public class DiscoManager extends AbstractManager {
} }
iqRequest.addChild(infoQueryRequest); iqRequest.addChild(infoQueryRequest);
final var future = connection.sendIqPacket(iqRequest); final var future = connection.sendIqPacket(iqRequest);
// TODO we need to remove the disco info associated with $entity in case of failure
// this might happen in (rather unlikely) scenarios where an item no longer speaks disco
return Futures.transform( return Futures.transform(
future, future,
iqResult -> { iqResult -> {

View file

@ -2,6 +2,8 @@ package im.conversations.android.xmpp.processor;
import android.content.Context; import android.content.Context;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket; 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.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.manager.DiscoManager;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -14,6 +16,23 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
@Override @Override
public void accept(final PresencePacket presencePacket) { public void accept(final PresencePacket presencePacket) {
final var from = presencePacket.getFrom();
final var address = from == null ? null : from.asBareJid();
final var resource = from == null ? null : from.getResource();
final var typeAttribute = presencePacket.getAttribute("type");
final PresenceType type;
try {
type = PresenceType.of(typeAttribute);
} catch (final IllegalArgumentException e) {
// log we dont parse presence of type $type
return;
}
final var show = PresenceShow.of(presencePacket.findChildContent("show"));
final var status = presencePacket.findChildContent("status");
getDatabase().presenceDao().set(getAccount(), address, resource, type, show, status);
// TODO store presence info
// TODO do this only for contacts? // TODO do this only for contacts?
fetchCapabilities(presencePacket); fetchCapabilities(presencePacket);
} }