Merge branch 'improve-notification-handling' into next
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 14 Apr 2011 18:26:08 +0000 (20:26 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 14 Apr 2011 18:26:08 +0000 (20:26 +0200)
This fixes #139.

src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/notify/ListNotification.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/resources/static/javascript/sone.js
src/main/resources/templates/include/head.html

index e18fabc..7a4bc4a 100644 (file)
@@ -1495,6 +1495,7 @@ public class Core implements IdentityListener, UpdateListener {
                synchronized (posts) {
                        posts.remove(post.getId());
                }
+               coreListenerManager.firePostRemoved(post);
                synchronized (newPosts) {
                        markPostKnown(post);
                        knownPosts.remove(post.getId());
index c66d376..3a79c93 100644 (file)
@@ -101,7 +101,8 @@ public class ListNotification<T> extends TemplateNotification {
        }
 
        /**
-        * Sets the elements to show in this notification.
+        * Sets the elements to show in this notification. This method will not call
+        * {@link #touch()}.
         *
         * @param elements
         *            The elements to show
@@ -109,7 +110,6 @@ public class ListNotification<T> extends TemplateNotification {
        public void setElements(Collection<? extends T> elements) {
                this.elements.clear();
                this.elements.addAll(elements);
-               touch();
        }
 
        /**
index a6ef8aa..fe024d6 100644 (file)
@@ -84,7 +84,7 @@ public class ListNotificationFilters {
         * @return The filtered new-post notification, or {@code null} if the
         *         notification should be removed
         */
-       private static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
+       public static ListNotification<Post> filterNewPostNotification(ListNotification<Post> newPostNotification, Sone currentSone) {
                if (currentSone == null) {
                        return null;
                }
@@ -119,7 +119,7 @@ public class ListNotificationFilters {
         * @return The filtered new-reply notification, or {@code null} if the
         *         notification should be removed
         */
-       private static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
+       public static ListNotification<Reply> filterNewReplyNotification(ListNotification<Reply> newReplyNotification, Sone currentSone) {
                if (currentSone == null) {
                        return null;
                }
index b3772f9..3a8c0fa 100644 (file)
@@ -70,6 +70,7 @@ import net.pterodactylus.sone.web.ajax.DistrustAjaxPage;
 import net.pterodactylus.sone.web.ajax.EditProfileFieldAjaxPage;
 import net.pterodactylus.sone.web.ajax.FollowSoneAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetLikesAjaxPage;
+import net.pterodactylus.sone.web.ajax.GetNotificationAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetPostAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetReplyAjaxPage;
 import net.pterodactylus.sone.web.ajax.GetStatusAjaxPage;
@@ -586,6 +587,7 @@ public class WebInterface implements CoreListener {
                pageToadlets.add(pageToadletFactory.createPageToadlet(new TemplatePage("OpenSearch.xml", "application/opensearchdescription+xml", templateContextFactory, openSearchTemplate)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetTranslationPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new GetStatusAjaxPage(this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new GetNotificationAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new DismissNotificationAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreatePostAjaxPage(this)));
                pageToadlets.add(pageToadletFactory.createPageToadlet(new CreateReplyAjaxPage(this)));
diff --git a/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java b/src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationAjaxPage.java
new file mode 100644 (file)
index 0000000..5b5162b
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * Sone - GetStatusAjaxPage.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.web.ajax;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.main.SonePlugin;
+import net.pterodactylus.sone.notify.ListNotification;
+import net.pterodactylus.sone.notify.ListNotificationFilters;
+import net.pterodactylus.sone.web.WebInterface;
+import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.notify.Notification;
+import net.pterodactylus.util.notify.TemplateNotification;
+import net.pterodactylus.util.template.TemplateContext;
+
+/**
+ * The “get notification” AJAX handler returns a number of rendered
+ * notifications.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class GetNotificationAjaxPage extends JsonPage {
+
+       /**
+        * Creates a new “get notification” AJAX page.
+        *
+        * @param webInterface
+        *            The Sone web interface
+        */
+       public GetNotificationAjaxPage(WebInterface webInterface) {
+               super("getNotification.ajax", webInterface);
+       }
+
+       //
+       // JSONPAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected boolean needsFormPassword() {
+               return false;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected boolean requiresLogin() {
+               return false;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       protected JsonObject createJsonObject(Request request) {
+               String[] notificationIds = request.getHttpRequest().getParam("notifications").split(",");
+               JsonObject jsonNotifications = new JsonObject();
+               Sone currentSone = webInterface.getCurrentSone(request.getToadletContext(), false);
+               for (String notificationId : notificationIds) {
+                       Notification notification = webInterface.getNotifications().getNotification(notificationId);
+                       ListNotificationFilters.filterNotifications(new ArrayList<Notification>(), currentSone);
+                       if ("new-post-notification".equals(notificationId)) {
+                               notification = ListNotificationFilters.filterNewPostNotification((ListNotification<Post>) notification, currentSone);
+                       } else if ("new-reply-notification".equals(notificationId)) {
+                               notification = ListNotificationFilters.filterNewReplyNotification((ListNotification<Reply>) notification, currentSone);
+                       }
+                       if (notification == null) {
+                               // TODO - show error
+                               continue;
+                       }
+                       jsonNotifications.put(notificationId, createJsonNotification(request, notification));
+               }
+               return createSuccessJsonObject().put("notifications", jsonNotifications);
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Creates a JSON object from the given notification.
+        *
+        * @param request
+        *            The request to load the session from
+        * @param notification
+        *            The notification to create a JSON object
+        * @return The JSON object
+        */
+       private JsonObject createJsonNotification(Request request, Notification notification) {
+               JsonObject jsonNotification = new JsonObject();
+               jsonNotification.put("id", notification.getId());
+               StringWriter notificationWriter = new StringWriter();
+               try {
+                       if (notification instanceof TemplateNotification) {
+                               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext());
+                               templateContext.set("currentSone", webInterface.getCurrentSone(request.getToadletContext(), false));
+                               templateContext.set("localSones", webInterface.getCore().getLocalSones());
+                               templateContext.set("request", request);
+                               templateContext.set("currentVersion", SonePlugin.VERSION);
+                               templateContext.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion());
+                               templateContext.set("latestEdition", webInterface.getCore().getUpdateChecker().getLatestEdition());
+                               templateContext.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
+                               templateContext.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
+                               templateContext.set("notification", notification);
+                               ((TemplateNotification) notification).render(templateContext, notificationWriter);
+                       } else {
+                               notification.render(notificationWriter);
+                       }
+               } catch (IOException ioe1) {
+                       /* StringWriter never throws, ignore. */
+               }
+               jsonNotification.put("text", notificationWriter.toString());
+               jsonNotification.put("createdTime", notification.getCreatedTime());
+               jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime());
+               jsonNotification.put("dismissable", notification.isDismissable());
+               return jsonNotification;
+       }
+
+}
index 426c08f..09a9d2b 100644 (file)
@@ -17,8 +17,6 @@
 
 package net.pterodactylus.sone.web.ajax;
 
-import java.io.IOException;
-import java.io.StringWriter;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -39,8 +37,6 @@ import net.pterodactylus.util.filter.Filters;
 import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.notify.TemplateNotification;
-import net.pterodactylus.util.template.TemplateContext;
 
 /**
  * The “get status” AJAX handler returns all information that is necessary to
@@ -86,9 +82,9 @@ public class GetStatusAjaxPage extends JsonPage {
                /* load notifications. */
                List<Notification> notifications = ListNotificationFilters.filterNotifications(new ArrayList<Notification>(webInterface.getNotifications().getNotifications()), currentSone);
                Collections.sort(notifications, Notification.LAST_UPDATED_TIME_SORTER);
-               JsonArray jsonNotifications = new JsonArray();
+               JsonArray jsonNotificationInformations = new JsonArray();
                for (Notification notification : notifications) {
-                       jsonNotifications.add(createJsonNotification(notification));
+                       jsonNotificationInformations.add(createJsonNotificationInformation(notification));
                }
                /* load new posts. */
                Set<Post> newPosts = webInterface.getNewPosts();
@@ -132,7 +128,7 @@ public class GetStatusAjaxPage extends JsonPage {
                        jsonReply.put("postSone", reply.getPost().getSone().getId());
                        jsonReplies.add(jsonReply);
                }
-               return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotifications).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
+               return createSuccessJsonObject().put("sones", jsonSones).put("notifications", jsonNotificationInformations).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
        }
 
        /**
@@ -179,31 +175,20 @@ public class GetStatusAjaxPage extends JsonPage {
        }
 
        /**
-        * Creates a JSON object from the given notification.
+        * Creates a JSON object that only contains the ID and the last-updated time
+        * of the given notification.
         *
+        * @see Notification#getId()
+        * @see Notification#getLastUpdatedTime()
         * @param notification
-        *            The notification to create a JSON object
-        * @return The JSON object
+        *            The notification
+        * @return A JSON object containing the notification ID and last-updated
+        *         time
         */
-       private JsonObject createJsonNotification(Notification notification) {
+       private JsonObject createJsonNotificationInformation(Notification notification) {
                JsonObject jsonNotification = new JsonObject();
                jsonNotification.put("id", notification.getId());
-               StringWriter notificationWriter = new StringWriter();
-               try {
-                       if (notification instanceof TemplateNotification) {
-                               TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext().mergeContext(((TemplateNotification) notification).getTemplateContext());
-                               templateContext.set("notification", notification);
-                               ((TemplateNotification) notification).render(templateContext, notificationWriter);
-                       } else {
-                               notification.render(notificationWriter);
-                       }
-               } catch (IOException ioe1) {
-                       /* StringWriter never throws, ignore. */
-               }
-               jsonNotification.put("text", notificationWriter.toString());
-               jsonNotification.put("createdTime", notification.getCreatedTime());
                jsonNotification.put("lastUpdatedTime", notification.getLastUpdatedTime());
-               jsonNotification.put("dismissable", notification.isDismissable());
                return jsonNotification;
        }
 
index d58f17f..5aaa6a5 100644 (file)
@@ -416,6 +416,17 @@ function getNotificationId(notificationElement) {
        return $(notificationElement).attr("id");
 }
 
+/**
+ * Returns the time the notification was last updated.
+ *
+ * @param notificationElement
+ *            The notification element
+ * @returns The last update time of the notification
+ */
+function getNotificationLastUpdatedTime(notificationElement) {
+       return $(notificationElement).attr("lastUpdatedTime");
+}
+
 function likePost(postId) {
        $.getJSON("like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
                if ((data == null) || !data.success) {
@@ -1010,25 +1021,16 @@ function getStatus() {
                                }
                        });
                        /* process notifications. */
+                       notificationIds = [];
                        $.each(data.notifications, function(index, value) {
                                oldNotification = getNotification(value.id);
-                               notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
-                               if (oldNotification.length != 0) {
-                                       if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) {
-                                               opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
-                                               notification.find(".short-text").toggleClass("hidden", opened);
-                                               notification.find(".text").toggleClass("hidden", !opened);
-                                       }
-                                       checkForRemovedSones(oldNotification, notification);
-                                       checkForRemovedPosts(oldNotification, notification);
-                                       checkForRemovedReplies(oldNotification, notification);
-                                       oldNotification.replaceWith(notification.show());
-                               } else {
-                                       $("#sone #notification-area").append(notification);
-                                       notification.slideDown();
-                                       setActivity();
+                               if ((oldNotification.length == 0) || (value.lastUpdatedTime > getNotificationLastUpdatedTime(oldNotification))) {
+                                       notificationIds.push(value.id);
                                }
                        });
+                       if (notificationIds.length > 0) {
+                               loadNotifications(notificationIds);
+                       }
                        /* process new posts. */
                        $.each(data.newPosts, function(index, value) {
                                loadNewPost(value.id, value.sone, value.recipient, value.time);
@@ -1050,6 +1052,40 @@ function getStatus() {
 }
 
 /**
+ * Requests multiple notifications from Sone and displays them.
+ *
+ * @param notificationIds
+ *            Array of IDs of the notifications to load
+ */
+function loadNotifications(notificationIds) {
+       $.getJSON("getNotification.ajax", {"notifications": notificationIds.join(",")}, function(data, textStatus) {
+               if (!data || !data.success) {
+                       // TODO - show error
+                       return;
+               }
+               $.each(data.notifications, function(index, value) {
+                       oldNotification = getNotification(value.id);
+                       notification = ajaxifyNotification(createNotification(value.id, value.lastUpdatedTime, value.text, value.dismissable)).hide();
+                       if (oldNotification.length != 0) {
+                               if ((oldNotification.find(".short-text").length > 0) && (notification.find(".short-text").length > 0)) {
+                                       opened = oldNotification.is(":visible") && oldNotification.find(".short-text").hasClass("hidden");
+                                       notification.find(".short-text").toggleClass("hidden", opened);
+                                       notification.find(".text").toggleClass("hidden", !opened);
+                               }
+                               checkForRemovedSones(oldNotification, notification);
+                               checkForRemovedPosts(oldNotification, notification);
+                               checkForRemovedReplies(oldNotification, notification);
+                               oldNotification.replaceWith(notification.show());
+                       } else {
+                               $("#sone #notification-area").append(notification);
+                               notification.slideDown();
+                               setActivity();
+                       }
+               })
+       });
+}
+
+/**
  * Returns the ID of the currently logged in Sone.
  *
  * @return The ID of the current Sone, or an empty string if no Sone is logged
@@ -1440,8 +1476,8 @@ function changeIcon(iconUrl) {
  *            <code>true</code> if the notification can be dismissed by the
  *            user
  */
-function createNotification(id, text, dismissable) {
-       notification = $("<div></div>").addClass("notification").attr("id", id);
+function createNotification(id, lastUpdatedTime, text, dismissable) {
+       notification = $("<div></div>").addClass("notification").attr("id", id).attr("lastUpdatedTime", lastUpdatedTime);
        if (dismissable) {
                dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
                dismissForm.find("input[name=notification]").val(id);
index fe9cf34..ef3b34b 100644 (file)
@@ -19,7 +19,7 @@
                        </form>
 
                        <%foreach webInterface.notifications.all notification>
-                               <div class="notification" id="<% notification.id|html>">
+                               <div class="notification" id="<% notification.id|html>" lastUpdatedTime="<%notification.lastUpdatedTime|html>">
                                        <%if notification.dismissable>
                                                <form class="dismiss" action="dismissNotification.html" method="post">
                                                        <input type="hidden" name="formPassword" value="<% formPassword|html>" />