@@ -3,31 +3,42 @@ package eu.siacs.conversations.entities;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Iterables;
+import com.google.common.primitives.Ints;
import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.utils.LanguageUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
+import im.conversations.android.xmpp.model.disco.info.InfoQuery;
public class Room implements AvatarService.Avatarable, Comparable<Room> {
- public String address;
- public String name;
- public String description;
- public String language;
- public int nusers;
+ public final String address;
+ public final String name;
+ public final String description;
+ public final String language;
+ public final int numberOfUsers;
- public Room(String address, String name, String description, String language, int nusers) {
+ public Room(
+ final String address,
+ final String name,
+ final String description,
+ final String language,
+ final Integer numberOfUsers) {
this.address = address;
this.name = name;
this.description = description;
this.language = language;
- this.nusers = nusers;
+ this.numberOfUsers = numberOfUsers == null ? 0 : numberOfUsers;
}
- public Room() {}
-
public String getName() {
- return name;
+ if (Strings.isNullOrEmpty(name)) {
+ final var jid = Jid.ofOrInvalid(address);
+ return jid.getLocal();
+ } else {
+ return name;
+ }
}
public String getDescription() {
@@ -81,9 +92,28 @@ public class Room implements AvatarService.Avatarable, Comparable<Room> {
@Override
public int compareTo(Room o) {
return ComparisonChain.start()
- .compare(o.nusers, nusers)
+ .compare(o.numberOfUsers, numberOfUsers)
.compare(Strings.nullToEmpty(name), Strings.nullToEmpty(o.name))
.compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address))
.result();
}
+
+ public static Room of(final Jid address, InfoQuery query) {
+ final var identity = Iterables.getFirst(query.getIdentities(), null);
+ final var ri =
+ query.getServiceDiscoveryExtension("http://jabber.org/protocol/muc#roominfo");
+ final String name = identity == null ? null : identity.getIdentityName();
+ String roomName = ri == null ? null : ri.getValue("muc#roomconfig_roomname");
+ String description = ri == null ? null : ri.getValue("muc#roominfo_description");
+ String language = ri == null ? null : ri.getValue("muc#roominfo_lang");
+ String occupants = ri == null ? null : ri.getValue("muc#roominfo_occupants");
+ final Integer numberOfUsers = Ints.tryParse(Strings.nullToEmpty(occupants));
+
+ return new Room(
+ address.toString(),
+ Strings.isNullOrEmpty(roomName) ? name : roomName,
+ description,
+ language,
+ numberOfUsers);
+ }
}
@@ -1,6 +1,5 @@
package eu.siacs.conversations.parser;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
@@ -10,13 +9,11 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
-import eu.siacs.conversations.entities.Room;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
-import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.stanza.Iq;
@@ -61,38 +58,6 @@ public class IqParser extends AbstractParser implements Consumer<Iq> {
return items;
}
- public static Room parseRoom(Iq packet) {
- final Element query = packet.findChild("query", Namespace.DISCO_INFO);
- if (query == null) {
- return null;
- }
- final Element x = query.findChild("x");
- if (x == null) {
- return null;
- }
- final Element identity = query.findChild("identity");
- Data data = Data.parse(x);
- String address = packet.getFrom().toString();
- String name = identity == null ? null : identity.getAttribute("name");
- String roomName = data.getValue("muc#roomconfig_roomname");
- String description = data.getValue("muc#roominfo_description");
- String language = data.getValue("muc#roominfo_lang");
- String occupants = data.getValue("muc#roominfo_occupants");
- int nusers;
- try {
- nusers = occupants == null ? 0 : Integer.parseInt(occupants);
- } catch (NumberFormatException e) {
- nusers = 0;
- }
-
- return new Room(
- address,
- TextUtils.isEmpty(roomName) ? name : roomName,
- description,
- language,
- nusers);
- }
-
private void rosterItems(final Account account, final Element query) {
final String version = query.getAttribute("ver");
if (version != null) {
@@ -5,24 +5,33 @@ import androidx.annotation.NonNull;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Ordering;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Room;
import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.http.services.MuclumbusService;
-import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
+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.ItemsQuery;
import im.conversations.android.xmpp.model.stanza.Iq;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import retrofit2.Call;
@@ -164,7 +173,7 @@ public class ChannelDiscoveryService {
private void discoverChannelsLocalServers(
final String query, final OnChannelSearchResultsFound listener) {
- final Map<Jid, Account> localMucService = getLocalMucServices();
+ final var localMucService = getLocalMucServices();
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
if (localMucService.isEmpty()) {
listener.onChannelSearchResultsFound(Collections.emptyList());
@@ -178,57 +187,104 @@ public class ChannelDiscoveryService {
listener.onChannelSearchResultsFound(results);
}
}
- final AtomicInteger queriesInFlight = new AtomicInteger();
- final List<Room> rooms = new ArrayList<>();
- for (final Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
- Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
- queriesInFlight.incrementAndGet();
- final var account = entry.getValue();
- service.sendIqPacket(
- account,
- itemsRequest,
- (itemsResponse) -> {
- if (itemsResponse.getType() == Iq.Type.RESULT) {
- final List<Jid> items = IqParser.items(itemsResponse);
- for (final Jid item : items) {
- final Iq infoRequest =
- service.getIqGenerator().queryDiscoInfo(item);
- queriesInFlight.incrementAndGet();
- service.sendIqPacket(
- account,
- infoRequest,
- infoResponse -> {
- if (infoResponse.getType() == Iq.Type.RESULT) {
- final Room room = IqParser.parseRoom(infoResponse);
- if (room != null) {
- rooms.add(room);
- }
- if (queriesInFlight.decrementAndGet() <= 0) {
- finishDiscoSearch(rooms, query, listener);
- }
- } else {
- queriesInFlight.decrementAndGet();
- }
- });
+ final var roomsRoomsFuture =
+ Futures.successfulAsList(
+ Collections2.transform(
+ localMucService.entrySet(),
+ e -> discoverRooms(e.getValue(), e.getKey())));
+ final var roomsFuture =
+ Futures.transform(
+ roomsRoomsFuture,
+ rooms -> {
+ final var builder = new ImmutableList.Builder<Room>();
+ for (final var inner : rooms) {
+ if (inner == null) {
+ continue;
+ }
+ builder.addAll(inner);
}
- }
- if (queriesInFlight.decrementAndGet() <= 0) {
- finishDiscoSearch(rooms, query, listener);
- }
- });
- }
+ return builder.build();
+ },
+ MoreExecutors.directExecutor());
+ Futures.addCallback(
+ roomsFuture,
+ new FutureCallback<>() {
+ @Override
+ public void onSuccess(ImmutableList<Room> rooms) {
+ finishDiscoSearch(rooms, query, listener);
+ }
+
+ @Override
+ public void onFailure(@NonNull Throwable throwable) {
+ Log.d(Config.LOGTAG, "could not perform room search", throwable);
+ }
+ },
+ MoreExecutors.directExecutor());
+ }
+
+ private ListenableFuture<Collection<Room>> discoverRooms(
+ final XmppConnection connection, final Jid server) {
+ final var request = new Iq(Iq.Type.GET);
+ request.addExtension(new ItemsQuery());
+ request.setTo(server);
+ final ListenableFuture<Collection<Item>> itemsFuture =
+ Futures.transform(
+ connection.sendIqPacket(request),
+ iq -> {
+ final var itemsQuery = iq.getExtension(ItemsQuery.class);
+ if (itemsQuery == null) {
+ return Collections.emptyList();
+ }
+ final var items = itemsQuery.getExtensions(Item.class);
+ return Collections2.filter(items, i -> Objects.nonNull(i.getJid()));
+ },
+ MoreExecutors.directExecutor());
+ final var roomsFutures =
+ Futures.transformAsync(
+ itemsFuture,
+ items -> {
+ final var infoFutures =
+ Collections2.transform(
+ items, i -> discoverRoom(connection, i.getJid()));
+ return Futures.successfulAsList(infoFutures);
+ },
+ MoreExecutors.directExecutor());
+ return Futures.transform(
+ roomsFutures,
+ rooms -> Collections2.filter(rooms, Objects::nonNull),
+ MoreExecutors.directExecutor());
+ }
+
+ private ListenableFuture<Room> discoverRoom(final XmppConnection connection, final Jid room) {
+ final var request = new Iq(Iq.Type.GET);
+ request.addExtension(new InfoQuery());
+ request.setTo(room);
+ final var infoQueryResponseFuture = connection.sendIqPacket(request);
+ return Futures.transform(
+ infoQueryResponseFuture,
+ result -> {
+ final var infoQuery = result.getExtension(InfoQuery.class);
+ if (infoQuery == null) {
+ return null;
+ }
+ return Room.of(room, infoQuery);
+ },
+ MoreExecutors.directExecutor());
}
private void finishDiscoSearch(
- List<Room> rooms, String query, OnChannelSearchResultsFound listener) {
- Collections.sort(rooms);
- cache.put(key(Method.LOCAL_SERVER, ""), rooms);
+ final List<Room> rooms,
+ final String query,
+ final OnChannelSearchResultsFound listener) {
+ Log.d(Config.LOGTAG, "finishDiscoSearch with " + rooms.size() + " rooms");
+ final var sorted = Ordering.natural().sortedCopy(rooms);
+ cache.put(key(Method.LOCAL_SERVER, ""), sorted);
if (query.isEmpty()) {
- listener.onChannelSearchResultsFound(rooms);
+ listener.onChannelSearchResultsFound(sorted);
} else {
- List<Room> results = copyMatching(rooms, query);
+ List<Room> results = copyMatching(sorted, query);
cache.put(key(Method.LOCAL_SERVER, query), results);
- listener.onChannelSearchResultsFound(rooms);
+ listener.onChannelSearchResultsFound(sorted);
}
}
@@ -242,23 +298,21 @@ public class ChannelDiscoveryService {
return result;
}
- private Map<Jid, Account> getLocalMucServices() {
- final HashMap<Jid, Account> localMucServices = new HashMap<>();
- for (Account account : service.getAccounts()) {
- if (account.isEnabled()) {
- final XmppConnection xmppConnection = account.getXmppConnection();
- if (xmppConnection == null) {
- continue;
- }
- for (final String mucService : xmppConnection.getMucServers()) {
- final Jid jid = Jid.of(mucService);
- if (!localMucServices.containsKey(jid)) {
- localMucServices.put(jid, account);
+ private Map<Jid, XmppConnection> getLocalMucServices() {
+ final ImmutableMap.Builder<Jid, XmppConnection> localMucServices =
+ new ImmutableMap.Builder<>();
+ for (final var account : service.getAccounts()) {
+ final var connection = account.getXmppConnection();
+ if (connection != null && account.isEnabled()) {
+ for (final String mucService : connection.getMucServers()) {
+ final Jid jid = Jid.ofOrInvalid(mucService);
+ if (Jid.Invalid.isValid(jid)) {
+ localMucServices.put(jid, connection);
}
}
}
}
- return localMucServices;
+ return localMucServices.buildKeepingLast();
}
private static String key(Method method, String query) {
@@ -4369,7 +4369,6 @@ public class XmppConnectionService extends Service {
public void fetchConferenceConfiguration(
final Conversation conversation, final OnConferenceConfigurationFetched callback) {
- final Iq request = mIqGenerator.queryDiscoInfo(conversation.getJid().asBareJid());
final var account = conversation.getAccount();
final var connection = account.getXmppConnection();
if (connection == null) {
@@ -4381,7 +4380,7 @@ public class XmppConnectionService extends Service {
.info(Entity.discoItem(conversation.getJid().asBareJid()), null);
Futures.addCallback(
future,
- new FutureCallback<InfoQuery>() {
+ new FutureCallback<>() {
@Override
public void onSuccess(InfoQuery result) {
final MucOptions mucOptions = conversation.getMucOptions();