WIP Bookmarks 2 support

This commit is contained in:
Daniel Gultsch 2019-09-28 00:32:29 +02:00
parent ea633f3d8f
commit 6923b2898c
10 changed files with 196 additions and 78 deletions

View file

@ -3,15 +3,23 @@ package eu.siacs.conversations.entities;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.StringUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid;
import rocks.xmpp.addr.Jid;
@ -33,11 +41,69 @@ public class Bookmark extends Element implements ListItem {
this.account = account;
}
public static Collection<Bookmark> parseFromStorage(Element storage, Account account) {
if (storage == null) {
return Collections.emptyList();
}
final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
final Bookmark bookmark = Bookmark.parse(item, account);
if (bookmark != null) {
final Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
bookmark.setBookmarkName(old.getBookmarkName());
}
}
}
}
return bookmarks.values();
}
public static Collection<Bookmark> parseFromPubsub(Element pubsub, Account account) {
if (pubsub == null) {
return Collections.emptyList();
}
final Element items = pubsub.findChild("items");
if (items != null && Namespace.BOOKMARK.equals(items.getAttribute("node"))) {
final List<Bookmark> bookmarks = new ArrayList<>();
for(Element item : items.getChildren()) {
if (item.getName().equals("item")) {
final Bookmark bookmark = Bookmark.parseFromItem(item, account);
if (bookmark != null) {
bookmarks.add(bookmark);
}
}
}
return bookmarks;
}
return Collections.emptyList();
}
public static Bookmark parse(Element element, Account account) {
Bookmark bookmark = new Bookmark(account);
bookmark.setAttributes(element.getAttributes());
bookmark.setChildren(element.getChildren());
bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid"));
if (bookmark.jid == null) {
return null;
}
return bookmark;
}
public static Bookmark parseFromItem(Element item, Account account) {
final Element conference = item.findChild("conference", Namespace.BOOKMARK);
if (conference == null) {
return null;
}
final Bookmark bookmark = new Bookmark(account);
bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
if (bookmark.jid == null) {
return null;
}
bookmark.setBookmarkName(conference.getAttribute("name"));
bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
bookmark.setNick(conference.findChildContent("nick"));
return bookmark;
}

View file

@ -38,7 +38,8 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
Namespace.NICK+"+notify",
Namespace.BOOKMARKS+"+notify",
//Namespace.BOOKMARKS+"+notify",
Namespace.BOOKMARK+"+notify",
"urn:xmpp:ping",
"jabber:iq:version",
"http://jabber.org/protocol/chatstates"

View file

@ -125,6 +125,10 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket retrieveBookmarks() {
return retrieve(Namespace.BOOKMARK, null);
}
public IqPacket publishNick(String nick) {
final Element item = new Element("item");
item.addChild("nick", Namespace.NICK).setContent(nick);
@ -146,9 +150,13 @@ public class IqGenerator extends AbstractGenerator {
return publish("urn:xmpp:avatar:data", item, options);
}
public IqPacket publishElement(final String namespace,final Element element, final Bundle options) {
public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
return publishElement(namespace, element, "curent", options);
}
public IqPacket publishElement(final String namespace,final Element element, String id, final Bundle options) {
final Element item = new Element("item");
item.setAttribute("id","current");
item.setAttribute("id",id);
item.addChild(element);
return publish(namespace, item, options);
}
@ -222,6 +230,19 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
}
public Element publishBookmarkItem(final Bookmark bookmark) {
final String name = bookmark.getBookmarkName();
final String nick = bookmark.getNick();
final Element conference = new Element("conference", Namespace.BOOKMARK);
if (name != null) {
conference.setAttribute("name", name);
}
if (nick != null) {
conference.addChild("nick").setContent(nick);
}
return conference;
}
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element item = new Element("item");

View file

@ -6,10 +6,12 @@ import android.util.Pair;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -20,6 +22,7 @@ import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
@ -224,11 +227,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
final Element i = items.findChild("item");
final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
mXmppConnectionService.processBookmarks(account, storage, true);
Collection<Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": processing bookmark PEP event");
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring bookmark PEP event because bookmark conversion was not detected");
}
} else {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+" received pubsub notification for node="+node);
}
}

View file

