1
0
Fork 1

join mucs upon bookmark retrival

This commit is contained in:
Daniel Gultsch 2023-03-04 15:00:14 +01:00 committed by Arne
parent 21831e4340
commit db6eb55b7d
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.Query;
import androidx.room.Transaction;
import com.google.common.util.concurrent.ListenableFuture;
import im.conversations.android.database.entity.ChatEntity;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.ChatIdentifier;
import im.conversations.android.database.model.ChatType;
import im.conversations.android.database.model.GroupIdentifier;
import im.conversations.android.database.model.MucWithNick;
import im.conversations.android.xmpp.model.stanza.Message;
import java.util.Arrays;
import java.util.List;
import org.jxmpp.jid.Jid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Dao
public abstract class ChatDao {
private static final Logger LOGGER = LoggerFactory.getLogger(ChatDao.class);
@Transaction
public ChatIdentifier getOrCreateChat(
final Account account,
@ -56,6 +62,76 @@ public abstract class ChatDao {
@Insert
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")
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.Index;
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.Nick;
import java.util.Map;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
@ -47,11 +49,13 @@ public class BookmarkEntity {
if (address == null) {
return null;
}
final Nick nick = conference.getNick();
final var entity = new BookmarkEntity();
entity.accountId = accountId;
entity.address = address;
entity.autoJoin = conference.isAutoJoin();
entity.name = conference.getConferenceName();
entity.nick = Strings.emptyToNull(nick == null ? null : nick.getContent());
return entity;
}
}

View file

@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteSource;
import com.google.common.primitives.Ints;
import im.conversations.android.IDs;
@ -11,6 +12,7 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.UUID;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.parts.Resourcepart;
public class Account extends AccountIdentifier {
@ -59,4 +61,23 @@ public class Account extends AccountIdentifier {
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.XmppConnection;
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.event.Retract;
import java.util.Collection;
@ -36,7 +37,7 @@ public class BookmarkManager extends AbstractManager {
new FutureCallback<>() {
@Override
public void onSuccess(final Map<String, Conference> bookmarks) {
getDatabase().bookmarkDao().setItems(getAccount(), bookmarks);
setBookmarks(bookmarks);
}
@Override
@ -47,6 +48,17 @@ public class BookmarkManager extends AbstractManager {
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) {
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 conference = new Conference();
conference.setAutoJoin(autoJoin);
if (nick != null) {
conference.addExtension(new Nick()).setContent(nick);
}
return Futures.transform(
getManager(PepManager.class)
.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.Futures;
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.XmppConnection;
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.user.MucUser;
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.parts.Resourcepart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -27,15 +29,36 @@ public class MultiUserChatManager extends AbstractManager {
super(context, connection);
}
public void enter(final BareJid room) {
final var discoInfoFuture = getManager(DiscoManager.class).info(Entity.discoItem(room));
public void joinMultiUserChats() {
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
Futures.addCallback(
discoInfoFuture,
new FutureCallback<>() {
@Override
public void onSuccess(final InfoQuery result) {
enter(room, result);
enter(mucWithNick, result);
}
@Override
@ -46,10 +69,16 @@ public class MultiUserChatManager extends AbstractManager {
MoreExecutors.directExecutor());
}
public void enter(final BareJid room, final InfoQuery infoQuery) {
// TODO look up bookmark, then nick then user local part
public void enter(final MucWithNick mucWithNick, final InfoQuery infoQuery) {
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();
presence.setTo(JidCreate.fullFrom(room, Resourcepart.fromOrThrowUnchecked("c3-test-user")));
presence.setTo(to);
final var muc = presence.addExtension(new MultiUserChat());
final var history = muc.addExtension(new History());
history.setMaxChars(0);

View file

@ -2,6 +2,8 @@ package im.conversations.android.xmpp.manager;
import android.content.Context;
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.model.nick.Nick;
import im.conversations.android.xmpp.model.pubsub.Items;
@ -25,4 +27,10 @@ public class NickManager extends AbstractManager {
}
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);
}
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) {
return pubSubManager().retract(pepService(), itemId, node);
}

View file

@ -17,4 +17,12 @@ public class Conference extends Extension {
public String getConferenceName() {
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.BookmarkManager;
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.RosterManager;
import java.util.function.Consumer;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -30,6 +28,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
final var account = getAccount();
final var database = getDatabase();
// TODO reset errorCondition; mucState in chats
database.presenceDao().deletePresences(account.id);
getManager(RosterManager.class).fetch();
@ -50,8 +49,5 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
getManager(AxolotlManager.class).publishIfNecessary();
getManager(PresenceManager.class).sendPresence();
getManager(MultiUserChatManager.class)
.enter(JidCreate.bareFromOrThrowUnchecked("xsf@muc.xmpp.org"));
}
}