Merge branch 'release-0.11'
[jSite.git] / src / main / java / de / todesbaum / jsite / application / UpdateChecker.java
diff --git a/src/main/java/de/todesbaum/jsite/application/UpdateChecker.java b/src/main/java/de/todesbaum/jsite/application/UpdateChecker.java
new file mode 100644 (file)
index 0000000..11be944
--- /dev/null
@@ -0,0 +1,302 @@
+/*
+ * jSite - UpdateChecker.java - Copyright © 2008–2012 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package de.todesbaum.jsite.application;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.io.Closer;
+import de.todesbaum.jsite.main.Main;
+import de.todesbaum.jsite.main.Version;
+import de.todesbaum.util.freenet.fcp2.Client;
+import de.todesbaum.util.freenet.fcp2.ClientGet;
+import de.todesbaum.util.freenet.fcp2.Connection;
+import de.todesbaum.util.freenet.fcp2.Message;
+import de.todesbaum.util.freenet.fcp2.Persistence;
+import de.todesbaum.util.freenet.fcp2.ReturnType;
+import de.todesbaum.util.freenet.fcp2.Verbosity;
+
+/**
+ * Checks for newer versions of jSite.
+ *
+ * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
+ */
+public class UpdateChecker implements Runnable {
+
+       /** The logger. */
+       private static final Logger logger = Logger.getLogger(UpdateChecker.class.getName());
+
+       /** Counter for connection names. */
+       private static int counter = 0;
+
+       /** The edition for the update check URL. */
+       private static final int UPDATE_EDITION = 18;
+
+       /** The URL for update checks. */
+       private static final String UPDATE_KEY = "USK@e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U,QvbWT0ImE0TwLODTl7EoJx2NBnwDxTbLTE6zkB-eGPs,AQACAAE";
+
+       /** Object used for synchronization. */
+       private final Object syncObject = new Object();
+
+       /** Update listeners. */
+       private final List<UpdateListener> updateListeners = new ArrayList<UpdateListener>();
+
+       /** Whether the main thread should stop. */
+       private boolean shouldStop = false;
+
+       /** Current last found edition of update key. */
+       private int lastUpdateEdition = UPDATE_EDITION;
+
+       /** Last found version. */
+       private Version lastVersion = Main.getVersion();
+
+       /** The freenet interface. */
+       private final Freenet7Interface freenetInterface;
+
+       /**
+        * Creates a new update checker that uses the given frame as its parent and
+        * communications via the given freenet interface.
+        *
+        * @param freenetInterface
+        *            The freenet interface
+        */
+       public UpdateChecker(Freenet7Interface freenetInterface) {
+               this.freenetInterface = freenetInterface;
+       }
+
+       //
+       // EVENT LISTENER MANAGEMENT
+       //
+
+       /**
+        * Adds an update listener to the list of registered listeners.
+        *
+        * @param updateListener
+        *            The update listener to add
+        */
+       public void addUpdateListener(UpdateListener updateListener) {
+               updateListeners.add(updateListener);
+       }
+
+       /**
+        * Removes the given listener from the list of registered listeners.
+        *
+        * @param updateListener
+        *            The update listener to remove
+        */
+       public void removeUpdateListener(UpdateListener updateListener) {
+               updateListeners.remove(updateListener);
+       }
+
+       /**
+        * Notifies all listeners that a version was found.
+        *
+        * @param foundVersion
+        *            The version that was found
+        * @param versionTimestamp
+        *            The timestamp of the version
+        */
+       protected void fireUpdateFound(Version foundVersion, long versionTimestamp) {
+               for (UpdateListener updateListener : updateListeners) {
+                       updateListener.foundUpdateData(foundVersion, versionTimestamp);
+               }
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the latest version that was found.
+        *
+        * @return The latest found version
+        */
+       public Version getLatestVersion() {
+               return lastVersion;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Starts the update checker.
+        */
+       public void start() {
+               new Thread(this).start();
+       }
+
+       /**
+        * Stops the update checker.
+        */
+       public void stop() {
+               synchronized (syncObject) {
+                       shouldStop = true;
+                       syncObject.notifyAll();
+               }
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Returns whether the update checker should stop.
+        *
+        * @return <code>true</code> if the update checker should stop,
+        *         <code>false</code> otherwise
+        */
+       private boolean shouldStop() {
+               synchronized (syncObject) {
+                       return shouldStop;
+               }
+       }
+
+       /**
+        * Creates the URI of the update file for the given edition.
+        *
+        * @param edition
+        *            The edition number
+        * @return The URI for the update file for the given edition
+        */
+       private static String constructUpdateKey(int edition) {
+               return UPDATE_KEY + "/jSite/" + edition + "/jSite.properties";
+       }
+
+       //
+       // INTERFACE Runnable
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void run() {
+               int currentEdition = lastUpdateEdition;
+               while (!shouldStop()) {
+
+                       /* try to connect. */
+                       Client client;
+                       while (true) {
+                               Connection connection = freenetInterface.getConnection("jSite-" + ++counter + "-UpdateChecker");
+                               try {
+                                       connection.connect();
+                                       logger.log(Level.INFO, "Connected to " + freenetInterface.getNode() + ".");
+                                       client = new Client(connection);
+                                       break;
+                               } catch (IOException ioe1) {
+                                       logger.log(Level.INFO, "Could not connect to " + freenetInterface.getNode() + ".", ioe1);
+                               }
+                               if (!connection.isConnected()) {
+                                       try {
+                                               Thread.sleep(60 * 1000);
+                                       } catch (InterruptedException ie1) {
+                                               /* ignore, we’re looping. */
+                                       }
+                               }
+                       }
+
+                       boolean checkNow = false;
+                       logger.log(Level.FINE, "Trying " + constructUpdateKey(currentEdition));
+                       ClientGet clientGet = new ClientGet("get-update-key");
+                       clientGet.setUri(constructUpdateKey(currentEdition));
+                       clientGet.setPersistence(Persistence.CONNECTION);
+                       clientGet.setReturnType(ReturnType.direct);
+                       clientGet.setVerbosity(Verbosity.ALL);
+                       try {
+                               client.execute(clientGet);
+                               boolean stop = false;
+                               while (!stop) {
+                                       Message message = client.readMessage();
+                                       logger.log(Level.FINEST, "Received message: " + message);
+                                       if (message == null) {
+                                               break;
+                                       }
+                                       if ("GetFailed".equals(message.getName())) {
+                                               if ("27".equals(message.get("code"))) {
+                                                       String editionString = message.get("redirecturi").split("/")[2];
+                                                       int editionNumber = -1;
+                                                       try {
+                                                               editionNumber = Integer.parseInt(editionString);
+                                                       } catch (NumberFormatException nfe1) {
+                                                               /* ignore. */
+                                                       }
+                                                       if (editionNumber != -1) {
+                                                               logger.log(Level.INFO, "Found new edition " + editionNumber);
+                                                               currentEdition = editionNumber;
+                                                               lastUpdateEdition = editionNumber;
+                                                               checkNow = true;
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                                       if ("AllData".equals(message.getName())) {
+                                               logger.log(Level.FINE, "Update data found.");
+                                               InputStream dataInputStream = null;
+                                               Properties properties = new Properties();
+                                               try {
+                                                       dataInputStream = message.getPayloadInputStream();
+                                                       properties.load(dataInputStream);
+                                               } finally {
+                                                       Closer.close(dataInputStream);
+                                               }
+
+                                               String foundVersionString = properties.getProperty("jSite.Version");
+                                               if (foundVersionString != null) {
+                                                       Version foundVersion = Version.parse(foundVersionString);
+                                                       if (foundVersion != null) {
+                                                               lastVersion = foundVersion;
+                                                               String versionTimestampString = properties.getProperty("jSite.Date");
+                                                               logger.log(Level.FINEST, "Version timestamp: " + versionTimestampString);
+                                                               long versionTimestamp = -1;
+                                                               try {
+                                                                       versionTimestamp = Long.parseLong(versionTimestampString);
+                                                               } catch (NumberFormatException nfe1) {
+                                                                       /* ignore. */
+                                                               }
+                                                               fireUpdateFound(foundVersion, versionTimestamp);
+                                                               stop = true;
+                                                               checkNow = true;
+                                                               ++currentEdition;
+                                                       }
+                                               }
+                                       }
+                               }
+                       } catch (IOException e) {
+                               logger.log(Level.INFO, "Got IOException: " + e.getMessage());
+                               e.printStackTrace();
+                       }
+                       if (!checkNow && !shouldStop()) {
+                               synchronized (syncObject) {
+                                       try {
+                                               syncObject.wait(15 * 60 * 1000);
+                                       } catch (InterruptedException ie1) {
+                                               /* ignore. */
+                                       }
+                               }
+                       }
+               }
+       }
+
+}