diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index aa7a1eedb..4b20b1cd9 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -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 parseFromStorage(Element storage, Account account) { + if (storage == null) { + return Collections.emptyList(); + } + final HashMap 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 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 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; } diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 5f93400e2..82faff3b5 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -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" diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index d428e6743..cc965fc52 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -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 preKeyRecords, final int deviceId, Bundle publishOptions) { final Element item = new Element("item"); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 1e7aacb1d..cb272e47a 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -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 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); } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cfce84a2c..532556489 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -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 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 previousBookmarks = account.getBookmarkedJids(); - final HashMap 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 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 bookmarks, final boolean pep) { + final Set 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); } diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index cef8d71a2..648f5c26e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -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); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 863fd5780..ee0334b67 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -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(); } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 9264d039e..e5c8a0619 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -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); diff --git a/src/main/java/eu/siacs/conversations/xml/Namespace.java b/src/main/java/eu/siacs/conversations/xml/Namespace.java index 26a0b33a2..18314d79b 100644 --- a/src/main/java/eu/siacs/conversations/xml/Namespace.java +++ b/src/main/java/eu/siacs/conversations/xml/Namespace.java @@ -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"; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java index f6bfd451e..959323cc6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java +++ b/src/main/java/eu/siacs/conversations/xmpp/pep/PublishOptions.java @@ -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);