join mucs upon bookmark retrival

This commit is contained in:
Daniel Gultsch 2023-03-04 15:00:14 +01:00 committed by Arne
parent 8173e47f34
commit de63e5e290
11 changed files with 228 additions and 15 deletions

View file

@ -5,19 +5,25 @@ 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.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.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.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,
@ -56,6 +62,76 @@ public abstract class ChatDao {
@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);
// TODO get pending only
@Query(
"SELECT 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")
public abstract ListenableFuture<List<MucWithNick>> getMultiUserChats(
final long account, final ChatType chatType);
} }

View file

@ -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;
} }
} }

View file

@ -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);
}
}
} }

View file

@ -0,0 +1,33 @@
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 BareJid address;
private final String nickBookmark;
private final String nickAccount;
public MucWithNick(BareJid address, String nickBookmark, String nickAccount) {
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();
}
}

View file

@ -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),

View file

@ -6,6 +6,8 @@ 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.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import im.conversations.android.database.model.ChatType;
import im.conversations.android.database.model.MucWithNick;
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.model.disco.info.InfoQuery; import im.conversations.android.xmpp.model.disco.info.InfoQuery;
@ -13,9 +15,9 @@ 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;
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;
@ -27,15 +29,36 @@ public class MultiUserChatManager extends AbstractManager {
super(context, connection); super(context, connection);
} }
public void enter(final BareJid room) { public void joinMultiUserChats() {
final var discoInfoFuture = getManager(DiscoManager.class).info(Entity.discoItem(room)); final var future = getDatabase().chatDao().getMultiUserChats(getAccount().id, ChatType.MUC);
Futures.addCallback(
future,
new FutureCallback<>() {
@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());
}
public void enter(final MucWithNick mucWithNick) {
final var discoInfoFuture =
getManager(DiscoManager.class).info(Entity.discoItem(mucWithNick.address));
// TODO catching // TODO catching
Futures.addCallback( Futures.addCallback(
discoInfoFuture, discoInfoFuture,
new FutureCallback<>() { new FutureCallback<>() {
@Override @Override
public void onSuccess(final InfoQuery result) { public void onSuccess(final InfoQuery result) {
enter(room, result); enter(mucWithNick, result);
} }
@Override @Override
@ -46,10 +69,16 @@ public class MultiUserChatManager extends AbstractManager {
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} }
public void enter(final BareJid room, final InfoQuery infoQuery) { public void enter(final MucWithNick mucWithNick, final InfoQuery infoQuery) {
// TODO look up bookmark, then nick then user local part 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);
final var muc = presence.addExtension(new MultiUserChat()); final var muc = presence.addExtension(new MultiUserChat());
final var history = muc.addExtension(new History()); final var history = muc.addExtension(new History());
history.setMaxChars(0); history.setMaxChars(0);

View file

@ -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);
}
} }

View file

@ -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);
} }

View file

@ -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);
}
} }

View file

@ -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);
}
}

View file

@ -8,12 +8,10 @@ import im.conversations.android.xmpp.manager.AxolotlManager;
import im.conversations.android.xmpp.manager.BlockingManager; import im.conversations.android.xmpp.manager.BlockingManager;
import im.conversations.android.xmpp.manager.BookmarkManager; import im.conversations.android.xmpp.manager.BookmarkManager;
import im.conversations.android.xmpp.manager.DiscoManager; import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.manager.MultiUserChatManager;
import im.conversations.android.xmpp.manager.PresenceManager; 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.jxmpp.jid.impl.JidCreate;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,6 +28,7 @@ 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();
// TODO reset errorCondition; mucState in chats
database.presenceDao().deletePresences(account.id); database.presenceDao().deletePresences(account.id);
getManager(RosterManager.class).fetch(); getManager(RosterManager.class).fetch();
@ -50,8 +49,5 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
getManager(AxolotlManager.class).publishIfNecessary(); getManager(AxolotlManager.class).publishIfNecessary();
getManager(PresenceManager.class).sendPresence(); getManager(PresenceManager.class).sendPresence();
getManager(MultiUserChatManager.class)
.enter(JidCreate.bareFromOrThrowUnchecked("xsf@muc.xmpp.org"));
} }
} }