join MultiUserChats on bind
This commit is contained in:
parent
8be8d7df8f
commit
a67979adf8
|
@ -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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -5,19 +5,28 @@ 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 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.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.LoggerFactory;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class ChatDao {
|
public abstract class ChatDao {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChatDao.class);
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
public ChatIdentifier getOrCreateChat(
|
public ChatIdentifier getOrCreateChat(
|
||||||
final Account account,
|
final Account account,
|
||||||
|
@ -53,9 +62,128 @@ 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);
|
||||||
|
|
||||||
|
@Query("UPDATE chat SET archived=:archived WHERE chat.id=:chatId")
|
||||||
|
public abstract void setArchived(final long chatId, final boolean archived);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"UPDATE chat SET archived=:archived WHERE chat.accountId=:account AND"
|
||||||
|
+ " chat.address=:address")
|
||||||
|
protected abstract void setArchived(
|
||||||
|
final long account, final String address, final boolean archived);
|
||||||
|
|
||||||
@Query("SELECT id,name FROM `group` ORDER BY name")
|
@Query("SELECT id,name FROM `group` ORDER BY name")
|
||||||
public abstract LiveData<List<GroupIdentifier>> getGroups();
|
public abstract LiveData<List<GroupIdentifier>> getGroups();
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
public void syncWithBookmarks(final Account account) {
|
||||||
|
final var chatsNotInBookmarks = getChatsNotInBookmarks(account.id, ChatType.MUC);
|
||||||
|
final var bookmarksNotInChat = getBookmarksNotInChats(account.id, ChatType.MUC);
|
||||||
|
LOGGER.info("chatsNotInBookmark {}", chatsNotInBookmarks);
|
||||||
|
LOGGER.info("bookmarkNotInChat {}", bookmarksNotInChat);
|
||||||
|
archive(account.id, chatsNotInBookmarks);
|
||||||
|
createOrUnarchiveMuc(account.id, bookmarksNotInChat);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void archive(final long account, final List<String> addresses) {
|
||||||
|
for (final String address : addresses) {
|
||||||
|
setArchived(account, address, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOrUnarchiveMuc(final long account, final List<Jid> addresses) {
|
||||||
|
for (final Jid address : addresses) {
|
||||||
|
createOrUnarchiveMuc(account, address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOrUnarchiveMuc(final long account, final Jid address) {
|
||||||
|
final var bareJid = address.asBareJid();
|
||||||
|
final var existing = get(account, bareJid);
|
||||||
|
if (existing != null) {
|
||||||
|
if (existing.archived) {
|
||||||
|
setArchived(existing.id, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var entity = new ChatEntity();
|
||||||
|
entity.accountId = account;
|
||||||
|
entity.address = bareJid.toString();
|
||||||
|
entity.type = ChatType.MUC;
|
||||||
|
entity.archived = false;
|
||||||
|
insert(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT chat.address FROM chat WHERE chat.accountId=:account AND chat.type=:chatType"
|
||||||
|
+ " AND archived=0 EXCEPT SELECT bookmark.address FROM bookmark WHERE"
|
||||||
|
+ " bookmark.accountId=:account AND bookmark.autoJoin=1")
|
||||||
|
protected abstract List<String> getChatsNotInBookmarks(long account, ChatType chatType);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT bookmark.address FROM bookmark WHERE bookmark.accountId=accountId AND"
|
||||||
|
+ " bookmark.autoJoin=1 EXCEPT SELECT chat.address FROM chat WHERE"
|
||||||
|
+ " chat.accountId=:account AND chat.type=:chatType AND archived=0")
|
||||||
|
protected abstract List<Jid> getBookmarksNotInChats(long account, ChatType chatType);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT chat.id as chatId,chat.address,bookmark.nick as nickBookmark,nick.nick as"
|
||||||
|
+ " nickAccount FROM chat LEFT JOIN bookmark ON chat.accountId=bookmark.accountId"
|
||||||
|
+ " AND chat.address=bookmark.address JOIN account ON account.id=chat.accountId"
|
||||||
|
+ " LEFT JOIN nick ON nick.accountId=chat.accountId AND"
|
||||||
|
+ " 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(
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import im.conversations.android.database.entity.PresenceEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.PresenceShow;
|
import im.conversations.android.database.model.PresenceShow;
|
||||||
import im.conversations.android.database.model.PresenceType;
|
import im.conversations.android.database.model.PresenceType;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
@ -41,7 +41,7 @@ public abstract class PresenceDao {
|
||||||
@Nullable final String status,
|
@Nullable final String status,
|
||||||
@Nullable final String vCardPhoto,
|
@Nullable final String vCardPhoto,
|
||||||
@Nullable final String occupantId,
|
@Nullable final String occupantId,
|
||||||
@Nullable final MultiUserChat multiUserChat) {
|
@Nullable final MucUser mucUser) {
|
||||||
if (resource.equals(Resourcepart.EMPTY)
|
if (resource.equals(Resourcepart.EMPTY)
|
||||||
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
|
&& Arrays.asList(PresenceType.ERROR, PresenceType.UNAVAILABLE).contains(type)) {
|
||||||
deletePresences(account.id, address);
|
deletePresences(account.id, address);
|
||||||
|
@ -63,7 +63,7 @@ public abstract class PresenceDao {
|
||||||
status,
|
status,
|
||||||
vCardPhoto,
|
vCardPhoto,
|
||||||
occupantId,
|
occupantId,
|
||||||
multiUserChat);
|
mucUser);
|
||||||
insert(entity);
|
insert(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ 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.base.Strings;
|
||||||
import im.conversations.android.xmpp.model.bookmark.Conference;
|
import im.conversations.android.xmpp.model.bookmark.Conference;
|
||||||
|
import im.conversations.android.xmpp.model.bookmark.Nick;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
@ -47,11 +49,13 @@ public class BookmarkEntity {
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
final Nick nick = conference.getNick();
|
||||||
final var entity = new BookmarkEntity();
|
final var entity = new BookmarkEntity();
|
||||||
entity.accountId = accountId;
|
entity.accountId = accountId;
|
||||||
entity.address = address;
|
entity.address = address;
|
||||||
entity.autoJoin = conference.isAutoJoin();
|
entity.autoJoin = conference.isAutoJoin();
|
||||||
entity.name = conference.getConferenceName();
|
entity.name = conference.getConferenceName();
|
||||||
|
entity.nick = Strings.emptyToNull(nick == null ? null : nick.getContent());
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import im.conversations.android.database.model.PresenceShow;
|
||||||
import im.conversations.android.database.model.PresenceType;
|
import im.conversations.android.database.model.PresenceType;
|
||||||
import im.conversations.android.xmpp.model.muc.Affiliation;
|
import im.conversations.android.xmpp.model.muc.Affiliation;
|
||||||
import im.conversations.android.xmpp.model.muc.Role;
|
import im.conversations.android.xmpp.model.muc.Role;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
@ -76,8 +76,8 @@ public class PresenceEntity {
|
||||||
final String status,
|
final String status,
|
||||||
final String vCardPhoto,
|
final String vCardPhoto,
|
||||||
final String occupantId,
|
final String occupantId,
|
||||||
final MultiUserChat multiUserChat) {
|
final MucUser mucUser) {
|
||||||
final var mucItem = multiUserChat == null ? null : multiUserChat.getItem();
|
final var mucUserItem = mucUser == null ? null : mucUser.getItem();
|
||||||
final var entity = new PresenceEntity();
|
final var entity = new PresenceEntity();
|
||||||
entity.accountId = account;
|
entity.accountId = account;
|
||||||
entity.address = address;
|
entity.address = address;
|
||||||
|
@ -86,12 +86,12 @@ public class PresenceEntity {
|
||||||
entity.show = show;
|
entity.show = show;
|
||||||
entity.status = status;
|
entity.status = status;
|
||||||
entity.vCardPhoto = vCardPhoto;
|
entity.vCardPhoto = vCardPhoto;
|
||||||
if (mucItem != null) {
|
if (mucUserItem != null) {
|
||||||
entity.occupantId = occupantId;
|
entity.occupantId = occupantId;
|
||||||
entity.mucUserAffiliation = mucItem.getAffiliation();
|
entity.mucUserAffiliation = mucUserItem.getAffiliation();
|
||||||
entity.mucUserRole = mucItem.getRole();
|
entity.mucUserRole = mucUserItem.getRole();
|
||||||
entity.mucUserJid = mucItem.getJid();
|
entity.mucUserJid = mucUserItem.getJid();
|
||||||
entity.mucUserSelf = multiUserChat.getStatus().contains(110);
|
entity.mucUserSelf = mucUser.getStatus().contains(110);
|
||||||
}
|
}
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import im.conversations.android.IDs;
|
import im.conversations.android.IDs;
|
||||||
|
@ -11,6 +12,7 @@ import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
|
||||||
public class Account extends AccountIdentifier {
|
public class Account extends AccountIdentifier {
|
||||||
|
|
||||||
|
@ -59,4 +61,23 @@ public class Account extends AccountIdentifier {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Resourcepart fallbackNick() {
|
||||||
|
final var localPart = address.getLocalpartOrNull();
|
||||||
|
if (localPart != null) {
|
||||||
|
final var resourceFromLocalPart = Resourcepart.fromOrNull(localPart.toString());
|
||||||
|
if (resourceFromLocalPart != null) {
|
||||||
|
return resourceFromLocalPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Resourcepart.fromOrThrowUnchecked(
|
||||||
|
BaseEncoding.base32Hex()
|
||||||
|
.lowerCase()
|
||||||
|
.omitPadding()
|
||||||
|
.encode(ByteSource.wrap(randomSeed).slice(0, 6).read()));
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
public enum MucState {
|
||||||
|
JOINING,
|
||||||
|
AVAILABLE,
|
||||||
|
ERROR_PRESENCE,
|
||||||
|
ERROR_IQ,
|
||||||
|
UNAVAILABLE,
|
||||||
|
NOT_A_MUC
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package im.conversations.android.database.model;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import org.jxmpp.jid.BareJid;
|
||||||
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
|
||||||
|
public class MucWithNick {
|
||||||
|
public final long chatId;
|
||||||
|
|
||||||
|
public final BareJid address;
|
||||||
|
private final String nickBookmark;
|
||||||
|
private final String nickAccount;
|
||||||
|
|
||||||
|
public MucWithNick(
|
||||||
|
final long chatId,
|
||||||
|
final BareJid address,
|
||||||
|
final String nickBookmark,
|
||||||
|
final String nickAccount) {
|
||||||
|
this.chatId = chatId;
|
||||||
|
this.address = address;
|
||||||
|
this.nickBookmark = nickBookmark;
|
||||||
|
this.nickAccount = nickAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resourcepart nick() {
|
||||||
|
final var bookmark = nickBookmark == null ? null : Resourcepart.fromOrNull(nickBookmark);
|
||||||
|
final var account = nickAccount == null ? null : Resourcepart.fromOrNull(nickAccount);
|
||||||
|
return bookmark != null ? bookmark : account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("address", address)
|
||||||
|
.add("nickBookmark", nickBookmark)
|
||||||
|
.add("nickAccount", nickAccount)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ import im.conversations.android.xmpp.model.error.Error;
|
||||||
import im.conversations.android.xmpp.model.jabber.Body;
|
import im.conversations.android.xmpp.model.jabber.Body;
|
||||||
import im.conversations.android.xmpp.model.jabber.Thread;
|
import im.conversations.android.xmpp.model.jabber.Thread;
|
||||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||||
import im.conversations.android.xmpp.model.reactions.Reactions;
|
import im.conversations.android.xmpp.model.reactions.Reactions;
|
||||||
import im.conversations.android.xmpp.model.reply.Reply;
|
import im.conversations.android.xmpp.model.reply.Reply;
|
||||||
|
@ -37,7 +37,7 @@ public class MessageTransformation extends Transformation {
|
||||||
Encrypted.class,
|
Encrypted.class,
|
||||||
OutOfBandData.class,
|
OutOfBandData.class,
|
||||||
DeliveryReceipt.class,
|
DeliveryReceipt.class,
|
||||||
MultiUserChat.class,
|
MucUser.class,
|
||||||
Displayed.class,
|
Displayed.class,
|
||||||
Replace.class,
|
Replace.class,
|
||||||
Reactions.class,
|
Reactions.class,
|
||||||
|
|
|
@ -14,7 +14,7 @@ import im.conversations.android.xmpp.model.DeliveryReceipt;
|
||||||
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
import im.conversations.android.xmpp.model.axolotl.Encrypted;
|
||||||
import im.conversations.android.xmpp.model.correction.Replace;
|
import im.conversations.android.xmpp.model.correction.Replace;
|
||||||
import im.conversations.android.xmpp.model.markers.Displayed;
|
import im.conversations.android.xmpp.model.markers.Displayed;
|
||||||
import im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import im.conversations.android.xmpp.model.reactions.Reactions;
|
import im.conversations.android.xmpp.model.reactions.Reactions;
|
||||||
import im.conversations.android.xmpp.model.reply.Reply;
|
import im.conversations.android.xmpp.model.reply.Reply;
|
||||||
import im.conversations.android.xmpp.model.retract.Retract;
|
import im.conversations.android.xmpp.model.retract.Retract;
|
||||||
|
@ -72,7 +72,7 @@ public class Transformer {
|
||||||
final ConversationsDatabase database, final MessageTransformation transformation) {
|
final ConversationsDatabase database, final MessageTransformation transformation) {
|
||||||
final var remote = transformation.remote;
|
final var remote = transformation.remote;
|
||||||
final var messageType = transformation.type;
|
final var messageType = transformation.type;
|
||||||
final var muc = transformation.getExtension(MultiUserChat.class);
|
final var muc = transformation.getExtension(MucUser.class);
|
||||||
|
|
||||||
final ChatIdentifier chat =
|
final ChatIdentifier chat =
|
||||||
database.chatDao()
|
database.chatDao()
|
||||||
|
|
|
@ -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("<");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import im.conversations.android.xml.Namespace;
|
||||||
import im.conversations.android.xmpp.NodeConfiguration;
|
import im.conversations.android.xmpp.NodeConfiguration;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.model.bookmark.Conference;
|
import im.conversations.android.xmpp.model.bookmark.Conference;
|
||||||
|
import im.conversations.android.xmpp.model.bookmark.Nick;
|
||||||
import im.conversations.android.xmpp.model.pubsub.Items;
|
import im.conversations.android.xmpp.model.pubsub.Items;
|
||||||
import im.conversations.android.xmpp.model.pubsub.event.Retract;
|
import im.conversations.android.xmpp.model.pubsub.event.Retract;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -36,7 +37,7 @@ public class BookmarkManager extends AbstractManager {
|
||||||
new FutureCallback<>() {
|
new FutureCallback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final Map<String, Conference> bookmarks) {
|
public void onSuccess(final Map<String, Conference> bookmarks) {
|
||||||
getDatabase().bookmarkDao().setItems(getAccount(), bookmarks);
|
setBookmarks(bookmarks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,6 +48,17 @@ public class BookmarkManager extends AbstractManager {
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setBookmarks(final Map<String, Conference> bookmarks) {
|
||||||
|
final var database = getDatabase();
|
||||||
|
final var account = getAccount();
|
||||||
|
database.runInTransaction(
|
||||||
|
() -> {
|
||||||
|
database.bookmarkDao().setItems(account, bookmarks);
|
||||||
|
database.chatDao().syncWithBookmarks(account);
|
||||||
|
});
|
||||||
|
getManager(MultiUserChatManager.class).joinMultiUserChats();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateItems(final Map<String, Conference> items) {
|
private void updateItems(final Map<String, Conference> items) {
|
||||||
getDatabase().bookmarkDao().updateItems(getAccount(), items);
|
getDatabase().bookmarkDao().updateItems(getAccount(), items);
|
||||||
}
|
}
|
||||||
|
@ -74,9 +86,18 @@ public class BookmarkManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Void> publishBookmark(final Jid address) {
|
public ListenableFuture<Void> publishBookmark(final Jid address, final boolean autoJoin) {
|
||||||
|
return publishBookmark(address, autoJoin, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Void> publishBookmark(
|
||||||
|
final Jid address, final boolean autoJoin, final String nick) {
|
||||||
final var itemId = address.toString();
|
final var itemId = address.toString();
|
||||||
final var conference = new Conference();
|
final var conference = new Conference();
|
||||||
|
conference.setAutoJoin(autoJoin);
|
||||||
|
if (nick != null) {
|
||||||
|
conference.addExtension(new Nick()).setContent(nick);
|
||||||
|
}
|
||||||
return Futures.transform(
|
return Futures.transform(
|
||||||
getManager(PepManager.class)
|
getManager(PepManager.class)
|
||||||
.publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
|
.publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
package im.conversations.android.xmpp.manager;
|
package im.conversations.android.xmpp.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
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 im.conversations.android.database.model.ChatIdentifier;
|
||||||
|
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.xml.Namespace;
|
||||||
|
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.muc.user.MultiUserChat;
|
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.MultiUserChat;
|
||||||
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import im.conversations.android.xmpp.model.stanza.Presence;
|
import im.conversations.android.xmpp.model.stanza.Presence;
|
||||||
import org.jxmpp.jid.BareJid;
|
import java.util.List;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -18,11 +36,157 @@ public class MultiUserChatManager extends AbstractManager {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enter(final BareJid room) {
|
public ListenableFuture<Integer> joinMultiUserChats() {
|
||||||
|
LOGGER.info("joining multi user chats. start");
|
||||||
|
return Futures.transform(
|
||||||
|
getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC),
|
||||||
|
this::joinMultiUserChats,
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
getManager(DiscoManager.class).info(Entity.discoItem(mucWithNick.address));
|
||||||
|
Futures.addCallback(
|
||||||
|
discoInfoFuture,
|
||||||
|
new ExistingMucJoiner(mucWithNick),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Jid to;
|
||||||
|
if (nick != null) {
|
||||||
|
to = JidCreate.fullFrom(mucWithNick.address, nick);
|
||||||
|
} else {
|
||||||
|
to = JidCreate.fullFrom(mucWithNick.address, getAccount().fallbackNick());
|
||||||
|
}
|
||||||
final var presence = new Presence();
|
final var presence = new Presence();
|
||||||
presence.setTo(JidCreate.fullFrom(room, Resourcepart.fromOrThrowUnchecked("c3-test-user")));
|
presence.setTo(to);
|
||||||
presence.addExtension(new MultiUserChat());
|
final var muc = presence.addExtension(new MultiUserChat());
|
||||||
|
final var history = muc.addExtension(new History());
|
||||||
|
history.setMaxChars(0);
|
||||||
LOGGER.info("sending {} ", presence);
|
LOGGER.info("sending {} ", presence);
|
||||||
connection.sendPresencePacket(presence);
|
connection.sendPresencePacket(presence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleSelfPresenceAvailable(final Presence presencePacket) {
|
||||||
|
final MucUser mucUser = presencePacket.getExtension(MucUser.class);
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
mucUser.getStatus().contains(MucUser.STATUS_CODE_SELF_PRESENCE));
|
||||||
|
// TODO flag chat as joined
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package im.conversations.android.xmpp.manager;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import im.conversations.android.xmpp.NodeConfiguration;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.model.nick.Nick;
|
import im.conversations.android.xmpp.model.nick.Nick;
|
||||||
import im.conversations.android.xmpp.model.pubsub.Items;
|
import im.conversations.android.xmpp.model.pubsub.Items;
|
||||||
|
@ -25,4 +27,10 @@ public class NickManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
getDatabase().nickDao().set(getAccount(), from.asBareJid(), nick);
|
getDatabase().nickDao().set(getAccount(), from.asBareJid(), nick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Void> publishNick(final String name) {
|
||||||
|
final Nick nick = new Nick();
|
||||||
|
nick.setContent(name);
|
||||||
|
return getManager(PepManager.class).publishSingleton(nick, NodeConfiguration.PRESENCE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ public class PepManager extends AbstractManager {
|
||||||
return pubSubManager().publishSingleton(pepService(), item, node, nodeConfiguration);
|
return pubSubManager().publishSingleton(pepService(), item, node, nodeConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Void> publishSingleton(
|
||||||
|
final Extension item, final NodeConfiguration nodeConfiguration) {
|
||||||
|
return pubSubManager().publishSingleton(pepService(), item, nodeConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
public ListenableFuture<Iq> retract(final String itemId, final String node) {
|
public ListenableFuture<Iq> retract(final String itemId, final String node) {
|
||||||
return pubSubManager().retract(pepService(), itemId, node);
|
return pubSubManager().retract(pepService(), itemId, node);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,12 @@ public class Conference extends Extension {
|
||||||
public String getConferenceName() {
|
public String getConferenceName() {
|
||||||
return this.getAttribute("name");
|
return this.getAttribute("name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAutoJoin(boolean autoJoin) {
|
||||||
|
setAttribute("autojoin", autoJoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Nick getNick() {
|
||||||
|
return this.getExtension(Nick.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.bookmark;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Nick extends Extension {
|
||||||
|
|
||||||
|
public Nick() {
|
||||||
|
super(Nick.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package im.conversations.android.xmpp.model.muc;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class History extends Extension {
|
||||||
|
|
||||||
|
public History() {
|
||||||
|
super(History.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxChars(final int maxChars) {
|
||||||
|
this.setAttribute("maxchars", maxChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxStanzas(final int maxStanzas) {
|
||||||
|
this.setAttribute("maxstanzas", maxStanzas);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.muc;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "x")
|
||||||
|
public class MultiUserChat extends Extension {
|
||||||
|
|
||||||
|
public MultiUserChat() {
|
||||||
|
super(MultiUserChat.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
@XmlPackage(namespace = Namespace.MUC)
|
||||||
|
package im.conversations.android.xmpp.model.muc;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
||||||
|
import im.conversations.android.xml.Namespace;
|
|
@ -7,10 +7,12 @@ import java.util.Collection;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@XmlElement(name = "x")
|
@XmlElement(name = "x")
|
||||||
public class MultiUserChat extends Extension {
|
public class MucUser extends Extension {
|
||||||
|
|
||||||
public MultiUserChat() {
|
public static final int STATUS_CODE_SELF_PRESENCE = 110;
|
||||||
super(MultiUserChat.class);
|
|
||||||
|
public MucUser() {
|
||||||
|
super(MucUser.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Item getItem() {
|
public Item getItem() {
|
|
@ -12,9 +12,13 @@ import im.conversations.android.xmpp.manager.PresenceManager;
|
||||||
import im.conversations.android.xmpp.manager.RosterManager;
|
import im.conversations.android.xmpp.manager.RosterManager;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BindProcessor extends XmppConnection.Delegate implements Consumer<Jid> {
|
public class BindProcessor extends XmppConnection.Delegate implements Consumer<Jid> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(BindProcessor.class);
|
||||||
|
|
||||||
public BindProcessor(final Context context, final XmppConnection connection) {
|
public BindProcessor(final Context context, final XmppConnection connection) {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +28,12 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
|
||||||
final var account = getAccount();
|
final var account = getAccount();
|
||||||
final var database = getDatabase();
|
final var database = getDatabase();
|
||||||
|
|
||||||
database.presenceDao().deletePresences(account.id);
|
// TODO reset errorCondition; mucState in chats
|
||||||
|
database.runInTransaction(
|
||||||
|
() -> {
|
||||||
|
database.chatDao().resetMucStates();
|
||||||
|
database.presenceDao().deletePresences(account.id);
|
||||||
|
});
|
||||||
|
|
||||||
getManager(RosterManager.class).fetch();
|
getManager(RosterManager.class).fetch();
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ import im.conversations.android.xml.Namespace;
|
||||||
import im.conversations.android.xmpp.Entity;
|
import im.conversations.android.xmpp.Entity;
|
||||||
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 im.conversations.android.xmpp.model.muc.user.MultiUserChat;
|
import im.conversations.android.xmpp.manager.MultiUserChatManager;
|
||||||
|
import im.conversations.android.xmpp.model.muc.user.MucUser;
|
||||||
import im.conversations.android.xmpp.model.occupant.OccupantId;
|
import im.conversations.android.xmpp.model.occupant.OccupantId;
|
||||||
import im.conversations.android.xmpp.model.stanza.Presence;
|
import im.conversations.android.xmpp.model.stanza.Presence;
|
||||||
import im.conversations.android.xmpp.model.vcard.update.VCardUpdate;
|
import im.conversations.android.xmpp.model.vcard.update.VCardUpdate;
|
||||||
|
@ -45,7 +46,7 @@ public class PresenceProcessor extends XmppConnection.Delegate implements Consum
|
||||||
|
|
||||||
final var vCardUpdate = presencePacket.getExtension(VCardUpdate.class);
|
final var vCardUpdate = presencePacket.getExtension(VCardUpdate.class);
|
||||||
final var vCardPhoto = vCardUpdate == null ? null : vCardUpdate.getHash();
|
final var vCardPhoto = vCardUpdate == null ? null : vCardUpdate.getHash();
|
||||||
final var muc = presencePacket.getExtension(MultiUserChat.class);
|
final var muc = presencePacket.getExtension(MucUser.class);
|
||||||
|
|
||||||
final String occupantId;
|
final String occupantId;
|
||||||
if (muc != null && presencePacket.hasExtension(OccupantId.class)) {
|
if (muc != null && presencePacket.hasExtension(OccupantId.class)) {
|
||||||
|
@ -72,6 +73,18 @@ 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 (type == null) {
|
||||||
|
mucManager.handleSelfPresenceAvailable(presencePacket);
|
||||||
|
} else if (type == PresenceType.UNAVAILABLE) {
|
||||||
|
mucManager.handleSelfPresenceUnavailable(presencePacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == PresenceType.ERROR) {
|
||||||
|
mucManager.handleErrorPresence(presencePacket);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO do this only for contacts?
|
// TODO do this only for contacts?
|
||||||
fetchCapabilities(presencePacket);
|
fetchCapabilities(presencePacket);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue