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 MemoryPostDatabase postDatabase;
136 private final MemoryBookmarkDatabase memoryBookmarkDatabase;
137 private final MemoryFriendDatabase memoryFriendDatabase;
140 * Creates a new memory database.
142 * @param soneProvider
144 * @param configuration
145 * The configuration for loading and saving elements
148 public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
149 this.soneProvider = soneProvider;
150 this.configuration = configuration;
151 this.configurationLoader = new ConfigurationLoader(configuration);
152 postDatabase = new MemoryPostDatabase(this, configurationLoader);
153 memoryBookmarkDatabase =
154 new MemoryBookmarkDatabase(this, configurationLoader);
155 memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader);
163 public Optional<LocalSone> getLocalSone(String localSoneId) {
164 lock.readLock().lock();
166 if (!localSones.contains(localSoneId)) {
167 return Optional.absent();
169 return Optional.of((LocalSone) allSones.get(localSoneId));
171 lock.readLock().unlock();
176 public LocalSone registerLocalSone(OwnIdentity ownIdentity) {
177 final LocalSone localSone = loadLocalSone(ownIdentity);
178 localSones.add(ownIdentity.getId());
182 private LocalSone loadLocalSone(OwnIdentity ownIdentity) {
183 LocalSone localSone = (LocalSone) newSoneBuilder().local().from(ownIdentity).build();
184 localSone.setLatestEdition(
185 Optional.fromNullable(
186 Longs.tryParse(ownIdentity.getProperty(LATEST_EDITION_PROPERTY)))
188 localSone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
189 localSone.setKnown(true);
195 public void loadSone(LocalSone sone) {
196 long soneTime = configurationLoader.getLocalSoneTime(sone.getId());
197 if (soneTime == -1) {
202 ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone);
203 Profile profile = configurationSoneParser.parseProfile();
206 Collection<Post> posts;
208 posts = configurationSoneParser.parsePosts(this);
209 } catch (InvalidPostFound ipf) {
210 logger.log(Level.WARNING, "Invalid post found, aborting load!");
215 Collection<PostReply> postReplies;
217 postReplies = configurationSoneParser.parsePostReplies(this);
218 } catch (InvalidPostReplyFound iprf) {
219 logger.log(Level.WARNING, "Invalid reply found, aborting load!");
223 /* load post likes. */
224 Set<String> likedPostIds = configurationSoneParser.parseLikedPostIds();
226 /* load reply likes. */
227 Set<String> likedReplyIds = configurationSoneParser.parseLikedPostReplyIds();
230 List<Album> topLevelAlbums;
232 topLevelAlbums = configurationSoneParser.parseTopLevelAlbums(this);
233 } catch (InvalidAlbumFound iaf) {
234 logger.log(Level.WARNING, "Invalid album found, aborting load!");
236 } catch (InvalidParentAlbumFound ipaf) {
237 logger.log(Level.WARNING,
238 format("Invalid parent album ID: %s", ipaf.getAlbumParentId()));
244 configurationSoneParser.parseImages(this);
245 } catch (InvalidImageFound iif) {
246 logger.log(WARNING, "Invalid image found, aborting load!");
248 } catch (InvalidParentAlbumFound ipaf) {
249 logger.log(Level.WARNING,
250 format("Invalid album image (%s) encountered, aborting load!",
251 ipaf.getAlbumParentId()));
256 String sonePrefix = "Sone/" + sone.getId();
257 String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null);
258 if (avatarId != null) {
259 final Map<String, Image> images = configurationSoneParser.getImages();
260 profile.setAvatar(images.get(avatarId));
264 sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null));
265 sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null));
266 sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null));
267 sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null));
268 sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null));
269 sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf(
270 configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars")
271 .getValue(ShowCustomAvatars.NEVER.name())));
273 /* if we’re still here, Sone was loaded successfully. */
274 lock.writeLock().lock();
276 sone.setTime(soneTime);
277 sone.setProfile(profile);
278 sone.setLikePostIds(likedPostIds);
279 sone.setLikeReplyIds(likedReplyIds);
281 String lastInsertFingerprint = configurationLoader.getLastInsertFingerprint(sone.getId());
282 lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint);
284 allSones.put(sone.getId(), sone);
285 storePosts(sone.getId(), posts);
286 storePostReplies(sone.getId(), postReplies);
287 storeAlbums(sone.getId(), topLevelAlbums);
288 storeImages(sone.getId(), from(topLevelAlbums).transformAndConcat(Album.FLATTENER).transformAndConcat(Album.IMAGES).toList());
290 lock.writeLock().unlock();
292 for (Post post : posts) {
295 for (PostReply reply : postReplies) {
296 reply.setKnown(true);
299 logger.info(String.format("Sone loaded successfully: %s", sone));
303 public String getLastInsertFingerprint(Sone sone) {
304 lock.readLock().lock();
306 if (!lastInsertFingerprints.containsKey(sone.getId())) {
309 return lastInsertFingerprints.get(sone.getId());
311 lock.readLock().unlock();
316 public void setLastInsertFingerprint(Sone sone, String lastInsertFingerprint) {
317 lock.writeLock().lock();
319 lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint);
321 lock.writeLock().unlock();
326 * Saves the database.
328 * @throws DatabaseException
329 * if an error occurs while saving
332 public void save() throws DatabaseException {
333 lock.writeLock().lock();
335 saveKnownPostReplies();
336 for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.<Sone>get())) {
340 lock.writeLock().unlock();
344 private synchronized void saveSone(Sone sone) {
345 logger.log(Level.INFO, String.format("Saving Sone: %s", sone));
347 /* save Sone into configuration. */
348 String sonePrefix = "Sone/" + sone.getId();
349 configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime());
350 configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(lastInsertFingerprints.get(sone.getId()));
353 Profile profile = sone.getProfile();
354 configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName());
355 configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName());
356 configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName());
357 configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay());
358 configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth());
359 configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear());
360 configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar());
362 /* save profile fields. */
363 int fieldCounter = 0;
364 for (Field profileField : profile.getFields()) {
365 String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++;
366 configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName());
367 configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue());
369 configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null);
373 for (Post post : sone.getPosts()) {
374 String postPrefix = sonePrefix + "/Posts/" + postCounter++;
375 configuration.getStringValue(postPrefix + "/ID").setValue(post.getId());
376 configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull());
377 configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime());
378 configuration.getStringValue(postPrefix + "/Text").setValue(post.getText());
380 configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null);
383 int replyCounter = 0;
384 for (PostReply reply : sone.getReplies()) {
385 String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
386 configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
387 configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId());
388 configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
389 configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
391 configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null);
393 /* save post likes. */
394 int postLikeCounter = 0;
395 for (String postId : sone.getLikedPostIds()) {
396 configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId);
398 configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null);
400 /* save reply likes. */
401 int replyLikeCounter = 0;
402 for (String replyId : sone.getLikedReplyIds()) {
403 configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId);
405 configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null);
407 /* save albums. first, collect in a flat structure, top-level first. */
408 List<Album> albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList();
410 int albumCounter = 0;
411 for (Album album : albums) {
412 String albumPrefix = sonePrefix + "/Albums/" + albumCounter++;
413 configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId());
414 configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle());
415 configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription());
416 configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId());
417 configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId());
419 configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null);
422 int imageCounter = 0;
423 for (Album album : albums) {
424 for (Image image : album.getImages()) {
425 if (!image.isInserted()) {
428 String imagePrefix = sonePrefix + "/Images/" + imageCounter++;
429 configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId());
430 configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId());
431 configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey());
432 configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle());
433 configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription());
434 configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime());
435 configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth());
436 configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight());
439 configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null);
442 configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow());
443 configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled());
444 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications());
445 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications());
446 configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications());
447 configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name());
449 configuration.save();
451 logger.log(Level.INFO, String.format("Sone %s saved.", sone));
452 } catch (ConfigurationException ce1) {
453 logger.log(Level.WARNING, String.format("Could not save Sone: %s", sone), ce1);
461 /** {@inheritDocs} */
463 protected void doStart() {
464 postDatabase.start();
465 memoryBookmarkDatabase.start();
466 loadKnownPostReplies();
470 /** {@inheritDocs} */
472 protected void doStop() {
475 memoryBookmarkDatabase.stop();
478 } catch (DatabaseException de1) {
484 public SoneBuilder newSoneBuilder() {
485 return new MemorySoneBuilder(this);
489 public void storeSone(Sone sone) {
490 lock.writeLock().lock();
494 allSones.put(sone.getId(), sone);
495 storePosts(sone.getId(), sone.getPosts());
496 storePostReplies(sone.getId(), sone.getReplies());
497 storeAlbums(sone.getId(), toAllAlbums.apply(sone));
498 storeImages(sone.getId(), toAllImages.apply(sone));
500 lock.writeLock().unlock();
504 private void storePosts(String soneId, Collection<Post> posts) {
505 postDatabase.storePosts(soneId, posts);
508 private void storePostReplies(String soneId, Collection<PostReply> postReplies) {
509 sonePostReplies.putAll(soneId, postReplies);
510 for (PostReply postReply : postReplies) {
511 allPostReplies.put(postReply.getId(), postReply);
515 private void storeAlbums(String soneId, Collection<Album> albums) {
516 soneAlbums.putAll(soneId, albums);
517 for (Album album : albums) {
518 allAlbums.put(album.getId(), album);
522 private void storeImages(String soneId, Collection<Image> images) {
523 soneImages.putAll(soneId, images);
524 for (Image image : images) {
525 allImages.put(image.getId(), image);
530 public void removeSone(Sone sone) {
531 lock.writeLock().lock();
533 allSones.remove(sone.getId());
534 postDatabase.removePostsFor(sone.getId());
535 Collection<PostReply> removedPostReplies =
536 sonePostReplies.removeAll(sone.getId());
537 for (PostReply removedPostReply : removedPostReplies) {
538 allPostReplies.remove(removedPostReply.getId());
540 Collection<Album> removedAlbums =
541 soneAlbums.removeAll(sone.getId());
542 for (Album removedAlbum : removedAlbums) {
543 allAlbums.remove(removedAlbum.getId());
545 Collection<Image> removedImages =
546 soneImages.removeAll(sone.getId());
547 for (Image removedImage : removedImages) {
548 allImages.remove(removedImage.getId());
551 lock.writeLock().unlock();
556 public Function<String, Optional<Sone>> soneLoader() {
557 return new Function<String, Optional<Sone>>() {
559 public Optional<Sone> apply(String soneId) {
560 return getSone(soneId);
566 public Optional<Sone> getSone(String soneId) {
567 lock.readLock().lock();
569 return fromNullable(allSones.get(soneId));
571 lock.readLock().unlock();
576 public Collection<Sone> getSones() {
577 lock.readLock().lock();
579 return new HashSet<Sone>(allSones.values());
581 lock.readLock().unlock();
586 public Collection<LocalSone> getLocalSones() {
587 lock.readLock().lock();
589 return from(allSones.values()).filter(LOCAL_SONE_FILTER).transform(new Function<Sone, LocalSone>() {
591 public LocalSone apply(Sone sone) {
592 // FIXME – Sones will not always implement LocalSone
593 return (LocalSone) sone;
597 lock.readLock().unlock();
602 public Collection<Sone> getRemoteSones() {
603 lock.readLock().lock();
605 return from(allSones.values())
606 .filter(not(LOCAL_SONE_FILTER)) .toSet();
608 lock.readLock().unlock();
613 public Collection<String> getFriends(Sone localSone) {
614 if (!localSone.isLocal()) {
615 return Collections.emptySet();
617 return memoryFriendDatabase.getFriends(localSone.getId());
621 public boolean isFriend(Sone localSone, String friendSoneId) {
622 if (!localSone.isLocal()) {
625 return memoryFriendDatabase.isFriend(localSone.getId(), friendSoneId);
629 public void addFriend(Sone localSone, String friendSoneId) {
630 if (!localSone.isLocal()) {
633 memoryFriendDatabase.addFriend(localSone.getId(), friendSoneId);
637 public void removeFriend(Sone localSone, String friendSoneId) {
638 if (!localSone.isLocal()) {
641 memoryFriendDatabase.removeFriend(localSone.getId(), friendSoneId);
645 // POSTPROVIDER METHODS
648 /** {@inheritDocs} */
650 public Optional<Post> getPost(String postId) {
651 return postDatabase.getPost(postId);
654 /** {@inheritDocs} */
656 public Collection<Post> getPosts(String soneId) {
657 return new HashSet<Post>(getPostsFrom(soneId));
660 /** {@inheritDocs} */
662 public Collection<Post> getDirectedPosts(final String recipientId) {
663 return postDatabase.getDirectedPosts(recipientId);
667 // POSTBUILDERFACTORY METHODS
670 /** {@inheritDocs} */
672 public PostBuilder newPostBuilder() {
673 return new MemoryPostBuilder(this, soneProvider);
680 /** {@inheritDocs} */
682 public void storePost(Post post) {
683 checkNotNull(post, "post must not be null");
684 postDatabase.storePost(post);
687 /** {@inheritDocs} */
689 public void removePost(Post post) {
690 checkNotNull(post, "post must not be null");
691 postDatabase.removePost(post.getId());
695 // POSTREPLYPROVIDER METHODS
698 /** {@inheritDocs} */
700 public Optional<PostReply> getPostReply(String id) {
701 lock.readLock().lock();
703 return fromNullable(allPostReplies.get(id));
705 lock.readLock().unlock();
709 /** {@inheritDocs} */
711 public List<PostReply> getReplies(final String postId) {
712 lock.readLock().lock();
714 return from(allPostReplies.values())
715 .filter(new Predicate<PostReply>() {
717 public boolean apply(PostReply postReply) {
718 return postReply.getPostId().equals(postId);
720 }).toSortedList(TIME_COMPARATOR);
722 lock.readLock().unlock();
727 // POSTREPLYBUILDERFACTORY METHODS
730 /** {@inheritDocs} */
732 public PostReplyBuilder newPostReplyBuilder() {
733 return new MemoryPostReplyBuilder(this, soneProvider);
737 // POSTREPLYSTORE METHODS
740 /** {@inheritDocs} */
742 public void storePostReply(PostReply postReply) {
743 lock.writeLock().lock();
745 allPostReplies.put(postReply.getId(), postReply);
747 lock.writeLock().unlock();
751 /** {@inheritDocs} */
753 public void removePostReply(PostReply postReply) {
754 lock.writeLock().lock();
756 allPostReplies.remove(postReply.getId());
758 lock.writeLock().unlock();
763 // ALBUMPROVDER METHODS
767 public Optional<Album> getAlbum(String albumId) {
768 lock.readLock().lock();
770 return fromNullable(allAlbums.get(albumId));
772 lock.readLock().unlock();
777 // ALBUMBUILDERFACTORY METHODS
781 public AlbumBuilder newAlbumBuilder() {
782 return new AlbumBuilderImpl();
786 // ALBUMSTORE METHODS
790 public void storeAlbum(Album album) {
791 lock.writeLock().lock();
793 allAlbums.put(album.getId(), album);
794 soneAlbums.put(album.getSone().getId(), album);
796 lock.writeLock().unlock();
801 public void removeAlbum(Album album) {
802 lock.writeLock().lock();
804 allAlbums.remove(album.getId());
805 soneAlbums.remove(album.getSone().getId(), album);
807 lock.writeLock().unlock();
812 // IMAGEPROVIDER METHODS
816 public Optional<Image> getImage(String imageId) {
817 lock.readLock().lock();
819 return fromNullable(allImages.get(imageId));
821 lock.readLock().unlock();
826 // IMAGEBUILDERFACTORY METHODS
830 public ImageBuilder newImageBuilder() {
831 return new ImageBuilderImpl();
835 // IMAGESTORE METHODS
839 public void storeImage(Image image) {
840 lock.writeLock().lock();
842 allImages.put(image.getId(), image);
843 soneImages.put(image.getSone().getId(), image);
845 lock.writeLock().unlock();
850 public void removeImage(Image image) {
851 lock.writeLock().lock();
853 allImages.remove(image.getId());
854 soneImages.remove(image.getSone().getId(), image);
856 lock.writeLock().unlock();
861 public void bookmarkPost(Post post) {
862 memoryBookmarkDatabase.bookmarkPost(post);
866 public void unbookmarkPost(Post post) {
867 memoryBookmarkDatabase.unbookmarkPost(post);
871 public boolean isPostBookmarked(Post post) {
872 return memoryBookmarkDatabase.isPostBookmarked(post);
876 public Set<Post> getBookmarkedPosts() {
877 return memoryBookmarkDatabase.getBookmarkedPosts();
881 // PACKAGE-PRIVATE METHODS
885 * Returns whether the given post is known.
889 * @return {@code true} if the post is known, {@code false} otherwise
891 boolean isPostKnown(Post post) {
892 return postDatabase.isPostKnown(post.getId());
896 * Sets whether the given post is known.
901 * {@code true} if the post is known, {@code false} otherwise
903 void setPostKnown(Post post, boolean known) {
904 postDatabase.setPostKnown(post.getId(), known);
908 * Returns whether the given post reply is known.
912 * @return {@code true} if the given post reply is known, {@code false}
915 boolean isPostReplyKnown(PostReply postReply) {
916 lock.readLock().lock();
918 return knownPostReplies.contains(postReply.getId());
920 lock.readLock().unlock();
925 * Sets whether the given post reply is known.
930 * {@code true} if the post reply is known, {@code false} otherwise
932 void setPostReplyKnown(PostReply postReply, boolean known) {
933 lock.writeLock().lock();
936 knownPostReplies.add(postReply.getId());
938 knownPostReplies.remove(postReply.getId());
941 lock.writeLock().unlock();
950 * Gets all posts for the given Sone, creating a new collection if there is
954 * The ID of the Sone to get the posts for
957 private Collection<Post> getPostsFrom(String soneId) {
958 return postDatabase.getPostsFrom(soneId);
961 /** Loads the known post replies. */
962 private void loadKnownPostReplies() {
963 Set<String> knownPostReplies = configurationLoader.loadKnownPostReplies();
964 lock.writeLock().lock();
966 this.knownPostReplies.clear();
967 this.knownPostReplies.addAll(knownPostReplies);
969 lock.writeLock().unlock();
974 * Saves the known post replies to the configuration.
976 * @throws DatabaseException
977 * if a configuration error occurs
979 private void saveKnownPostReplies() throws DatabaseException {
980 lock.readLock().lock();
982 int replyCounter = 0;
983 for (String knownReplyId : knownPostReplies) {
984 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(
987 configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
988 } catch (ConfigurationException ce1) {
989 throw new DatabaseException("Could not save database.", ce1);
991 lock.readLock().unlock();