X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fdatabase%2Fmemory%2FMemoryDatabase.java;h=d5d5396ce2c5a74e6c6d0e9d7c3f28292a8cc2f1;hb=f333f58180a7f112394cd768d86c95a3c9edf794;hp=61eb9be8ba9b0ffdeff3da61b1f7ef591832dde8;hpb=84c51832a2d10656e85c2d6eeb45998c34998da4;p=Sone.git diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java index 61eb9be..d5d5396 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -19,8 +19,15 @@ package net.pterodactylus.sone.database.memory; import static com.google.common.base.Optional.fromNullable; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.FluentIterable.from; +import static java.lang.String.format; +import static java.util.logging.Level.WARNING; +import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR; +import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER; +import static net.pterodactylus.sone.data.Sone.toAllAlbums; +import static net.pterodactylus.sone.data.Sone.toAllImages; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -29,17 +36,27 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; - +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.pterodactylus.sone.core.ConfigurationSoneParser; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidAlbumFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound; +import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound; import net.pterodactylus.sone.data.Album; +import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Image; +import net.pterodactylus.sone.data.LocalSone; import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Reply; +import net.pterodactylus.sone.data.Profile; +import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.impl.AlbumBuilderImpl; import net.pterodactylus.sone.data.impl.ImageBuilderImpl; import net.pterodactylus.sone.database.AlbumBuilder; @@ -49,25 +66,37 @@ import net.pterodactylus.sone.database.ImageBuilder; import net.pterodactylus.sone.database.PostBuilder; import net.pterodactylus.sone.database.PostDatabase; import net.pterodactylus.sone.database.PostReplyBuilder; +import net.pterodactylus.sone.database.SoneBuilder; import net.pterodactylus.sone.database.SoneProvider; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; +import net.pterodactylus.sone.main.SonePlugin; +import net.pterodactylus.sone.utils.Optionals; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; +import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; +import com.google.common.primitives.Longs; import com.google.common.util.concurrent.AbstractService; import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Memory-based {@link PostDatabase} implementation. * * @author David ‘Bombe’ Roden */ +@Singleton public class MemoryDatabase extends AbstractService implements Database { + private static final Logger logger = Logger.getLogger("Sone.Database.Memory"); + private static final String LATEST_EDITION_PROPERTY = "Sone.LatestEdition"; /** The lock. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -76,18 +105,11 @@ public class MemoryDatabase extends AbstractService implements Database { /** The configuration. */ private final Configuration configuration; + private final ConfigurationLoader configurationLoader; - /** All posts by their ID. */ - private final Map allPosts = new HashMap(); - - /** All posts by their Sones. */ - private final Multimap sonePosts = HashMultimap.create(); - - /** All posts by their recipient. */ - private final Map> recipientPosts = new HashMap>(); - - /** Whether posts are known. */ - private final Set knownPosts = new HashSet(); + private final Set localSones = new HashSet(); + private final Map allSones = new HashMap(); + private final Map lastInsertFingerprints = new HashMap(); /** All post replies by their ID. */ private final Map allPostReplies = new HashMap(); @@ -99,17 +121,21 @@ public class MemoryDatabase extends AbstractService implements Database { public int compare(String leftString, String rightString) { return leftString.compareTo(rightString); } - }, PostReply.TIME_COMPARATOR); - - /** Replies by post. */ - private final Map> postReplies = new HashMap>(); + }, TIME_COMPARATOR); /** Whether post replies are known. */ private final Set knownPostReplies = new HashSet(); private final Map allAlbums = new HashMap(); + private final Multimap soneAlbums = HashMultimap.create(); private final Map allImages = new HashMap(); + private final Multimap soneImages = HashMultimap.create(); + + private final MemorySoneDatabase soneDatabase; + private final MemoryPostDatabase postDatabase; + private final MemoryBookmarkDatabase memoryBookmarkDatabase; + private final MemoryFriendDatabase memoryFriendDatabase; /** * Creates a new memory database. @@ -123,12 +149,181 @@ public class MemoryDatabase extends AbstractService implements Database { public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) { this.soneProvider = soneProvider; this.configuration = configuration; + this.configurationLoader = new ConfigurationLoader(configuration); + soneDatabase = new MemorySoneDatabase(configurationLoader); + postDatabase = new MemoryPostDatabase(this, configurationLoader); + memoryBookmarkDatabase = + new MemoryBookmarkDatabase(this, configurationLoader); + memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader); } // // DATABASE METHODS // + @Override + public Optional getLocalSone(String localSoneId) { + lock.readLock().lock(); + try { + if (!localSones.contains(localSoneId)) { + return Optional.absent(); + } + return Optional.of((LocalSone) allSones.get(localSoneId)); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public LocalSone registerLocalSone(OwnIdentity ownIdentity) { + final LocalSone localSone = loadLocalSone(ownIdentity); + localSones.add(ownIdentity.getId()); + return localSone; + } + + private LocalSone loadLocalSone(OwnIdentity ownIdentity) { + LocalSone localSone = (LocalSone) newSoneBuilder().local().from(ownIdentity).build(); + localSone.setLatestEdition( + Optional.fromNullable( + Longs.tryParse(ownIdentity.getProperty(LATEST_EDITION_PROPERTY))) + .or(0L)); + localSone.setClient(new Client("Sone", SonePlugin.VERSION.toString())); + localSone.setKnown(true); + + loadSone(localSone); + return localSone; + } + + public void loadSone(LocalSone sone) { + long soneTime = configurationLoader.getLocalSoneTime(sone.getId()); + if (soneTime == -1) { + return; + } + + /* load profile. */ + ConfigurationSoneParser configurationSoneParser = new ConfigurationSoneParser(configuration, sone); + Profile profile = configurationSoneParser.parseProfile(); + + /* load posts. */ + Collection posts; + try { + posts = configurationSoneParser.parsePosts(this); + } catch (InvalidPostFound ipf) { + logger.log(Level.WARNING, "Invalid post found, aborting load!"); + return; + } + + /* load replies. */ + Collection postReplies; + try { + postReplies = configurationSoneParser.parsePostReplies(this); + } catch (InvalidPostReplyFound iprf) { + logger.log(Level.WARNING, "Invalid reply found, aborting load!"); + return; + } + + /* load post likes. */ + Set likedPostIds = configurationSoneParser.parseLikedPostIds(); + + /* load reply likes. */ + Set likedReplyIds = configurationSoneParser.parseLikedPostReplyIds(); + + /* load albums. */ + List topLevelAlbums; + try { + topLevelAlbums = configurationSoneParser.parseTopLevelAlbums(this); + } catch (InvalidAlbumFound iaf) { + logger.log(Level.WARNING, "Invalid album found, aborting load!"); + return; + } catch (InvalidParentAlbumFound ipaf) { + logger.log(Level.WARNING, + format("Invalid parent album ID: %s", ipaf.getAlbumParentId())); + return; + } + + /* load images. */ + try { + configurationSoneParser.parseImages(this); + } catch (InvalidImageFound iif) { + logger.log(WARNING, "Invalid image found, aborting load!"); + return; + } catch (InvalidParentAlbumFound ipaf) { + logger.log(Level.WARNING, + format("Invalid album image (%s) encountered, aborting load!", + ipaf.getAlbumParentId())); + return; + } + + /* load avatar. */ + String sonePrefix = "Sone/" + sone.getId(); + String avatarId = configuration.getStringValue(sonePrefix + "/Profile/Avatar").getValue(null); + if (avatarId != null) { + final Map images = configurationSoneParser.getImages(); + profile.setAvatar(images.get(avatarId)); + } + + /* load options. */ + sone.getOptions().setAutoFollow(configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").getValue(null)); + sone.getOptions().setSoneInsertNotificationEnabled(configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").getValue(null)); + sone.getOptions().setShowNewSoneNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").getValue(null)); + sone.getOptions().setShowNewPostNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").getValue(null)); + sone.getOptions().setShowNewReplyNotifications(configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").getValue(null)); + sone.getOptions().setShowCustomAvatars(ShowCustomAvatars.valueOf( + configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars") + .getValue(ShowCustomAvatars.NEVER.name()))); + + /* if we’re still here, Sone was loaded successfully. */ + lock.writeLock().lock(); + try { + updateSoneTime(sone, soneTime); + sone.setProfile(profile); + sone.setLikePostIds(likedPostIds); + sone.setLikeReplyIds(likedReplyIds); + + String lastInsertFingerprint = configurationLoader.getLastInsertFingerprint(sone.getId()); + lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint); + + allSones.put(sone.getId(), sone); + storePosts(sone.getId(), posts); + storePostReplies(sone.getId(), postReplies); + storeAlbums(sone.getId(), topLevelAlbums); + storeImages(sone.getId(), from(topLevelAlbums).transformAndConcat(Album.FLATTENER).transformAndConcat(Album.IMAGES).toList()); + } finally { + lock.writeLock().unlock(); + } + for (Post post : posts) { + post.setKnown(true); + } + for (PostReply reply : postReplies) { + reply.setKnown(true); + } + + logger.info(String.format("Sone loaded successfully: %s", sone)); + } + + @Override + public String getLastInsertFingerprint(Sone sone) { + lock.readLock().lock(); + try { + if (!lastInsertFingerprints.containsKey(sone.getId())) { + return ""; + } + return lastInsertFingerprints.get(sone.getId()); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void setLastInsertFingerprint(Sone sone, String lastInsertFingerprint) { + lock.writeLock().lock(); + try { + lastInsertFingerprints.put(sone.getId(), lastInsertFingerprint); + } finally { + lock.writeLock().unlock(); + } + } + /** * Saves the database. * @@ -137,8 +332,128 @@ public class MemoryDatabase extends AbstractService implements Database { */ @Override public void save() throws DatabaseException { - saveKnownPosts(); - saveKnownPostReplies(); + lock.writeLock().lock(); + try { + saveKnownPostReplies(); + for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.get())) { + saveSone(localSone); + } + } finally { + lock.writeLock().unlock(); + } + } + + private synchronized void saveSone(Sone sone) { + logger.log(Level.INFO, String.format("Saving Sone: %s", sone)); + try { + /* save Sone into configuration. */ + String sonePrefix = "Sone/" + sone.getId(); + configuration.getLongValue(sonePrefix + "/Time").setValue(sone.getTime()); + configuration.getStringValue(sonePrefix + "/LastInsertFingerprint").setValue(lastInsertFingerprints.get(sone.getId())); + + /* save profile. */ + Profile profile = sone.getProfile(); + configuration.getStringValue(sonePrefix + "/Profile/FirstName").setValue(profile.getFirstName()); + configuration.getStringValue(sonePrefix + "/Profile/MiddleName").setValue(profile.getMiddleName()); + configuration.getStringValue(sonePrefix + "/Profile/LastName").setValue(profile.getLastName()); + configuration.getIntValue(sonePrefix + "/Profile/BirthDay").setValue(profile.getBirthDay()); + configuration.getIntValue(sonePrefix + "/Profile/BirthMonth").setValue(profile.getBirthMonth()); + configuration.getIntValue(sonePrefix + "/Profile/BirthYear").setValue(profile.getBirthYear()); + configuration.getStringValue(sonePrefix + "/Profile/Avatar").setValue(profile.getAvatar()); + + /* save profile fields. */ + int fieldCounter = 0; + for (Field profileField : profile.getFields()) { + String fieldPrefix = sonePrefix + "/Profile/Fields/" + fieldCounter++; + configuration.getStringValue(fieldPrefix + "/Name").setValue(profileField.getName()); + configuration.getStringValue(fieldPrefix + "/Value").setValue(profileField.getValue()); + } + configuration.getStringValue(sonePrefix + "/Profile/Fields/" + fieldCounter + "/Name").setValue(null); + + /* save posts. */ + int postCounter = 0; + for (Post post : sone.getPosts()) { + String postPrefix = sonePrefix + "/Posts/" + postCounter++; + configuration.getStringValue(postPrefix + "/ID").setValue(post.getId()); + configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull()); + configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime()); + configuration.getStringValue(postPrefix + "/Text").setValue(post.getText()); + } + configuration.getStringValue(sonePrefix + "/Posts/" + postCounter + "/ID").setValue(null); + + /* save replies. */ + int replyCounter = 0; + for (PostReply reply : sone.getReplies()) { + String replyPrefix = sonePrefix + "/Replies/" + replyCounter++; + configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId()); + configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId()); + configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime()); + configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText()); + } + configuration.getStringValue(sonePrefix + "/Replies/" + replyCounter + "/ID").setValue(null); + + /* save post likes. */ + int postLikeCounter = 0; + for (String postId : sone.getLikedPostIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter++ + "/ID").setValue(postId); + } + configuration.getStringValue(sonePrefix + "/Likes/Post/" + postLikeCounter + "/ID").setValue(null); + + /* save reply likes. */ + int replyLikeCounter = 0; + for (String replyId : sone.getLikedReplyIds()) { + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter++ + "/ID").setValue(replyId); + } + configuration.getStringValue(sonePrefix + "/Likes/Reply/" + replyLikeCounter + "/ID").setValue(null); + + /* save albums. first, collect in a flat structure, top-level first. */ + List albums = FluentIterable.from(sone.getRootAlbum().getAlbums()).transformAndConcat(Album.FLATTENER).toList(); + + int albumCounter = 0; + for (Album album : albums) { + String albumPrefix = sonePrefix + "/Albums/" + albumCounter++; + configuration.getStringValue(albumPrefix + "/ID").setValue(album.getId()); + configuration.getStringValue(albumPrefix + "/Title").setValue(album.getTitle()); + configuration.getStringValue(albumPrefix + "/Description").setValue(album.getDescription()); + configuration.getStringValue(albumPrefix + "/Parent").setValue(album.getParent().equals(sone.getRootAlbum()) ? null : album.getParent().getId()); + configuration.getStringValue(albumPrefix + "/AlbumImage").setValue(album.getAlbumImage() == null ? null : album.getAlbumImage().getId()); + } + configuration.getStringValue(sonePrefix + "/Albums/" + albumCounter + "/ID").setValue(null); + + /* save images. */ + int imageCounter = 0; + for (Album album : albums) { + for (Image image : album.getImages()) { + if (!image.isInserted()) { + continue; + } + String imagePrefix = sonePrefix + "/Images/" + imageCounter++; + configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId()); + configuration.getStringValue(imagePrefix + "/Album").setValue(album.getId()); + configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey()); + configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle()); + configuration.getStringValue(imagePrefix + "/Description").setValue(image.getDescription()); + configuration.getLongValue(imagePrefix + "/CreationTime").setValue(image.getCreationTime()); + configuration.getIntValue(imagePrefix + "/Width").setValue(image.getWidth()); + configuration.getIntValue(imagePrefix + "/Height").setValue(image.getHeight()); + } + } + configuration.getStringValue(sonePrefix + "/Images/" + imageCounter + "/ID").setValue(null); + + /* save options. */ + configuration.getBooleanValue(sonePrefix + "/Options/AutoFollow").setValue(sone.getOptions().isAutoFollow()); + configuration.getBooleanValue(sonePrefix + "/Options/EnableSoneInsertNotifications").setValue(sone.getOptions().isSoneInsertNotificationEnabled()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewSones").setValue(sone.getOptions().isShowNewSoneNotifications()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewPosts").setValue(sone.getOptions().isShowNewPostNotifications()); + configuration.getBooleanValue(sonePrefix + "/Options/ShowNotification/NewReplies").setValue(sone.getOptions().isShowNewReplyNotifications()); + configuration.getStringValue(sonePrefix + "/Options/ShowCustomAvatars").setValue(sone.getOptions().getShowCustomAvatars().name()); + + configuration.save(); + + logger.log(Level.INFO, String.format("Sone %s saved.", sone)); + } catch (ConfigurationException ce1) { + logger.log(Level.WARNING, String.format("Could not save Sone: %s", sone), ce1); + } } // @@ -148,7 +463,10 @@ public class MemoryDatabase extends AbstractService implements Database { /** {@inheritDocs} */ @Override protected void doStart() { - loadKnownPosts(); + soneDatabase.start(); + memoryFriendDatabase.start(); + postDatabase.start(); + memoryBookmarkDatabase.start(); loadKnownPostReplies(); notifyStarted(); } @@ -157,6 +475,10 @@ public class MemoryDatabase extends AbstractService implements Database { @Override protected void doStop() { try { + soneDatabase.stop(); + memoryFriendDatabase.stop(); + postDatabase.stop(); + memoryBookmarkDatabase.stop(); save(); notifyStopped(); } catch (DatabaseException de1) { @@ -164,138 +486,229 @@ public class MemoryDatabase extends AbstractService implements Database { } } - // - // POSTPROVIDER METHODS - // + @Override + public SoneBuilder newSoneBuilder() { + return new MemorySoneBuilder(this); + } - /** {@inheritDocs} */ @Override - public Optional getPost(String postId) { - lock.readLock().lock(); + public void storeSone(Sone sone) { + lock.writeLock().lock(); try { - return fromNullable(allPosts.get(postId)); + removeSone(sone); + + allSones.put(sone.getId(), sone); + storePosts(sone.getId(), sone.getPosts()); + storePostReplies(sone.getId(), sone.getReplies()); + storeAlbums(sone.getId(), toAllAlbums.apply(sone)); + storeImages(sone.getId(), toAllImages.apply(sone)); } finally { - lock.readLock().unlock(); + lock.writeLock().unlock(); } } - /** {@inheritDocs} */ @Override - public Collection getPosts(String soneId) { - return new HashSet(getPostsFrom(soneId)); + public boolean isSoneKnown(Sone sone) { + return soneDatabase.isKnownSone(sone.getId()); } - /** {@inheritDocs} */ @Override - public Collection getDirectedPosts(String recipientId) { - lock.readLock().lock(); - try { - Collection posts = recipientPosts.get(recipientId); - return (posts == null) ? Collections.emptySet() : new HashSet(posts); - } finally { - lock.readLock().unlock(); - } + public void setSoneKnown(Sone sone) { + soneDatabase.setSoneKnown(sone.getId()); } - // - // POSTBUILDERFACTORY METHODS - // - - /** {@inheritDocs} */ @Override - public PostBuilder newPostBuilder() { - return new MemoryPostBuilder(this, soneProvider); + public void updateSoneTime(Sone sone, long soneTime) { + soneDatabase.updateSoneTime(sone.getId(), soneTime); } - // - // POSTSTORE METHODS - // + private void storePosts(String soneId, Collection posts) { + postDatabase.storePosts(soneId, posts); + } + + private void storePostReplies(String soneId, Collection postReplies) { + sonePostReplies.putAll(soneId, postReplies); + for (PostReply postReply : postReplies) { + allPostReplies.put(postReply.getId(), postReply); + } + } + + private void storeAlbums(String soneId, Collection albums) { + soneAlbums.putAll(soneId, albums); + for (Album album : albums) { + allAlbums.put(album.getId(), album); + } + } + + private void storeImages(String soneId, Collection images) { + soneImages.putAll(soneId, images); + for (Image image : images) { + allImages.put(image.getId(), image); + } + } - /** {@inheritDocs} */ @Override - public void storePost(Post post) { - checkNotNull(post, "post must not be null"); + public void removeSone(Sone sone) { lock.writeLock().lock(); try { - allPosts.put(post.getId(), post); - getPostsFrom(post.getSone().getId()).add(post); - if (post.getRecipientId().isPresent()) { - getPostsTo(post.getRecipientId().get()).add(post); + allSones.remove(sone.getId()); + postDatabase.removePostsFor(sone.getId()); + Collection removedPostReplies = + sonePostReplies.removeAll(sone.getId()); + for (PostReply removedPostReply : removedPostReplies) { + allPostReplies.remove(removedPostReply.getId()); + } + Collection removedAlbums = + soneAlbums.removeAll(sone.getId()); + for (Album removedAlbum : removedAlbums) { + allAlbums.remove(removedAlbum.getId()); + } + Collection removedImages = + soneImages.removeAll(sone.getId()); + for (Image removedImage : removedImages) { + allImages.remove(removedImage.getId()); } } finally { lock.writeLock().unlock(); } } - /** {@inheritDocs} */ @Override - public void removePost(Post post) { - checkNotNull(post, "post must not be null"); - lock.writeLock().lock(); - try { - allPosts.remove(post.getId()); - getPostsFrom(post.getSone().getId()).remove(post); - if (post.getRecipientId().isPresent()) { - getPostsTo(post.getRecipientId().get()).remove(post); + public Function> soneLoader() { + return new Function>() { + @Override + public Optional apply(String soneId) { + return getSone(soneId); } - post.getSone().removePost(post); + }; + } + + @Override + public Optional getSone(String soneId) { + lock.readLock().lock(); + try { + return fromNullable(allSones.get(soneId)); } finally { - lock.writeLock().unlock(); + lock.readLock().unlock(); } } - /** {@inheritDocs} */ @Override - public void storePosts(Sone sone, Collection posts) throws IllegalArgumentException { - checkNotNull(sone, "sone must not be null"); - /* verify that all posts are from the same Sone. */ - for (Post post : posts) { - if (!sone.equals(post.getSone())) { - throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post)); - } + public Collection getSones() { + lock.readLock().lock(); + try { + return new HashSet(allSones.values()); + } finally { + lock.readLock().unlock(); } + } - lock.writeLock().lock(); + @Override + public Collection getLocalSones() { + lock.readLock().lock(); try { - /* remove all posts by the Sone. */ - getPostsFrom(sone.getId()).clear(); - for (Post post : posts) { - allPosts.remove(post.getId()); - if (post.getRecipientId().isPresent()) { - getPostsTo(post.getRecipientId().get()).remove(post); + return from(allSones.values()).filter(LOCAL_SONE_FILTER).transform(new Function() { + @Override + public LocalSone apply(Sone sone) { + // FIXME – Sones will not always implement LocalSone + return (LocalSone) sone; } - } - - /* add new posts. */ - getPostsFrom(sone.getId()).addAll(posts); - for (Post post : posts) { - allPosts.put(post.getId(), post); - if (post.getRecipientId().isPresent()) { - getPostsTo(post.getRecipientId().get()).add(post); - } - } + }).toSet(); } finally { - lock.writeLock().unlock(); + lock.readLock().unlock(); } } - /** {@inheritDocs} */ @Override - public void removePosts(Sone sone) { - checkNotNull(sone, "sone must not be null"); - lock.writeLock().lock(); + public Collection getRemoteSones() { + lock.readLock().lock(); try { - /* remove all posts by the Sone. */ - getPostsFrom(sone.getId()).clear(); - for (Post post : sone.getPosts()) { - allPosts.remove(post.getId()); - if (post.getRecipientId().isPresent()) { - getPostsTo(post.getRecipientId().get()).remove(post); - } - } + return from(allSones.values()) + .filter(not(LOCAL_SONE_FILTER)) .toSet(); } finally { - lock.writeLock().unlock(); + lock.readLock().unlock(); + } + } + + @Override + public Collection getFriends(LocalSone localSone) { + if (!localSone.isLocal()) { + return Collections.emptySet(); + } + return memoryFriendDatabase.getFriends(localSone.getId()); + } + + @Override + public Optional getSoneFollowingTime(String remoteSoneId) { + return memoryFriendDatabase.getSoneFollowingTime(remoteSoneId); + } + + @Override + public boolean isFriend(LocalSone localSone, String friendSoneId) { + if (!localSone.isLocal()) { + return false; } + return memoryFriendDatabase.isFriend(localSone.getId(), friendSoneId); + } + + @Override + public void addFriend(LocalSone localSone, String friendSoneId) { + memoryFriendDatabase.addFriend(localSone.getId(), friendSoneId); + } + + @Override + public void removeFriend(LocalSone localSone, String friendSoneId) { + memoryFriendDatabase.removeFriend(localSone.getId(), friendSoneId); + } + + // + // POSTPROVIDER METHODS + // + + /** {@inheritDocs} */ + @Override + public Optional getPost(String postId) { + return postDatabase.getPost(postId); + } + + /** {@inheritDocs} */ + @Override + public Collection getPosts(String soneId) { + return new HashSet(getPostsFrom(soneId)); + } + + /** {@inheritDocs} */ + @Override + public Collection getDirectedPosts(final String recipientId) { + return postDatabase.getDirectedPosts(recipientId); + } + + // + // POSTBUILDERFACTORY METHODS + // + + /** {@inheritDocs} */ + @Override + public PostBuilder newPostBuilder() { + return new MemoryPostBuilder(this, soneProvider); + } + + // + // POSTSTORE METHODS + // + + /** {@inheritDocs} */ + @Override + public void storePost(Post post) { + checkNotNull(post, "post must not be null"); + postDatabase.storePost(post); + } + + /** {@inheritDocs} */ + @Override + public void removePost(Post post) { + checkNotNull(post, "post must not be null"); + postDatabase.removePost(post.getId()); } // @@ -315,13 +728,16 @@ public class MemoryDatabase extends AbstractService implements Database { /** {@inheritDocs} */ @Override - public List getReplies(String postId) { + public List getReplies(final String postId) { lock.readLock().lock(); try { - if (!postReplies.containsKey(postId)) { - return Collections.emptyList(); - } - return new ArrayList(postReplies.get(postId)); + return from(allPostReplies.values()) + .filter(new Predicate() { + @Override + public boolean apply(PostReply postReply) { + return postReply.getPostId().equals(postId); + } + }).toSortedList(TIME_COMPARATOR); } finally { lock.readLock().unlock(); } @@ -347,46 +763,6 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allPostReplies.put(postReply.getId(), postReply); - if (postReplies.containsKey(postReply.getPostId())) { - postReplies.get(postReply.getPostId()).add(postReply); - } else { - TreeSet replies = new TreeSet(Reply.TIME_COMPARATOR); - replies.add(postReply); - postReplies.put(postReply.getPostId(), replies); - } - } finally { - lock.writeLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public void storePostReplies(Sone sone, Collection postReplies) { - checkNotNull(sone, "sone must not be null"); - /* verify that all posts are from the same Sone. */ - for (PostReply postReply : postReplies) { - if (!sone.equals(postReply.getSone())) { - throw new IllegalArgumentException(String.format("PostReply from different Sone found: %s", postReply)); - } - } - - lock.writeLock().lock(); - try { - /* remove all post replies of the Sone. */ - for (PostReply postReply : getRepliesFrom(sone.getId())) { - removePostReply(postReply); - } - for (PostReply postReply : postReplies) { - allPostReplies.put(postReply.getId(), postReply); - sonePostReplies.put(postReply.getSone().getId(), postReply); - if (this.postReplies.containsKey(postReply.getPostId())) { - this.postReplies.get(postReply.getPostId()).add(postReply); - } else { - TreeSet replies = new TreeSet(Reply.TIME_COMPARATOR); - replies.add(postReply); - this.postReplies.put(postReply.getPostId(), replies); - } - } } finally { lock.writeLock().unlock(); } @@ -398,27 +774,6 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allPostReplies.remove(postReply.getId()); - if (postReplies.containsKey(postReply.getPostId())) { - postReplies.get(postReply.getPostId()).remove(postReply); - if (postReplies.get(postReply.getPostId()).isEmpty()) { - postReplies.remove(postReply.getPostId()); - } - } - } finally { - lock.writeLock().unlock(); - } - } - - /** {@inheritDocs} */ - @Override - public void removePostReplies(Sone sone) { - checkNotNull(sone, "sone must not be null"); - - lock.writeLock().lock(); - try { - for (PostReply postReply : sone.getReplies()) { - removePostReply(postReply); - } } finally { lock.writeLock().unlock(); } @@ -456,6 +811,7 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allAlbums.put(album.getId(), album); + soneAlbums.put(album.getSone().getId(), album); } finally { lock.writeLock().unlock(); } @@ -466,6 +822,7 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allAlbums.remove(album.getId()); + soneAlbums.remove(album.getSone().getId(), album); } finally { lock.writeLock().unlock(); } @@ -503,6 +860,7 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allImages.put(image.getId(), image); + soneImages.put(image.getSone().getId(), image); } finally { lock.writeLock().unlock(); } @@ -513,11 +871,32 @@ public class MemoryDatabase extends AbstractService implements Database { lock.writeLock().lock(); try { allImages.remove(image.getId()); + soneImages.remove(image.getSone().getId(), image); } finally { lock.writeLock().unlock(); } } + @Override + public void bookmarkPost(Post post) { + memoryBookmarkDatabase.bookmarkPost(post); + } + + @Override + public void unbookmarkPost(Post post) { + memoryBookmarkDatabase.unbookmarkPost(post); + } + + @Override + public boolean isPostBookmarked(Post post) { + return memoryBookmarkDatabase.isPostBookmarked(post); + } + + @Override + public Set getBookmarkedPosts() { + return memoryBookmarkDatabase.getBookmarkedPosts(); + } + // // PACKAGE-PRIVATE METHODS // @@ -530,12 +909,7 @@ public class MemoryDatabase extends AbstractService implements Database { * @return {@code true} if the post is known, {@code false} otherwise */ boolean isPostKnown(Post post) { - lock.readLock().lock(); - try { - return knownPosts.contains(post.getId()); - } finally { - lock.readLock().unlock(); - } + return postDatabase.isPostKnown(post.getId()); } /** @@ -547,16 +921,7 @@ public class MemoryDatabase extends AbstractService implements Database { * {@code true} if the post is known, {@code false} otherwise */ void setPostKnown(Post post, boolean known) { - lock.writeLock().lock(); - try { - if (known) { - knownPosts.add(post.getId()); - } else { - knownPosts.remove(post.getId()); - } - } finally { - lock.writeLock().unlock(); - } + postDatabase.setPostKnown(post.getId(), known); } /** @@ -610,114 +975,16 @@ public class MemoryDatabase extends AbstractService implements Database { * @return All posts */ private Collection getPostsFrom(String soneId) { - lock.readLock().lock(); - try { - return sonePosts.get(soneId); - } finally { - lock.readLock().unlock(); - } - } - - /** - * Gets all posts that are directed the given Sone, creating a new collection - * if there is none yet. - * - * @param recipientId - * The ID of the Sone to get the posts for - * @return All posts - */ - private Collection getPostsTo(String recipientId) { - Collection posts = null; - lock.readLock().lock(); - try { - posts = recipientPosts.get(recipientId); - } finally { - lock.readLock().unlock(); - } - if (posts != null) { - return posts; - } - - posts = new HashSet(); - lock.writeLock().lock(); - try { - recipientPosts.put(recipientId, posts); - } finally { - lock.writeLock().unlock(); - } - - return posts; - } - - /** Loads the known posts. */ - private void loadKnownPosts() { - lock.writeLock().lock(); - try { - int postCounter = 0; - while (true) { - String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null); - if (knownPostId == null) { - break; - } - knownPosts.add(knownPostId); - } - } finally { - lock.writeLock().unlock(); - } - } - - /** - * Saves the known posts to the configuration. - * - * @throws DatabaseException - * if a configuration error occurs - */ - private void saveKnownPosts() throws DatabaseException { - lock.readLock().lock(); - try { - int postCounter = 0; - for (String knownPostId : knownPosts) { - configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId); - } - configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null); - } catch (ConfigurationException ce1) { - throw new DatabaseException("Could not save database.", ce1); - } finally { - lock.readLock().unlock(); - } - } - - /** - * Returns all replies by the given Sone. - * - * @param id - * The ID of the Sone - * @return The post replies of the Sone, sorted by time (newest first) - */ - private Collection getRepliesFrom(String id) { - lock.readLock().lock(); - try { - if (sonePostReplies.containsKey(id)) { - return Collections.unmodifiableCollection(sonePostReplies.get(id)); - } - return Collections.emptySet(); - } finally { - lock.readLock().unlock(); - } + return postDatabase.getPostsFrom(soneId); } /** Loads the known post replies. */ private void loadKnownPostReplies() { + Set knownPostReplies = configurationLoader.loadKnownPostReplies(); lock.writeLock().lock(); try { - int replyCounter = 0; - while (true) { - String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null); - if (knownReplyId == null) { - break; - } - knownPostReplies.add(knownReplyId); - } + this.knownPostReplies.clear(); + this.knownPostReplies.addAll(knownPostReplies); } finally { lock.writeLock().unlock(); } @@ -734,7 +1001,8 @@ public class MemoryDatabase extends AbstractService implements Database { try { int replyCounter = 0; for (String knownReplyId : knownPostReplies) { - configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); + configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue( + knownReplyId); } configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); } catch (ConfigurationException ce1) {