join MultiUserChats on bind

This commit is contained in:
Daniel Gultsch 2023-03-05 08:38:12 +01:00 committed by Arne
parent de63e5e290
commit c0eb99c3d5
14 changed files with 347 additions and 58 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "b5e8a59bbd86e133c0bc2edd303ad2a0", "identityHash": "9620a1b63d595091a2b463e89b504eb7",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
@ -1009,7 +1009,7 @@
}, },
{ {
"tableName": "chat", "tableName": "chat",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `type` TEXT, `archived` 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, `type` TEXT, `archived` INTEGER NOT NULL, `mucState` TEXT, `errorCondition` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -1040,6 +1040,18 @@
"columnName": "archived", "columnName": "archived",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
},
{
"fieldPath": "mucState",
"columnName": "mucState",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "errorCondition",
"columnName": "errorCondition",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -2007,6 +2019,60 @@
} }
] ]
}, },
{
"tableName": "muc_status_code",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `chatId` INTEGER NOT NULL, `code` INTEGER NOT NULL, FOREIGN KEY(`chatId`) REFERENCES `chat`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "chatId",
"columnName": "chatId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "code",
"columnName": "code",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_muc_status_code_chatId",
"unique": false,
"columnNames": [
"chatId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_muc_status_code_chatId` ON `${TABLE_NAME}` (`chatId`)"
}
],
"foreignKeys": [
{
"table": "chat",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"chatId"
],
"referencedColumns": [
"id"
]
}
]
},
{ {
"tableName": "nick", "tableName": "nick",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `nick` 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, `nick` TEXT, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
@ -2528,7 +2594,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, 'b5e8a59bbd86e133c0bc2edd303ad2a0')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9620a1b63d595091a2b463e89b504eb7')"
] ]
} }
} }

View file

@ -46,6 +46,7 @@ import im.conversations.android.database.entity.MessageEntity;
import im.conversations.android.database.entity.MessageReactionEntity; import im.conversations.android.database.entity.MessageReactionEntity;
import im.conversations.android.database.entity.MessageStateEntity; import im.conversations.android.database.entity.MessageStateEntity;
import im.conversations.android.database.entity.MessageVersionEntity; import im.conversations.android.database.entity.MessageVersionEntity;
import im.conversations.android.database.entity.MucStatusCodeEntity;
import im.conversations.android.database.entity.NickEntity; import im.conversations.android.database.entity.NickEntity;
import im.conversations.android.database.entity.PresenceEntity; import im.conversations.android.database.entity.PresenceEntity;
import im.conversations.android.database.entity.RosterItemEntity; import im.conversations.android.database.entity.RosterItemEntity;
@ -81,6 +82,7 @@ import im.conversations.android.database.entity.ServiceRecordCacheEntity;
MessageStateEntity.class, MessageStateEntity.class,
MessageContentEntity.class, MessageContentEntity.class,
MessageVersionEntity.class, MessageVersionEntity.class,
MucStatusCodeEntity.class,
NickEntity.class, NickEntity.class,
PresenceEntity.class, PresenceEntity.class,
MessageReactionEntity.class, MessageReactionEntity.class,

View file

