/*
* jSite - UpdateChecker.java - Copyright © 2008–2014 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.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 = 11;
/** The URL for update checks. */
private static final String UPDATE_KEY = "USK@1waTsw46L9-JEQ8yX1khjkfHcn--g0MlMsTlYHax9zQ,oYyxr5jyFnaTsVGDQWk9e3ddOWGKnqEASxAk08MHT2Y,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;
/** 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, Version currentVersion) {
this.freenetInterface = freenetInterface;
this.lastVersion = currentVersion;
}
//
// 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. */
}
}
}
}
}
}