1package eu.siacs.conversations.entities;
2
3import android.content.Context;
4import androidx.annotation.NonNull;
5import androidx.annotation.Nullable;
6import com.google.common.base.Strings;
7import com.google.common.collect.ImmutableList;
8import eu.siacs.conversations.utils.StringUtils;
9import eu.siacs.conversations.utils.UIHelper;
10import eu.siacs.conversations.xml.Element;
11import eu.siacs.conversations.xml.Namespace;
12import eu.siacs.conversations.xmpp.Jid;
13import java.lang.ref.WeakReference;
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Locale;
19import java.util.Map;
20
21public class Bookmark extends Element implements ListItem {
22
23 private final Account account;
24 private WeakReference<Conversation> conversation;
25 private Jid jid;
26 protected Element extensions = new Element("extensions", Namespace.BOOKMARKS2);
27
28 public Bookmark(final Account account, final Jid jid) {
29 super("conference");
30 this.jid = jid;
31 this.setAttribute("jid", jid);
32 this.account = account;
33 }
34
35 private Bookmark(Account account) {
36 super("conference");
37 this.account = account;
38 }
39
40 public static Map<Jid, Bookmark> parseFromStorage(Element storage, Account account) {
41 if (storage == null) {
42 return Collections.emptyMap();
43 }
44 final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
45 for (final Element item : storage.getChildren()) {
46 if (item.getName().equals("conference")) {
47 final Bookmark bookmark = Bookmark.parse(item, account);
48 if (bookmark != null) {
49 final Bookmark old = bookmarks.put(bookmark.jid, bookmark);
50 if (old != null
51 && old.getBookmarkName() != null
52 && bookmark.getBookmarkName() == null) {
53 bookmark.setBookmarkName(old.getBookmarkName());
54 }
55 }
56 }
57 }
58 return bookmarks;
59 }
60
61 public static Map<Jid, Bookmark> parseFromPubSub(final Element pubSub, final Account account) {
62 if (pubSub == null) {
63 return Collections.emptyMap();
64 }
65 final Element items = pubSub.findChild("items");
66 if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) {
67 final Map<Jid, Bookmark> bookmarks = new HashMap<>();
68 for (Element item : items.getChildren()) {
69 if (item.getName().equals("item")) {
70 final Bookmark bookmark = Bookmark.parseFromItem(item, account);
71 if (bookmark != null) {
72 bookmarks.put(bookmark.jid, bookmark);
73 }
74 }
75 }
76 return bookmarks;
77 }
78 return Collections.emptyMap();
79 }
80
81 public static Bookmark parse(Element element, Account account) {
82 Bookmark bookmark = new Bookmark(account);
83 bookmark.setAttributes(element.getAttributes());
84 bookmark.setChildren(element.getChildren());
85 bookmark.jid = Jid.Invalid.getNullForInvalid(bookmark.getAttributeAsJid("jid"));
86 if (bookmark.jid == null) {
87 return null;
88 }
89 return bookmark;
90 }
91
92 public static Bookmark parseFromItem(Element item, Account account) {
93 final Element conference = item.findChild("conference", Namespace.BOOKMARKS2);
94 if (conference == null) {
95 return null;
96 }
97 final Bookmark bookmark = new Bookmark(account);
98 bookmark.jid = Jid.Invalid.getNullForInvalid(item.getAttributeAsJid("id"));
99 // TODO verify that we only use bare jids and ignore full jids
100 if (bookmark.jid == null) {
101 return null;
102 }
103 bookmark.setBookmarkName(conference.getAttribute("name"));
104 bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
105 bookmark.setNick(conference.findChildContent("nick"));
106 bookmark.setPassword(conference.findChildContent("password"));
107 final Element extensions = conference.findChild("extensions", Namespace.BOOKMARKS2);
108 if (extensions != null) {
109 for (final Element ext : extensions.getChildren()) {
110 if (ext.getName().equals("group") && ext.getNamespace().equals("jabber:iq:roster")) {
111 bookmark.addGroup(ext.getContent());
112 }
113 }
114 bookmark.extensions = extensions;
115 }
116 return bookmark;
117 }
118
119 public Element getExtensions() {
120 return extensions;
121 }
122
123 public void addGroup(final String group) {
124 addChild("group", "jabber:iq:roster").setContent(group);
125 extensions.addChild("group", "jabber:iq:roster").setContent(group);
126 }
127
128 public void setGroups(List<String> groups) {
129 final List<Element> children = ImmutableList.copyOf(getChildren());
130 for (final Element el : children) {
131 if (el.getName().equals("group")) {
132 removeChild(el);
133 }
134 }
135
136 final List<Element> extChildren = ImmutableList.copyOf(extensions.getChildren());
137 for (final Element el : extChildren) {
138 if (el.getName().equals("group")) {
139 extensions.removeChild(el);
140 }
141 }
142
143 for (final String group : groups) {
144 addGroup(group);
145 }
146 }
147
148 public void setAutojoin(boolean autojoin) {
149 if (autojoin) {
150 this.setAttribute("autojoin", "true");
151 } else {
152 this.setAttribute("autojoin", "false");
153 }
154 }
155
156 @Override
157 public int compareTo(final @NonNull ListItem another) {
158 if (getJid().isDomainJid() && !another.getJid().isDomainJid()) {
159 return -1;
160 } else if (!getJid().isDomainJid() && another.getJid().isDomainJid()) {
161 return 1;
162 }
163
164 if (getDisplayName().equals(another.getDisplayName())) {
165 return getJid().compareTo(another.getJid());
166 }
167
168 return this.getDisplayName().compareToIgnoreCase(
169 another.getDisplayName());
170 }
171
172 @Override
173 public String getDisplayName() {
174 final Conversation c = getConversation();
175 final String name = getBookmarkName();
176 if (c != null) {
177 return c.getName().toString();
178 } else if (printableValue(name, false)) {
179 return name.trim();
180 } else {
181 Jid jid = this.getJid();
182 return jid != null && jid.getLocal() != null ? jid.getLocal() : "";
183 }
184 }
185
186 public static boolean printableValue(@Nullable String value, boolean permitNone) {
187 return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value));
188 }
189
190 public static boolean printableValue(@Nullable String value) {
191 return printableValue(value, true);
192 }
193
194 @Override
195 public Jid getJid() {
196 return this.jid;
197 }
198
199 public Jid getFullJid() {
200 return getFullJid(getNick(), true);
201 }
202
203 private Jid getFullJid(final String nick, boolean tryFix) {
204 try {
205 return jid == null || nick == null || nick.trim().isEmpty() ? jid : jid.withResource(nick);
206 } catch (final IllegalArgumentException e) {
207 try {
208 return tryFix ? getFullJid(gnu.inet.encoding.Punycode.encode(nick), false) : null;
209 } catch (final Exception e2) {
210 return null;
211 }
212 }
213 }
214
215 public List<Tag> getGroupTags() {
216 ArrayList<Tag> tags = new ArrayList<>();
217
218 for (Element element : getChildren()) {
219 if (element.getName().equals("group") && element.getContent() != null) {
220 String group = element.getContent();
221 tags.add(new Tag(group));
222 }
223 }
224
225 return tags;
226 }
227
228 @Override
229 public List<Tag> getTags(Context context) {
230 ArrayList<Tag> tags = new ArrayList<>();
231 tags.add(new Tag("Channel"));
232 tags.addAll(getGroupTags());
233 return tags;
234 }
235
236 public String getNick() {
237 return Strings.emptyToNull(this.findChildContent("nick"));
238 }
239
240 public void setNick(String nick) {
241 Element element = this.findChild("nick");
242 if (element == null) {
243 element = this.addChild("nick");
244 }
245 element.setContent(nick);
246 }
247
248 public boolean autojoin() {
249 return this.getAttributeAsBoolean("autojoin");
250 }
251
252 public String getPassword() {
253 return this.findChildContent("password");
254 }
255
256 public void setPassword(String password) {
257 Element element = this.findChild("password");
258 if (element != null) {
259 element.setContent(password);
260 }
261 }
262
263 @Override
264 public boolean match(Context context, String needle) {
265 if (needle == null) {
266 return true;
267 }
268 needle = needle.toLowerCase(Locale.US);
269 String[] parts = needle.split("[,\\s]+");
270 if (parts.length > 1) {
271 for (String part : parts) {
272 if (!match(context, part)) {
273 return false;
274 }
275 }
276 return true;
277 } else if (parts.length > 0) {
278 final Jid jid = getJid();
279 return (jid != null && jid.toString().contains(parts[0])) ||
280 getDisplayName().toLowerCase(Locale.US).contains(parts[0]) ||
281 matchInTag(context, parts[0]);
282 } else {
283 final Jid jid = getJid();
284 return (jid != null && jid.toString().contains(needle)) ||
285 getDisplayName().toLowerCase(Locale.US).contains(needle);
286 }
287 }
288
289 private boolean matchInTag(Context context, String needle) {
290 needle = needle.toLowerCase(Locale.US);
291 for (Tag tag : getTags(context)) {
292 if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
293 return true;
294 }
295 }
296 return false;
297 }
298
299 public Account getAccount() {
300 return this.account;
301 }
302
303 public synchronized Conversation getConversation() {
304 return this.conversation != null ? this.conversation.get() : null;
305 }
306
307 public synchronized void setConversation(Conversation conversation) {
308 if (this.conversation != null) {
309 this.conversation.clear();
310 }
311 if (conversation == null) {
312 this.conversation = null;
313 } else {
314 this.conversation = new WeakReference<>(conversation);
315 }
316 }
317
318 public String getBookmarkName() {
319 return this.getAttribute("name");
320 }
321
322 public boolean setBookmarkName(String name) {
323 String before = getBookmarkName();
324 if (name != null) {
325 this.setAttribute("name", name);
326 } else {
327 this.removeAttribute("name");
328 }
329 return StringUtils.changed(before, name);
330 }
331
332 @Override
333 public int getAvatarBackgroundColor() {
334 return UIHelper.getColorForName(
335 jid != null ? jid.asBareJid().toString() : getDisplayName());
336 }
337
338 @Override
339 public String getAvatarName() {
340 return getDisplayName();
341 }
342}