NativeBookmarkManager.java

  1package eu.siacs.conversations.xmpp.manager;
  2
  3import android.util.Log;
  4import androidx.annotation.NonNull;
  5import com.google.common.collect.ImmutableMap;
  6import com.google.common.util.concurrent.FutureCallback;
  7import com.google.common.util.concurrent.Futures;
  8import com.google.common.util.concurrent.ListenableFuture;
  9import com.google.common.util.concurrent.MoreExecutors;
 10import eu.siacs.conversations.Config;
 11import eu.siacs.conversations.entities.Account;
 12import eu.siacs.conversations.entities.Bookmark;
 13import eu.siacs.conversations.services.XmppConnectionService;
 14import eu.siacs.conversations.xml.Namespace;
 15import eu.siacs.conversations.xmpp.Jid;
 16import eu.siacs.conversations.xmpp.XmppConnection;
 17import im.conversations.android.xmpp.NodeConfiguration;
 18import im.conversations.android.xmpp.model.bookmark2.Conference;
 19import im.conversations.android.xmpp.model.bookmark2.Nick;
 20import im.conversations.android.xmpp.model.bookmark2.Password;
 21import im.conversations.android.xmpp.model.pubsub.Items;
 22import im.conversations.android.xmpp.model.pubsub.event.Retract;
 23import java.util.Collection;
 24import java.util.Collections;
 25import java.util.Map;
 26
 27public class NativeBookmarkManager extends AbstractBookmarkManager {
 28
 29    public NativeBookmarkManager(final XmppConnectionService service, XmppConnection connection) {
 30        super(service, connection);
 31    }
 32
 33    public void fetch() {
 34        final var future = getManager(PepManager.class).fetchItems(Conference.class);
 35        Futures.addCallback(
 36                future,
 37                new FutureCallback<>() {
 38                    @Override
 39                    public void onSuccess(final Map<String, Conference> bookmarks) {
 40                        final var builder = new ImmutableMap.Builder<Jid, Bookmark>();
 41                        for (final var entry : bookmarks.entrySet()) {
 42                            final Bookmark bookmark =
 43                                    itemToBookmark(entry.getKey(), entry.getValue(), getAccount());
 44                            if (bookmark == null) {
 45                                continue;
 46                            }
 47                            builder.put(bookmark.getJid(), bookmark);
 48                        }
 49                        processBookmarksInitial(builder.buildKeepingLast(), true);
 50                    }
 51
 52                    @Override
 53                    public void onFailure(@NonNull final Throwable throwable) {
 54                        Log.d(Config.LOGTAG, "Could not fetch bookmarks", throwable);
 55                    }
 56                },
 57                MoreExecutors.directExecutor());
 58    }
 59
 60    public void handleItems(final Items items) {
 61        this.handleItems(items.getItemMap(Conference.class));
 62        this.handleRetractions(items.getRetractions());
 63    }
 64
 65    private void handleRetractions(final Collection<Retract> retractions) {
 66        final var account = getAccount();
 67        for (final var retract : retractions) {
 68            final Jid id = Jid.Invalid.getNullForInvalid(retract.getAttributeAsJid("id"));
 69            if (id != null) {
 70                account.removeBookmark(id);
 71                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
 72                processDeletedBookmark(id);
 73                service.updateConversationUi();
 74            }
 75        }
 76    }
 77
 78    private void handleItems(final Map<String, Conference> items) {
 79        final var account = getAccount();
 80        for (final var item : items.entrySet()) {
 81            final Bookmark bookmark = itemToBookmark(item.getKey(), item.getValue(), account);
 82            if (bookmark == null) {
 83                continue;
 84            }
 85            account.putBookmark(bookmark);
 86            getManager(BookmarkManager.class).processModifiedBookmark(bookmark, true);
 87            service.updateConversationUi();
 88        }
 89    }
 90
 91    public ListenableFuture<Void> publish(final Bookmark bookmark) {
 92        final var address = bookmark.getJid();
 93        final var name = bookmark.getBookmarkName();
 94        final var nick = bookmark.getNick();
 95        final String password = bookmark.getPassword();
 96        final var itemId = address.toString();
 97        final var conference = new Conference();
 98        conference.setAutoJoin(bookmark.autojoin());
 99        if (nick != null) {
100            conference.addExtension(new Nick()).setContent(nick);
101        }
102        if (name != null) {
103            conference.setConferenceName(name);
104        }
105        if (password != null) {
106            conference.addExtension(new Password()).setContent(password);
107        }
108        conference.addExtension(bookmark.getExtensions());
109        return Futures.transform(
110                getManager(PepManager.class)
111                        .publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
112                result -> null,
113                MoreExecutors.directExecutor());
114    }
115
116    public ListenableFuture<Void> retract(final Jid address) {
117        final var itemId = address.toString();
118        return Futures.transform(
119                getManager(PepManager.class).retract(itemId, Namespace.BOOKMARKS2),
120                result -> null,
121                MoreExecutors.directExecutor());
122    }
123
124    private void deleteAllItems() {
125        final var account = getAccount();
126        final var previous = account.getBookmarkedJids();
127        account.setBookmarks(Collections.emptyMap());
128        processDeletedBookmarks(previous);
129    }
130
131    public void handleDelete() {
132        Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": deleted bookmarks node");
133        this.deleteAllItems();
134    }
135
136    public void handlePurge() {
137        Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": purged bookmarks");
138        this.deleteAllItems();
139    }
140
141    public boolean hasFeature() {
142        final var pep = getManager(PepManager.class);
143        final var disco = getManager(DiscoManager.class);
144        return pep.hasPublishOptions()
145                && pep.hasConfigNodeMax()
146                && disco.hasAccountFeature(Namespace.BOOKMARKS2_COMPAT);
147    }
148
149    private static Bookmark itemToBookmark(
150            final String id, final Conference conference, final Account account) {
151        if (id == null || conference == null) {
152            return null;
153        }
154        final var jid = Jid.Invalid.getNullForInvalid(Jid.ofOrInvalid(id));
155        if (jid == null || jid.isFullJid()) {
156            return null;
157        }
158        final Bookmark bookmark = new Bookmark(account, jid);
159
160        // TODO use proper API
161
162        bookmark.setBookmarkName(conference.getAttribute("name"));
163        bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
164        bookmark.setNick(conference.findChildContent("nick"));
165        bookmark.setPassword(conference.findChildContent("password"));
166        final var extensions = conference.getExtensions();
167        if (extensions != null) {
168            for (final var ext : extensions.getChildren()) {
169                if (ext.getName().equals("group") && ext.getNamespace().equals("jabber:iq:roster")) {
170                    bookmark.addGroup(ext.getContent());
171                }
172            }
173
174            bookmark.setExtensions(conference.getExtensions());
175        }
176        return bookmark;
177    }
178}