@ -313,9 +313,12 @@ public class XmppConnectionService extends Service {
mJingleConnectionManager.cancelInTransmission();
mQuickConversationsService.considerSyncBackground(false);
fetchRosterFromServer(account);
if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
fetchBookmarks2(account);
/*if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
fetchBookmarks(account);
}
}*/
final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
final boolean catchup = getMessageArchiveService().inCatchup(account);
if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) {
@ -1559,7 +1562,8 @@ public class XmppConnectionService extends Service {
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element query1 = response.query();
final Element storage = query1.findChild("storage", "storage:bookmarks");
processBookmarks(a, storage, false);
Collection<Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
processBookmarksInitial(a, bookmarks, false);
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks");
}
@ -1567,59 +1571,63 @@ public class XmppConnectionService extends Service {
sendIqPacket(account, iqPacket, callback);
}
public void processBookmarks(Account account, Element storage, final boolean pep) {
final Set<Jid> previousBookmarks = account.getBookmarkedJids();
final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
final boolean synchronizeWithBookmarks = synchronizeWithBookmarks();
if (storage != null) {
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
final Bookmark bookmark = Bookmark.parse(item, account);
Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
bookmark.setBookmarkName(old.getBookmarkName());
}
if (bookmark.getJid() == null) {
continue;
}
previousBookmarks.remove(bookmark.getJid().asBareJid());
Conversation conversation = find(bookmark);
if (conversation != null) {
if (conversation.getMode() != Conversation.MODE_MULTI) {
continue;
}
bookmark.setConversation(conversation);
if (pep && synchronizeWithBookmarks && !bookmark.autojoin()) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving conference ("+conversation.getJid()+") after receiving pep");
archiveConversation(conversation, false);
}
} else if (synchronizeWithBookmarks && bookmark.autojoin()) {
conversation = findOrCreateConversation(account, bookmark.getFullJid(), true, true, false);
bookmark.setConversation(conversation);
}
public void fetchBookmarks2(final Account account) {
final IqPacket retrieve = mIqGenerator.retrieveBookmarks();
sendIqPacket(account, retrieve, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket response) {
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
final Collection<Bookmark> bookmarks = Bookmark.parseFromPubsub(pubsub, account);
Log.d(Config.LOGTAG,"bookmarks2 "+pubsub);
Log.d(Config.LOGTAG,"bookmarks2"+ bookmarks);
processBookmarksInitial(account, bookmarks, true);
}
}
if (pep && synchronizeWithBookmarks) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + previousBookmarks.size() + " bookmarks have been removed");
for (Jid jid : previousBookmarks) {
final Conversation conversation = find(account, jid);
if (conversation != null && conversation.getMucOptions().getError() == MucOptions.Error.DESTROYED) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving destroyed conference ("+conversation.getJid()+") after receiving pep");
archiveConversation(conversation, false);
}
}
}
}
account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks.values()));
});
}
public void pushBookmarks(Account account) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null && connection.getFeatures().bookmarksConversion()) {
pushBookmarksPep(account);
} else {
pushBookmarksPrivateXml(account);
public void processBookmarksInitial(Account account, Collection<Bookmark> bookmarks, final boolean pep) {
final Set<Jid> previousBookmarks = account.getBookmarkedJids();
final boolean synchronizeWithBookmarks = synchronizeWithBookmarks();
for (Bookmark bookmark : bookmarks) {
previousBookmarks.remove(bookmark.getJid().asBareJid());
Conversation conversation = find(bookmark);
if (conversation != null) {
if (conversation.getMode() != Conversation.MODE_MULTI) {
continue;
}
bookmark.setConversation(conversation);
if (pep && synchronizeWithBookmarks && !bookmark.autojoin()) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving conference ("+conversation.getJid()+") after receiving pep");
archiveConversation(conversation, false);
}
} else if (synchronizeWithBookmarks && bookmark.autojoin()) {
conversation = findOrCreateConversation(account, bookmark.getFullJid(), true, true, false);
bookmark.setConversation(conversation);
}
}
if (pep && synchronizeWithBookmarks) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + previousBookmarks.size() + " bookmarks have been removed");
for (Jid jid : previousBookmarks) {
final Conversation conversation = find(account, jid);
if (conversation != null && conversation.getMucOptions().getError() == MucOptions.Error.DESTROYED) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": archiving destroyed conference ("+conversation.getJid()+") after receiving pep");
archiveConversation(conversation, false);
}
}
}
account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks));
}
public void createBookmark(final Account account, final Bookmark bookmark) {
final Element item = mIqGenerator.publishBookmarkItem(bookmark);
pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARK, item, bookmark.getJid().asBareJid().toEscapedString(), PublishOptions.persistentWhitelistAccessMaxItems());
}
public void deleteBookmark(final Account account, final Bookmark bookmark) {
final XmppConnection connection = account.getXmppConnection();
}
private void pushBookmarksPrivateXml(Account account) {
@ -1643,14 +1651,18 @@ public class XmppConnectionService extends Service {
}
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options) {
pushNodeAndEnforcePublishOptions(account, node, element, options, true);
pushNodeAndEnforcePublishOptions(account, node, element, null, options, true);
}
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options, final boolean retry) {
final IqPacket packet = mIqGenerator.publishElement(node, element, options);
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options) {
pushNodeAndEnforcePublishOptions(account, node, element, id, options, true);
}
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options, final boolean retry) {
final IqPacket packet = mIqGenerator.publishElement(node, element, id, options);
sendIqPacket(account, packet, (a, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
return;
@ -1659,7 +1671,7 @@ public class XmppConnectionService extends Service {
pushNodeConfiguration(account, node, options, new OnConfigurationPushed() {
@Override
public void onPushSucceeded() {
pushNodeAndEnforcePublishOptions(account, node, element, options, false);
pushNodeAndEnforcePublishOptions(account, node, element, id, options, false);
}
@Override
@ -2043,10 +2055,10 @@ public class XmppConnectionService extends Service {
Account account = bookmark.getAccount();
bookmark.setConversation(null);
account.getBookmarks().remove(bookmark);
pushBookmarks(account);
deleteBookmark(account, bookmark);
} else if (bookmark.autojoin()) {
bookmark.setAutojoin(false);
pushBookmarks(bookmark.getAccount());
createBookmark(bookmark.getAccount(), bookmark);
}
}
}
@ -2767,10 +2779,11 @@ public class XmppConnectionService extends Service {
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
if (conversation.getBookmark() != null) {
final Bookmark bookmark = conversation.getBookmark();
if (synchronizeWithBookmarks()) {
conversation.getBookmark().setAutojoin(true);
bookmark.setAutojoin(true);
}
pushBookmarks(conversation.getAccount());
createBookmark(conversation.getAccount(), bookmark);
}
updateConversation(conversation);
joinMuc(conversation);
@ -2824,7 +2837,7 @@ public class XmppConnectionService extends Service {
}
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persist nick '" + full.getResource() + "' into bookmark for " + conversation.getJid().asBareJid());
bookmark.setNick(full.getResource());
pushBookmarks(bookmark.getAccount());
createBookmark(bookmark.getAccount(), bookmark);
}
}
@ -2859,7 +2872,7 @@ public class XmppConnectionService extends Service {
Bookmark bookmark = conversation.getBookmark();
if (bookmark != null) {
bookmark.setNick(nick);
pushBookmarks(bookmark.getAccount());
createBookmark(bookmark.getAccount(), bookmark);
}
joinMuc(conversation);
}
@ -3027,7 +3040,7 @@ public class XmppConnectionService extends Service {
if (bookmark != null && (sameBefore || bookmark.getBookmarkName() == null)) {
if (bookmark.setBookmarkName(StringUtils.nullOnEmpty(mucOptions.getName()))) {
pushBookmarks(account);
createBookmark(account, bookmark);
}
}
@ -4442,7 +4455,7 @@ public class XmppConnectionService extends Service {
}
bookmark.setAutojoin(getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin)));
account.getBookmarks().add(bookmark);
pushBookmarks(account);
createBookmark(account, bookmark);
bookmark.setConversation(conversation);
}

View file

@ -223,16 +223,17 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin);
final Account account = xmppConnectionService.findAccountByJid(jid);
final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true);
if (conversation.getBookmark() != null) {
if (!conversation.getBookmark().autojoin() && syncAutoJoin) {
Bookmark bookmark = conversation.getBookmark();
if (bookmark != null) {
if (!bookmark.autojoin() && syncAutojoin) {
conversation.getBookmark().setAutojoin(true);
xmppConnectionService.pushBookmarks(account);
xmppConnectionService.createBookmark(account, bookmark);
}
} else {
final Bookmark bookmark = new Bookmark(account, conversation.getJid().asBareJid());
bookmark.setAutojoin(syncAutoJoin);
bookmark = new Bookmark(account, conversation.getJid().asBareJid());
bookmark.setAutojoin(syncAutojoin);
account.getBookmarks().add(bookmark);
xmppConnectionService.pushBookmarks(account);
xmppConnectionService.createBookmark(account, bookmark);
}
switchToConversation(conversation);
}

View file

@ -390,7 +390,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
Bookmark bookmark = mConversation.getBookmark();
account.getBookmarks().remove(bookmark);
bookmark.setConversation(null);
xmppConnectionService.pushBookmarks(account);
xmppConnectionService.deleteBookmark(account, bookmark);
updateView();
}

