Store the “known” status of a post in the database.
[Sone.git] / src / main / java / net / pterodactylus / sone / data / impl / DefaultSone.java
1 /*
2  * Sone - SoneImpl.java - Copyright © 2010–2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.data.impl;
19
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Set;
27 import java.util.concurrent.CopyOnWriteArraySet;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import net.pterodactylus.sone.core.Options;
32 import net.pterodactylus.sone.data.Album;
33 import net.pterodactylus.sone.data.Client;
34 import net.pterodactylus.sone.data.Post;
35 import net.pterodactylus.sone.data.PostReply;
36 import net.pterodactylus.sone.data.Profile;
37 import net.pterodactylus.sone.data.Reply;
38 import net.pterodactylus.sone.data.Sone;
39 import net.pterodactylus.sone.database.AlbumBuilder;
40 import net.pterodactylus.sone.database.Database;
41 import net.pterodactylus.sone.database.PostBuilder;
42 import net.pterodactylus.sone.database.PostReplyBuilder;
43 import net.pterodactylus.sone.freenet.wot.Identity;
44 import net.pterodactylus.util.logging.Logging;
45
46 import freenet.keys.FreenetURI;
47
48 import com.google.common.base.Optional;
49 import com.google.common.hash.Hasher;
50 import com.google.common.hash.Hashing;
51
52 /**
53  * Dumb, store-everything-in-memory {@link Sone} implementation.
54  *
55  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
56  */
57 public class DefaultSone implements Sone {
58
59         /** The logger. */
60         private static final Logger logger = Logging.getLogger(DefaultSone.class);
61
62         /** The database. */
63         private final Database database;
64
65         /** The ID of this Sone. */
66         private final String id;
67
68         /** Whether the Sone is local. */
69         private final boolean local;
70
71         /** The latest edition of the Sone. */
72         private volatile long latestEdition;
73
74         /** The time of the last inserted update. */
75         private volatile long time;
76
77         /** The status of this Sone. */
78         private volatile SoneStatus status = SoneStatus.unknown;
79
80         /** The profile of this Sone. */
81         private volatile Profile profile = new Profile(this);
82
83         /** The client used by the Sone. */
84         private final Client client;
85
86         /** Whether this Sone is known. */
87         private volatile boolean known;
88
89         /** All friend Sones. */
90         private final Set<String> friendSones = new CopyOnWriteArraySet<String>();
91
92         /** All posts. */
93         private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
94
95         /** All replies. */
96         private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
97
98         /** The IDs of all liked posts. */
99         private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
100
101         /** The IDs of all liked replies. */
102         private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
103
104         /** The root album containing all albums. */
105         private final Album rootAlbum;
106
107         /** Sone-specific options. */
108         private Options options = new Options();
109
110         /**
111          * Creates a new Sone.
112          *
113          * @param id
114          *              The ID of the Sone
115          * @param local
116          *              {@code true} if the Sone is a local Sone, {@code false} otherwise
117          */
118         public DefaultSone(Database database, String id, boolean local, Client client) {
119                 this.database = database;
120                 this.id = id;
121                 this.local = local;
122                 this.client = client;
123                 rootAlbum = new DefaultAlbumBuilder(database, this, null).build();
124         }
125
126         //
127         // ACCESSORS
128         //
129
130         public String getId() {
131                 return id;
132         }
133
134         public Identity getIdentity() {
135                 return database.getIdentity(id).get();
136         }
137
138         public String getName() {
139                 return getIdentity().getNickname();
140         }
141
142         public boolean isLocal() {
143                 return local;
144         }
145
146         public long getLatestEdition() {
147                 return latestEdition;
148         }
149
150         public void setLatestEdition(long latestEdition) {
151                 if (!(latestEdition > this.latestEdition)) {
152                         logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition));
153                         return;
154                 }
155                 this.latestEdition = latestEdition;
156         }
157
158         public long getTime() {
159                 return time;
160         }
161
162         public Sone setTime(long time) {
163                 this.time = time;
164                 return this;
165         }
166
167         public SoneStatus getStatus() {
168                 return status;
169         }
170
171         public Sone setStatus(SoneStatus status) {
172                 this.status = checkNotNull(status, "status must not be null");
173                 return this;
174         }
175
176         public Profile getProfile() {
177                 return new Profile(profile);
178         }
179
180         public void setProfile(Profile profile) {
181                 this.profile = new Profile(profile);
182         }
183
184         public Client getClient() {
185                 return client;
186         }
187
188         public boolean isKnown() {
189                 return known;
190         }
191
192         public Sone setKnown(boolean known) {
193                 this.known = known;
194                 return this;
195         }
196
197         public List<String> getFriends() {
198                 List<String> friends = new ArrayList<String>(friendSones);
199                 return friends;
200         }
201
202         public boolean hasFriend(String friendSoneId) {
203                 return friendSones.contains(friendSoneId);
204         }
205
206         public Sone addFriend(String friendSone) {
207                 if (!friendSone.equals(id)) {
208                         friendSones.add(friendSone);
209                 }
210                 return this;
211         }
212
213         public Sone removeFriend(String friendSoneId) {
214                 friendSones.remove(friendSoneId);
215                 return this;
216         }
217
218         public List<Post> getPosts() {
219                 List<Post> sortedPosts;
220                 synchronized (this) {
221                         sortedPosts = new ArrayList<Post>(posts);
222                 }
223                 Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
224                 return sortedPosts;
225         }
226
227         public Sone setPosts(Collection<Post> posts) {
228                 synchronized (this) {
229                         this.posts.clear();
230                         this.posts.addAll(posts);
231                 }
232                 return this;
233         }
234
235         public void addPost(Post post) {
236                 if (post.getSone().equals(this) && posts.add(post)) {
237                         logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
238                 }
239         }
240
241         public void removePost(Post post) {
242                 if (post.getSone().equals(this)) {
243                         posts.remove(post);
244                 }
245         }
246
247         public Set<PostReply> getReplies() {
248                 return Collections.unmodifiableSet(replies);
249         }
250
251         public Sone setReplies(Collection<PostReply> replies) {
252                 this.replies.clear();
253                 this.replies.addAll(replies);
254                 return this;
255         }
256
257         public void addReply(PostReply reply) {
258                 if (reply.getSone().equals(this)) {
259                         replies.add(reply);
260                 }
261         }
262
263         public void removeReply(PostReply reply) {
264                 if (reply.getSone().equals(this)) {
265                         replies.remove(reply);
266                 }
267         }
268
269         public Set<String> getLikedPostIds() {
270                 return Collections.unmodifiableSet(likedPostIds);
271         }
272
273         public Sone setLikePostIds(Set<String> likedPostIds) {
274                 this.likedPostIds.clear();
275                 this.likedPostIds.addAll(likedPostIds);
276                 return this;
277         }
278
279         public boolean isLikedPostId(String postId) {
280                 return likedPostIds.contains(postId);
281         }
282
283         public Sone removeLikedPostId(String postId) {
284                 likedPostIds.remove(postId);
285                 return this;
286         }
287
288         public Set<String> getLikedReplyIds() {
289                 return Collections.unmodifiableSet(likedReplyIds);
290         }
291
292         public Sone setLikeReplyIds(Set<String> likedReplyIds) {
293                 this.likedReplyIds.clear();
294                 this.likedReplyIds.addAll(likedReplyIds);
295                 return this;
296         }
297
298         public boolean isLikedReplyId(String replyId) {
299                 return likedReplyIds.contains(replyId);
300         }
301
302         public Sone addLikedReplyId(String replyId) {
303                 likedReplyIds.add(replyId);
304                 return this;
305         }
306
307         public Sone removeLikedReplyId(String replyId) {
308                 likedReplyIds.remove(replyId);
309                 return this;
310         }
311
312         public Album getRootAlbum() {
313                 return rootAlbum;
314         }
315
316         public Options getOptions() {
317                 return options;
318         }
319
320         /* TODO - remove this method again, maybe add an option provider */
321         public void setOptions(Options options) {
322                 this.options = options;
323         }
324
325         @Override
326         public AlbumBuilder newAlbumBuilder() {
327                 return new DefaultAlbumBuilder(database, this, rootAlbum.getId());
328         }
329
330         public PostBuilder newPostBuilder() {
331                 return new DefaultPostBuilder(database, getId()) {
332                         @Override
333                         public Post build(Optional<PostCreated> postCreated) {
334                                 Post post = super.build(postCreated);
335                                 database.storePost(post);
336                                 return post;
337                         }
338                 };
339         }
340
341         @Override
342         public PostReplyBuilder newPostReplyBuilder(String postId) throws IllegalStateException {
343                 return new DefaultPostReplyBuilder(database, getId(), postId) {
344                         @Override
345                         public PostReply build(Optional<PostReplyCreated> postReplyCreated) {
346                                 PostReply postReply = super.build(postReplyCreated);
347                                 database.storePostReply(postReply);
348                                 return postReply;
349                         }
350                 };
351         }
352
353         public Modifier modify() {
354                 return new Modifier() {
355                         private long latestEdition = DefaultSone.this.latestEdition;
356                         @Override
357                         public Modifier setLatestEdition(long latestEdition) {
358                                 this.latestEdition = latestEdition;
359                                 return this;
360                         }
361
362                         @Override
363                         public Sone update() {
364                                 DefaultSone.this.latestEdition = latestEdition;
365                                 return DefaultSone.this;
366                         }
367                 };
368         }
369
370         //
371         // FINGERPRINTABLE METHODS
372         //
373
374         @Override
375         public synchronized String getFingerprint() {
376                 Hasher hash = Hashing.sha256().newHasher();
377                 hash.putString(profile.getFingerprint());
378
379                 hash.putString("Posts(");
380                 for (Post post : getPosts()) {
381                         hash.putString("Post(").putString(post.getId()).putString(")");
382                 }
383                 hash.putString(")");
384
385                 List<PostReply> replies = new ArrayList<PostReply>(getReplies());
386                 Collections.sort(replies, Reply.TIME_COMPARATOR);
387                 hash.putString("Replies(");
388                 for (PostReply reply : replies) {
389                         hash.putString("Reply(").putString(reply.getId()).putString(")");
390                 }
391                 hash.putString(")");
392
393                 List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
394                 Collections.sort(likedPostIds);
395                 hash.putString("LikedPosts(");
396                 for (String likedPostId : likedPostIds) {
397                         hash.putString("Post(").putString(likedPostId).putString(")");
398                 }
399                 hash.putString(")");
400
401                 List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
402                 Collections.sort(likedReplyIds);
403                 hash.putString("LikedReplies(");
404                 for (String likedReplyId : likedReplyIds) {
405                         hash.putString("Reply(").putString(likedReplyId).putString(")");
406                 }
407                 hash.putString(")");
408
409                 hash.putString("Albums(");
410                 for (Album album : rootAlbum.getAlbums()) {
411                         if (!Album.NOT_EMPTY.apply(album)) {
412                                 continue;
413                         }
414                         hash.putString(album.getFingerprint());
415                 }
416                 hash.putString(")");
417
418                 return hash.hash().toString();
419         }
420
421         //
422         // INTERFACE Comparable<Sone>
423         //
424
425         @Override
426         public int compareTo(Sone sone) {
427                 return NICE_NAME_COMPARATOR.compare(this, sone);
428         }
429
430         //
431         // OBJECT METHODS
432         //
433
434         @Override
435         public int hashCode() {
436                 return id.hashCode();
437         }
438
439         @Override
440         public boolean equals(Object object) {
441                 if (!(object instanceof Sone)) {
442                         return false;
443                 }
444                 return ((Sone) object).getId().equals(id);
445         }
446
447         @Override
448         public String toString() {
449                 return getClass().getName() + "[id=" + id + ",friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + "),albums(" + getRootAlbum().getAlbums().size() + ")]";
450         }
451
452 }