From: David ‘Bombe’ Roden Date: Sun, 13 Nov 2011 18:52:48 +0000 (+0100) Subject: Merge branch 'sone-following-times' into next X-Git-Tag: 0.7.3^2~8 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=bb34588a1b7a80a2a98574d9b928f1131ab88a79;hp=e4ae59234c87e77f95714462adae370790c6d5e6 Merge branch 'sone-following-times' into next This resolves #195. --- diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index 37fdef9..0043feb 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -136,6 +136,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis /* synchronize access on itself. */ private final Map soneStatuses = new HashMap(); + /** The times Sones were followed. */ + private final Map soneFollowingTimes = new HashMap(); + /** Locked local Sones. */ /* synchronize on itself. */ private final Set lockedSones = new HashSet(); @@ -588,6 +591,23 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** + * Returns the time when the given was first followed by any local Sone. + * + * @param sone + * The Sone to get the time for + * @return The time (in milliseconds since Jan 1, 1970) the Sone has first + * been followed, or {@link Long#MAX_VALUE} + */ + public long getSoneFollowingTime(Sone sone) { + synchronized (soneFollowingTimes) { + if (soneFollowingTimes.containsKey(sone)) { + return soneFollowingTimes.get(sone); + } + return Long.MAX_VALUE; + } + } + + /** * Returns whether the target Sone is trusted by the origin Sone. * * @param origin @@ -1012,7 +1032,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis sone.getOptions().addBooleanOption("ShowNotification/NewSones", new DefaultOption(true)); sone.getOptions().addBooleanOption("ShowNotification/NewPosts", new DefaultOption(true)); sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption(true)); - sone.addFriend("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"); + followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI")); touchConfiguration(); return sone; } @@ -1045,8 +1065,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis coreListenerManager.fireNewSoneFound(sone); for (Sone localSone : getLocalSones()) { if (localSone.getOptions().getBooleanOption("AutoFollow").get()) { - localSone.addFriend(sone.getId()); - touchConfiguration(); + followSone(localSone, sone); } } } @@ -1068,6 +1087,90 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } /** + * Lets the given local Sone follow the Sone with the given ID. + * + * @param sone + * The local Sone that should follow another Sone + * @param soneId + * The ID of the Sone to follow + */ + public void followSone(Sone sone, String soneId) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check(); + 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) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Followed Sone", followedSone).check(); + sone.addFriend(followedSone.getId()); + synchronized (soneFollowingTimes) { + if (!soneFollowingTimes.containsKey(followedSone)) { + long now = System.currentTimeMillis(); + soneFollowingTimes.put(followedSone, now); + for (Post post : followedSone.getPosts()) { + if (post.getTime() < now) { + markPostKnown(post); + } + } + for (PostReply reply : followedSone.getReplies()) { + if (reply.getTime() < now) { + markReplyKnown(reply); + } + } + } + } + touchConfiguration(); + } + + /** + * Lets the given local Sone unfollow the Sone with the given ID. + * + * @param sone + * The local Sone that should unfollow another Sone + * @param soneId + * The ID of the Sone being unfollowed + */ + public void unfollowSone(Sone sone, String soneId) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check(); + 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) { + Validation.begin().isNotNull("Sone", sone).isNotNull("Unfollowed Sone", unfollowedSone).check(); + sone.removeFriend(unfollowedSone.getId()); + boolean unfollowedSoneStillFollowed = false; + for (Sone localSone : getLocalSones()) { + unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId()); + } + if (!unfollowedSoneStillFollowed) { + synchronized (soneFollowingTimes) { + soneFollowingTimes.remove(unfollowedSone); + } + } + touchConfiguration(); + } + + /** * Retrieves the trust relationship from the origin to the target. If the * trust relationship can not be retrieved, {@code null} is returned. * @@ -1199,9 +1302,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (newPosts) { for (Post post : sone.getPosts()) { post.setSone(storedSone); - if (!storedPosts.contains(post) && !knownPosts.contains(post.getId())) { - newPosts.add(post.getId()); - coreListenerManager.fireNewPostFound(post); + if (!storedPosts.contains(post)) { + if (post.getTime() < getSoneFollowingTime(sone)) { + knownPosts.add(post.getId()); + } else if (!knownPosts.contains(post.getId())) { + newPosts.add(post.getId()); + coreListenerManager.fireNewPostFound(post); + } } posts.put(post.getId(), post); } @@ -1220,9 +1327,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis synchronized (newReplies) { for (PostReply reply : sone.getReplies()) { reply.setSone(storedSone); - if (!storedReplies.contains(reply) && !knownReplies.contains(reply.getId())) { - newReplies.add(reply.getId()); - coreListenerManager.fireNewReplyFound(reply); + if (!storedReplies.contains(reply)) { + if (reply.getTime() < getSoneFollowingTime(sone)) { + knownReplies.add(reply.getId()); + } else if (!knownReplies.contains(reply.getId())) { + newReplies.add(reply.getId()); + coreListenerManager.fireNewReplyFound(reply); + } } replies.put(reply.getId(), reply); } @@ -1524,7 +1635,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis sone.setReplies(replies); sone.setLikePostIds(likedPostIds); sone.setLikeReplyIds(likedReplyIds); - sone.setFriends(friends); + for (String friendId : friends) { + followSone(sone, friendId); + } sone.setAlbums(topLevelAlbums); soneInserters.get(sone).setLastInsertFingerprint(lastInsertFingerprint); } @@ -2196,6 +2309,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis configuration.getStringValue("KnownSone/" + soneCounter + "/ID").setValue(null); } + /* save Sone following times. */ + soneCounter = 0; + synchronized (soneFollowingTimes) { + for (Entry soneFollowingTime : soneFollowingTimes.entrySet()) { + configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey().getId()); + configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue()); + ++soneCounter; + } + configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(null); + } + /* save known posts. */ int postCounter = 0; synchronized (newPosts) { @@ -2312,6 +2436,20 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis } } + /* load Sone following times. */ + soneCounter = 0; + while (true) { + String soneId = configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").getValue(null); + if (soneId == null) { + break; + } + long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE); + synchronized (soneFollowingTimes) { + soneFollowingTimes.put(getSone(soneId), time); + } + ++soneCounter; + } + /* load known posts. */ int postCounter = 0; while (true) { diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index ca970c2..006765a 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -380,19 +380,6 @@ public class Sone implements Fingerprintable, Comparable { } /** - * Sets all friends of this Sone at once. - * - * @param friends - * The new (and only) friends of this Sone - * @return This Sone (for method chaining) - */ - public Sone setFriends(Collection friends) { - friendSones.clear(); - friendSones.addAll(friends); - return this; - } - - /** * Returns whether this Sone has the given Sone as a friend Sone. * * @param friendSoneId diff --git a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java index 143e0ba..4083b99 100644 --- a/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/FollowSonePage.java @@ -55,9 +55,11 @@ public class FollowSonePage extends SoneTemplatePage { Sone currentSone = getCurrentSone(request.getToadletContext()); String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 1200); for (String soneId : soneIds.split("[ ,]+")) { - currentSone.addFriend(soneId); + if (webInterface.getCore().hasSone(soneId)) { + webInterface.getCore().followSone(currentSone, soneId); + webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId)); + } } - webInterface.getCore().touchConfiguration(); throw new RedirectException(returnPage); } } diff --git a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java index d8e53ce..f07880d 100644 --- a/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java +++ b/src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java @@ -55,9 +55,8 @@ public class UnfollowSonePage extends SoneTemplatePage { Sone currentSone = getCurrentSone(request.getToadletContext()); String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 2000); for (String soneId : soneIds.split("[ ,]+")) { - currentSone.removeFriend(soneId); + webInterface.getCore().unfollowSone(currentSone, soneId); } - webInterface.getCore().touchConfiguration(); throw new RedirectException(returnPage); } } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java index 5c3e5f4..764fa28 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java @@ -52,8 +52,8 @@ public class FollowSoneAjaxPage extends JsonPage { if (currentSone == null) { return createErrorJsonObject("auth-required"); } - currentSone.addFriend(soneId); - webInterface.getCore().touchConfiguration(); + webInterface.getCore().followSone(currentSone, soneId); + webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId)); return createSuccessJsonObject(); } diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java index 26f1a11..5d94d52 100644 --- a/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java +++ b/src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java @@ -52,8 +52,7 @@ public class UnfollowSoneAjaxPage extends JsonPage { if (currentSone == null) { return createErrorJsonObject("auth-required"); } - currentSone.removeFriend(soneId); - webInterface.getCore().touchConfiguration(); + webInterface.getCore().unfollowSone(currentSone, soneId); return createSuccessJsonObject(); }