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}