Merge branch 'show-versions' into next
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 9 Jan 2011 20:52:44 +0000 (21:52 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sun, 9 Jan 2011 20:52:44 +0000 (21:52 +0100)
15 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/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/UpdateChecker.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/UpdateListener.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/i18n/sone.en.properties
src/main/resources/static/css/sone.css
src/main/resources/templates/include/tail.html
src/main/resources/templates/notify/newVersionNotification.html [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index e1fe047..e9ff09c 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.7.6</version>
+                       <version>0.7.7</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
index 493221b..78df9ef 100644 (file)
@@ -45,6 +45,7 @@ import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.util.version.Version;
 import freenet.keys.FreenetURI;
 
 /**
@@ -52,7 +53,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core implements IdentityListener {
+public class Core implements IdentityListener, UpdateListener {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
@@ -98,6 +99,9 @@ public class Core implements IdentityListener {
        /** The Sone downloader. */
        private final SoneDownloader soneDownloader;
 
+       /** The update checker. */
+       private final UpdateChecker updateChecker;
+
        /** Whether the core has been stopped. */
        private volatile boolean stopped;
 
@@ -162,6 +166,7 @@ public class Core implements IdentityListener {
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
                this.soneDownloader = new SoneDownloader(this, freenetInterface);
+               this.updateChecker = new UpdateChecker(freenetInterface);
        }
 
        //
@@ -233,6 +238,15 @@ public class Core implements IdentityListener {
        }
 
        /**
+        * Returns the update checker.
+        *
+        * @return The update checker
+        */
+       public UpdateChecker getUpdateChecker() {
+               return updateChecker;
+       }
+
+       /**
         * Returns the status of the given Sone.
         *
         * @param sone
@@ -1400,6 +1414,8 @@ public class Core implements IdentityListener {
         */
        public void start() {
                loadConfiguration();
+               updateChecker.addUpdateListener(this);
+               updateChecker.start();
        }
 
        /**
@@ -1411,6 +1427,8 @@ public class Core implements IdentityListener {
                                soneInserter.stop();
                        }
                }
+               updateChecker.stop();
+               updateChecker.removeUpdateListener(this);
                soneDownloader.stop();
                saveConfiguration();
                stopped = true;
@@ -1622,4 +1640,16 @@ public class Core implements IdentityListener {
                /* TODO */
        }
 
+       //
+       // INTERFACE UpdateListener
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void updateFound(Version version, long releaseTime) {
+               coreListenerManager.fireUpdateFound(version, releaseTime);
+       }
+
 }
index 5d5e715..5fbb333 100644 (file)
@@ -22,6 +22,7 @@ import java.util.EventListener;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.version.Version;
 
 /**
  * Listener interface for objects that want to be notified on certain
@@ -127,4 +128,14 @@ public interface CoreListener extends EventListener {
         */
        public void soneUnlocked(Sone sone);
 
+       /**
+        * Notifies a listener that a new version has been found.
+        *
+        * @param version
+        *            The version that was found
+        * @param releaseTime
+        *            The release time of the new version
+        */
+       public void updateFound(Version version, long releaseTime);
+
 }
index 464342c..4fc9532 100644 (file)
@@ -21,6 +21,7 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.event.AbstractListenerManager;
+import net.pterodactylus.util.version.Version;
 
 /**
  * Manager for {@link CoreListener}s.
@@ -197,4 +198,19 @@ public class CoreListenerManager extends AbstractListenerManager<Core, CoreListe
                }
        }
 
+       /**
+        * Notifies all listeners that a new version was found.
+        *
+        * @see CoreListener#updateFound(Version, long)
+        * @param version
+        *            The new version
+        * @param releaseTime
+        *            The release time of the new version
+        */
+       void fireUpdateFound(Version version, long releaseTime) {
+               for (CoreListener coreListener : getListeners()) {
+                       coreListener.updateFound(version, releaseTime);
+               }
+       }
+
 }
index 6737d63..52c2857 100644 (file)
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.core;
 
 import java.net.MalformedURLException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.logging.Level;
