introduce Manager concept to bundle functionality like roster, blocking, …

This commit is contained in:
Daniel Gultsch 2023-01-15 16:20:10 +01:00
parent 20962554a4
commit 07c1669813
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
10 changed files with 214 additions and 108 deletions

View file

@ -0,0 +1,23 @@
package im.conversations.android.xmpp;
import android.content.Context;
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableClassToInstanceMap;
import im.conversations.android.xmpp.manager.AbstractManager;
import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.manager.BookmarkManager;
import im.conversations.android.xmpp.manager.RosterManager;
public final class Managers {
private Managers() {}
public static ClassToInstanceMap<AbstractManager> initialize(
final Context context, final XmppConnection connection) {
return new ImmutableClassToInstanceMap.Builder<AbstractManager>()
.put(BlockingManager.class, new BlockingManager(context, connection))
.put(BookmarkManager.class, new BookmarkManager(context, connection))
.put(RosterManager.class, new RosterManager(context, connection))
.build();
}
}

View file

@ -16,6 +16,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ClassToInstanceMap;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.XmppDomainVerifier;
@ -60,6 +61,7 @@ import im.conversations.android.database.CredentialStore;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Connection; import im.conversations.android.database.model.Connection;
import im.conversations.android.database.model.Credential; import im.conversations.android.database.model.Credential;
import im.conversations.android.xmpp.manager.AbstractManager;
import im.conversations.android.xmpp.processor.BindProcessor; import im.conversations.android.xmpp.processor.BindProcessor;
import im.conversations.android.xmpp.processor.IqProcessor; import im.conversations.android.xmpp.processor.IqProcessor;
import im.conversations.android.xmpp.processor.JingleProcessor; import im.conversations.android.xmpp.processor.JingleProcessor;
@ -155,6 +157,7 @@ public class XmppConnection implements Runnable {
private final Consumer<MessagePacket> messagePacketConsumer; private final Consumer<MessagePacket> messagePacketConsumer;
private final BiFunction<Jid, String, Boolean> messageAcknowledgeProcessor; private final BiFunction<Jid, String, Boolean> messageAcknowledgeProcessor;
private final Consumer<Jid> bindConsumer; private final Consumer<Jid> bindConsumer;
private final ClassToInstanceMap<AbstractManager> managers;
private Consumer<XmppConnection> statusListener = null; private Consumer<XmppConnection> statusListener = null;
private SaslMechanism saslMechanism; private SaslMechanism saslMechanism;
private HashedToken.Mechanism hashTokenRequest; private HashedToken.Mechanism hashTokenRequest;
@ -178,12 +181,17 @@ public class XmppConnection implements Runnable {
this.jinglePacketConsumer = new JingleProcessor(context, this); this.jinglePacketConsumer = new JingleProcessor(context, this);
this.messageAcknowledgeProcessor = new MessageAcknowledgeProcessor(context, this); this.messageAcknowledgeProcessor = new MessageAcknowledgeProcessor(context, this);
this.bindConsumer = new BindProcessor(context, this); this.bindConsumer = new BindProcessor(context, this);
this.managers = Managers.initialize(context, this);
} }
public Account getAccount() { public Account getAccount() {
return account; return account;
} }
public <T extends AbstractManager> T getManager(Class<T> type) {
return this.managers.getInstance(type);
}
private String fixResource(final String resource) { private String fixResource(final String resource) {
if (Strings.isNullOrEmpty(resource)) { if (Strings.isNullOrEmpty(resource)) {
return null; return null;
@ -2778,4 +2786,27 @@ public class XmppConnection implements Runnable {
account.address.getDomain(), Namespace.EXTERNAL_SERVICE_DISCOVERY); account.address.getDomain(), Namespace.EXTERNAL_SERVICE_DISCOVERY);
} }
} }
public abstract static class Delegate {
protected final Context context;
protected final XmppConnection connection;
protected Delegate(final Context context, final XmppConnection connection) {
this.context = context;
this.connection = connection;
}
protected Account getAccount() {
return connection.getAccount();
}
protected ConversationsDatabase getDatabase() {
return ConversationsDatabase.getInstance(context);
}
public <T extends AbstractManager> T getManager(Class<T> type) {
return connection.managers.getInstance(type);
}
}
} }

View file

