return loadIds("KnownPosts");
}
+ public synchronized void saveKnownPosts(Set<String> knownPosts) {
+ saveIds("KnownPosts", knownPosts);
+ }
+
public synchronized Set<String> loadKnownPostReplies() {
return loadIds("KnownReplies");
}
private final Map<String, Sone> allSones = new HashMap<String, Sone>();
private final Map<String, String> lastInsertFingerprints = new HashMap<String, String>();
- /** All posts by their ID. */
- private final Map<String, Post> allPosts = new HashMap<String, Post>();
-
- /** All posts by their Sones. */
- private final Multimap<String, Post> sonePosts = HashMultimap.create();
-
- /** Whether posts are known. */
- private final Set<String> knownPosts = new HashSet<String>();
-
/** All post replies by their ID. */
private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
private final Map<String, Image> allImages = new HashMap<String, Image>();
private final Multimap<String, Image> soneImages = HashMultimap.create();
+ private final MemoryPostDatabase postDatabase;
private final MemoryBookmarkDatabase memoryBookmarkDatabase;
private final MemoryFriendDatabase memoryFriendDatabase;
this.soneProvider = soneProvider;
this.configuration = configuration;
this.configurationLoader = new ConfigurationLoader(configuration);
+ postDatabase = new MemoryPostDatabase(this, configurationLoader);
memoryBookmarkDatabase =
new MemoryBookmarkDatabase(this, configurationLoader);
memoryFriendDatabase = new MemoryFriendDatabase(configurationLoader);
public void save() throws DatabaseException {
lock.writeLock().lock();
try {
- saveKnownPosts();
saveKnownPostReplies();
for (Sone localSone : from(localSones).transform(soneLoader()).transform(Optionals.<Sone>get())) {
saveSone(localSone);
/** {@inheritDocs} */
@Override
protected void doStart() {
+ postDatabase.start();
memoryBookmarkDatabase.start();
- loadKnownPosts();
loadKnownPostReplies();
notifyStarted();
}
@Override
protected void doStop() {
try {
+ postDatabase.stop();
memoryBookmarkDatabase.stop();
save();
notifyStopped();
}
private void storePosts(String soneId, Collection<Post> posts) {
- sonePosts.putAll(soneId, posts);
- for (Post post : posts) {
- allPosts.put(post.getId(), post);
- }
+ postDatabase.storePosts(soneId, posts);
}
private void storePostReplies(String soneId, Collection<PostReply> postReplies) {
lock.writeLock().lock();
try {
allSones.remove(sone.getId());
- Collection<Post> removedPosts = sonePosts.removeAll(sone.getId());
- for (Post removedPost : removedPosts) {
- allPosts.remove(removedPost.getId());
- }
+ postDatabase.removePostsFor(sone.getId());
Collection<PostReply> removedPostReplies =
sonePostReplies.removeAll(sone.getId());
for (PostReply removedPostReply : removedPostReplies) {
/** {@inheritDocs} */
@Override
public Optional<Post> getPost(String postId) {
- lock.readLock().lock();
- try {
- return fromNullable(allPosts.get(postId));
- } finally {
- lock.readLock().unlock();
- }
+ return postDatabase.getPost(postId);
}
/** {@inheritDocs} */
/** {@inheritDocs} */
@Override
public Collection<Post> getDirectedPosts(final String recipientId) {
- lock.readLock().lock();
- try {
- return from(sonePosts.values()).filter(new Predicate<Post>() {
- @Override
- public boolean apply(Post post) {
- return post.getRecipientId().asSet().contains(recipientId);
- }
- }).toSet();
- } finally {
- lock.readLock().unlock();
- }
+ return postDatabase.getDirectedPosts(recipientId);
}
//
@Override
public void storePost(Post post) {
checkNotNull(post, "post must not be null");
- lock.writeLock().lock();
- try {
- allPosts.put(post.getId(), post);
- getPostsFrom(post.getSone().getId()).add(post);
- } finally {
- lock.writeLock().unlock();
- }
+ postDatabase.storePost(post);
}
/** {@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);
- post.getSone().removePost(post);
- } finally {
- lock.writeLock().unlock();
- }
+ postDatabase.removePost(post.getId());
}
//
* @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());
}
/**
* {@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);
}
/**
* @return All posts
*/
private Collection<Post> getPostsFrom(String soneId) {
- lock.readLock().lock();
- try {
- return sonePosts.get(soneId);
- } finally {
- lock.readLock().unlock();
- }
- }
-
- /** Loads the known posts. */
- private void loadKnownPosts() {
- Set<String> knownPosts = configurationLoader.loadKnownPosts();
- lock.writeLock().lock();
- try {
- this.knownPosts.clear();
- this.knownPosts.addAll(knownPosts);
- } 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();
- }
+ return postDatabase.getPostsFrom(soneId);
}
/** Loads the known post replies. */
--- /dev/null
+package net.pterodactylus.sone.database.memory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.utils.Optionals;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Groups {@link Post}-related database functions.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPostDatabase {
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ private final MemoryDatabase database;
+ private final ConfigurationLoader configurationLoader;
+ private final Multimap<String, String> sonePosts = HashMultimap.create();
+ private final Map<String, String> postSones = new HashMap<String, String>();
+ private final Map<String, Long> postTimes = new HashMap<String, Long>();
+ private final Map<String, String> postRecipients = new HashMap<String, String>();
+ private final Multimap<String, String> recipientPosts = HashMultimap.create();
+ private final Map<String, String> postTexts = new HashMap<String, String>();
+ private final Set<String> knownPosts = new HashSet<String>();
+
+ MemoryPostDatabase(MemoryDatabase database, ConfigurationLoader configurationLoader) {
+ this.database = database;
+ this.configurationLoader = configurationLoader;
+ }
+
+ void start() {
+ loadKnownPosts();
+ }
+
+ void stop() {
+ saveKnownPosts();
+ }
+
+ Optional<Post> getPost(String postId) {
+ lock.readLock().lock();
+ try {
+ if (!postSones.containsKey(postId)) {
+ return Optional.absent();
+ }
+ String sender = postSones.get(postId);
+ String recipientId = postRecipients.get(postId);
+ long time = postTimes.get(postId);
+ String text = postTexts.get(postId);
+ return Optional.<Post>of(
+ new MemoryPost(database, database, postId, sender, recipientId, time, text));
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ Collection<Post> getPostsFrom(String soneId) {
+ lock.readLock().lock();
+ try {
+ return FluentIterable.from(sonePosts.get(soneId))
+ .transform(postLoader)
+ .transform(Optionals.<Post>get())
+ .toList();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ Collection<Post> getDirectedPosts(String recipientId) {
+ lock.readLock().lock();
+ try {
+ return FluentIterable.from(recipientPosts.get(recipientId))
+ .transform(postLoader)
+ .transform(Optionals.<Post>get())
+ .toList();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void storePosts(String soneId, Collection<Post> posts) {
+ lock.writeLock().lock();
+ try {
+ removePostsFor(soneId);
+ for (Post post : posts) {
+ storePost(post);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void storePost(Post post) {
+ String soneId = post.getSone().getId();
+ final String postId = post.getId();
+ sonePosts.put(soneId, postId);
+ postSones.put(postId, soneId);
+ postRecipients.put(postId, post.getRecipientId().orNull());
+ if (post.getRecipientId().isPresent()) {
+ recipientPosts.put(post.getRecipientId().get(), postId);
+ }
+ postTimes.put(postId, post.getTime());
+ postTexts.put(postId, post.getText());
+ }
+
+ void removePostsFor(String soneId) {
+ lock.writeLock().lock();
+ try {
+ for (String postId : sonePosts.removeAll(soneId)) {
+ removePost(postId);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void removePost(String postId) {
+ lock.writeLock().lock();
+ try {
+ postSones.remove(postId);
+ String recipient = postRecipients.remove(postId);
+ if (recipient != null) {
+ recipientPosts.remove(recipient, postId);
+ }
+ postTimes.remove(postId);
+ postTexts.remove(postId);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ boolean isPostKnown(String postId) {
+ lock.readLock().lock();
+ try {
+ return knownPosts.contains(postId);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void setPostKnown(String postId, boolean known) {
+ lock.writeLock().lock();
+ try {
+ if (known) {
+ knownPosts.add(postId);
+ } else {
+ knownPosts.remove(postId);
+ }
+ saveKnownPosts();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void loadKnownPosts() {
+ Set<String> knownPosts = configurationLoader.loadKnownPosts();
+ lock.writeLock().lock();
+ try {
+ this.knownPosts.clear();
+ this.knownPosts.addAll(knownPosts);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void saveKnownPosts() {
+ lock.readLock().lock();
+ try {
+ configurationLoader.saveKnownPosts(knownPosts);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ private Function<String, Optional<Post>> postLoader = new Function<String, Optional<Post>>() {
+ @Override
+ public Optional<Post> apply(String input) {
+ return getPost(input);
+ }
+ };
+
+}
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
import net.pterodactylus.sone.TestValue;
import net.pterodactylus.util.config.Value;
import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
/**
* Unit test for {@link ConfigurationLoader}.
}
@Test
+ public void loaderCanSaveKnownPosts() throws ConfigurationException {
+ Map<String, Value<String>> configurationValues = prepareConfigurationValues();
+ HashSet<String> originalPosts = new LinkedHashSet<String>(asList("Post1", "Post2"));
+ configurationLoader.saveKnownPosts(originalPosts);
+ assertThat(configurationValues.get("KnownPosts/0/ID").getValue(), is("Post1"));
+ assertThat(configurationValues.get("KnownPosts/1/ID").getValue(), is("Post2"));
+ assertThat(configurationValues.get("KnownPosts/2/ID").getValue(), nullValue());
+ }
+
+ private Map<String, Value<String>> prepareConfigurationValues() {
+ final Map<String, Value<String>> configurationValues = new HashMap<String, Value<String>>();
+ when(configuration.getStringValue(anyString())).thenAnswer(new Answer<Value<String>>() {
+ @Override
+ public Value<String> answer(InvocationOnMock invocation) throws Throwable {
+ Value<String> stringValue = TestValue.from(null);
+ configurationValues.put((String) invocation.getArguments()[0], stringValue);
+ return stringValue;
+ }
+ });
+ return configurationValues;
+ }
+
+ @Test
public void loaderCanLoadKnownPostReplies() {
when(configuration.getStringValue("KnownReplies/0/ID"))
.thenReturn(TestValue.from("PostReply2"));