View file

@ -431,7 +431,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
bookmark.setConversation(conversation);
if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
bookmark.setAutojoin(true);
xmppConnectionService.pushBookmarks(bookmark.getAccount());
xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
}
SoftKeyboardUtils.hideSoftKeyboard(this);
switchToConversation(conversation);
@ -480,7 +480,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
bookmark.setConversation(null);
Account account = bookmark.getAccount();
account.getBookmarks().remove(bookmark);
xmppConnectionService.pushBookmarks(account);
xmppConnectionService.deleteBookmark(account, bookmark);
filter(mSearchEditText.getText().toString());
});
builder.create().show();
@ -1042,7 +1042,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
bookmark.setNick(nick);
}
account.getBookmarks().add(bookmark);
xmppConnectionService.pushBookmarks(account);
xmppConnectionService.createBookmark(account, bookmark);
final Conversation conversation = xmppConnectionService
.findOrCreateConversation(account, conferenceJid, true, true, true);
bookmark.setConversation(conversation);

View file

@ -33,4 +33,5 @@ public final class Namespace {
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
public static final String BOOKMARK = "urn:xmpp:bookmarks:0";
}

View file

@ -25,6 +25,15 @@ public class PublishOptions {
return options;
}
public static Bundle persistentWhitelistAccessMaxItems() {
final Bundle options = new Bundle();
options.putString("pubsub#persist_items","true");
options.putString("pubsub#access_model", "whitelist");
options.putString("pubsub#send_last_published_item","never");
options.putString("pubsub#max_items","128"); //YOLO!
return options;
}
public static boolean preconditionNotMet(IqPacket response) {
final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null;
return error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR);