Merge branch 'next' into edit-wot-trust
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 15 Dec 2010 07:27:42 +0000 (08:27 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Wed, 15 Dec 2010 07:27:42 +0000 (08:27 +0100)
13 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/CoreListener.java
src/main/java/net/pterodactylus/sone/core/CoreListenerManager.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/resources/i18n/sone.en.properties
src/main/resources/templates/notify/configNotReadNotification.html [new file with mode: 0644]
src/main/resources/templates/notify/firstStartNotification.html [new file with mode: 0644]
src/main/resources/templates/notify/lockedSonesNotification.html [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index d662f01..fdf98e2 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.3.2</version>
+       <version>0.3.4</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
index d688f71..ba08be1 100644 (file)
@@ -728,7 +728,9 @@ public class Core implements IdentityListener {
         */
        public void lockSone(Sone sone) {
                synchronized (lockedSones) {
-                       lockedSones.add(sone);
+                       if (lockedSones.add(sone)) {
+                               coreListenerManager.fireSoneLocked(sone);
+                       }
                }
        }
 
@@ -741,7 +743,9 @@ public class Core implements IdentityListener {
         */
        public void unlockSone(Sone sone) {
                synchronized (lockedSones) {
-                       lockedSones.remove(sone);
+                       if (lockedSones.remove(sone)) {
+                               coreListenerManager.fireSoneUnlocked(sone);
+                       }
                }
        }
 
@@ -1166,7 +1170,7 @@ public class Core implements IdentityListener {
         * @param sone
         *            The Sone to save
         */
-       public void saveSone(Sone sone) {
+       public synchronized void saveSone(Sone sone) {
                if (!isLocalSone(sone)) {
                        logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone);
                        return;
@@ -1237,6 +1241,7 @@ public class Core implements IdentityListener {
                        }
                        configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
 
+                       configuration.save();
                        logger.log(Level.INFO, "Sone %s saved.", sone);
                } catch (ConfigurationException ce1) {
                        logger.log(Level.WARNING, "Could not save Sone: " + sone, ce1);
@@ -1460,7 +1465,7 @@ public class Core implements IdentityListener {
        /**
         * Saves the current options.
         */
-       public void saveConfiguration() {
+       public synchronized void saveConfiguration() {
                /* store the options first. */
                try {
                        configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
index 950e890..5d5e715 100644 (file)
@@ -111,4 +111,20 @@ public interface CoreListener extends EventListener {
         */
        public void replyRemoved(Reply reply);
 
+       /**
+        * Notifies a listener when a Sone was locked.
+        *
+        * @param sone
+        *            The Sone that was locked
+        */
+       public void soneLocked(Sone sone);
+
+       /**
+        * Notifies a listener that a Sone was unlocked.
+        *
+        * @param sone
+        *            The Sone that was unlocked
+        */
+       public void soneUnlocked(Sone sone);
+
 }
index 7ba226b..464342c 100644 (file)
@@ -171,4 +171,30 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
                }
        }
 
+       /**
+        * Notifies all listeners that the given Sone was locked.
+        *
+        * @see CoreListener#soneLocked(Sone)
+        * @param sone
+        *            The Sone that was locked
+        */
+       void fireSoneLocked(Sone sone) {
+               for (CoreListener coreListener : getListeners()) {
+                       coreListener.soneLocked(sone);
+               }
+       }
+
+       /**
+        * Notifies all listeners that the given Sone was unlocked.
+        *
+        * @see CoreListener#soneUnlocked(Sone)
+        * @param sone
+        *            The Sone that was unlocked
+        */
+       void fireSoneUnlocked(Sone sone) {
+               for (CoreListener coreListener : getListeners()) {
+                       coreListener.soneUnlocked(sone);
+               }
+       }
+
 }
index 41c1f82..fec4e8f 100644 (file)
@@ -260,7 +260,7 @@ public class SoneInserter extends AbstractService {
                        soneProperties.put("posts", new ArrayList<Post>(sone.getPosts()));
                        soneProperties.put("replies", new HashSet<Reply>(sone.getReplies()));
                        soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
-                       soneProperties.put("likeReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
+                       soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
                }
 
                //
index 95d291f..8e1c35b 100644 (file)
@@ -44,7 +44,6 @@ import freenet.pluginmanager.FredPluginL10n;
 import freenet.pluginmanager.FredPluginThreadless;
 import freenet.pluginmanager.FredPluginVersioned;
 import freenet.pluginmanager.PluginRespirator;
-import freenet.pluginmanager.PluginStore;
 
 /**
  * This class interfaces with Freenet. It is the class that is loaded by the
@@ -79,7 +78,7 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 3, 2);
+       public static final Version VERSION = new Version(0, 3, 4);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
@@ -96,9 +95,6 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
        /** The l10n helper. */
        private PluginL10n l10n;
 
-       /** The plugin store. */
-       private PluginStore pluginStore;
-
        /** The identity manager. */
        private IdentityManager identityManager;
 
@@ -147,10 +143,13 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                /* create a configuration. */
                Configuration oldConfiguration;
                Configuration newConfiguration = null;
+               boolean firstStart = !new File("sone.properties").exists();
+               boolean newConfig = false;
                try {
                        oldConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), false));
                        newConfiguration = oldConfiguration;
                } catch (ConfigurationException ce1) {
+                       newConfig = true;
                        logger.log(Level.INFO, "Could not load configuration file, trying plugin store…", ce1);
                        try {
                                newConfiguration = new Configuration(new MapConfigurationBackend(new File("sone.properties"), true));
@@ -195,6 +194,8 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                                core.setConfiguration(newConfiguration);
                        }
                        webInterface.start();
+                       webInterface.setFirstStart(firstStart);
+                       webInterface.setNewConfig(newConfig);
                        identityManager.start();
                        startupFailed = false;
                } finally {
@@ -223,14 +224,8 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
 
                        /* stop the identity manager. */
                        identityManager.stop();
-
-                       /* TODO wait for core to stop? */
-                       try {
-                               pluginRespirator.putStore(pluginStore);
-                       } catch (DatabaseDisabledException dde1) {
-                               logger.log(Level.WARNING, "Could not store plugin store, database is disabled.", dde1);
-                       }
-
+               } catch (Throwable t1) {
+                       logger.log(Level.SEVERE, "Error while shutting down!", t1);
                } finally {
                        /* shutdown logger. */
                        Logging.shutdown();
index ee8b9bf..659ce9b 100644 (file)
@@ -23,8 +23,11 @@ import java.io.Reader;
 import java.io.UnsupportedEncodingException;
 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.UUID;
 import java.util.logging.Level;
@@ -71,6 +74,7 @@ import net.pterodactylus.sone.web.page.PageToadlet;
 import net.pterodactylus.sone.web.page.PageToadletFactory;
 import net.pterodactylus.sone.web.page.StaticPage;
 import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
 import net.pterodactylus.util.notify.TemplateNotification;
 import net.pterodactylus.util.template.DateFilter;
@@ -131,6 +135,12 @@ public class WebInterface implements CoreListener {
        /** The “Sone rescued” notification. */
        private final ListNotification<Sone> sonesRescuedNotification;
 
+       /** Sone locked notification ticker objects. */
+       private final Map<Sone, Object> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, Object>());
+
+       /** The “Sone locked” notification. */
+       private final ListNotification<Sone> lockedSonesNotification;
+
        /**
         * Creates a new web interface.
         *
@@ -176,6 +186,9 @@ public class WebInterface implements CoreListener {
 
                Template sonesRescuedTemplate = templateFactory.createTemplate(createReader("/templates/notify/sonesRescuedNotification.html"));
                sonesRescuedNotification = new ListNotification<Sone>("sones-rescued-notification", "sones", sonesRescuedTemplate);
+
+               Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html"));
+               lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
        }
 
        //
@@ -328,6 +341,51 @@ public class WebInterface implements CoreListener {
                return new HashSet<Reply>(newReplyNotification.getElements());
        }
 
+       /**
+        * Sets whether the current start of the plugin is the first start. It is
+        * considered a first start if the configuration file does not exist.
+        *
+        * @param firstStart
+        *            {@code true} if no configuration file existed when Sone was
+        *            loaded, {@code false} otherwise
+        */
+       public void setFirstStart(boolean firstStart) {
+               if (firstStart) {
+                       Template firstStartNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/firstStartNotification.html"));
+                       Notification firstStartNotification = new TemplateNotification("first-start-notification", firstStartNotificationTemplate);
+                       notificationManager.addNotification(firstStartNotification);
+               }
+       }
+
+       /**
+        * Sets whether Sone was started with a fresh configuration file.
+        *
+        * @param newConfig
+        *            {@code true} if Sone was started with a fresh configuration,
+        *            {@code false} if the existing configuration could be read
+        */
+       public void setNewConfig(boolean newConfig) {
+               if (newConfig && !hasFirstStartNotification()) {
+                       Template configNotReadNotificationTemplate = templateFactory.createTemplate(createReader("/templates/notify/configNotReadNotification.html"));
+                       Notification configNotReadNotification = new TemplateNotification("config-not-read-notification", configNotReadNotificationTemplate);
+                       notificationManager.addNotification(configNotReadNotification);
+               }
+       }
+
+       //
+       // PRIVATE ACCESSORS
+       //
+
+       /**
+        * Returns whether the first start notification is currently displayed.
+        *
+        * @return {@code true} if the first-start notification is currently
+        *         displayed, {@code false} otherwise
+        */
+       private boolean hasFirstStartNotification() {
+               return notificationManager.getNotification("first-start-notification") != null;
+       }
+
        //
        // ACTIONS
        //
@@ -526,7 +584,9 @@ public class WebInterface implements CoreListener {
        @Override
        public void newSoneFound(Sone sone) {
                newSoneNotification.add(sone);
-               notificationManager.addNotification(newSoneNotification);
+               if (!hasFirstStartNotification()) {
+                       notificationManager.addNotification(newSoneNotification);
+               }
        }
 
        /**
@@ -535,7 +595,11 @@ public class WebInterface implements CoreListener {
        @Override
        public void newPostFound(Post post) {
                newPostNotification.add(post);
-               notificationManager.addNotification(newPostNotification);
+               if (!hasFirstStartNotification()) {
+                       notificationManager.addNotification(newPostNotification);
+               } else {
+                       getCore().markPostKnown(post);
+               }
        }
 
        /**
@@ -547,7 +611,11 @@ public class WebInterface implements CoreListener {
                        return;
                }
                newReplyNotification.add(reply);
-               notificationManager.addNotification(newReplyNotification);
+               if (!hasFirstStartNotification()) {
+                       notificationManager.addNotification(newReplyNotification);
+               } else {
+                       getCore().markReplyKnown(reply);
+               }
        }
 
        /**
@@ -579,7 +647,7 @@ public class WebInterface implements CoreListener {
         */
        @Override
        public void postRemoved(Post post) {
-               /* TODO */
+               newPostNotification.remove(post);
        }
 
        /**
@@ -587,7 +655,37 @@ public class WebInterface implements CoreListener {
         */
        @Override
        public void replyRemoved(Reply reply) {
-               /* TODO */
+               newReplyNotification.remove(reply);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void soneLocked(final Sone sone) {
+               Object tickerObject = Ticker.getInstance().registerEvent(System.currentTimeMillis() + (5 * 60) * 1000, new Runnable() {
+
+                       @Override
+                       @SuppressWarnings("synthetic-access")
+                       public void run() {
+                               lockedSonesNotification.add(sone);
+                               notificationManager.addNotification(lockedSonesNotification);
+                       }
+               }, "Sone Locked Notification");
+               lockedSonesTickerObjects.put(sone, tickerObject);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void soneUnlocked(Sone sone) {
+               Object tickerObject = lockedSonesTickerObjects.remove(sone);
+               if (tickerObject == null) {
+                       return;
+               }
+               lockedSonesNotification.remove(sone);
+               Ticker.getInstance().deregisterEvent(tickerObject);
        }
 
        /**
index 7c96d56..92d5da6 100644 (file)
@@ -20,9 +20,11 @@ package net.pterodactylus.sone.web.ajax;
 import java.io.StringWriter;
 
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateException;
 
@@ -60,8 +62,7 @@ public class GetPostAjaxPage extends JsonPage {
                if (post == null) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               postTemplate.set("currentSone", getCurrentSone(request.getToadletContext()));
-               return createSuccessJsonObject().put("post", createJsonPost(post));
+               return createSuccessJsonObject().put("post", createJsonPost(post, getCurrentSone(request.getToadletContext())));
        }
 
        /**
@@ -82,18 +83,22 @@ public class GetPostAjaxPage extends JsonPage {
         *
         * @param post
         *            The post to create a JSON object from
+        * @param currentSone
+        *            The currently logged in Sone (to store in the template)
         * @return The JSON representation of the post
         */
-       private JsonObject createJsonPost(Post post) {
+       private JsonObject createJsonPost(Post post, Sone currentSone) {
                JsonObject jsonPost = new JsonObject();
                jsonPost.put("id", post.getId());
                jsonPost.put("sone", post.getSone().getId());
                jsonPost.put("recipient", (post.getRecipient() == null) ? null : post.getRecipient().getId());
                jsonPost.put("time", post.getTime());
-               postTemplate.set("post", post);
                StringWriter stringWriter = new StringWriter();
+               DataProvider dataProvider = postTemplate.createDataProvider();
+               dataProvider.setData("post", post);
+               dataProvider.setData("currentSone", currentSone);
                try {
-                       postTemplate.render(stringWriter);
+                       postTemplate.render(dataProvider, stringWriter);
                } catch (TemplateException te1) {
                        /* TODO - shouldn’t happen. */
                } finally {
index 77b3ada..e589191 100644 (file)
@@ -20,9 +20,11 @@ package net.pterodactylus.sone.web.ajax;
 import java.io.StringWriter;
 
 import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
+import net.pterodactylus.util.template.DataProvider;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateException;
 
@@ -63,8 +65,7 @@ public class GetReplyAjaxPage extends JsonPage {
                if ((reply == null) || (reply.getSone() == null)) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               replyTemplate.set("currentSone", getCurrentSone(request.getToadletContext()));
-               return createSuccessJsonObject().put("reply", createJsonReply(reply));
+               return createSuccessJsonObject().put("reply", createJsonReply(reply, getCurrentSone(request.getToadletContext())));
        }
 
        /**
@@ -84,18 +85,22 @@ public class GetReplyAjaxPage extends JsonPage {
         *
         * @param reply
         *            The reply to convert
+        * @param currentSone
+        *            The currently logged in Sone (to store in the template)
         * @return The JSON representation of the reply
         */
-       private JsonObject createJsonReply(Reply reply) {
+       private JsonObject createJsonReply(Reply reply, Sone currentSone) {
                JsonObject jsonReply = new JsonObject();
                jsonReply.put("id", reply.getId());
                jsonReply.put("postId", reply.getPost().getId());
                jsonReply.put("soneId", reply.getSone().getId());
                jsonReply.put("time", reply.getTime());
-               replyTemplate.set("reply", reply);
                StringWriter stringWriter = new StringWriter();
+               DataProvider dataProvider = replyTemplate.createDataProvider();
+               dataProvider.setData("reply", reply);
+               dataProvider.setData("currentSone", currentSone);
                try {
-                       replyTemplate.render(stringWriter);
+                       replyTemplate.render(dataProvider, stringWriter);
                } catch (TemplateException te1) {
                        /* TODO - shouldn’t happen. */
                } finally {
index d0c44f5..e080e4d 100644 (file)
@@ -176,7 +176,9 @@ WebInterface.SelectBox.Yes=Yes
 WebInterface.SelectBox.No=No
 WebInterface.ClickToShow.Replies=Click here to show hidden replies.
 
+Notification.FirstStart.Text=This seems to be the first time you start Sone. To start, create a new Sone from a web of trust identity and start following other Sones.
 Notification.Startup.Text=Sone is currently starting up. It may take a while to retrieve all identities and Sones from the web of trust. If you are missing some elements, please be patient, they will probably reappear very soon.
+Notification.ConfigNotRead.Text=The configuration file “sone.properties” could not be read, probably because it was not saved correctly. This can happen on versions prior to Sone 0.3.3 and there is nothing you can do about it.
 Notification.Button.Dismiss=Dismiss
 Notification.NewSone.Text=New Sones have been discovered:
 Notification.NewPost.Text=New posts have been discovered by the following Sones:
@@ -184,4 +186,4 @@ Notification.NewReply.Text=New replies have been discovered by the following Son
 Notification.SoneIsBeingRescued.Text=The following Sones are currently being rescued:
 Notification.SoneRescued.Text=The following Sones have been rescued:
 Notification.SoneRescued.Text.RememberToUnlock=Please remember to control the posts and replies you have given and don’t forget to unlock your Sones!
-
+Notification.LockedSones.Text=The following Sones have been locked for more than 5 minutes. Please check if you really want to keep these Sones locked:
diff --git a/src/main/resources/templates/notify/configNotReadNotification.html b/src/main/resources/templates/notify/configNotReadNotification.html
new file mode 100644 (file)
index 0000000..3de81a4
--- /dev/null
@@ -0,0 +1 @@
+<div class="text"><%= Notification.ConfigNotRead.Text|l10n|html></div>
diff --git a/src/main/resources/templates/notify/firstStartNotification.html b/src/main/resources/templates/notify/firstStartNotification.html
new file mode 100644 (file)
index 0000000..b4ce468
--- /dev/null
@@ -0,0 +1 @@
+<div class="text"><%= Notification.FirstStart.Text|l10n|html></div>
diff --git a/src/main/resources/templates/notify/lockedSonesNotification.html b/src/main/resources/templates/notify/lockedSonesNotification.html
new file mode 100644 (file)
index 0000000..b2b8b95
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="text">
+       <%= Notification.LockedSones.Text|l10n|html>
+       <%foreach sones sone>
+               <a href="viewSone.html?sone=<% sone.id|html>" title="<% sone.requestUri|html>"><% sone.niceName|html></a><%notlast>,<%/notlast><%last>.<%/last>
+       <%/foreach>
+</div>