@ -0,0 +1,11 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
import im.conversations.android.xmpp.XmppConnection;
public class AbstractManager extends XmppConnection.Delegate {
protected AbstractManager(final Context context, final XmppConnection connection) {
super(context, connection);
}
}

View file

@ -0,0 +1,42 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
import com.google.common.collect.Collections2;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.blocking.Block;
import im.conversations.android.xmpp.model.blocking.Blocklist;
import im.conversations.android.xmpp.model.blocking.Unblock;
import java.util.Objects;
public class BlockingManager extends AbstractManager {
public BlockingManager(Context context, XmppConnection connection) {
super(context, connection);
}
public void handlePush(final Block block) {}
public void handlePush(final Unblock unblock) {}
public void fetch() {
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
iqPacket.addChild(new Blocklist());
connection.sendIqPacket(iqPacket, this::handleFetchResult);
}
private void handleFetchResult(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

@ -0,0 +1,12 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
import im.conversations.android.xmpp.XmppConnection;
public class BookmarkManager extends AbstractManager {
public BookmarkManager(Context context, XmppConnection connection) {
super(context, connection);
}
public void fetch() {}
}

View file

@ -0,0 +1,65 @@
package im.conversations.android.xmpp.manager;
import android.content.Context;
import android.util.Log;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.roster.Item;
import im.conversations.android.xmpp.model.roster.Query;
import java.util.Objects;
public class RosterManager extends AbstractManager {
public RosterManager(final Context context, final XmppConnection connection) {
super(context, connection);
}
public void handlePush(final Query query) {
final var version = query.getVersion();
final var items = query.getExtensions(Item.class);
getDatabase().rosterDao().update(getAccount(), version, items);
}
public void fetch() {
final var account = getAccount();
final var database = getDatabase();
final String rosterVersion = database.accountDao().getRosterVersion(account.id);
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
final Query rosterQuery = new Query();
iqPacket.addChild(rosterQuery);
if (Strings.isNullOrEmpty(rosterVersion)) {
Log.d(Config.LOGTAG, account.address + ": fetching roster");
} else {
Log.d(Config.LOGTAG, account.address + ": fetching roster version " + rosterVersion);
rosterQuery.setVersion(rosterVersion);
}
connection.sendIqPacket(iqPacket, this::handleFetchResult);
}
private void handleFetchResult(final IqPacket result) {
if (result.getType() != IqPacket.TYPE.RESULT) {
return;
}
final var query = result.getExtension(Query.class);
if (query == null) {
// No query in result means further modifications are sent via pushes
return;
}
final var account = getAccount();
final var database = getDatabase();
final var version = query.getVersion();
final var items = query.getExtensions(Item.class);
// In a roster result (Section 2.1.4), the client MUST ignore values of the c'subscription'
// attribute other than "none", "to", "from", or "both".
final var validItems =
Collections2.filter(
items,
i ->
Item.RESULT_SUBSCRIPTIONS.contains(i.getSubscription())
&& Objects.nonNull(i.getJid()));
database.rosterDao().set(account, version, validItems);
}
}

View file

@ -1,25 +0,0 @@
package im.conversations.android.xmpp.processor;
import android.content.Context;
import im.conversations.android.database.ConversationsDatabase;
import im.conversations.android.database.model.Account;
import im.conversations.android.xmpp.XmppConnection;
abstract class AbstractBaseProcessor {
protected final Context context;
protected final XmppConnection connection;
AbstractBaseProcessor(final Context context, final XmppConnection connection) {
this.context = context;
this.connection = connection;
}
protected Account getAccount() {
return connection.getAccount();
}
protected ConversationsDatabase getDatabase() {
return ConversationsDatabase.getInstance(context);
}
}

View file

@ -1,20 +1,14 @@
package im.conversations.android.xmpp.processor; package im.conversations.android.xmpp.processor;
import android.content.Context; import android.content.Context;
import android.util.Log;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
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 im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.blocking.Blocklist; import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.model.roster.Item; import im.conversations.android.xmpp.manager.BookmarkManager;
import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.manager.RosterManager;
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 XmppConnection.Delegate implements Consumer<Jid> {
public BindProcessor(final Context context, final XmppConnection connection) { public BindProcessor(final Context context, final XmppConnection connection) {
super(context, connection); super(context, connection);
@ -30,79 +24,18 @@ public class BindProcessor extends AbstractBaseProcessor implements Consumer<Jid
if (firstLogin) { if (firstLogin) {
// TODO publish display name if this is the first attempt // TODO publish display name if this is the first attempt
// iirc this is used when the display name is set from a certificate or something // IIRC this is used when the display name is set from a certificate or something
} }
database.presenceDao().deletePresences(account.id); database.presenceDao().deletePresences(account.id);
fetchRoster(); getManager(RosterManager.class).fetch();
// TODO check feature // TODO check feature before fetching
fetchBlocklist(); getManager(BlockingManager.class).fetch();
// TODO fetch bookmarks getManager(BookmarkManager.class).fetch();
// TODO send initial presence // TODO send initial presence
} }
private void fetchRoster() {
final var account = getAccount();
final var database = getDatabase();
final String rosterVersion = database.accountDao().getRosterVersion(account.id);
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
final Query rosterQuery = new Query();
iqPacket.addChild(rosterQuery);
if (Strings.isNullOrEmpty(rosterVersion)) {
Log.d(Config.LOGTAG, account.address + ": fetching roster");
} else {
Log.d(Config.LOGTAG, account.address + ": fetching roster version " + rosterVersion);
rosterQuery.setVersion(rosterVersion);
}
connection.sendIqPacket(iqPacket, this::handleFetchRosterResult);
}
private void handleFetchRosterResult(final IqPacket result) {
if (result.getType() != IqPacket.TYPE.RESULT) {
return;
}
final var query = result.getExtension(Query.class);
if (query == null) {
// No query in result means further modifications are sent via pushes
return;
}
final var account = getAccount();
final var database = getDatabase();
final var version = query.getVersion();
final var items = query.getExtensions(Item.class);
// In a roster result (Section 2.1.4), the client MUST ignore values of the c'subscription'
// attribute other than "none", "to", "from", or "both".
final var validItems =
Collections2.filter(
items,
i ->
Item.RESULT_SUBSCRIPTIONS.contains(i.getSubscription())
&& 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

@ -4,11 +4,15 @@ import android.content.Context;
import com.google.common.base.Preconditions; 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.manager.BlockingManager;
import im.conversations.android.xmpp.manager.RosterManager;
import im.conversations.android.xmpp.model.blocking.Block;
import im.conversations.android.xmpp.model.blocking.Unblock;
import im.conversations.android.xmpp.model.roster.Query; import im.conversations.android.xmpp.model.roster.Query;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Consumer; import java.util.function.Consumer;
public class IqProcessor extends AbstractBaseProcessor implements Consumer<IqPacket> { public class IqProcessor extends XmppConnection.Delegate implements Consumer<IqPacket> {
public IqProcessor(final Context context, final XmppConnection connection) { public IqProcessor(final Context context, final XmppConnection connection) {
super(context, connection); super(context, connection);
@ -22,11 +26,21 @@ public class IqProcessor extends AbstractBaseProcessor implements Consumer<IqPac
if (type == IqPacket.TYPE.SET if (type == IqPacket.TYPE.SET
&& connection.fromAccount(packet) && connection.fromAccount(packet)
&& packet.hasExtension(Query.class)) { && packet.hasExtension(Query.class)) {
handleRosterPush(packet.getExtension(Query.class)); getManager(RosterManager.class).handlePush(packet.getExtension(Query.class));
return;
} }
if (type == IqPacket.TYPE.SET
&& connection.fromAccount(packet)
&& packet.hasExtension(Block.class)) {
getManager(BlockingManager.class).handlePush(packet.getExtension(Block.class));
return;
} }
if (type == IqPacket.TYPE.SET
private void handleRosterPush(final Query query) { && connection.fromAccount(packet)
final String version = query.getVersion(); && packet.hasExtension(Unblock.class)) {
getManager(BlockingManager.class).handlePush(packet.getExtension(Unblock.class));
return;
}
// TODO return feature not implemented
} }
} }

View file

@ -5,7 +5,7 @@ import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import java.util.function.BiFunction; import java.util.function.BiFunction;
public class MessageAcknowledgeProcessor extends AbstractBaseProcessor public class MessageAcknowledgeProcessor extends XmppConnection.Delegate
implements BiFunction<Jid, String, Boolean> { implements BiFunction<Jid, String, Boolean> {
public MessageAcknowledgeProcessor(final Context context, final XmppConnection connection) { public MessageAcknowledgeProcessor(final Context context, final XmppConnection connection) {