2 * Sone - MemoryDatabase.java - Copyright © 2013 David Roden
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.
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.
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/>.
18 package net.pterodactylus.sone.database.memory;
20 import static com.google.common.base.Optional.fromNullable;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Predicates.not;
23 import static com.google.common.collect.FluentIterable.from;
24 import static java.lang.String.format;
25 import static java.util.logging.Level.WARNING;
26 import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR;
27 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
28 import static net.pterodactylus.sone.data.Sone.toAllAlbums;
29 import static net.pterodactylus.sone.data.Sone.toAllImages;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
39 import java.util.concurrent.locks.ReadWriteLock;
40 import java.util.concurrent.locks.ReentrantReadWriteLock;
41 import java.util.logging.Level;
42 import java.util.logging.Logger;
44 import net.pterodactylus.sone.core.ConfigurationSoneParser;
45 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound;
46 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
47 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
48 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
49 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
50 import net.pterodactylus.sone.data.Album;
51 import net.pterodactylus.sone.data.Client;
52 import net.pterodactylus.sone.data.Image;
53 import net.pterodactylus.sone.data.LocalSone;
54 import net.pterodactylus.sone.data.Post;
55 import net.pterodactylus.sone.data.PostReply;
56 import net.pterodactylus.sone.data.Profile;
57 import net.pterodactylus.sone.data.Profile.Field;
58 import net.pterodactylus.sone.data.Sone;
59 import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
60 import net.pterodactylus.sone.data.impl.AlbumBuilderImpl;
61 import net.pterodactylus.sone.data.impl.ImageBuilderImpl;
62 import net.pterodactylus.sone.database.AlbumBuilder;
63 import net.pterodactylus.sone.database.Database;
64 import net.pterodactylus.sone.database.DatabaseException;
65 import net.pterodactylus.sone.database.ImageBuilder;
66 import net.pterodactylus.sone.database.PostBuilder;
67 import net.pterodactylus.sone.database.PostDatabase;
68 import net.pterodactylus.sone.database.PostReplyBuilder;
69 import net.pterodactylus.sone.database.SoneBuilder;
70 import net.pterodactylus.sone.database.SoneProvider;
71 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
72 import net.pterodactylus.sone.main.SonePlugin;
73 import net.pterodactylus.sone.utils.Optionals;
74 import net.pterodactylus.util.config.Configuration;
75 import net.pterodactylus.util.config.ConfigurationException;
77 import com.google.common.base.Function;
78 import com.google.common.base.Optional;
79 import com.google.common.base.Predicate;
80 import com.google.common.collect.FluentIterable;
81 import com.google.common.collect.HashMultimap;
82 import com.google.common.collect.Multimap;
83 import com.google.common.collect.SortedSetMultimap;
84 import com.google.common.collect.TreeMultimap;
85 import com.google.common.primitives.Longs;
86 import com.google.common.util.concurrent.AbstractService;
87 import com.google.inject.Inject;
88 import com.google.inject.Singleton;
91 * Memory-based {@link PostDatabase} implementation.
93 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
96 public class MemoryDatabase extends AbstractService implements Database {
98 private static final Logger logger = Logger.getLogger("Sone.Database.Memory");
99 private static final String LATEST_EDITION_PROPERTY = "Sone.LatestEdition";
101 private final ReadWriteLock lock = new ReentrantReadWriteLock();
103 /** The Sone provider. */
104 private final SoneProvider soneProvider;
106 /** The configuration. */
107 private final Configuration configuration;
108 private final ConfigurationLoader configurationLoader;
110 private final Set<String> localSones = new HashSet<String>();
111 private final Map<String, Sone> allSones = new HashMap<String, Sone>();
112 private final Map<String, String> lastInsertFingerprints = new HashMap<String, String>();
114 /** All post replies by their ID. */
115 private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
117 /** Replies sorted by Sone. */
118 private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
121 public int compare(String leftString, String rightString) {
122 return leftString.compareTo(rightString);
126 /** Whether post replies are known. */
127 private final Set<String> knownPostReplies = new HashSet<String>();
129 private final Map<String, Album> allAlbums = new HashMap<String, Album>();
130 private final Multimap<String, Album> soneAlbums = HashMultimap.create();
132 private final Map<String, Image> allImages = new HashMap<String, Image>();
133 private final Multimap<String, Image> soneImages = HashMultimap.create();
135 private final MemorySoneDatabase soneDatabase;
136 private final MemoryPostDatabase postDatabase;
137 private final MemoryBookmarkDatabase memoryBookmarkDatabase;
138 private final MemoryFriendDatabase memoryFriendDatabase;
141 * Creates a new memory database.
143 * @param soneProvider
145 * @param configuration
146 * The configuration for loading and saving elements
149 public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
150 this.soneProvider = soneProvider;
151 this.configuration = configuration;
152 this.configurationLoader = new ConfigurationLoader(configuration);
153 soneDatabase = new MemorySoneDatabase(configurationLoader);
154 postDatabase = new MemoryPostDatabase(this, configurationLoader);
155 memoryBookmarkDatabase =
156 new MemoryBookmarkDatabase(this, configurationLoader);
157 memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader);
165 public Optional<LocalSone> getLocalSone(String localSoneId) {
166 lock.readLock().lock();
168 if (!localSones.contains(localSoneId)) {
169 return Optional.absent();
171 return Optional.of((LocalSone) allSones.get(localSoneId));
173 lock.readLock().unlock();
178 public LocalSone registerLocalSone(OwnIdentity ownIdentity) {
179 final LocalSone localSone = loadLocalSone(ownIdentity);
180 localSones.add(ownIdentity.getId());
184 private LocalSone loadLocalSone(OwnIdentity ownIdentity) {
185 final SoneBuilder soneBuilder = newSoneBuilder().from(ownIdentity).using(
186 new Client("Sone", SonePlugin.VERSION.toString()));
188 loadElements(soneBuilder, ownIdentity.getId());
190 LocalSone localSone = soneBuilder.buildLocal();
192 localSone.setKnown(true);
193 localSone.setLatestEdition(
194 Optional.fromNullable(
195 Longs.tryParse(ownIdentity.getProperty(LATEST_EDITION_PROPERTY)))
200 private void loadElements(SoneBuilder soneBuilder, String soneId) {
201 ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, soneId);
204 Set<Post> posts = configurationSoneParser.parsePosts(this);
205 soneBuilder.withPosts(posts);
206 for (Post post : posts) {
209 } catch (InvalidPostFound ipf) {
210 logger.log(Level.WARNING, "Invalid post found, aborting load!");
215 Set<PostReply> postReplies = configurationSoneParser.parsePostReplies(this);
216 soneBuilder.withPostReplies(postReplies);
217 for (PostReply reply : postReplies) {
218 reply.setKnown(true);
220 } catch (InvalidPostReplyFound iprf) {
221 logger.log(Level.WARNING, "Invalid reply found, aborting load!");
226 private void loadSone(LocalSone sone) {
227 long soneTime = configurationLoader.getLocalSoneTime(sone.getId());
228 if (soneTime == -1) {
233 ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone.getId());
234 Profile profile = configurationSoneParser.parseProfile(sone);
236 /* load post likes. */
237 Set<String> likedPostIds = configurationSoneParser.parseLikedPostIds();
239 /* load reply likes. */
240 Set<String> likedReplyIds = configurationSoneParser.parseLikedPostReplyIds();
243 List<Album> topLevelAlbums;
245 topLevelAlbums = configurationSoneParser.parseTopLevelAlbums(this, sone);
246 } catch (InvalidAlbumFound iaf) {
247 logger.log(Level.WARNING, "Invalid album found, aborting load!");
249 } catch (InvalidParentAlbumFound ipaf) {
250 logger.log(Level.WARNING,
251 format("Invalid parent album ID: %s", ipaf.getAlbumParentId()));
257 configurationSoneParser.parseImages(this, sone);
258 } catch (InvalidImageFound iif) {
259 logger.log(WARNING, "Invalid image found, aborting load!");
261 } catch (InvalidParentAlbumFound ipaf) {
262 logger.log(Level.WARNING,
263 format("Invalid album image (%s) encountered, aborting load!",
264 ipaf.getAlbumParentId()));
269 String sonePrefix = "Sone/" + sone.getId();
270 String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null);
271 if (avatarId != null) {
272 final Map<String, Image> images = configurationSoneParser.getImages();
273 profile.setAvatar(images.get(avatarId));
277 sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
278 sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
279 sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
280 sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
281 sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
282 sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(
283 configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars")
284 .getValue(ShowCustomAvatars.NEVER.name())));
286 /* if we’re still here, Sone was loaded successfully. */
287 lock.writeLock().lock();
289 updateSoneTime(sone, soneTime);
290 sone.setProfile(profile);
291 sone.setLikePostIds(likedPostIds);
292 sone.setLikeReplyIds(likedReplyIds);
294 String lastInsertFingerprint = configurationLoader.getLastInsertFingerprint(sone.getId());
295 lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint);
297 allSones.put(sone.getId(), sone);
298 storePosts(sone.getId(), sone.getPosts());
299 storePostReplies(sone.getId(), sone.getReplies());
300 storeAlbums(sone.getId(), topLevelAlbums);
301 storeImages(sone.getId(), from(topLevelAlbums).transformAndConcat(Album.FLATTENER).transformAndConcat(Album.IMAGES).toList());
303 lock.writeLock().unlock();
306 logger.info(String.format("Sone loaded successfully: %s", sone));
310 public String getLastInsertFingerprint(Sone sone) {
311 lock.readLock().lock();
313 if (!lastInsertFingerprints.containsKey(sone.getId())) {
316 return lastInsertFingerprints.get(sone.getId());
318 lock.readLock().unlock();
323 public void setLastInsertFingerprint(Sone sone, String lastInsertFingerprint) {
324 lock.writeLock().lock();
326 lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint);
328 lock.writeLock().unlock();
333 * Saves the database.
335 * @throws DatabaseException
336 * if an error occurs while saving
339 public void save() throws DatabaseException {
340 lock.writeLock().lock();
342 saveKnownPostReplies();
343 for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.<Sone>get())) {
347 lock.writeLock().unlock();
351 private synchronized void saveSone(Sone sone) {
352 logger.log(Level.INFO, String.format("Saving Sone: %s", sone));
354 /* save Sone into configuration. */
355 String sonePrefix = "Sone/" + sone.getId();
356 configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
357 configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(lastInsertFingerprints.get(sone.getId()));
360 Profile profile = sone.getProfile();
361 configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName());
362 configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName());
363 configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName());
364 configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay());
365 configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth());
366 configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear());
367 configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar());
369 /* save profile fields. */
370 int fieldCounter = 0;
371 for (Field profileField : profile.getFields()) {
372 String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++;
373 configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName());
374 configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue());
376 configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null);
380 for (Post post : sone.getPosts()) {
381 String postPrefix = sonePrefix + "/Posts/" + postCounter++;
382 configuration.getStringValue(postPrefix + "/ID").setValue(post.getId());
383 configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull());
384 configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime());
385 configuration.getStringValue(postPrefix + "/Text").setValue(post.getText());
387 configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null);
390 int replyCounter = 0;
391 for (PostReply reply : sone.getReplies()) {
392 String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
393 configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
394 configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId());
395 configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
396 configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
398 configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null);
400 /* save post likes. */
401 int postLikeCounter = 0;
402 for (String postId : sone.getLikedPostIds()) {
403 configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId);
405 configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null);
407 /* save reply likes. */
408 int replyLikeCounter = 0;
409 for (String replyId : sone.getLikedReplyIds()) {
410 configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId);
412 configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null);
414 /* save albums. first, collect in a flat structure, top-level first. */
415 List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
417 int albumCounter = 0;
418 for (Album album : albums) {
419 String albumPrefix = sonePrefix + "/Albums/" + albumCounter++;
420 configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId());
421 configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
422 configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
423 configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
424 configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
426 configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
429 int imageCounter = 0;
430 for (Album album : albums) {
431 for (Image image : album.getImages()) {
432 if (!image.isInserted()) {
435 String imagePrefix = sonePrefix + "/Images/" + imageCounter++;
436 configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId());
437 configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId());
438 configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey());
439 configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle());
440 configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription());
441 configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime());
442 configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth());
443 configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight());
446 configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null);
449 configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow());
450 configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled());
451 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications());
452 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
453 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
454 configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
456 configuration.save();
458 logger.log(Level.INFO, String.format("Sone %s saved.", sone));
459 } catch (ConfigurationException ce1) {
460 logger.log(Level.WARNING, String.format("Could not save Sone: %s", sone), ce1);
468 /** {@inheritDocs} */
470 protected void doStart() {
471 soneDatabase.start();
472 memoryFriendDatabase.start();
473 postDatabase.start();
474 memoryBookmarkDatabase.start();
475 loadKnownPostReplies();
479 /** {@inheritDocs} */
481 protected void doStop() {
484 memoryFriendDatabase.stop();
486 memoryBookmarkDatabase.stop();
489 } catch (DatabaseException de1) {
495 public SoneBuilder newSoneBuilder() {
496 return new MemorySoneBuilder(this);
500 public void storeSone(Sone sone) {
501 lock.writeLock().lock();
505 allSones.put(sone.getId(), sone);
506 storePosts(sone.getId(), sone.getPosts());
507 storePostReplies(sone.getId(), sone.getReplies());
508 storeAlbums(sone.getId(), toAllAlbums.apply(sone));
509 storeImages(sone.getId(), toAllImages.apply(sone));
511 lock.writeLock().unlock();
516 public boolean isSoneKnown(Sone sone) {
517 return soneDatabase.isKnownSone(sone.getId());
521 public void setSoneKnown(Sone sone) {
522 soneDatabase.setSoneKnown(sone.getId());
526 public void updateSoneTime(Sone sone, long soneTime) {
527 soneDatabase.updateSoneTime(sone.getId(), soneTime);
530 private void storePosts(String soneId, Collection<Post> posts) {
531 postDatabase.storePosts(soneId, posts);
534 private void storePostReplies(String soneId, Collection<PostReply> postReplies) {
535 sonePostReplies.putAll(soneId, postReplies);
536 for (PostReply postReply : postReplies) {
537 allPostReplies.put(postReply.getId(), postReply);
541 private void storeAlbums(String soneId, Collection<Album> albums) {
542 soneAlbums.putAll(soneId, albums);
543 for (Album album : albums) {
544 allAlbums.put(album.getId(), album);
548 private void storeImages(String soneId, Collection<Image> images) {
549 soneImages.putAll(soneId, images);
550 for (Image image : images) {
551 allImages.put(image.getId(), image);
556 public void removeSone(Sone sone) {
557 lock.writeLock().lock();
559 allSones.remove(sone.getId());
560 postDatabase.removePostsFor(sone.getId());
561 Collection<PostReply> removedPostReplies =
562 sonePostReplies.removeAll(sone.getId());
563 for (PostReply removedPostReply : removedPostReplies) {
564 allPostReplies.remove(removedPostReply.getId());
566 Collection<Album> removedAlbums =
567 soneAlbums.removeAll(sone.getId());
568 for (Album removedAlbum : removedAlbums) {
569 allAlbums.remove(removedAlbum.getId());
571 Collection<Image> removedImages =
572 soneImages.removeAll(sone.getId());
573 for (Image removedImage : removedImages) {
574 allImages.remove(removedImage.getId());
577 lock.writeLock().unlock();
582 public Function<String, Optional<Sone>> soneLoader() {
583 return new Function<String, Optional<Sone>>() {
585 public Optional<Sone> apply(String soneId) {
586 return getSone(soneId);
592 public Optional<Sone> getSone(String soneId) {
593 lock.readLock().lock();
595 return fromNullable(allSones.get(soneId));
597 lock.readLock().unlock();
602 public Collection<Sone> getSones() {
603 lock.readLock().lock();
605 return new HashSet<Sone>(allSones.values());
607 lock.readLock().unlock();
612 public Collection<LocalSone> getLocalSones() {
613 lock.readLock().lock();
615 return from(allSones.values()).filter(LOCAL_SONE_FILTER).transform(new Function<Sone, LocalSone>() {
617 public LocalSone apply(Sone sone) {
618 // FIXME – Sones will not always implement LocalSone
619 return (LocalSone) sone;
623 lock.readLock().unlock();
628 public Collection<Sone> getRemoteSones() {
629 lock.readLock().lock();
631 return from(allSones.values())
632 .filter(not(LOCAL_SONE_FILTER)) .toSet();
634 lock.readLock().unlock();
639 public Collection<String> getFriends(LocalSone localSone) {
640 if (!localSone.isLocal()) {
641 return Collections.emptySet();
643 return memoryFriendDatabase.getFriends(localSone.getId());
647 public Optional<Long> getSoneFollowingTime(String remoteSoneId) {
648 return memoryFriendDatabase.getSoneFollowingTime(remoteSoneId);
652 public boolean isFriend(LocalSone localSone, String friendSoneId) {
653 if (!localSone.isLocal()) {
656 return memoryFriendDatabase.isFriend(localSone.getId(), friendSoneId);
660 public void addFriend(LocalSone localSone, String friendSoneId) {
661 memoryFriendDatabase.addFriend(localSone.getId(), friendSoneId);
665 public void removeFriend(LocalSone localSone, String friendSoneId) {
666 memoryFriendDatabase.removeFriend(localSone.getId(), friendSoneId);
670 // POSTPROVIDER METHODS
673 /** {@inheritDocs} */
675 public Optional<Post> getPost(String postId) {
676 return postDatabase.getPost(postId);
679 /** {@inheritDocs} */
681 public Collection<Post> getPosts(String soneId) {
682 return new HashSet<Post>(getPostsFrom(soneId));
685 /** {@inheritDocs} */
687 public Collection<Post> getDirectedPosts(final String recipientId) {
688 return postDatabase.getDirectedPosts(recipientId);
692 // POSTBUILDERFACTORY METHODS
695 /** {@inheritDocs} */
697 public PostBuilder newPostBuilder() {
698 return new MemoryPostBuilder(this, soneProvider);
705 /** {@inheritDocs} */
707 public void storePost(Post post) {
708 checkNotNull(post, "post must not be null");
709 postDatabase.storePost(post);
712 /** {@inheritDocs} */
714 public void removePost(Post post) {
715 checkNotNull(post, "post must not be null");
716 postDatabase.removePost(post.getId());
720 // POSTREPLYPROVIDER METHODS
723 /** {@inheritDocs} */
725 public Optional<PostReply> getPostReply(String id) {
726 lock.readLock().lock();
728 return fromNullable(allPostReplies.get(id));
730 lock.readLock().unlock();
734 /** {@inheritDocs} */
736 public List<PostReply> getReplies(final String postId) {
737 lock.readLock().lock();
739 return from(allPostReplies.values())
740 .filter(new Predicate<PostReply>() {
742 public boolean apply(PostReply postReply) {
743 return postReply.getPostId().equals(postId);
745 }).toSortedList(TIME_COMPARATOR);
747 lock.readLock().unlock();
752 // POSTREPLYBUILDERFACTORY METHODS
755 /** {@inheritDocs} */
757 public PostReplyBuilder newPostReplyBuilder() {
758 return new MemoryPostReplyBuilder(this, soneProvider);
762 // POSTREPLYSTORE METHODS
765 /** {@inheritDocs} */
767 public void storePostReply(PostReply postReply) {
768 lock.writeLock().lock();
770 allPostReplies.put(postReply.getId(), postReply);
772 lock.writeLock().unlock();
776 /** {@inheritDocs} */
778 public void removePostReply(PostReply postReply) {
779 lock.writeLock().lock();
781 allPostReplies.remove(postReply.getId());
783 lock.writeLock().unlock();
788 // ALBUMPROVDER METHODS
792 public Optional<Album> getAlbum(String albumId) {
793 lock.readLock().lock();
795 return fromNullable(allAlbums.get(albumId));
797 lock.readLock().unlock();
802 // ALBUMBUILDERFACTORY METHODS
806 public AlbumBuilder newAlbumBuilder() {
807 return new AlbumBuilderImpl();
811 // ALBUMSTORE METHODS
815 public void storeAlbum(Album album) {
816 lock.writeLock().lock();
818 allAlbums.put(album.getId(), album);
819 soneAlbums.put(album.getSone().getId(), album);
821 lock.writeLock().unlock();
826 public void removeAlbum(Album album) {
827 lock.writeLock().lock();
829 allAlbums.remove(album.getId());
830 soneAlbums.remove(album.getSone().getId(), album);
832 lock.writeLock().unlock();
837 // IMAGEPROVIDER METHODS
841 public Optional<Image> getImage(String imageId) {
842 lock.readLock().lock();
844 return fromNullable(allImages.get(imageId));
846 lock.readLock().unlock();
851 // IMAGEBUILDERFACTORY METHODS
855 public ImageBuilder newImageBuilder() {
856 return new ImageBuilderImpl();
860 // IMAGESTORE METHODS
864 public void storeImage(Image image) {
865 lock.writeLock().lock();
867 allImages.put(image.getId(), image);
868 soneImages.put(image.getSone().getId(), image);
870 lock.writeLock().unlock();
875 public void removeImage(Image image) {
876 lock.writeLock().lock();
878 allImages.remove(image.getId());
879 soneImages.remove(image.getSone().getId(), image);
881 lock.writeLock().unlock();
886 public void bookmarkPost(Post post) {
887 memoryBookmarkDatabase.bookmarkPost(post);
891 public void unbookmarkPost(Post post) {
892 memoryBookmarkDatabase.unbookmarkPost(post);
896 public boolean isPostBookmarked(Post post) {
897 return memoryBookmarkDatabase.isPostBookmarked(post);
901 public Set<Post> getBookmarkedPosts() {
902 return memoryBookmarkDatabase.getBookmarkedPosts();
906 // PACKAGE-PRIVATE METHODS
910 * Returns whether the given post is known.
914 * @return {@code true} if the post is known, {@code false} otherwise
916 boolean isPostKnown(Post post) {
917 return postDatabase.isPostKnown(post.getId());
921 * Sets whether the given post is known.
926 * {@code true} if the post is known, {@code false} otherwise
928 void setPostKnown(Post post, boolean known) {
929 postDatabase.setPostKnown(post.getId(), known);
933 * Returns whether the given post reply is known.
937 * @return {@code true} if the given post reply is known, {@code false}
940 boolean isPostReplyKnown(PostReply postReply) {
941 lock.readLock().lock();
943 return knownPostReplies.contains(postReply.getId());
945 lock.readLock().unlock();
950 * Sets whether the given post reply is known.
955 * {@code true} if the post reply is known, {@code false} otherwise
957 void setPostReplyKnown(PostReply postReply, boolean known) {
958 lock.writeLock().lock();
961 knownPostReplies.add(postReply.getId());
963 knownPostReplies.remove(postReply.getId());
966 lock.writeLock().unlock();
975 * Gets all posts for the given Sone, creating a new collection if there is
979 * The ID of the Sone to get the posts for
982 private Collection<Post> getPostsFrom(String soneId) {
983 return postDatabase.getPostsFrom(soneId);
986 /** Loads the known post replies. */
987 private void loadKnownPostReplies() {
988 Set<String> knownPostReplies = configurationLoader.loadKnownPostReplies();
989 lock.writeLock().lock();
991 this.knownPostReplies.clear();
992 this.knownPostReplies.addAll(knownPostReplies);
994 lock.writeLock().unlock();
999 * Saves the known post replies to the configuration.
1001 * @throws DatabaseException
1002 * if a configuration error occurs
1004 private void saveKnownPostReplies() throws DatabaseException {
1005 lock.readLock().lock();
1007 int replyCounter = 0;
1008 for (String knownReplyId : knownPostReplies) {
1009 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(
1012 configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
1013 } catch (ConfigurationException ce1) {
1014 throw new DatabaseException("Could not save database.", ce1);
1016 lock.readLock().unlock();