@ -7,13 +7,16 @@ import androidx.room.Query;
import androidx.room.Transaction; import androidx.room.Transaction;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import im.conversations.android.database.entity.ChatEntity; import im.conversations.android.database.entity.ChatEntity;
import im.conversations.android.database.entity.MucStatusCodeEntity;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.ChatIdentifier; import im.conversations.android.database.model.ChatIdentifier;
import im.conversations.android.database.model.ChatType; import im.conversations.android.database.model.ChatType;
import im.conversations.android.database.model.GroupIdentifier; import im.conversations.android.database.model.GroupIdentifier;
import im.conversations.android.database.model.MucState;
import im.conversations.android.database.model.MucWithNick; import im.conversations.android.database.model.MucWithNick;
import im.conversations.android.xmpp.model.stanza.Message; import im.conversations.android.xmpp.model.stanza.Message;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -59,6 +62,12 @@ public abstract class ChatDao {
+ " address=:address") + " address=:address")
protected abstract ChatIdentifier get(final long accountId, final Jid address); protected abstract ChatIdentifier get(final long accountId, final Jid address);
@Query(
"SELECT id,address,type,archived FROM chat WHERE accountId=:accountId AND"
+ " address=:address AND type=:chatType")
public abstract ChatIdentifier get(
final long accountId, final Jid address, final ChatType chatType);
@Insert @Insert
protected abstract long insert(ChatEntity chatEntity); protected abstract long insert(ChatEntity chatEntity);
@ -125,13 +134,56 @@ public abstract class ChatDao {
+ " chat.accountId=:account AND chat.type=:chatType AND archived=0") + " chat.accountId=:account AND chat.type=:chatType AND archived=0")
protected abstract List<Jid> getBookmarksNotInChats(long account, ChatType chatType); protected abstract List<Jid> getBookmarksNotInChats(long account, ChatType chatType);
// TODO get pending only
@Query( @Query(
"SELECT chat.address,bookmark.nick as nickBookmark,nick.nick as nickAccount FROM chat" "SELECT chat.id as chatId,chat.address,bookmark.nick as nickBookmark,nick.nick as"
+ " LEFT JOIN bookmark ON chat.accountId=bookmark.accountId AND" + " nickAccount FROM chat LEFT JOIN bookmark ON chat.accountId=bookmark.accountId"
+ " chat.address=bookmark.address JOIN account ON account.id=chat.accountId LEFT" + " AND chat.address=bookmark.address JOIN account ON account.id=chat.accountId"
+ " JOIN nick ON nick.accountId=chat.accountId AND nick.address=account.address" + " LEFT JOIN nick ON nick.accountId=chat.accountId AND"
+ " WHERE chat.accountId=:account AND chat.type=:chatType AND chat.archived=0") + " nick.address=account.address WHERE chat.accountId=:account AND"
+ " chat.type=:chatType AND chat.archived=0 AND chat.mucState IS NULL")
public abstract ListenableFuture<List<MucWithNick>> getMultiUserChats( public abstract ListenableFuture<List<MucWithNick>> getMultiUserChats(
final long account, final ChatType chatType); final long account, final ChatType chatType);
@Query("UPDATE chat SET mucState=:mucState, errorCondition=:errorCondition WHERE id=:chatId")
protected abstract void setMucStateInternal(
final long chatId, final MucState mucState, final String errorCondition);
@Transaction
public void setMucState(
final long chatId, final MucState mucState, final String errorCondition) {
setMucStateInternal(chatId, mucState, errorCondition);
deleteStatusCodes(chatId);
}
@Transaction
public void setMucState(
final long chatId, final MucState mucState, final Collection<Integer> statusCodes) {
setMucStateInternal(chatId, mucState, null);
deleteStatusCodes(chatId);
insertStatusCode(MucStatusCodeEntity.of(chatId, statusCodes));
}
@Transaction
public void setMucState(final long chatId, final MucState mucState) {
setMucStateInternal(chatId, mucState, null);
deleteStatusCodes(chatId);
}
@Transaction
public void resetMucStates() {
this.nullMucStates();
this.deleteStatusCodes();
}
@Insert
protected abstract void insertStatusCode(final Collection<MucStatusCodeEntity> entities);
@Query("UPDATE chat SET mucState=null,errorCondition=null")
protected abstract void nullMucStates();
@Query("DELETE FROM muc_status_code")
protected abstract void deleteStatusCodes();
@Query("DELETE FROM muc_status_code WHERE chatId=:chatId")
protected abstract void deleteStatusCodes(final long chatId);
} }

View file

