X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fcore%2FCore.java;h=bafe7a80406f5c730ff6876be6db07de5ec461c2;hp=5a54f76349aa9af3774fb83d8e81b37a2c562773;hb=d535f744751ba449e0f34fe3a42f1020988f14be;hpb=9cb50c365e3bf7a74ffa40a45630a0e7be40b526 diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 5a54f76..bafe7a8 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -32,6 +32,8 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,7 +56,11 @@ import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Client; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.PostBuilder; +import net.pterodactylus.sone.data.PostBuilderFactory; import net.pterodactylus.sone.data.PostReply; +import net.pterodactylus.sone.data.PostReplyBuilder; +import net.pterodactylus.sone.data.PostReplyBuilderFactory; import net.pterodactylus.sone.data.Profile; import net.pterodactylus.sone.data.Profile.Field; import net.pterodactylus.sone.data.Reply; @@ -62,8 +68,6 @@ 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.data.impl.PostImpl; -import net.pterodactylus.sone.data.impl.PostReplyImpl; import net.pterodactylus.sone.fcp.FcpInterface; import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired; import net.pterodactylus.sone.freenet.wot.Identity; @@ -82,7 +86,6 @@ import net.pterodactylus.util.logging.Logging; import net.pterodactylus.util.number.Numbers; import net.pterodactylus.util.service.AbstractService; import net.pterodactylus.util.thread.NamedThreadFactory; -import net.pterodactylus.util.thread.Ticker; import com.google.common.base.Predicate; import com.google.common.base.Predicates; @@ -98,7 +101,7 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Core extends AbstractService implements SoneProvider, PostProvider { +public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider { /** The logger. */ private static final Logger logger = Logging.getLogger(Core.class); @@ -146,7 +149,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider private volatile FcpInterface fcpInterface; /** The times Sones were followed. */ - private final Map soneFollowingTimes = new HashMap(); + private final Map soneFollowingTimes = new HashMap(); /** Locked local Sones. */ /* synchronize on itself. */ @@ -167,12 +170,18 @@ public class Core extends AbstractService implements SoneProvider, PostProvider /** All known Sones. */ private final Set knownSones = new HashSet(); + /** The post builder. */ + private final PostBuilderFactory postBuilderFactory; + /** All posts. */ private final Map posts = new HashMap(); /** All known posts. */ private final Set knownPosts = new HashSet(); + /** The post reply builder factory. */ + private final PostReplyBuilderFactory postReplyBuilderFactory; + /** All replies. */ private final Map replies = new HashMap(); @@ -196,7 +205,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider private final Map temporaryImages = new HashMap(); /** Ticker for threads that mark own elements as known. */ - private final Ticker localElementTicker = new Ticker(); + private final ScheduledExecutorService localElementTicker = Executors.newScheduledThreadPool(1); /** The time the configuration was last touched. */ private volatile long lastConfigurationUpdate; @@ -214,9 +223,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider * The WebOfTrust updater * @param eventBus * The event bus + * @param postBuilderFactory + * The post builder + * @param postReplyBuilderFactory + * The post reply builder factory */ @Inject - public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus) { + public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostBuilderFactory postBuilderFactory, PostReplyBuilderFactory postReplyBuilderFactory) { super("Sone Core"); this.configuration = configuration; this.freenetInterface = freenetInterface; @@ -226,6 +239,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider this.updateChecker = new UpdateChecker(eventBus, freenetInterface); this.webOfTrustUpdater = webOfTrustUpdater; this.eventBus = eventBus; + this.postBuilderFactory = postBuilderFactory; + this.postReplyBuilderFactory = postReplyBuilderFactory; } // @@ -342,30 +357,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider * @return The Sone with the given ID, or {@code null} if there is no such * Sone */ - public Sone getSone(String id) { - return getSone(id, true); - } - - /** - * Returns the Sone with the given ID, regardless whether it’s local or - * remote. - * - * @param id - * The ID of the Sone to get - * @param create - * {@code true} to create a new Sone if none exists, - * {@code false} to return {@code null} if a Sone with the given - * ID does not exist - * @return The Sone with the given ID, or {@code null} if there is no such - * Sone - */ @Override - public Sone getSone(String id, boolean create) { + public Sone getSone(String id) { synchronized (sones) { - if (!sones.containsKey(id) && create) { - Sone sone = new Sone(id, false); - sones.put(id, sone); - } return sones.get(id); } } @@ -510,89 +504,63 @@ public class Core extends AbstractService implements SoneProvider, PostProvider } /** - * Returns the post with the given ID. + * Returns a post builder. * - * @param postId - * The ID of the post to get - * @return The post with the given ID, or a new post with the given ID + * @return A new post builder */ - public Post getPost(String postId) { - return getPost(postId, true); + public PostBuilder postBuilder() { + return postBuilderFactory.newPostBuilder(); } /** - * Returns the post with the given ID, optionally creating a new post. - * - * @param postId - * The ID of the post to get - * @param create - * {@code true} it create a new post if no post with the given ID - * exists, {@code false} to return {@code null} - * @return The post, or {@code null} if there is no such post + * {@inheritDoc} */ @Override - public Post getPost(String postId, boolean create) { + public Post getPost(String postId) { synchronized (posts) { - Post post = posts.get(postId); - if ((post == null) && create) { - post = new PostImpl(postId); - posts.put(postId, post); - } - return post; + return posts.get(postId); } } /** - * Returns all posts that have the given Sone as recipient. - * - * @see Post#getRecipient() - * @param recipient - * The recipient of the posts - * @return All posts that have the given Sone as recipient + * {@inheritDoc} */ - public Set getDirectedPosts(Sone recipient) { - checkNotNull(recipient, "recipient must not be null"); - Set directedPosts = new HashSet(); + @Override + public Collection getDirectedPosts(final String recipientId) { + checkNotNull(recipientId, "recipient must not be null"); synchronized (posts) { - for (Post post : posts.values()) { - if (recipient.equals(post.getRecipient())) { - directedPosts.add(post); + return Collections2.filter(posts.values(), new Predicate() { + + @Override + public boolean apply(Post post) { + return (post.getRecipient() != null) && (post.getRecipient().getId().equals(recipientId)); } - } + }); } - return directedPosts; } - /** - * Returns the reply with the given ID. If there is no reply with the given - * ID yet, a new one is created, unless {@code create} is false in which - * case {@code null} is returned. + * Returns a post reply builder. * - * @param replyId - * The ID of the reply to get - * @param create - * {@code true} to always return a {@link Reply}, {@code false} - * to return {@code null} if no reply can be found - * @return The reply, or {@code null} if there is no such reply + * @return A new post reply builder */ - public PostReply getPostReply(String replyId, boolean create) { + public PostReplyBuilder postReplyBuilder() { + return postReplyBuilderFactory.newPostReplyBuilder(); + } + + /** + * {@inheritDoc} + */ + @Override + public PostReply getPostReply(String replyId) { synchronized (replies) { - PostReply reply = replies.get(replyId); - if (create && (reply == null)) { - reply = new PostReplyImpl(replyId); - replies.put(replyId, reply); - } - return reply; + return replies.get(replyId); } } /** - * Returns all replies for the given post, order ascending by time. - * - * @param post - * The post to get all replies for - * @return All replies for the given post + * {@inheritDoc} */ + @Override public List getReplies(Post post) { Set sones = getSones(); List replies = new ArrayList(); @@ -676,7 +644,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider Set posts = new HashSet(); synchronized (bookmarkedPosts) { for (String bookmarkedPostId : bookmarkedPosts) { - Post post = getPost(bookmarkedPostId, false); + Post post = getPost(bookmarkedPostId); if (post != null) { posts.add(post); } @@ -857,7 +825,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption(true)); sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption(ShowCustomAvatars.NEVER)); - followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI")); + followSone(sone, "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"); touchConfiguration(); return sone; } @@ -877,7 +845,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider synchronized (sones) { final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity); boolean newSone = sone.getRequestUri() == null; - sone.setRequestUri(getSoneUri(identity.getRequestUri())); + sone.setRequestUri(SoneUri.create(identity.getRequestUri())); sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0)); if (newSone) { synchronized (knownSones) { @@ -888,7 +856,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider eventBus.post(new NewSoneFoundEvent(sone)); for (Sone localSone : getLocalSones()) { if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { - followSone(localSone, sone); + followSone(localSone, sone.getId()); } } } @@ -918,33 +886,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider public void followSone(Sone sone, String soneId) { checkNotNull(sone, "sone must not be null"); checkNotNull(soneId, "soneId must not be null"); - Sone followedSone = getSone(soneId, true); - if (followedSone == null) { - logger.log(Level.INFO, String.format("Ignored Sone with invalid ID: %s", soneId)); - return; - } - followSone(sone, getSone(soneId)); - } - - /** - * Lets the given local Sone follow the other given Sone. If the given Sone - * was not followed by any local Sone before, this will mark all elements of - * the followed Sone as read that have been created before the current - * moment. - * - * @param sone - * The local Sone that should follow the other Sone - * @param followedSone - * The Sone that should be followed - */ - public void followSone(Sone sone, Sone followedSone) { - checkNotNull(sone, "sone must not be null"); - checkNotNull(followedSone, "followedSone must not be null"); - sone.addFriend(followedSone.getId()); + sone.addFriend(soneId); synchronized (soneFollowingTimes) { - if (!soneFollowingTimes.containsKey(followedSone)) { + if (!soneFollowingTimes.containsKey(soneId)) { long now = System.currentTimeMillis(); - soneFollowingTimes.put(followedSone, now); + soneFollowingTimes.put(soneId, now); + Sone followedSone = getSone(soneId); + if (followedSone == null) { + return; + } for (Post post : followedSone.getPosts()) { if (post.getTime() < now) { markPostKnown(post); @@ -971,30 +921,14 @@ public class Core extends AbstractService implements SoneProvider, PostProvider public void unfollowSone(Sone sone, String soneId) { checkNotNull(sone, "sone must not be null"); checkNotNull(soneId, "soneId must not be null"); - unfollowSone(sone, getSone(soneId, false)); - } - - /** - * Lets the given local Sone unfollow the other given Sone. If the given - * local Sone is the last local Sone that followed the given Sone, its - * following time will be removed. - * - * @param sone - * The local Sone that should unfollow another Sone - * @param unfollowedSone - * The Sone being unfollowed - */ - public void unfollowSone(Sone sone, Sone unfollowedSone) { - checkNotNull(sone, "sone must not be null"); - checkNotNull(unfollowedSone, "unfollowedSone must not be null"); - sone.removeFriend(unfollowedSone.getId()); + sone.removeFriend(soneId); boolean unfollowedSoneStillFollowed = false; for (Sone localSone : getLocalSones()) { - unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId()); + unfollowedSoneStillFollowed |= localSone.hasFriend(soneId); } if (!unfollowedSoneStillFollowed) { synchronized (soneFollowingTimes) { - soneFollowingTimes.remove(unfollowedSone); + soneFollowingTimes.remove(soneId); } } touchConfiguration(); @@ -1109,16 +1043,18 @@ public class Core extends AbstractService implements SoneProvider, PostProvider List storedPosts = storedSone.getPosts(); synchronized (knownPosts) { for (Post post : sone.getPosts()) { - post.setSone(storedSone).setKnown(knownPosts.contains(post.getId())); - if (!storedPosts.contains(post)) { - if (post.getTime() < getSoneFollowingTime(sone)) { - knownPosts.add(post.getId()); - post.setKnown(true); - } else if (!knownPosts.contains(post.getId())) { - eventBus.post(new NewPostFoundEvent(post)); + PostBuilder postBuilder = postBuilderFactory.newPostBuilder(); + postBuilder.copyPost(post).from(storedSone.getId()); + Post newPost = postBuilder.build().setKnown(knownPosts.contains(post.getId())); + if (!storedPosts.contains(newPost)) { + if (newPost.getTime() < getSoneFollowingTime(sone)) { + knownPosts.add(newPost.getId()); + newPost.setKnown(true); + } else if (!knownPosts.contains(newPost.getId())) { + eventBus.post(new NewPostFoundEvent(newPost)); } } - posts.put(post.getId(), post); + posts.put(newPost.getId(), newPost); } } } @@ -1134,7 +1070,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider Set storedReplies = storedSone.getReplies(); synchronized (knownReplies) { for (PostReply reply : sone.getReplies()) { - reply.setSone(storedSone).setKnown(knownReplies.contains(reply.getId())); + reply.setKnown(knownReplies.contains(reply.getId())); if (!storedReplies.contains(reply)) { if (reply.getTime() < getSoneFollowingTime(sone)) { knownReplies.add(reply.getId()); @@ -1311,11 +1247,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider logger.log(Level.WARNING, "Invalid post found, aborting load!"); return; } - Post post = getPost(postId).setSone(sone).setTime(postTime).setText(postText); + PostBuilder postBuilder = postBuilderFactory.newPostBuilder().withId(postId).from(sone.getId()).withTime(postTime).withText(postText); if ((postRecipientId != null) && (postRecipientId.length() == 43)) { - post.setRecipient(getSone(postRecipientId)); + postBuilder.to(postRecipientId); } - posts.add(post); + posts.add(postBuilder.build()); } /* load replies. */ @@ -1333,7 +1269,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider logger.log(Level.WARNING, "Invalid reply found, aborting load!"); return; } - replies.add(getPostReply(replyId, true).setSone(sone).setPost(getPost(postId)).setTime(replyTime).setText(replyText)); + PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder(); + postReplyBuilder.withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText); + replies.add(postReplyBuilder.build()); } /* load post likes. */ @@ -1537,17 +1475,19 @@ 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; } - final Post post = new PostImpl(sone, time, text.trim()); + PostBuilder postBuilder = postBuilderFactory.newPostBuilder(); + postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim()); if (recipient != null) { - post.setRecipient(recipient); + postBuilder.to(recipient.getId()); } + final Post post = postBuilder.build(); synchronized (posts) { posts.put(post.getId(), post); } eventBus.post(new NewPostFoundEvent(post)); sone.addPost(post); touchConfiguration(); - localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() { + localElementTicker.schedule(new Runnable() { /** * {@inheritDoc} @@ -1556,7 +1496,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider public void run() { markPostKnown(post); } - }, "Mark " + post + " read."); + }, 10, TimeUnit.SECONDS); return post; } @@ -1656,30 +1596,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider * @return The created reply */ public PostReply createReply(Sone sone, Post post, String text) { - return createReply(sone, post, System.currentTimeMillis(), text); - } - - /** - * Creates a new reply. - * - * @param sone - * The Sone that creates the reply - * @param post - * The post that this reply refers to - * @param time - * The time of the reply - * @param text - * The text of the reply - * @return The created reply - */ - public PostReply createReply(Sone sone, Post post, long time, String text) { checkNotNull(text, "text must not be null"); checkArgument(text.trim().length() > 0, "text must not be empty"); if (!sone.isLocal()) { logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone)); return null; } - final PostReply reply = new PostReplyImpl(sone, post, System.currentTimeMillis(), text.trim()); + PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder(); + postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim()); + final PostReply reply = postReplyBuilder.build(); synchronized (replies) { replies.put(reply.getId(), reply); } @@ -1688,7 +1613,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider } sone.addReply(reply); touchConfiguration(); - localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() { + localElementTicker.schedule(new Runnable() { /** * {@inheritDoc} @@ -1697,7 +1622,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider public void run() { markReplyKnown(reply); } - }, "Mark " + reply + " read."); + }, 10, TimeUnit.SECONDS); return reply; } @@ -1940,6 +1865,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider */ @Override public void serviceStop() { + localElementTicker.shutdownNow(); synchronized (sones) { for (Entry soneInserter : soneInserters.entrySet()) { soneInserter.getValue().stop(); @@ -2134,8 +2060,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider /* save Sone following times. */ soneCounter = 0; synchronized (soneFollowingTimes) { - for (Entry soneFollowingTime : soneFollowingTimes.entrySet()) { - configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey().getId()); + for (Entry soneFollowingTime : soneFollowingTimes.entrySet()) { + configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey()); configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue()); ++soneCounter; } @@ -2258,7 +2184,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider logger.log(Level.WARNING, String.format("Ignoring Sone with invalid ID: %s", soneId)); } else { synchronized (soneFollowingTimes) { - soneFollowingTimes.put(getSone(soneId), time); + soneFollowingTimes.put(soneId, time); } } ++soneCounter; @@ -2318,23 +2244,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider } /** - * Generate a Sone URI from the given URI and latest edition. - * - * @param uriString - * The URI to derive the Sone URI from - * @return The derived URI - */ - private static FreenetURI getSoneUri(String uriString) { - try { - FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]); - return uri; - } catch (MalformedURLException mue1) { - logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uriString), mue1); - return null; - } - } - - /** * Notifies the core that a new {@link OwnIdentity} was added. * * @param ownIdentityAddedEvent @@ -2424,7 +2333,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider /* some local identity still trusts this identity, don’t remove. */ return; } - Sone sone = getSone(identity.getId(), false); + Sone sone = getSone(identity.getId()); if (sone == null) { /* TODO - we don’t have the Sone anymore. should this happen? */ return;