retrieve blocklist on bind

This commit is contained in:
Daniel Gultsch 2023-01-15 10:11:49 +01:00
parent 6b232f7a5a
commit 20962554a4
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
14 changed files with 198 additions and 31 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "d16845c3eb73e5fdbc9902903b74428a", "identityHash": "aa5e73a1cf9ba959e118f66b89b9d227",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
@ -1066,7 +1066,7 @@
}, },
{ {
"tableName": "roster", "tableName": "roster",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `subscription` TEXT, `ask` INTEGER NOT NULL, `name` 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, `address` TEXT NOT NULL, `subscription` TEXT, `isPendingOut` INTEGER NOT NULL, `name` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -1093,8 +1093,8 @@
"notNull": false "notNull": false
}, },
{ {
"fieldPath": "ask", "fieldPath": "isPendingOut",
"columnName": "ask", "columnName": "isPendingOut",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
@ -1195,7 +1195,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, 'd16845c3eb73e5fdbc9902903b74428a')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'aa5e73a1cf9ba959e118f66b89b9d227')"
] ]
} }
} }

View file

@ -67,6 +67,10 @@ public class Element {
return null; return null;
} }
public <E extends Extension> boolean hasExtension(final Class<E> clazz) {
return Iterables.any(this.children, clazz::isInstance);
}
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.children, clazz::isInstance); final var extension = Iterables.find(this.children, clazz::isInstance);
if (extension == null) { if (extension == null) {

View file

@ -6,6 +6,7 @@ import androidx.room.Room;
import androidx.room.RoomDatabase; import androidx.room.RoomDatabase;
import androidx.room.TypeConverters; import androidx.room.TypeConverters;
import im.conversations.android.database.dao.AccountDao; import im.conversations.android.database.dao.AccountDao;
import im.conversations.android.database.dao.BlockingDao;
import im.conversations.android.database.dao.MessageDao; import im.conversations.android.database.dao.MessageDao;
import im.conversations.android.database.dao.PresenceDao; import im.conversations.android.database.dao.PresenceDao;
import im.conversations.android.database.dao.RosterDao; import im.conversations.android.database.dao.RosterDao;
@ -74,4 +75,6 @@ public abstract class ConversationsDatabase extends RoomDatabase {
public abstract MessageDao messageDao(); public abstract MessageDao messageDao();
public abstract RosterDao rosterDao(); public abstract RosterDao rosterDao();
public abstract BlockingDao blockingDao();
} }

View file

@ -0,0 +1,29 @@
package im.conversations.android.database.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Transaction;
import com.google.common.collect.Collections2;
import im.conversations.android.database.entity.BlockedItemEntity;
import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.model.blocking.Item;
import java.util.Collection;
@Dao
public abstract class BlockingDao {
@Insert
abstract void insert(Collection<BlockedItemEntity> entities);
@Query("DELETE FROM blocked WHERE accountId=:account")
abstract void clear(final long account);
@Transaction
public void setBlocklist(final Account account, final Collection<Item> blockedItems) {
final var entities =
Collections2.transform(blockedItems, i -> BlockedItemEntity.of(account.id, i));
clear(account.id);
insert(entities);
}
}

View file

@ -1,32 +1,56 @@
package im.conversations.android.database.dao; package im.conversations.android.database.dao;
import static androidx.room.OnConflictStrategy.REPLACE;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.Query; import androidx.room.Query;
import androidx.room.Transaction; import androidx.room.Transaction;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.RosterItemEntity; import im.conversations.android.database.entity.RosterItemEntity;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.model.roster.Item;
import java.util.Collection; import java.util.Collection;
@Dao @Dao
public abstract class RosterDao { public abstract class RosterDao {
@Insert @Insert(onConflict = REPLACE)
protected abstract void insert(Collection<RosterItemEntity> rosterItems); protected abstract long insert(RosterItemEntity rosterItem);
@Query("DELETE FROM roster WHERE accountId=:account") @Query("DELETE FROM roster WHERE accountId=:account")
protected abstract void clear(final long account); protected abstract void clear(final long account);
@Query("DELETE FROM roster WHERE accountId=:account AND address=:address")
protected abstract void delete(final long account, final Jid address);
@Query("UPDATE account SET rosterVersion=:version WHERE id=:account") @Query("UPDATE account SET rosterVersion=:version WHERE id=:account")
protected abstract void setRosterVersion(final long account, final String version); protected abstract void setRosterVersion(final long account, final String version);
@Transaction @Transaction
public void setRoster( public void set(
final Account account, final Account account, final String version, final Collection<Item> rosterItems) {
final String version,
final Collection<RosterItemEntity> rosterItems) {
clear(account.id); clear(account.id);
insert(rosterItems); for (final Item item : rosterItems) {
final long id = insert(RosterItemEntity.of(account.id, item));
// TODO insert groups
}
setRosterVersion(account.id, version);
}
public void update(
final Account account, final String version, final Collection<Item> updates) {
for (final Item item : updates) {
final Item.Subscription subscription = item.getSubscription();
if (subscription == null) {
continue;
}
if (subscription == Item.Subscription.REMOVE) {
delete(account.id, item.getJid());
}
final RosterItemEntity entity = RosterItemEntity.of(account.id, item);
final long id = insert(entity);
}
setRosterVersion(account.id, version); setRosterVersion(account.id, version);
} }
} }

View file

@ -5,6 +5,8 @@ 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.xmpp.model.blocking.Item;
@Entity( @Entity(
tableName = "blocked", tableName = "blocked",
@ -26,5 +28,12 @@ public class BlockedItemEntity {
@NonNull public Long accountId; @NonNull public Long accountId;
@NonNull public String address; @NonNull public Jid address;
public static BlockedItemEntity of(final long accountId, final Item item) {
final var entity = new BlockedItemEntity();
entity.accountId = accountId;
entity.address = item.getJid();
return entity;
}
} }

View file

@ -5,10 +5,8 @@ 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 com.google.common.collect.Collections2;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.model.roster.Item;
import java.util.Collection;
@Entity( @Entity(
tableName = "roster", tableName = "roster",
@ -47,9 +45,4 @@ public class RosterItemEntity {
entity.name = item.getItemName(); entity.name = item.getItemName();
return entity; return entity;
} }
public static Collection<RosterItemEntity> of(
final long accountId, final Collection<Item> items) {
return Collections2.transform(items, i -> of(accountId, i));
}
} }

View file

@ -7,8 +7,6 @@ import eu.siacs.conversations.xml.Element;
import im.conversations.android.annotation.XmlElement; import im.conversations.android.annotation.XmlElement;
import im.conversations.android.annotation.XmlPackage; import im.conversations.android.annotation.XmlPackage;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.roster.Item;
import im.conversations.android.xmpp.model.roster.Query;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
@ -18,8 +16,15 @@ import java.util.Map;
public final class Extensions { public final class Extensions {
// TODO these two maps can easily be generated by an annotation processor
private static final List<Class<? extends Extension>> ELEMENTS = private static final List<Class<? extends Extension>> ELEMENTS =
Arrays.asList(Query.class, Item.class); Arrays.asList(
im.conversations.android.xmpp.model.roster.Query.class,
im.conversations.android.xmpp.model.roster.Item.class,
im.conversations.android.xmpp.model.blocking.Item.class,
im.conversations.android.xmpp.model.blocking.Block.class,
im.conversations.android.xmpp.model.blocking.Blocklist.class,
im.conversations.android.xmpp.model.blocking.Unblock.class);
private static final Map<Id, Class<? extends Extension>> EXTENSION_CLASS_MAP; private static final Map<Id, Class<? extends Extension>> EXTENSION_CLASS_MAP;

View file

@ -0,0 +1,13 @@
package im.conversations.android.xmpp.model.blocking;
import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Block extends Extension {
public Block() {
super("block", Namespace.BLOCKING);
}
}

View file

@ -0,0 +1,12 @@
package im.conversations.android.xmpp.model.blocking;
import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Blocklist extends Extension {
public Blocklist() {
super("blocklist", Namespace.BLOCKING);
}
}

View file

@ -0,0 +1,18 @@
package im.conversations.android.xmpp.model.blocking;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Item extends Extension {
public Item() {
super("item", Namespace.BLOCKING);
}
public Jid getJid() {
return getAttributeAsJid("jid");
}
}

View file

@ -0,0 +1,13 @@
package im.conversations.android.xmpp.model.blocking;
import eu.siacs.conversations.xml.Namespace;
import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension;
@XmlElement
public class Unblock extends Extension {
public Unblock() {
super("unblock", Namespace.BLOCKING);
}
}

View file

@ -7,10 +7,11 @@ import com.google.common.collect.Collections2;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
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.database.entity.RosterItemEntity;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.blocking.Blocklist;
import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.model.roster.Item;
import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.model.roster.Query;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid> { public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid> {
@ -36,6 +37,9 @@ public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid
fetchRoster(); fetchRoster();
// TODO check feature
fetchBlocklist();
// TODO fetch bookmarks // TODO fetch bookmarks
// TODO send initial presence // TODO send initial presence
@ -61,7 +65,7 @@ public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid
if (result.getType() != IqPacket.TYPE.RESULT) { if (result.getType() != IqPacket.TYPE.RESULT) {
return; return;
} }
final Query query = result.getExtension(Query.class); final var query = result.getExtension(Query.class);
if (query == null) { if (query == null) {
// No query in result means further modifications are sent via pushes // No query in result means further modifications are sent via pushes
return; return;
@ -75,8 +79,30 @@ public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid
final var validItems = final var validItems =
Collections2.filter( Collections2.filter(
items, items,
i -> i != null && Item.RESULT_SUBSCRIPTIONS.contains(i.getSubscription())); i ->
final var entities = RosterItemEntity.of(account.id, validItems); Item.RESULT_SUBSCRIPTIONS.contains(i.getSubscription())
database.rosterDao().setRoster(account, version, entities); && Objects.nonNull(i.getJid()));
database.rosterDao().set(account, version, validItems);
}
private void fetchBlocklist() {
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
iqPacket.addChild(new Blocklist());
connection.sendIqPacket(iqPacket, this::handleFetchBlocklistResult);
}
private void handleFetchBlocklistResult(final IqPacket result) {
if (result.getType() != IqPacket.TYPE.RESULT) {
return;
}
final var blocklist = result.getExtension(Blocklist.class);
if (blocklist == null) {
return;
}
final var account = getAccount();
final var items =
blocklist.getExtensions(im.conversations.android.xmpp.model.blocking.Item.class);
final var filteredItems = Collections2.filter(items, i -> Objects.nonNull(i.getJid()));
getDatabase().blockingDao().setBlocklist(account, filteredItems);
} }
} }

View file

@ -1,14 +1,32 @@
package im.conversations.android.xmpp.processor; package im.conversations.android.xmpp.processor;
import android.content.Context; import android.content.Context;
import com.google.common.base.Preconditions;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.roster.Query;
import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
public class IqProcessor implements Consumer<IqPacket> { public class IqProcessor extends AbstractBaseProcessor implements Consumer<IqPacket> {
public IqProcessor(final Context context, final XmppConnection connection) {} public IqProcessor(final Context context, final XmppConnection connection) {
super(context, connection);
}
@Override @Override
public void accept(final IqPacket packet) {} public void accept(final IqPacket packet) {
final IqPacket.TYPE type = packet.getType();
Preconditions.checkArgument(
Arrays.asList(IqPacket.TYPE.GET, IqPacket.TYPE.SET).contains(type));
if (type == IqPacket.TYPE.SET
&& connection.fromAccount(packet)
&& packet.hasExtension(Query.class)) {
handleRosterPush(packet.getExtension(Query.class));
}
}
private void handleRosterPush(final Query query) {
final String version = query.getVersion();
}
} }