@ -21,8 +21,6 @@ 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.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.Identity;
import im.conversations.android.xmpp.model.disco.info.InfoQuery; 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.Item;
import java.util.Collection; import java.util.Collection;
@ -156,13 +154,11 @@ public abstract class DiscoDao {
insertDiscoIdentities( insertDiscoIdentities(
Collections2.transform( Collections2.transform(
infoQuery.getExtensions(Identity.class), infoQuery.getIdentities(), i -> DiscoIdentityEntity.of(discoId, i)));
i -> DiscoIdentityEntity.of(discoId, i)));
insertDiscoFeatures( insertDiscoFeatures(
Collections2.transform( Collections2.transform(
infoQuery.getExtensions(Feature.class), infoQuery.getFeatures(), 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, data.getFormType())); final var extensionId = insert(DiscoExtensionEntity.of(discoId, data.getFormType()));
for (final var field : data.getFields()) { for (final var field : data.getFields()) {

View file

@ -1,11 +1,13 @@
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;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import im.conversations.android.database.model.ChatType; import im.conversations.android.database.model.ChatType;
import im.conversations.android.database.model.MucState;
@Entity( @Entity(
tableName = "chat", tableName = "chat",
@ -32,4 +34,7 @@ public class ChatEntity {
public ChatType type; public ChatType type;
public boolean archived; public boolean archived;
@Nullable public MucState mucState;
@Nullable public String errorCondition;
} }

View file

@ -0,0 +1,40 @@
package im.conversations.android.database.entity;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.google.common.collect.Collections2;
import java.util.Collection;
@Entity(
tableName = "muc_status_code",
foreignKeys = {
@ForeignKey(
entity = ChatEntity.class,
parentColumns = {"id"},
childColumns = {"chatId"},
onDelete = ForeignKey.CASCADE)
},
indices = {@Index(value = "chatId")})
public class MucStatusCodeEntity {
@PrimaryKey(autoGenerate = true)
public Long id;
@NonNull public Long chatId;
@NonNull public Integer code;
public static Collection<MucStatusCodeEntity> of(
final long chatId, final Collection<Integer> codes) {
return Collections2.transform(codes, c -> of(chatId, c));
}
private static MucStatusCodeEntity of(final long chatId, final int code) {
final var entity = new MucStatusCodeEntity();
entity.chatId = chatId;
entity.code = code;
return entity;
}
}

View file

@ -0,0 +1,10 @@
package im.conversations.android.database.model;
public enum MucState {
JOINING,
AVAILABLE,
ERROR_PRESENCE,
ERROR_IQ,
UNAVAILABLE,
NOT_A_MUC
}

View file

@ -5,12 +5,18 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.jid.parts.Resourcepart;
public class MucWithNick { public class MucWithNick {
public final long chatId;
public final BareJid address; public final BareJid address;
private final String nickBookmark; private final String nickBookmark;
private final String nickAccount; private final String nickAccount;
public MucWithNick(BareJid address, String nickBookmark, String nickAccount) { public MucWithNick(
final long chatId,
final BareJid address,
final String nickBookmark,
final String nickAccount) {
this.chatId = chatId;
this.address = address; this.address = address;
this.nickBookmark = nickBookmark; this.nickBookmark = nickBookmark;
this.nickAccount = nickAccount; this.nickAccount = nickAccount;

View file

@ -37,7 +37,7 @@ public final class EntityCapabilities {
blankNull(a.getIdentityName()), blankNull(a.getIdentityName()),
blankNull(b.getIdentityName())) blankNull(b.getIdentityName()))
.result()) .result())
.sortedCopy(info.getExtensions(Identity.class)); .sortedCopy(info.getIdentities());
for (final Identity id : orderedIdentities) { for (final Identity id : orderedIdentities) {
s.append(blankNull(id.getCategory())) s.append(blankNull(id.getCategory()))
@ -52,9 +52,7 @@ public final class EntityCapabilities {
final List<String> features = final List<String> features =
Ordering.natural() Ordering.natural()
.sortedCopy( .sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar));
Collections2.transform(
info.getExtensions(Feature.class), Feature::getVar));
for (final String feature : features) { for (final String feature : features) {
s.append(clean(feature)).append("<"); s.append(clean(feature)).append("<");
} }

View file

@ -62,8 +62,8 @@ public class EntityCapabilities2 {
} }
private static String algorithm(final InfoQuery infoQuery) { private static String algorithm(final InfoQuery infoQuery) {
return features(infoQuery.getExtensions(Feature.class)) return features(infoQuery.getFeatures())
+ identities(infoQuery.getExtensions(Identity.class)) + identities(infoQuery.getIdentities())
+ extensions(infoQuery.getExtensions(Data.class)); + extensions(infoQuery.getExtensions(Data.class));
} }

View file

@ -5,12 +5,19 @@ import androidx.annotation.NonNull;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import im.conversations.android.database.model.ChatIdentifier;
import im.conversations.android.database.model.ChatType; import im.conversations.android.database.model.ChatType;
import im.conversations.android.database.model.MucState;
import im.conversations.android.database.model.MucWithNick; import im.conversations.android.database.model.MucWithNick;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xmpp.Entity; import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.IqErrorException;
import im.conversations.android.xmpp.XmppConnection; import im.conversations.android.xmpp.XmppConnection;
import im.conversations.android.xmpp.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.info.InfoQuery;
import im.conversations.android.xmpp.model.error.Condition;
import im.conversations.android.xmpp.model.error.Error;
import im.conversations.android.xmpp.model.muc.History; import im.conversations.android.xmpp.model.muc.History;
import im.conversations.android.xmpp.model.muc.MultiUserChat; import im.conversations.android.xmpp.model.muc.MultiUserChat;
import im.conversations.android.xmpp.model.muc.user.MucUser; import im.conversations.android.xmpp.model.muc.user.MucUser;
@ -29,47 +36,41 @@ public class MultiUserChatManager extends AbstractManager {
super(context, connection); super(context, connection);
} }
public void joinMultiUserChats() { public ListenableFuture<Integer> joinMultiUserChats() {
final var future = getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC); LOGGER.info("joining multi user chats. start");
Futures.addCallback( return Futures.transform(
future, getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC),
new FutureCallback<>() { this::joinMultiUserChats,
@Override
public void onSuccess(List<MucWithNick> result) {
for (final MucWithNick mucWithNick : result) {
enter(mucWithNick);
}
}
@Override
public void onFailure(Throwable t) {
LOGGER.info("Could not query db", t);
}
},
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
public void enter(final MucWithNick mucWithNick) { private int joinMultiUserChats(final List<MucWithNick> chats) {
LOGGER.info("joining {} chats", chats.size());
for (final MucWithNick chat : chats) {
this.enterExisting(chat);
}
return chats.size();
}
public void enterExisting(final MucWithNick mucWithNick) {
getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.JOINING);
final var discoInfoFuture = final var discoInfoFuture =
getManager(DiscoManager.class).info(Entity.discoItem(mucWithNick.address)); getManager(DiscoManager.class).info(Entity.discoItem(mucWithNick.address));
// TODO catching
Futures.addCallback( Futures.addCallback(
discoInfoFuture, discoInfoFuture,
new FutureCallback<>() { new ExistingMucJoiner(mucWithNick),
@Override
public void onSuccess(final InfoQuery result) {
enter(mucWithNick, result);
}
@Override
public void onFailure(@NonNull final Throwable throwable) {
// TODO mark chat as ERROR_IQ
}
},
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
public void enter(final MucWithNick mucWithNick, final InfoQuery infoQuery) { private void enterExisting(final MucWithNick mucWithNick, final InfoQuery infoQuery) {
if (infoQuery.hasFeature(Namespace.MUC)) {
sendJoinPresence(mucWithNick);
} else {
getDatabase().chatDao().setMucState(mucWithNick.chatId, MucState.NOT_A_MUC);
}
}
private void sendJoinPresence(final MucWithNick mucWithNick) {
final var nick = mucWithNick.nick(); final var nick = mucWithNick.nick();
final Jid to; final Jid to;
if (nick != null) { if (nick != null) {
@ -86,16 +87,106 @@ public class MultiUserChatManager extends AbstractManager {
connection.sendPresencePacket(presence); connection.sendPresencePacket(presence);
} }
public void handleSelfPresence(final Presence presencePacket) { public void handleSelfPresenceAvailable(final Presence presencePacket) {
final MucUser mucUser = presencePacket.getExtension(MucUser.class); final MucUser mucUser = presencePacket.getExtension(MucUser.class);
Preconditions.checkArgument( Preconditions.checkArgument(
mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)); mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE));
// TODO flag chat as joined // TODO flag chat as joined
LOGGER.info("Received self presence for {}", presencePacket.getFrom()); LOGGER.info("Received self presence for {}", presencePacket.getFrom());
final var database = getDatabase();
database.runInTransaction(
() -> {
final ChatIdentifier chatIdentifier =
database.chatDao()
.get(
getAccount().id,
presencePacket.getFrom().asBareJid(),
ChatType.MUC);
if (chatIdentifier == null || chatIdentifier.archived) {
LOGGER.info(
"Available presence received for archived or non existent chat");
return;
}
// TODO set status codes
database.chatDao()
.setMucState(
chatIdentifier.id, MucState.AVAILABLE, mucUser.getStatus());
});
}
public void handleSelfPresenceUnavailable(final Presence presencePacket) {
final MucUser mucUser = presencePacket.getExtension(MucUser.class);
Preconditions.checkArgument(
mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE));
final var database = getDatabase();
database.runInTransaction(
() -> {
final ChatIdentifier chatIdentifier =
database.chatDao()
.get(
getAccount().id,
presencePacket.getFrom().asBareJid(),
ChatType.MUC);
if (chatIdentifier == null) {
LOGGER.error("Unavailable presence received for non existent chat");
} else if (chatIdentifier.archived) {
database.chatDao().setMucState(chatIdentifier.id, null);
} else {
// TODO set status codes
database.chatDao().setMucState(chatIdentifier.id, MucState.UNAVAILABLE);
}
});
} }
public void handleErrorPresence(final Presence presencePacket) { public void handleErrorPresence(final Presence presencePacket) {
LOGGER.info("Received error presence from {}", presencePacket.getFrom()); LOGGER.info("Received error presence from {}", presencePacket.getFrom());
final var database = getDatabase();
database.runInTransaction(
() -> {
final ChatIdentifier chatIdentifier =
database.chatDao()
.get(
getAccount().id,
presencePacket.getFrom().asBareJid(),
ChatType.MUC);
if (chatIdentifier == null) {
// this is fine. error is simply not for a MUC
return;
}
final Error error = presencePacket.getError();
final Condition condition = error == null ? null : error.getCondition();
final String errorCondition = condition == null ? null : condition.getName();
database.chatDao()
.setMucState(
chatIdentifier.id, MucState.ERROR_PRESENCE, errorCondition);
});
}
private class ExistingMucJoiner implements FutureCallback<InfoQuery> {
private final MucWithNick chat;
private ExistingMucJoiner(final MucWithNick chat) {
this.chat = chat;
}
@Override
public void onSuccess(final InfoQuery result) {
enterExisting(chat, result);
}
@Override
public void onFailure(@NonNull final Throwable throwable) {
final String errorCondition;
if (throwable instanceof IqErrorException) {
final var iqErrorException = (IqErrorException) throwable;
final Error error = iqErrorException.getError();
final Condition condition = error == null ? null : error.getCondition();
errorCondition = condition == null ? null : condition.getName();
} else {
errorCondition = null;
}
getDatabase().chatDao().setMucState(chat.chatId, MucState.ERROR_IQ, errorCondition);
}
} }
} }

View file

@ -1,7 +1,9 @@
package im.conversations.android.xmpp.model.disco.info; package im.conversations.android.xmpp.model.disco.info;
import com.google.common.collect.Iterables;
import im.conversations.android.annotation.XmlElement; import im.conversations.android.annotation.XmlElement;
import im.conversations.android.xmpp.model.Extension; import im.conversations.android.xmpp.model.Extension;
import java.util.Collection;
@XmlElement(name = "query") @XmlElement(name = "query")
public class InfoQuery extends Extension { public class InfoQuery extends Extension {
@ -17,4 +19,16 @@ public class InfoQuery extends Extension {
public String getNode() { public String getNode() {
return this.getAttribute("node"); return this.getAttribute("node");
} }
public Collection<Feature> getFeatures() {
return this.getExtensions(Feature.class);
}
public boolean hasFeature(final String feature) {
return Iterables.any(getFeatures(), f -> feature.equals(f.getVar()));
}
public Collection<Identity> getIdentities() {
return this.getExtensions(Identity.class);
}
} }

View file

@ -29,7 +29,11 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
final var database = getDatabase(); final var database = getDatabase();
// TODO reset errorCondition; mucState in chats // TODO reset errorCondition; mucState in chats
database.presenceDao().deletePresences(account.id); database.runInTransaction(
() -> {
database.chatDao().resetMucStates();
database.presenceDao().deletePresences(account.id);
});
getManager(RosterManager.class).fetch(); getManager(RosterManager.class).fetch();

View file

@ -73,11 +73,16 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
occupantId, occupantId,
muc); muc);
final var mucManager = getManager(MultiUserChatManager.class);
if (muc != null && muc.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)) { if (muc != null && muc.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE)) {
getManager(MultiUserChatManager.class).handleSelfPresence(presencePacket); if (type == null) {
mucManager.handleSelfPresenceAvailable(presencePacket);
} else if (type == PresenceType.UNAVAILABLE) {
mucManager.handleSelfPresenceUnavailable(presencePacket);
}
} }
if (type == PresenceType.ERROR) { if (type == PresenceType.ERROR) {
getManager(MultiUserChatManager.class).handleErrorPresence(presencePacket); mucManager.handleErrorPresence(presencePacket);
} }
// TODO do this only for contacts? // TODO do this only for contacts?