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.Bookmark;
12import eu.siacs.conversations.services.XmppConnectionService;
13import eu.siacs.conversations.xml.Namespace;
14import eu.siacs.conversations.xmpp.Jid;
15import eu.siacs.conversations.xmpp.XmppConnection;
16import im.conversations.android.xmpp.NodeConfiguration;
17import im.conversations.android.xmpp.model.bookmark2.Conference;
18import im.conversations.android.xmpp.model.bookmark2.Nick;
19import im.conversations.android.xmpp.model.bookmark2.Password;
20import im.conversations.android.xmpp.model.pubsub.Items;
21import im.conversations.android.xmpp.model.pubsub.event.Retract;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Map;
25
26public class BookmarkManager extends AbstractBookmarkManager {
27
28 public BookmarkManager(final XmppConnectionService service, XmppConnection connection) {
29 super(service, connection);
30 }
31
32 public void fetch() {
33 final var future = getManager(PepManager.class).fetchItems(Conference.class);
34 Futures.addCallback(
35 future,
36 new FutureCallback<>() {
37 @Override
38 public void onSuccess(final Map<String, Conference> bookmarks) {
39 final var builder = new ImmutableMap.Builder<Jid, Bookmark>();
40 for (final var entry : bookmarks.entrySet()) {
41 final Bookmark bookmark =
42 Bookmark.parseFromItem(
43 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 // TODO parseFromItem can be included in this Manager
82 final Bookmark bookmark =
83 Bookmark.parseFromItem(item.getKey(), item.getValue(), account);
84 if (bookmark == null) {
85 continue;
86 }
87 account.putBookmark(bookmark);
88 service.processModifiedBookmark(bookmark);
89 service.updateConversationUi();
90 }
91 }
92
93 public ListenableFuture<Void> publish(final Bookmark bookmark) {
94 final var address = bookmark.getJid();
95 final var name = bookmark.getBookmarkName();
96 final var nick = bookmark.getNick();
97 final String password = bookmark.getPassword();
98 final var itemId = address.toString();
99 final var conference = new Conference();
100 conference.setAutoJoin(bookmark.autojoin());
101 if (nick != null) {
102 conference.addExtension(new Nick()).setContent(nick);
103 }
104 if (name != null) {
105 conference.setConferenceName(name);
106 }
107 if (password != null) {
108 conference.addExtension(new Password()).setContent(password);
109 }
110 conference.addExtension(bookmark.getExtensions());
111 return Futures.transform(
112 getManager(PepManager.class)
113 .publish(conference, itemId, NodeConfiguration.WHITELIST_MAX_ITEMS),
114 result -> null,
115 MoreExecutors.directExecutor());
116 }
117
118 public ListenableFuture<Void> retract(final Jid address) {
119 final var itemId = address.toString();
120 return Futures.transform(
121 getManager(PepManager.class).retract(itemId, Namespace.BOOKMARKS2),
122 result -> null,
123 MoreExecutors.directExecutor());
124 }
125
126 private void deleteAllItems() {
127 final var account = getAccount();
128 final var previous = account.getBookmarkedJids();
129 account.setBookmarks(Collections.emptyMap());
130 processDeletedBookmarks(previous);
131 }
132
133 public void handleDelete() {
134 Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": deleted bookmarks node");
135 this.deleteAllItems();
136 }
137
138 public void handlePurge() {
139 Log.d(Config.LOGTAG, getAccount().getJid().asBareJid() + ": purged bookmarks");
140 this.deleteAllItems();
141 }
142
143 public boolean hasFeature() {
144 final var pep = getManager(PepManager.class);
145 final var disco = getManager(DiscoManager.class);
146 return pep.hasPublishOptions()
147 && pep.hasConfigNodeMax()
148 && disco.hasAccountFeature(Namespace.BOOKMARKS2_COMPAT);
149 }
150}