@@ -60,6 +61,9 @@ public class FreenetInterface {
        /** The USK callbacks. */
        private final Map<String, USKCallback> soneUskCallbacks = new HashMap<String, USKCallback>();
 
+       /** The not-Sone-related USK callbacks. */
+       private final Map<FreenetURI, USKCallback> uriUskCallbacks = Collections.synchronizedMap(new HashMap<FreenetURI, USKCallback>());
+
        /**
         * Creates a new Freenet interface.
         *
@@ -197,4 +201,83 @@ public class FreenetInterface {
                }
        }
 
+       /**
+        * Registers an arbitrary URI and calls the given callback if a new edition
+        * is found.
+        *
+        * @param uri
+        *            The URI to watch
+        * @param callback
+        *            The callback to call
+        */
+       public void registerUsk(FreenetURI uri, final Callback callback) {
+               USKCallback uskCallback = new USKCallback() {
+
+                       @Override
+                       public void onFoundEdition(long edition, USK key, ObjectContainer objectContainer, ClientContext clientContext, boolean metadata, short codec, byte[] data, boolean newKnownGood, boolean newSlotToo) {
+                               callback.editionFound(key.getURI(), edition, newKnownGood, newSlotToo);
+                       }
+
+                       @Override
+                       public short getPollingPriorityNormal() {
+                               return RequestStarter.PREFETCH_PRIORITY_CLASS;
+                       }
+
+                       @Override
+                       public short getPollingPriorityProgress() {
+                               return RequestStarter.INTERACTIVE_PRIORITY_CLASS;
+                       }
+
+               };
+               try {
+                       node.clientCore.uskManager.subscribe(USK.create(uri), uskCallback, true, (HighLevelSimpleClientImpl) client);
+                       uriUskCallbacks.put(uri, uskCallback);
+               } catch (MalformedURLException mue1) {
+                       logger.log(Level.WARNING, "Could not subscribe to USK: " + uri, uri);
+               }
+       }
+
+       /**
+        * Unregisters the USK watcher for the given URI.
+        *
+        * @param uri
+        *            The URI to unregister the USK watcher for
+        */
+       public void unregisterUsk(FreenetURI uri) {
+               USKCallback uskCallback = uriUskCallbacks.remove(uri);
+               if (uskCallback == null) {
+                       logger.log(Level.INFO, "Could not unregister unknown USK: " + uri);
+                       return;
+               }
+               try {
+                       node.clientCore.uskManager.unsubscribe(USK.create(uri), uskCallback);
+               } catch (MalformedURLException mue1) {
+                       logger.log(Level.INFO, "Could not unregister invalid USK: " + uri);
+               }
+       }
+
+       /**
+        * Callback for USK watcher events.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static interface Callback {
+
+               /**
+                * Notifies a listener that a new edition was found for a URI.
+                *
+                * @param uri
+                *            The URI that a new edition was found for
+                * @param edition
+                *            The found edition
+                * @param newKnownGood
+                *            Whether the found edition was actually fetched
+                * @param newSlot
+                *            Whether the found edition is higher than all previously
+                *            found editions
+                */
+               public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot);
+
+       }
+
 }
diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java b/src/main/java/net/pterodactylus/sone/core/UpdateChecker.java
new file mode 100644 (file)
index 0000000..0839cb9
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Sone - UpdateChecker.java - Copyright © 2011 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.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.Date;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.sone.main.SonePlugin;
+import net.pterodactylus.util.collection.Pair;
+import net.pterodactylus.util.io.Closer;
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.version.Version;
+import freenet.client.FetchResult;
+import freenet.keys.FreenetURI;
+import freenet.support.api.Bucket;
+
+/**
+ * Watches the official Sone homepage for new releases.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UpdateChecker {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(UpdateChecker.class);
+
+       /** The key of the Sone homepage. */
+       private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
+
+       /** The current latest known edition. */
+       private static final int LATEST_EDITION = 23;
+
+       /** The Freenet interface. */
+       private final FreenetInterface freenetInterface;
+
+       /** The update listener manager. */
+       private final UpdateListenerManager updateListenerManager = new UpdateListenerManager();
+
+       /** The current URI of the homepage. */
+       private FreenetURI currentUri;
+
+       /** The current latest known version. */
+       private Version currentLatestVersion = SonePlugin.VERSION;
+
+       /** The release date of the latest version. */
+       private long latestVersionDate;
+
+       /**
+        * Creates a new update checker.
+        *
+        * @param freenetInterface
+        *            The freenet interface to use
+        */
+       public UpdateChecker(FreenetInterface freenetInterface) {
+               this.freenetInterface = freenetInterface;
+       }
+
+       //
+       // EVENT LISTENER MANAGEMENT
+       //
+
+       /**
+        * Adds the given listener to the list of registered listeners.
+        *
+        * @param updateListener
+        *            The listener to add
+        */
+       public void addUpdateListener(UpdateListener updateListener) {
+               updateListenerManager.addListener(updateListener);
+       }
+
+       /**
+        * Removes the given listener from the list of registered listeners.
+        *
+        * @param updateListener
+        *            The listener to remove
+        */
+       public void removeUpdateListener(UpdateListener updateListener) {
+               updateListenerManager.removeListener(updateListener);
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns whether a version that is later than the currently running
+        * version has been found.
+        *
+        * @return {@code true} if a new version was found
+        */
+       public boolean hasLatestVersion() {
+               return currentLatestVersion.compareTo(SonePlugin.VERSION) > 0;
+       }
+
+       /**
+        * Returns the latest version. If no new latest version has been found, the
+        * current version is returned.
+        *
+        * @return The latest known version
+        */
+       public Version getLatestVersion() {
+               return currentLatestVersion;
+       }
+
+       /**
+        * Returns the release time of the latest version. If no new latest version
+        * has been found, the returned value is undefined.
+        *
+        * @return The release time of the latest version, if a new version was
+        *         found
+        */
+       public long getLatestVersionDate() {
+               return latestVersionDate;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Starts the update checker.
+        */
+       public void start() {
+               try {
+                       currentUri = new FreenetURI(SONE_HOMEPAGE + LATEST_EDITION);
+               } catch (MalformedURLException mue1) {
+                       /* this can not really happen unless I screw up. */
+                       logger.log(Level.SEVERE, "Sone Homepage URI invalid!", mue1);
+               }
+               freenetInterface.registerUsk(currentUri, new FreenetInterface.Callback() {
+
+                       @Override
+                       @SuppressWarnings("synthetic-access")
+                       public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot) {
+                               logger.log(Level.FINEST, "Found update for %s: %d, %s, %s", new Object[] { uri, edition, newKnownGood, newSlot });
+                               if (newKnownGood || newSlot) {
+                                       Pair<FreenetURI, FetchResult> uriResult = freenetInterface.fetchUri(uri.setMetaString(new String[] { "sone.properties" }));
+                                       if (uriResult == null) {
+                                               logger.log(Level.WARNING, "Could not fetch properties of latest homepage: %s", uri);
+                                               return;
+                                       }
+                                       Bucket resultBucket = uriResult.getRight().asBucket();
+                                       try {
+                                               parseProperties(resultBucket.getInputStream());
+                                       } catch (IOException ioe1) {
+                                               logger.log(Level.WARNING, "Could not parse sone.properties of " + uri, ioe1);
+                                       } finally {
+                                               resultBucket.free();
+                                       }
+                               }
+                       }
+               });
+       }
+
+       /**
+        * Stops the update checker.
+        */
+       public void stop() {
+               freenetInterface.unregisterUsk(currentUri);
+       }
+
+       //
+       // PRIVATE ACTIONS
+       //
+
+       /**
+        * Parses the properties of the latest version and fires events, if
+        * necessary.
+        *
+        * @see UpdateListener#updateFound(Version, long)
+        * @see UpdateListenerManager#fireUpdateFound(Version, long)
+        * @param propertiesInputStream
+        *            The input stream to parse
+        * @throws IOException
+        *             if an I/O error occured
+        */
+       private void parseProperties(InputStream propertiesInputStream) throws IOException {
+               Properties properties = new Properties();
+               InputStreamReader inputStreamReader = null;
+               try {
+                       inputStreamReader = new InputStreamReader(propertiesInputStream, "UTF-8");
+                       properties.load(inputStreamReader);
+               } finally {
+                       Closer.close(inputStreamReader);
+               }
+               String versionString = properties.getProperty("CurrentVersion/Version");
+               String releaseTimeString = properties.getProperty("CurrentVersion/ReleaseTime");
+               if ((versionString == null) || (releaseTimeString == null)) {
+                       logger.log(Level.INFO, "Invalid data parsed from properties.");
+                       return;
+               }
+               Version version = Version.parse(versionString);
+               long releaseTime = 0;
+               try {
+                       releaseTime = Long.parseLong(releaseTimeString);
+               } catch (NumberFormatException nfe1) {
+                       /* ignore. */
+               }
+               if ((version == null) || (releaseTime == 0)) {
+                       logger.log(Level.INFO, "Could not parse data from properties.");
+                       return;
+               }
+               if (version.compareTo(currentLatestVersion) > 0) {
+                       currentLatestVersion = version;
+                       latestVersionDate = releaseTime;
+                       logger.log(Level.INFO, "Found new version: %s (%tc)", new Object[] { version, new Date(releaseTime) });
+                       updateListenerManager.fireUpdateFound(version, releaseTime);
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateListener.java b/src/main/java/net/pterodactylus/sone/core/UpdateListener.java
new file mode 100644 (file)
index 0000000..1469afa
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sone - UpdateListener.java - Copyright © 2011 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.core;
+
+import java.util.EventListener;
+
+import net.pterodactylus.util.version.Version;
+
+/**
+ * Listener interface for {@link UpdateChecker} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface UpdateListener extends EventListener {
+
+       /**
+        * Notifies a listener that a newer version than the current version was
+        * found.
+        *
+        * @param version
+        *            The version that was found
+        * @param releaseTime
+        *            The release time of the version
+        */
+       public void updateFound(Version version, long releaseTime);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java b/src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java
new file mode 100644 (file)
index 0000000..3f3b21f
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - UpdateListenerManager.java - Copyright © 2011 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.core;
+
+import net.pterodactylus.util.event.AbstractListenerManager;
+import net.pterodactylus.util.version.Version;
+
+/**
+ * Listener manager for {@link UpdateListener} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UpdateListenerManager extends AbstractListenerManager<Void, UpdateListener> {
+
+       /**
+        * Creates a new update listener manager.
+        */
+       public UpdateListenerManager() {
+               super(null);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Notifies all listeners that a new version has been found.
+        *
+        * @param version
+        *            The new version
+        * @param releaseTime
+        *            The release time of the new version
+        */
+       void fireUpdateFound(Version version, long releaseTime) {
+               for (UpdateListener updateListener : getListeners()) {
+                       updateListener.updateFound(version, releaseTime);
+               }
+       }
+
+}
index 68c9d90..219f989 100644 (file)
@@ -166,28 +166,28 @@ public class SonePlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10
                        }
                }
 
-               /* create freenet interface. */
-               FreenetInterface freenetInterface = new FreenetInterface(pluginRespirator.getNode());
+               boolean startupFailed = true;
+               try {
+                       /* create freenet interface. */
+                       FreenetInterface freenetInterface = new FreenetInterface(pluginRespirator.getNode());
 
-               /* create web of trust connector. */
-               PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
-               WebOfTrustConnector webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
-               identityManager = new IdentityManager(webOfTrustConnector);
-               identityManager.setContext("Sone");
+                       /* create web of trust connector. */
+                       PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
+                       WebOfTrustConnector webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
+                       identityManager = new IdentityManager(webOfTrustConnector);
+                       identityManager.setContext("Sone");
 
-               /* create core. */
-               core = new Core(oldConfiguration, freenetInterface, identityManager);
+                       /* create core. */
+                       core = new Core(oldConfiguration, freenetInterface, identityManager);
 
-               /* create the web interface. */
-               webInterface = new WebInterface(this);
-               core.addCoreListener(webInterface);
+                       /* create the web interface. */
+                       webInterface = new WebInterface(this);
+                       core.addCoreListener(webInterface);
 
-               /* create the identity manager. */
-               identityManager.addIdentityListener(core);
+                       /* create the identity manager. */
+                       identityManager.addIdentityListener(core);
 
-               /* start core! */
-               boolean startupFailed = true;
-               try {
+                       /* start core! */
                        core.start();
                        if ((newConfiguration != null) && (oldConfiguration != newConfiguration)) {
                                logger.log(Level.INFO, "Setting configuration to file-based configuration.");
index 23ca247..3d4cb80 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Arrays;
 import java.util.Collection;
 
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.web.page.Page;
 import net.pterodactylus.sone.web.page.TemplatePage;
 import net.pterodactylus.util.template.Template;
@@ -188,6 +189,10 @@ public class SoneTemplatePage extends TemplatePage {
                super.processTemplate(request, template);
                template.set("currentSone", getCurrentSone(request.getToadletContext(), false));
                template.set("request", request);
+               template.set("currentVersion", SonePlugin.VERSION);
+               template.set("hasLatestVersion", webInterface.getCore().getUpdateChecker().hasLatestVersion());
+               template.set("latestVersion", webInterface.getCore().getUpdateChecker().getLatestVersion());
+               template.set("latestVersionTime", webInterface.getCore().getUpdateChecker().getLatestVersionDate());
        }
 
        /**
index d3ec1ee..6085ff8 100644 (file)
@@ -88,6 +88,7 @@ import net.pterodactylus.util.template.TemplateFactory;
 import net.pterodactylus.util.template.TemplateProvider;
 import net.pterodactylus.util.template.XmlFilter;
 import net.pterodactylus.util.thread.Ticker;
+import net.pterodactylus.util.version.Version;
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContainer;
@@ -141,6 +142,9 @@ public class WebInterface implements CoreListener {
        /** The “Sone locked” notification. */
        private final ListNotification<Sone> lockedSonesNotification;
 
+       /** The “new version” notification. */
+       private final TemplateNotification newVersionNotification;
+
        /**
         * Creates a new web interface.
         *
@@ -189,6 +193,9 @@ public class WebInterface implements CoreListener {
 
                Template lockedSonesTemplate = templateFactory.createTemplate(createReader("/templates/notify/lockedSonesNotification.html"));
                lockedSonesNotification = new ListNotification<Sone>("sones-locked-notification", "sones", lockedSonesTemplate);
+
+               Template newVersionTemplate = templateFactory.createTemplate(createReader("/templates/notify/newVersionNotification.html"));
+               newVersionNotification = new TemplateNotification("new-version-notification", newVersionTemplate);
        }
 
        //
@@ -701,6 +708,16 @@ public class WebInterface implements CoreListener {
        }
 
        /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void updateFound(Version version, long releaseTime) {
+               newVersionNotification.set("version", version);
+               newVersionNotification.set("releaseTime", releaseTime);
+               notificationManager.addNotification(newVersionNotification);
+       }
+
+       /**
         * Template provider implementation that uses
         * {@link WebInterface#createReader(String)} to load templates for
         * inclusion.
index 533a01b..740cf45 100644 (file)
@@ -175,6 +175,8 @@ WebInterface.SelectBox.Choose=Choose…
 WebInterface.SelectBox.Yes=Yes
 WebInterface.SelectBox.No=No
 WebInterface.ClickToShow.Replies=Click here to show hidden replies.
+WebInterface.VersionInformation.CurrentVersion=Current Version:
+WebInterface.VersionInformation.LatestVersion=Latest Version:
 
 Notification.ClickHereToRead=Click here to read the full text of the notification.
 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.
@@ -191,3 +193,4 @@ Notification.SoneIsBeingRescued.Text=The following Sones are currently being res
 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:
+Notification.NewVersion.Text=A new version of the Sone plugin was found: Version {version}.
index 89d9854..dfd6b9a 100644 (file)
@@ -444,6 +444,10 @@ textarea {
        color: #888;
 }
 
+#sone #tail #version-information {
+       margin-top: 1em;
+}
+
 #sone #add-sone textarea, #sone #create-sone textarea, #sone #load-sone textarea, #sone #edit-profile textarea {
        height: 1.5em;
 }
index 180bcf2..dd2e25e 100644 (file)
@@ -7,6 +7,13 @@
                                <img src="images/flattr-badge-large.png" alt="Flattr Sone" title="Flattr Sone" />
                        </a>
                </div>
+
+               <div id="version-information">
+                       <div class="current-version"><%= WebInterface.VersionInformation.CurrentVersion|l10n|html> <b><% currentVersion|html></b></div>
+                       <%if hasLatestVersion>
+                               <div class="latest-version"><%= WebInterface.VersionInformation.LatestVersion|l10n|html> <b><% latestVersion|html></b></div>
+                       <%/if>
+               </div>
        </div>
 
 </div>
diff --git a/src/main/resources/templates/notify/newVersionNotification.html b/src/main/resources/templates/notify/newVersionNotification.html
new file mode 100644 (file)
index 0000000..23a5855
--- /dev/null
@@ -0,0 +1 @@
+<div class="text"><%= Notification.NewVersion.Text|l10n|html|replace needle="{version}" replacementKey=version></div>