From a43efdbac39a7462d5b9392ac064ea0e1042abb4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sat, 9 Feb 2013 03:36:15 +0100 Subject: [PATCH] Add and use memory-based post reply implementation. --- .../java/net/pterodactylus/sone/core/Core.java | 190 ++++-------- .../net/pterodactylus/sone/database/Database.java | 39 +++ .../sone/database/DatabaseException.java | 66 ++++ .../pterodactylus/sone/database/PostDatabase.java | 30 +- .../sone/database/PostReplyDatabase.java | 30 ++ .../sone/database/PostReplyProvider.java | 1 - .../sone/database/PostReplyStore.java | 69 +++++ .../sone/database/memory/MemoryDatabase.java | 342 +++++++++++++++++++-- .../sone/database/memory/MemoryPost.java | 4 +- .../sone/database/memory/MemoryPostReply.java | 180 +++++++++++ .../net/pterodactylus/sone/main/SonePlugin.java | 16 +- 11 files changed, 774 insertions(+), 193 deletions(-) create mode 100644 src/main/java/net/pterodactylus/sone/database/Database.java create mode 100644 src/main/java/net/pterodactylus/sone/database/DatabaseException.java create mode 100644 src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java create mode 100644 src/main/java/net/pterodactylus/sone/database/PostReplyStore.java create mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index c684695..2d24690 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -64,11 +64,11 @@ import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.Sone.ShowCustomAvatars; import net.pterodactylus.sone.data.Sone.SoneStatus; import net.pterodactylus.sone.data.TemporaryImage; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.DatabaseException; import net.pterodactylus.sone.database.PostBuilder; -import net.pterodactylus.sone.database.PostDatabase; import net.pterodactylus.sone.database.PostProvider; import net.pterodactylus.sone.database.PostReplyBuilder; -import net.pterodactylus.sone.database.PostReplyBuilderFactory; import net.pterodactylus.sone.database.PostReplyProvider; import net.pterodactylus.sone.database.SoneProvider; import net.pterodactylus.sone.fcp.FcpInterface; @@ -90,13 +90,10 @@ import net.pterodactylus.util.number.Numbers; import net.pterodactylus.util.service.AbstractService; import net.pterodactylus.util.thread.NamedThreadFactory; -import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Collections2; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.Ordering; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; @@ -178,16 +175,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, private final Set knownSones = new HashSet(); /** The post database. */ - private final PostDatabase postDatabase; - - /** The post reply builder factory. */ - private final PostReplyBuilderFactory postReplyBuilderFactory; - - /** All replies. */ - private final Map replies = new HashMap(); - - /** All known replies. */ - private final Set knownReplies = new HashSet(); + private final Database database; /** All bookmarked posts. */ /* synchronize access on itself. */ @@ -224,13 +212,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, * The WebOfTrust updater * @param eventBus * The event bus - * @param postDatabase - * The post database - * @param postReplyBuilderFactory - * The post reply builder factory + * @param database + * The database */ @Inject - public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostDatabase postDatabase, PostReplyBuilderFactory postReplyBuilderFactory) { + public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) { super("Sone Core"); this.configuration = configuration; this.freenetInterface = freenetInterface; @@ -240,8 +226,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, this.updateChecker = new UpdateChecker(eventBus, freenetInterface); this.webOfTrustUpdater = webOfTrustUpdater; this.eventBus = eventBus; - this.postDatabase = postDatabase; - this.postReplyBuilderFactory = postReplyBuilderFactory; + this.database = database; } // @@ -492,7 +477,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, * @return A new post builder */ public PostBuilder postBuilder() { - return postDatabase.newPostBuilder(); + return database.newPostBuilder(); } /** @@ -500,7 +485,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Override public Optional getPost(String postId) { - return postDatabase.getPost(postId); + return database.getPost(postId); } /** @@ -508,7 +493,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Override public Collection getPosts(String soneId) { - return postDatabase.getPosts(soneId); + return database.getPosts(soneId); } /** @@ -517,7 +502,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, @Override public Collection getDirectedPosts(final String recipientId) { checkNotNull(recipientId, "recipient must not be null"); - return postDatabase.getDirectedPosts(recipientId); + return database.getDirectedPosts(recipientId); } /** @@ -526,7 +511,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, * @return A new post reply builder */ public PostReplyBuilder postReplyBuilder() { - return postReplyBuilderFactory.newPostReplyBuilder(); + return database.newPostReplyBuilder(); } /** @@ -534,9 +519,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Override public Optional getPostReply(String replyId) { - synchronized (replies) { - return Optional.fromNullable(replies.get(replyId)); - } + return database.getPostReply(replyId); } /** @@ -544,19 +527,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Override public List getReplies(final String postId) { - return Ordering.from(Reply.TIME_COMPARATOR).sortedCopy(FluentIterable.from(getSones()).transformAndConcat(new Function>() { - - @Override - public Iterable apply(Sone sone) { - return sone.getReplies(); - } - }).filter(new Predicate() { - - @Override - public boolean apply(PostReply reply) { - return postId.equals(reply.getPostId()); - } - })); + return database.getReplies(postId); } /** @@ -1017,7 +988,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, return; } /* find removed posts. */ - Collection existingPosts = postDatabase.getPosts(sone.getId()); + Collection existingPosts = database.getPosts(sone.getId()); for (Post oldPost : existingPosts) { if (!sone.getPosts().contains(oldPost)) { eventBus.post(new PostRemovedEvent(oldPost)); @@ -1035,32 +1006,26 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, } } /* store posts. */ - postDatabase.storePosts(sone, sone.getPosts()); - synchronized (replies) { - if (!soneRescueMode) { - for (PostReply reply : storedSone.get().getReplies()) { - replies.remove(reply.getId()); - if (!sone.getReplies().contains(reply)) { - eventBus.post(new PostReplyRemovedEvent(reply)); - } + database.storePosts(sone, sone.getPosts()); + if (!soneRescueMode) { + for (PostReply reply : storedSone.get().getReplies()) { + if (!sone.getReplies().contains(reply)) { + eventBus.post(new PostReplyRemovedEvent(reply)); } } - Set storedReplies = storedSone.get().getReplies(); - synchronized (knownReplies) { - for (PostReply reply : sone.getReplies()) { - reply.setKnown(knownReplies.contains(reply.getId())); - if (!storedReplies.contains(reply)) { - if (reply.getTime() < getSoneFollowingTime(sone)) { - knownReplies.add(reply.getId()); - reply.setKnown(true); - } else if (!knownReplies.contains(reply.getId())) { - eventBus.post(new NewPostReplyFoundEvent(reply)); - } - } - replies.put(reply.getId(), reply); - } + } + Set storedReplies = storedSone.get().getReplies(); + for (PostReply reply : sone.getReplies()) { + if (storedReplies.contains(reply)) { + continue; + } + if (reply.getTime() < getSoneFollowingTime(sone)) { + reply.setKnown(true); + } else if (!reply.isKnown()) { + eventBus.post(new NewPostReplyFoundEvent(reply)); } } + database.storePostReplies(sone, sone.getReplies()); synchronized (albums) { synchronized (images) { for (Album album : storedSone.get().getAlbums()) { @@ -1220,8 +1185,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, logger.log(Level.WARNING, "Invalid reply found, aborting load!"); return; } - PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder(); - postReplyBuilder.withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText); + PostReplyBuilder postReplyBuilder = postReplyBuilder().withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText); replies.add(postReplyBuilder.build()); } @@ -1349,19 +1313,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, knownSones.add(friend); } } - postDatabase.storePosts(sone, posts); + database.storePosts(sone, posts); for (Post post : posts) { post.setKnown(true); } - synchronized (this.replies) { - for (PostReply postReply : replies) { - this.replies.put(postReply.getId(), postReply); - } - } - synchronized (knownReplies) { - for (PostReply reply : replies) { - knownReplies.add(reply.getId()); - } + database.storePostReplies(sone, replies); + for (PostReply reply : replies) { + reply.setKnown(true); } } @@ -1430,13 +1388,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone)); return null; } - PostBuilder postBuilder = postDatabase.newPostBuilder(); + PostBuilder postBuilder = database.newPostBuilder(); postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim()); if (recipient.isPresent()) { postBuilder.to(recipient.get().getId()); } final Post post = postBuilder.build(); - postDatabase.storePost(post); + database.storePost(post); eventBus.post(new NewPostFoundEvent(post)); sone.addPost(post); touchConfiguration(); @@ -1464,7 +1422,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, logger.log(Level.WARNING, String.format("Tried to delete post of non-local Sone: %s", post.getSone())); return; } - postDatabase.removePost(post); + database.removePost(post); eventBus.post(new PostRemovedEvent(post)); markPostKnown(post); touchConfiguration(); @@ -1548,15 +1506,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone)); return null; } - PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder(); + PostReplyBuilder postReplyBuilder = postReplyBuilder(); postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim()); final PostReply reply = postReplyBuilder.build(); - synchronized (replies) { - replies.put(reply.getId(), reply); - } - synchronized (knownReplies) { - eventBus.post(new NewPostReplyFoundEvent(reply)); - } + database.storePostReply(reply); + eventBus.post(new NewPostReplyFoundEvent(reply)); sone.addReply(reply); touchConfiguration(); localElementTicker.schedule(new Runnable() { @@ -1584,13 +1538,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, logger.log(Level.FINE, String.format("Tried to delete non-local reply: %s", reply)); return; } - synchronized (replies) { - replies.remove(reply.getId()); - } - synchronized (knownReplies) { - markReplyKnown(reply); - knownReplies.remove(reply.getId()); - } + database.removePostReply(reply); + markReplyKnown(reply); sone.removeReply(reply); touchConfiguration(); } @@ -1603,12 +1552,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, * The reply to mark as known */ public void markReplyKnown(PostReply reply) { + boolean previouslyKnown = reply.isKnown(); reply.setKnown(true); - synchronized (knownReplies) { - eventBus.post(new MarkPostReplyKnownEvent(reply)); - if (knownReplies.add(reply.getId())) { - touchConfiguration(); - } + eventBus.post(new MarkPostReplyKnownEvent(reply)); + if (!previouslyKnown) { + touchConfiguration(); } } @@ -1785,6 +1733,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, identityManager.start(); webOfTrustUpdater.init(); webOfTrustUpdater.start(); + database.start(); } /** @@ -1819,6 +1768,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, } } saveConfiguration(); + database.stop(); webOfTrustUpdater.stop(); updateChecker.stop(); soneDownloader.stop(); @@ -2016,16 +1966,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, } /* save known posts. */ - postDatabase.saveKnownPosts(configuration, "KnownPosts/"); - - /* save known replies. */ - int replyCounter = 0; - synchronized (knownReplies) { - for (String knownReplyId : knownReplies) { - configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); - } - configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); - } + database.save(); /* save bookmarked posts. */ int bookmarkedPostCounter = 0; @@ -2041,6 +1982,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, } catch (ConfigurationException ce1) { logger.log(Level.SEVERE, "Could not store configuration!", ce1); + } catch (DatabaseException de1) { + logger.log(Level.SEVERE, "Could not save database!", de1); } finally { synchronized (configuration) { storingConfiguration = false; @@ -2125,21 +2068,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, ++soneCounter; } - /* load known posts. */ - postDatabase.loadKnownPosts(configuration, "KnownPosts/"); - - /* load known replies. */ - int replyCounter = 0; - while (true) { - String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null); - if (knownReplyId == null) { - break; - } - synchronized (knownReplies) { - knownReplies.add(knownReplyId); - } - } - /* load bookmarked posts. */ int bookmarkedPostCounter = 0; while (true) { @@ -2263,17 +2191,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, /* TODO - we don’t have the Sone anymore. should this happen? */ return; } - postDatabase.removePosts(sone.get()); + database.removePosts(sone.get()); for (Post post : sone.get().getPosts()) { eventBus.post(new PostRemovedEvent(post)); } - synchronized (replies) { - synchronized (knownReplies) { - for (PostReply reply : sone.get().getReplies()) { - replies.remove(reply.getId()); - eventBus.post(new PostReplyRemovedEvent(reply)); - } - } + database.removePostReplies(sone.get()); + for (PostReply reply : sone.get().getReplies()) { + eventBus.post(new PostReplyRemovedEvent(reply)); } synchronized (sones) { sones.remove(identity.getId()); diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java new file mode 100644 index 0000000..c93c061 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/Database.java @@ -0,0 +1,39 @@ +/* + * Sone - Database.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database; + +import com.google.common.util.concurrent.Service; + +/** + * Database for Sone data. This interface combines the various provider, store, + * and builder factory interfaces into a single interface and adds some methods + * necessary for lifecycle management. + * + * @author David ‘Bombe’ Roden + */ +public interface Database extends Service, PostDatabase, PostReplyDatabase { + + /** + * Saves the database. + * + * @throws DatabaseException + * if an error occurs while saving + */ + public void save() throws DatabaseException; + +} diff --git a/src/main/java/net/pterodactylus/sone/database/DatabaseException.java b/src/main/java/net/pterodactylus/sone/database/DatabaseException.java new file mode 100644 index 0000000..43d3e77 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/DatabaseException.java @@ -0,0 +1,66 @@ +/* + * Sone - DatabaseException.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database; + +/** + * Exception that signals a database error. + * + * @author David ‘Bombe’ Roden + */ +public class DatabaseException extends Exception { + + /** + * Creates a new database exception. + */ + public DatabaseException() { + super(); + } + + /** + * Creates a new database exception. + * + * @param message + * The message of the exception + */ + public DatabaseException(String message) { + super(message); + } + + /** + * Creates a new database exception. + * + * @param cause + * The cause of the exception + */ + public DatabaseException(Throwable cause) { + super(cause); + } + + /** + * Creates a new database exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public DatabaseException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/database/PostDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostDatabase.java index 1f9a2a8..40e6290 100644 --- a/src/main/java/net/pterodactylus/sone/database/PostDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/PostDatabase.java @@ -17,9 +17,6 @@ package net.pterodactylus.sone.database; -import net.pterodactylus.util.config.Configuration; -import net.pterodactylus.util.config.ConfigurationException; - /** * Combines a {@link PostProvider}, a {@link PostBuilderFactory}, and a * {@link PostStore} into a complete post database. @@ -28,31 +25,6 @@ import net.pterodactylus.util.config.ConfigurationException; */ public interface PostDatabase extends PostProvider, PostBuilderFactory, PostStore { - /* - * these methods have to be here until the database knows how to save its - * own stuff. all the configuration-specific stuff will have to leave! - */ - - /** - * Loads the knows posts. - * - * @param configuration - * The configuration to load the known posts from - * @param prefix - * The prefix for the configuration keys - */ - public void loadKnownPosts(Configuration configuration, String prefix); - - /** - * Saves the knows posts. - * - * @param configuration - * The configuration to save the known posts to - * @param prefix - * The prefix for the configuration keys - * @throws ConfigurationException - * if a value can not be stored in the configuration - */ - public void saveKnownPosts(Configuration configuration, String prefix) throws ConfigurationException; + /* nothing here. */ } diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java new file mode 100644 index 0000000..c9a809f --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java @@ -0,0 +1,30 @@ +/* + * Sone - PostReplyDatabase.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database; + +/** + * Combines a {@link PostReplyProvider}, a {@link PostReplyBuilderFactory}, and + * a {@link PostReplyStore} into a complete post reply database. + * + * @author David ‘Bombe’ Roden + */ +public interface PostReplyDatabase extends PostReplyProvider, PostReplyBuilderFactory, PostReplyStore { + + /* nothing here. */ + +} diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java b/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java index 950012f..e186e5b 100644 --- a/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java +++ b/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java @@ -19,7 +19,6 @@ package net.pterodactylus.sone.database; import java.util.List; -import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; import com.google.common.base.Optional; diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyStore.java b/src/main/java/net/pterodactylus/sone/database/PostReplyStore.java new file mode 100644 index 0000000..a3cefb3 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/PostReplyStore.java @@ -0,0 +1,69 @@ +/* + * Sone - PostReplyStore.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database; + +import java.util.Collection; + +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; + +/** + * Defines a store for {@link PostReply post replies}. + * + * @author David ‘Bombe’ Roden + */ +public interface PostReplyStore { + + /** + * Stores the given post reply. + * + * @param postReply + * The post reply + */ + public void storePostReply(PostReply postReply); + + /** + * Stores the given post replies as exclusive collection of post replies for + * the given Sone. This will remove all other post replies from this Sone! + * + * @param sone + * The Sone to store all post replies for + * @param postReplies + * The post replies of the Sone + * @throws IllegalArgumentException + * if one of the replies does not belong to the given Sone + */ + public void storePostReplies(Sone sone, Collection postReplies) throws IllegalArgumentException; + + /** + * Removes the given post reply from this store. + * + * @param postReply + * The post reply to remove + */ + public void removePostReply(PostReply postReply); + + /** + * Removes all post replies of the given Sone. + * + * @param sone + * The Sone to remove all post replies for + */ + public void removePostReplies(Sone sone); + +} 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 eed00ea..35d3c37 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java @@ -1,5 +1,5 @@ /* - * Sone - MemoryPostDatabase.java - Copyright © 2013 David Roden + * Sone - MemoryDatabase.java - Copyright © 2013 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,26 +19,37 @@ package net.pterodactylus.sone.database.memory; import static com.google.common.base.Preconditions.checkNotNull; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; 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.UUID; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Reply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.data.impl.AbstractPostBuilder; +import net.pterodactylus.sone.data.impl.AbstractPostReplyBuilder; +import net.pterodactylus.sone.database.Database; +import net.pterodactylus.sone.database.DatabaseException; import net.pterodactylus.sone.database.PostBuilder; import net.pterodactylus.sone.database.PostDatabase; +import net.pterodactylus.sone.database.PostReplyBuilder; import net.pterodactylus.sone.database.SoneProvider; import net.pterodactylus.util.config.Configuration; import net.pterodactylus.util.config.ConfigurationException; import com.google.common.base.Optional; +import com.google.common.util.concurrent.AbstractService; import com.google.inject.Inject; /** @@ -46,7 +57,7 @@ import com.google.inject.Inject; * * @author David ‘Bombe’ Roden */ -public class MemoryPostDatabase implements PostDatabase { +public class MemoryDatabase extends AbstractService implements Database { /** The lock. */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -54,6 +65,9 @@ public class MemoryPostDatabase implements PostDatabase { /** The Sone provider. */ private final SoneProvider soneProvider; + /** The configuration. */ + private final Configuration configuration; + /** All posts by their ID. */ private final Map allPosts = new HashMap(); @@ -66,15 +80,70 @@ public class MemoryPostDatabase implements PostDatabase { /** Whether posts are known. */ private final Set knownPosts = new HashSet(); + /** All post replies by their ID. */ + private final Map allPostReplies = new HashMap(); + + /** Replies by post. */ + private final Map> postReplies = new HashMap>(); + + /** Whether post replies are known. */ + private final Set knownPostReplies = new HashSet(); + /** * Creates a new memory database. * * @param soneProvider * The Sone provider + * @param configuration + * The configuration for loading and saving elements */ @Inject - public MemoryPostDatabase(SoneProvider soneProvider) { + public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) { this.soneProvider = soneProvider; + this.configuration = configuration; + } + + // + // DATABASE METHODS + // + + /** + * Saves the database. + * + * @throws DatabaseException + * if an error occurs while saving + */ + @Override + public void save() throws DatabaseException { + saveKnownPosts(); + saveKnownPostReplies(); + } + + // + // SERVICE METHODS + // + + /** + * {@inheritDocs} + */ + @Override + protected void doStart() { + loadKnownPosts(); + loadKnownPostReplies(); + notifyStarted(); + } + + /** + * {@inheritDocs} + */ + @Override + protected void doStop() { + try { + save(); + notifyStopped(); + } catch (DatabaseException de1) { + notifyFailed(de1); + } } // @@ -228,23 +297,98 @@ public class MemoryPostDatabase implements PostDatabase { } // - // POSTDATABASE METHODS + // POSTREPLYPROVIDER METHODS // /** * {@inheritDocs} */ @Override - public void loadKnownPosts(Configuration configuration, String prefix) { + public Optional getPostReply(String id) { + lock.readLock().lock(); + try { + return Optional.fromNullable(allPostReplies.get(id)); + } finally { + lock.readLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public List getReplies(String postId) { + lock.readLock().lock(); + try { + if (!postReplies.containsKey(postId)) { + return Collections.emptyList(); + } + return new ArrayList(postReplies.get(postId)); + } finally { + lock.readLock().unlock(); + } + } + + // + // POSTREPLYBUILDERFACTORY METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public PostReplyBuilder newPostReplyBuilder() { + return new MemoryPostReplyBuilder(); + } + + // + // POSTREPLYSTORE METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public void storePostReply(PostReply postReply) { lock.writeLock().lock(); try { - int postCounter = 0; - while (true) { - String knownPostId = configuration.getStringValue(prefix + postCounter++ + "/ID").getValue(null); - if (knownPostId == null) { - break; + 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 { + for (PostReply postReply : postReplies) { + allPostReplies.put(postReply.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); } - knownPosts.add(knownPostId); } } finally { lock.writeLock().unlock(); @@ -255,16 +399,35 @@ public class MemoryPostDatabase implements PostDatabase { * {@inheritDocs} */ @Override - public void saveKnownPosts(Configuration configuration, String prefix) throws ConfigurationException { - lock.readLock().lock(); + public void removePostReply(PostReply postReply) { + lock.writeLock().lock(); try { - int postCounter = 0; - for (String knownPostId : knownPosts) { - configuration.getStringValue(prefix + postCounter++ + "/ID").setValue(knownPostId); + 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()); + } } - configuration.getStringValue(prefix + postCounter + "/ID").setValue(null); } finally { - lock.readLock().unlock(); + 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(); } } @@ -309,6 +472,45 @@ public class MemoryPostDatabase implements PostDatabase { } } + /** + * Returns whether the given post reply is known. + * + * @param postReply + * The post reply + * @return {@code true} if the given post reply is known, {@code false} + * otherwise + */ + boolean isPostReplyKnown(PostReply postReply) { + lock.readLock().lock(); + try { + return knownPostReplies.contains(postReply.getId()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Sets whether the given post reply is known. + * + * @param postReply + * The post reply + * @param known + * {@code true} if the post reply is known, {@code false} + * otherwise + */ + void setPostReplyKnown(PostReply postReply, boolean known) { + lock.writeLock().lock(); + try { + if (known) { + knownPostReplies.add(postReply.getId()); + } else { + knownPostReplies.remove(postReply.getId()); + } + } finally { + lock.writeLock().unlock(); + } + } + // // PRIVATE METHODS // @@ -376,6 +578,86 @@ public class MemoryPostDatabase implements PostDatabase { } /** + * 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(); + } + } + + /** + * Loads the known post replies. + */ + private void 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); + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Saves the known post replies to the configuration. + * + * @throws DatabaseException + * if a configuration error occurs + */ + private void saveKnownPostReplies() throws DatabaseException { + lock.readLock().lock(); + try { + int replyCounter = 0; + for (String knownReplyId : knownPostReplies) { + configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId); + } + configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null); + } catch (ConfigurationException ce1) { + throw new DatabaseException("Could not save database.", ce1); + } finally { + lock.readLock().unlock(); + } + } + + /** * {@link PostBuilder} implementation that creates a {@link MemoryPost}. * * @author David ‘Bombe’ Roden @@ -398,11 +680,33 @@ public class MemoryPostDatabase implements PostDatabase { @Override public Post build() throws IllegalStateException { validate(); - Post post = new MemoryPost(MemoryPostDatabase.this, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text); + Post post = new MemoryPost(MemoryDatabase.this, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text); post.setKnown(isPostKnown(post)); return post; } } + /** + * {@link PostReplyBuilder} implementation that creates + * {@link MemoryPostReply} objects. + * + * @author David ‘Bombe’ Roden + */ + private class MemoryPostReplyBuilder extends AbstractPostReplyBuilder { + + /** + * {@inheritDocs} + */ + @Override + public PostReply build() throws IllegalStateException { + validate(); + + PostReply postReply = new MemoryPostReply(MemoryDatabase.this, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, currentTime ? System.currentTimeMillis() : time, text, postId); + postReply.setKnown(isPostReplyKnown(postReply)); + return postReply; + } + + } + } diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java index 84f3714..22fa7e6 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java @@ -34,7 +34,7 @@ import com.google.common.base.Optional; class MemoryPost implements Post { /** The post database. */ - private final MemoryPostDatabase postDatabase; + private final MemoryDatabase postDatabase; /** The Sone provider. */ private final SoneProvider soneProvider; @@ -72,7 +72,7 @@ class MemoryPost implements Post { * @param text * The text of the post */ - public MemoryPost(MemoryPostDatabase postDatabase, SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) { + public MemoryPost(MemoryDatabase postDatabase, SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) { this.postDatabase = postDatabase; this.soneProvider = soneProvider; this.id = UUID.fromString(id); diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java new file mode 100644 index 0000000..a6686ca --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java @@ -0,0 +1,180 @@ +/* + * Sone - MemoryPostReply.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.SoneProvider; + +import com.google.common.base.Optional; + +/** + * Memory-based {@link PostReply} implementation. + * + * @author David ‘Bombe’ Roden + */ +class MemoryPostReply implements PostReply { + + /** The database. */ + private final MemoryDatabase database; + + /** The Sone provider. */ + private final SoneProvider soneProvider; + + /** The ID of the post reply. */ + private final String id; + + /** The ID of the owning Sone. */ + private final String soneId; + + /** The time of the post reply. */ + private final long time; + + /** The text of the post reply. */ + private final String text; + + /** The ID of the post this post reply refers to. */ + private final String postId; + + /** + * Creates a new memory-based {@link PostReply} implementation. + * + * @param database + * The database + * @param soneProvider + * The Sone provider + * @param id + * The ID of the post reply + * @param soneId + * The ID of the owning Sone + * @param time + * The time of the post reply + * @param text + * The text of the post reply + * @param postId + * The ID of the post this post reply refers to + */ + public MemoryPostReply(MemoryDatabase database, SoneProvider soneProvider, String id, String soneId, long time, String text, String postId) { + this.database = database; + this.soneProvider = soneProvider; + this.id = id; + this.soneId = soneId; + this.time = time; + this.text = text; + this.postId = postId; + } + + // + // REPLY METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public String getId() { + return id; + } + + /** + * {@inheritDocs} + */ + @Override + public Sone getSone() { + return soneProvider.getSone(soneId).get(); + } + + /** + * {@inheritDocs} + */ + @Override + public long getTime() { + return time; + } + + /** + * {@inheritDocs} + */ + @Override + public String getText() { + return text; + } + + /** + * {@inheritDocs} + */ + @Override + public boolean isKnown() { + return database.isPostReplyKnown(this); + } + + /** + * {@inheritDocs} + */ + @Override + public PostReply setKnown(boolean known) { + database.setPostReplyKnown(this, known); + return this; + } + + // + // POSTREPLY METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public String getPostId() { + return postId; + } + + /** + * {@inheritDocs} + */ + @Override + public Optional getPost() { + return database.getPost(postId); + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDocs} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof MemoryPostReply)) { + return false; + } + MemoryPostReply memoryPostReply = (MemoryPostReply) object; + return memoryPostReply.id.equals(id); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java index f389da1..26e8bf2 100644 --- a/src/main/java/net/pterodactylus/sone/main/SonePlugin.java +++ b/src/main/java/net/pterodactylus/sone/main/SonePlugin.java @@ -25,14 +25,12 @@ import java.util.logging.Logger; import net.pterodactylus.sone.core.Core; import net.pterodactylus.sone.core.FreenetInterface; import net.pterodactylus.sone.core.WebOfTrustUpdater; -import net.pterodactylus.sone.data.impl.DefaultPostBuilderFactory; -import net.pterodactylus.sone.data.impl.DefaultPostReplyBuilderFactory; +import net.pterodactylus.sone.database.Database; import net.pterodactylus.sone.database.PostBuilderFactory; -import net.pterodactylus.sone.database.PostDatabase; import net.pterodactylus.sone.database.PostProvider; import net.pterodactylus.sone.database.PostReplyBuilderFactory; import net.pterodactylus.sone.database.SoneProvider; -import net.pterodactylus.sone.database.memory.MemoryPostDatabase; +import net.pterodactylus.sone.database.memory.MemoryDatabase; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend; import net.pterodactylus.sone.freenet.plugin.PluginConnector; @@ -216,7 +214,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr @Override protected void configure() { bind(Core.class).in(Singleton.class); - bind(MemoryPostDatabase.class).in(Singleton.class); + bind(MemoryDatabase.class).in(Singleton.class); bind(EventBus.class).toInstance(eventBus); bind(Configuration.class).toInstance(startConfiguration); bind(FreenetInterface.class).in(Singleton.class); @@ -227,11 +225,11 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr bind(String.class).annotatedWith(Names.named("WebOfTrustContext")).toInstance("Sone"); bind(SonePlugin.class).toInstance(SonePlugin.this); bind(FcpInterface.class).in(Singleton.class); - bind(PostDatabase.class).to(MemoryPostDatabase.class); - bind(PostBuilderFactory.class).to(MemoryPostDatabase.class); - bind(PostReplyBuilderFactory.class).to(DefaultPostReplyBuilderFactory.class).in(Singleton.class); + bind(Database.class).to(MemoryDatabase.class); + bind(PostBuilderFactory.class).to(MemoryDatabase.class); + bind(PostReplyBuilderFactory.class).to(MemoryDatabase.class); bind(SoneProvider.class).to(Core.class).in(Singleton.class); - bind(PostProvider.class).to(MemoryPostDatabase.class); + bind(PostProvider.class).to(MemoryDatabase.class); bindListener(Matchers.any(), new TypeListener() { @Override -- 2.7.4