Author: toad
Date: 2006-11-18 02:04:03 +0000 (Sat, 18 Nov 2006)
New Revision: 10979
Added:
trunk/freenet/src/freenet/node/updater/NodeUpdaterManager.java
trunk/freenet/src/freenet/node/updater/RevocationChecker.java
trunk/freenet/src/freenet/node/updater/UpdateDeployContext.java
trunk/freenet/src/freenet/node/updater/UpdaterParserException.java
Removed:
trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
Modified:
trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java
trunk/freenet/src/freenet/node/Node.java
trunk/freenet/src/freenet/node/TextModeClientInterface.java
trunk/freenet/src/freenet/node/updater/NodeUpdater.java
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
Log:
Major refactoring of auto-update code. Now support auto-updating
freenet-ext.jar.
Modified: trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java
===================================================================
--- trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java 2006-11-17
23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/clients/http/WelcomeToadlet.java 2006-11-18
02:04:03 UTC (rev 10979)
@@ -82,8 +82,7 @@
content.addChild("p").addChild("#", "Thank you for
using Freenet.");
writeReply(ctx, 200, "text/html", "OK",
pageNode.generate());
Logger.normal(this, "Node is updating/restarting");
- node.ps.queueTimedJob(new Runnable() {
- public void run() {
node.getNodeUpdater().Update(); }}, 0);
+ node.getNodeUpdater().arm();
return;
}else if
(request.getPartAsString(GenericReadFilterCallback.magicHTTPEscapeString,
MAX_URL_LENGTH).length()>0){
if(noPassword) {
Modified: trunk/freenet/src/freenet/node/Node.java
===================================================================
--- trunk/freenet/src/freenet/node/Node.java 2006-11-17 23:15:28 UTC (rev
10978)
+++ trunk/freenet/src/freenet/node/Node.java 2006-11-18 02:04:03 UTC (rev
10979)
@@ -79,6 +79,7 @@
import freenet.keys.SSKBlock;
import freenet.keys.SSKVerifyException;
import freenet.node.updater.NodeUpdater;
+import freenet.node.updater.NodeUpdaterManager;
import freenet.node.useralerts.BuildOldAgeUserAlert;
import freenet.node.useralerts.ExtOldAgeUserAlert;
import freenet.node.useralerts.MeaningfulNodeNameUserAlert;
@@ -402,6 +403,8 @@
public static final int EXIT_COULD_NOT_START_UPDATER = 21;
public static final int EXIT_EXTRA_PEER_DATA_DIR = 22;
public static final int EXIT_THROTTLE_FILE_ERROR = 23;
+ public static final int EXIT_RESTART_FAILED = 24;
+
public static final int PEER_NODE_STATUS_CONNECTED = 1;
public static final int PEER_NODE_STATUS_ROUTING_BACKED_OFF = 2;
public static final int PEER_NODE_STATUS_TOO_NEW = 3;
@@ -465,7 +468,7 @@
public int lastVersion;
/** NodeUpdater **/
- public NodeUpdater nodeUpdater;
+ public NodeUpdaterManager nodeUpdater;
// Things that's needed to keep track of
public final PluginManager pluginManager;
@@ -1258,7 +1261,7 @@
remoteSskInsertBytesReceivedAverage = new
TimeDecayingRunningAverage(1024+1024+500, 180000, 0.0, 1024*1024*1024,
throttleFS == null ? null :
throttleFS.subset("RemoteSskInsertBytesReceivedAverage"));
clientCore = new NodeClientCore(this, config, nodeConfig,
nodeDir, portNumber, sortOrder, throttleFS == null ? null :
throttleFS.subset("RequestStarters"));
-
+
nodeConfig.finishedInitialization();
writeNodeFile();
@@ -1362,24 +1365,17 @@
// pluginManager3 = new
freenet.plugin_new.PluginManager(pluginManagerConfig);
ipDetector.start();
-
+
// Node Updater
try{
- nodeUpdater = NodeUpdater.maybeCreate(this, config);
+ nodeUpdater = NodeUpdaterManager.maybeCreate(this,
config);
Logger.normal(this, "Starting the node updater");
+ nodeUpdater.start();
}catch (Exception e) {
e.printStackTrace();
throw new
NodeInitException(EXIT_COULD_NOT_START_UPDATER, "Could not start Updater: "+e);
}
- /*
- SimpleToadletServer server = new SimpleToadletServer(port+2000);
- FProxyToadlet fproxy = new
FProxyToadlet(n.makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS));
- PProxyToadlet pproxy = new
PProxyToadlet(n.makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS),
n.pluginManager);
- server.register(fproxy, "/", false);
- server.register(pproxy, "/plugins/", true);
- * */
-
// Start testnet handler
if(testnetHandler != null)
testnetHandler.start();
@@ -2595,7 +2591,7 @@
config.store();
}
- public NodeUpdater getNodeUpdater(){
+ public NodeUpdaterManager getNodeUpdater(){
return nodeUpdater;
}
Modified: trunk/freenet/src/freenet/node/TextModeClientInterface.java
===================================================================
--- trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-11-17
23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/TextModeClientInterface.java 2006-11-18
02:04:03 UTC (rev 10979)
@@ -331,7 +331,7 @@
// FIXME run on separate thread
n.ps.queueTimedJob(new Runnable() {
public void run() {
- n.getNodeUpdater().Update();
+ n.getNodeUpdater().arm();
}
}, 0);
outsb.append("\r\n");
Deleted: trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/AutoUpdateAllowedCallback.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -1,34 +0,0 @@
-/* This code is part of Freenet. It is distributed under the GNU General
- * Public License, version 2 (or at your option any later version). See
- * http://www.gnu.org/ for further details of the GPL. */
-package freenet.node.updater;
-
-import freenet.config.BooleanCallback;
-import freenet.config.InvalidConfigValueException;
-import freenet.node.Node;
-import freenet.support.Logger;
-
-public class AutoUpdateAllowedCallback implements BooleanCallback {
-
- final Node node;
-
- AutoUpdateAllowedCallback(Node n) {
- this.node = n;
- }
-
- public boolean get() {
- if((node.getNodeUpdater()==null) ||
!(node.getNodeUpdater().isRunning()))
- return false;
- else
- return node.getNodeUpdater().isAutoUpdateAllowed;
- }
-
- public void set(boolean val) throws InvalidConfigValueException {
- if(val == get()) return;
- if(node.getNodeUpdater()!=null){
- node.getNodeUpdater().setAutoupdateAllowed(val);
- Logger.normal(this, "Node auto update is now allowed =
"+val);
- }else
- throw new InvalidConfigValueException("Nodeupdater:
unable to set node-autoupdate allowed if the updater isn't started");
- }
-}
\ No newline at end of file
Modified: trunk/freenet/src/freenet/node/updater/NodeUpdater.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/NodeUpdater.java 2006-11-17
23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/NodeUpdater.java 2006-11-18
02:04:03 UTC (rev 10979)
@@ -1,20 +1,10 @@
package freenet.node.updater;
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
-import java.util.Properties;
-import org.tanukisoftware.wrapper.WrapperManager;
-
import freenet.client.FetchException;
import freenet.client.FetchResult;
import freenet.client.FetcherContext;
@@ -23,8 +13,6 @@
import freenet.client.async.ClientCallback;
import freenet.client.async.ClientGetter;
import freenet.client.async.USKCallback;
-import freenet.config.Config;
-import freenet.config.SubConfig;
import freenet.keys.FreenetURI;
import freenet.keys.USK;
import freenet.node.Node;
@@ -32,108 +20,70 @@
import freenet.node.RequestStarter;
import freenet.node.Ticker;
import freenet.node.Version;
-import freenet.node.useralerts.RevocationKeyFoundUserAlert;
-import freenet.node.useralerts.UpdatedVersionAvailableUserAlert;
import freenet.support.Logger;
import freenet.support.io.ArrayBucket;
public class NodeUpdater implements ClientCallback, USKCallback {
- public final static String UPDATE_URI = "freenet:USK at
SIDKS6l-eOU8IQqDo03d~3qqBd-69WG60aDgg4nWqss,CPFqYi95Is3GwzAdAKtAuFMCXDZFFWC3~uPoidCD67s,AQABAAE/update/"+Version.buildNumber();
- public final static String REVOCATION_URI = "freenet:SSK at
VOfCZVTYPaatJ~eB~4lu2cPrWEmGyt4bfbB1v15Z6qQ,B6EynLhm7QE0se~rMgWWhl7wh3rFWjxJsEUcyohAm8A,AQABAAE/revoked";
- public final static int REVOCATION_DNF_MIN = 3;
-
static private boolean logMINOR;
private FetcherContext ctx;
- private FetcherContext ctxRevocation;
private FetchResult result;
private ClientGetter cg;
- private ClientGetter revocationGetter;
private boolean finalCheck;
private final FreenetURI URI;
private final FreenetURI revocationURI;
private final Ticker ticker;
public final NodeClientCore core;
private final Node node;
+ public final NodeUpdaterManager manager;
private final int currentVersion;
private int availableVersion;
private int fetchingVersion;
private int fetchedVersion;
+ private int writtenVersion;
- private String revocationMessage;
- private boolean hasBeenBlown;
- private int revocationDNFCounter;
-
private boolean isRunning;
private boolean isFetching;
- public boolean isAutoUpdateAllowed;
+ public final boolean extUpdate;
- private final UpdatedVersionAvailableUserAlert alert;
- private RevocationKeyFoundUserAlert revocationAlert;
-
- public NodeUpdater(Node n, boolean isAutoUpdateAllowed, FreenetURI URI,
FreenetURI revocationURI) {
+ NodeUpdater(NodeUpdaterManager manager, FreenetURI URI, FreenetURI
revocationURI, boolean extUpdate, int current) {
logMINOR = Logger.shouldLog(Logger.MINOR, this);
- if(URI.lastMetaString() != null &&
URI.lastMetaString().length() == 0) {
- // FIXME remove this hack, put in because of bad default
- System.err.println("Correcting auto-update URI:
Removing extra /");
- URI = URI.popMetaString();
- }
- if(revocationURI.lastMetaString() != null &&
revocationURI.lastMetaString().length() == 0) {
- // FIXME remove this hack, put in because of bad default
- System.err.println("Correcting auto-update revocation
URI: Removing extra /");
- revocationURI = revocationURI.popMetaString();
- }
- this.URI = URI;
- URI.setSuggestedEdition(Version.buildNumber()+1);
+ this.manager = manager;
+ this.node = manager.node;
+ this.URI = URI.setSuggestedEdition(Version.buildNumber()+1);
this.revocationURI = revocationURI;
- this.revocationAlert = null;
- this.revocationDNFCounter = 0;
- this.ticker = n.ps;
- this.core = n.clientCore;
- this.node = n;
- n.nodeUpdater = this;
- this.currentVersion = Version.buildNumber();
- this.availableVersion = currentVersion;
- this.hasBeenBlown = false;
+ this.ticker = node.ps;
+ this.core = node.clientCore;
+ this.currentVersion = current;
+ this.availableVersion = current;
this.isRunning = true;
- this.isAutoUpdateAllowed = isAutoUpdateAllowed;
this.cg = null;
this.isFetching = false;
+ this.extUpdate = extUpdate;
- this.alert= new
UpdatedVersionAvailableUserAlert(currentVersion, this);
- alert.isValid(false);
- core.alerts.register(alert);
-
FetcherContext tempContext = core.makeClient((short)0,
true).getFetcherContext();
tempContext.allowSplitfiles = true;
tempContext.dontEnterImplicitArchives = false;
this.ctx = tempContext;
- ctxRevocation = core.makeClient((short)0,
true).getFetcherContext();
- ctxRevocation.allowSplitfiles = false;
- ctxRevocation.cacheLocalRequests = false;
- ctxRevocation.maxArchiveLevels = 1;
- // big enough ?
- ctxRevocation.maxOutputLength = 4096;
- ctxRevocation.maxTempLength = 4096;
- ctxRevocation.maxSplitfileBlockRetries = -1; // if we find
content, try forever to get it.
- ctxRevocation.maxNonSplitfileRetries = 0; // but return quickly
normally
-
+ }
+
+ void start() {
try{
// start at next version, not interested in this version
USK
myUsk=USK.create(URI.setSuggestedEdition(currentVersion+1));
ctx.uskManager.subscribe(myUsk, this, true, this);
}catch(MalformedURLException e){
Logger.error(this,"The auto-update URI isn't valid and
can't be used");
- blow("The auto-update URI isn't valid and can't be
used");
+ manager.blow("The auto-update URI isn't valid and can't
be used");
}
}
public synchronized void onFoundEdition(long l, USK key){
logMINOR = Logger.shouldLog(Logger.MINOR, this);
if(logMINOR) Logger.minor(this, "Found edition "+l);
- System.err.println("Found new update edition "+l);
+ System.err.println("Found new "+(extUpdate?"freenet-ext.jar " :
"")+"update edition "+l);
if(!isRunning) return;
int found = (int)key.suggestedEdition;
@@ -145,41 +95,34 @@
maybeUpdate();
}
}, 60*1000); // leave some time in case we get later
editions
+ manager.onStartFetching(extUpdate);
}
}
public void maybeUpdate(){
ClientGetter toStart = null;
+ if(!manager.isEnabled()) return;
+ if(manager.isBlown()) return;
synchronized(this) {
- try{
- if(logMINOR)
- Logger.minor(this, "maybeUpdate:
isFetching="+isFetching+", isRunning="+isRunning+",
isUpdatable="+isUpdatable()+", availableVersion="+availableVersion);
- if(isFetching || (!isRunning) ||
(!isUpdatable())) return;
- if(availableVersion == fetchedVersion) return;
- fetchingVersion = availableVersion;
- }catch (PrivkeyHasBeenBlownException e){
- // Handled in blow().
- isRunning=false;
- return;
- }
+ if(logMINOR)
+ Logger.minor(this, "maybeUpdate:
isFetching="+isFetching+", isRunning="+isRunning+",
availableVersion="+availableVersion);
+ if(isFetching || (!isRunning)) return;
+ if(availableVersion == fetchedVersion) return;
+ fetchingVersion = availableVersion;
-
- alert.set(availableVersion,fetchedVersion,result !=
null && result.asBucket() != null && result.asBucket().size() > 0);
- alert.isValid(true);
Logger.normal(this,"Starting the update process
("+availableVersion+ ')');
System.err.println("Starting the update process: found
the update ("+availableVersion+"), now fetching it.");
// We fetch it
try{
if((cg==null)||cg.isCancelled()){
if(logMINOR) Logger.minor(this,
"Scheduling request for "+URI.setSuggestedEdition(availableVersion));
- System.err.println("Starting fetch for
"+availableVersion);
+ System.err.println("Starting
"+(extUpdate?"freenet-ext.jar ":"")+"fetch for "+availableVersion);
cg = new ClientGetter(this,
core.requestStarters.chkFetchScheduler, core.requestStarters.sskFetchScheduler,
URI.setSuggestedEdition(availableVersion), ctx,
RequestStarter.UPDATE_PRIORITY_CLASS,
this, new
ArrayBucket());
toStart = cg;
}
isFetching = true;
- queueFetchRevocation(0);
}catch (Exception e) {
Logger.error(this, "Error while starting the
fetching: "+e, e);
isFetching=false;
@@ -196,233 +139,33 @@
}
}
- /** Synchronization object only used by Update(); simply a mutex on the
- * innerUpdate() method, which can block for some time. */
- private volatile Object updateSync = new Object();
+ private final Object writeJarSync = new Object();
- public void Update() {
-
- if(!WrapperManager.isControlledByNativeWrapper()) {
- Logger.error(this, "Cannot update because not running
under wrapper");
- System.err.println("Cannot update because not running
under wrapper");
- return;
- }
-
+ public void writeJarTo(File fNew) throws IOException {
+ int fetched;
synchronized(this) {
- if(!isRunning) return;
+ fetched = fetchedVersion;
}
-
- synchronized(updateSync) {
- innerUpdate();
- }
- }
-
- /**
- * We try to update the node :p
- * Must run on its own thread.
- */
- private void innerUpdate(){
- if(logMINOR) Logger.minor(this, "Update() called");
- synchronized(this) {
- if((result == null) || hasBeenBlown) {
- if(logMINOR) Logger.minor(this, "Returning:
result="+result+", isAutoUpdateAllowed="+isAutoUpdateAllowed+",
hasBeenBlown="+hasBeenBlown);
- return;
- }
+ synchronized(writeJarSync) {
+ ArrayBucket bucket = (ArrayBucket) result.asBucket();
+ byte[] data = bucket.toByteArray();
+ fNew.delete();
- this.revocationDNFCounter = 0;
- this.finalCheck = true;
-
- System.err.println("Searching for revocation key");
- this.queueFetchRevocation(100);
- while(revocationDNFCounter <
NodeUpdater.REVOCATION_DNF_MIN) {
- if(this.hasBeenBlown) {
- Logger.error(this, "The revocation key
has been found on the network : blocking auto-update");
- return;
- }
- try {
- wait(100*1000);
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
-
-
- System.err.println("Update in progress");
- Logger.normal(this, "Update in progress");
- try{
- coreUpdate();
- } catch(Exception e) {
- Logger.error(this, "Error while updating the node :
"+e);
- System.out.println("Exception : "+e);
- e.printStackTrace();
- }
- }
-
- private void coreUpdate() {
- ArrayBucket bucket = (ArrayBucket) result.asBucket();
- byte[] data = bucket.toByteArray();
-
- File fRunning = new File("freenet.jar");
- File fNew = new File("freenet.jar.new");
-
- boolean nastyRestart = false;
-
- Properties p = WrapperManager.getProperties();
- String cp1 = p.getProperty("wrapper.java.classpath.1");
-
- if((File.separatorChar == '\\') ||
(System.getProperty("os.name").toLowerCase().startsWith("win"))
- || (cp1 != null &&
(cp1.equals("freenet-cvs-snapshot.jar")||cp1.equals("freenet-latest-stable.jar"))))
{
- nastyRestart = true;
-
- if(cp1 == null) {
- Logger.error(this, "wrapper.java.classpath.1 =
null - maybe wrapper.conf is broken?");
- System.err.println("wrapper.java.classpath.1 =
null - maybe wrapper.conf is broken?");
- } else if(cp1.equals("freenet-cvs-snapshot.jar") ||
cp1.equals("freenet-stable-latest.jar")) {
- // Upgrade from an older version
- fNew = fRunning;
- fRunning = new File(cp1);
- } else if(cp1.equals("freenet.jar")) {
- // Cool!
- } else if(cp1.equals("freenet.jar.new")) {
- // Swapped; we are running .new
- File tmp = fRunning;
- fRunning = fNew;
- fNew = tmp;
- } else {
- cp1 = p.getProperty("wrapper.java.classpath.2");
- if(cp1 != null && cp1.equals("freenet.jar")) {
- // Cool!
- } else if(cp1 != null &&
cp1.equals("freenet.jar.new")) {
- // Swapped; we are running .new
- File tmp = fRunning;
- fRunning = fNew;
- fNew = tmp;
- } else {
- Logger.error(this, "Cannot restart on
Windows due to non-standard config file!");
- System.err.println("Cannot restart on
Windows due to non-standard config file!");
- return;
- }
- }
- }
-
- fNew.delete();
-
- FileOutputStream fos;
- try {
+ FileOutputStream fos;
fos = new FileOutputStream(fNew);
fos.write(data);
fos.flush();
fos.close();
- } catch (FileNotFoundException e) {
- System.err.println("Could not write new jar
"+fNew.getAbsolutePath()+" : File not found ("+e+"). Update failed.");
- Logger.error(this, "Could not write new jar
"+fNew.getAbsolutePath()+" : File not found ("+e+"). Update failed.");
- return;
- } catch (IOException e) {
- System.err.println("Could not write new jar
"+fNew.getAbsolutePath()+" : I/O error ("+e+"). Update failed.");
- Logger.error(this, "Could not write new jar
"+fNew.getAbsolutePath()+" : I/O error ("+e+"). Update failed.");
- return;
}
-
- System.out.println("################## New jar written!
"+fetchedVersion+ ' ' +fNew.getAbsolutePath());
-
- if(!nastyRestart) {
- // Easy way.
- if(!fNew.renameTo(fRunning)) {
- fRunning.delete();
- if(!fNew.renameTo(fRunning)) {
- System.err.println("ERROR renaming the
file!: "+fNew+" to "+fRunning);
- return;
- }
- }
- } else {
- // Hard way.
- // Don't just write it out from properties; we want to
keep it as close to what it was as possible.
-
- try {
-
- File oldConfig = new File("wrapper.conf");
- File newConfig = new File("wrapper.conf.new");
-
- FileInputStream fis = new
FileInputStream(oldConfig);
- BufferedInputStream bis = new
BufferedInputStream(fis);
- InputStreamReader isr = new
InputStreamReader(bis);
- BufferedReader br = new BufferedReader(isr);
-
- fos = new FileOutputStream(newConfig);
- OutputStreamWriter osw = new
OutputStreamWriter(fos);
- BufferedWriter bw = new BufferedWriter(osw);
-
- String line;
- boolean succeeded = false;
- boolean stillSucceeded = false;
- while((line = br.readLine()) != null) {
-
if(line.equals("wrapper.java.classpath.1="+fRunning.getName())) {
-
bw.write("wrapper.java.classpath.1="+fNew.getName()+"\r\n");
- succeeded = true;
- } else
if(line.equals("wrapper.java.classpath.2="+fRunning.getName())) {
-
bw.write("wrapper.java.classpath.2="+fNew.getName()+"\r\n");
- succeeded = true;
- } else {
-
if(line.equalsIgnoreCase("wrapper.restart.reload_configuration=TRUE"))
- stillSucceeded = true;
- else
if(line.equalsIgnoreCase("wrapper.restart.reload_configuration=TRUE"))
- line =
"wrapper.restart.reload_configuration=TRUE";
- bw.write(line+"\r\n");
- }
- }
- br.close();
-
- if(!succeeded) {
- System.err.println("Not able to update
because of non-standard config");
- Logger.error(this, "Not able to update
because of non-standard config");
- return;
- }
-
- if(!stillSucceeded) {
- // Add it.
-
bw.write("wrapper.restart.reload_configuration=TRUE");
- }
-
- bw.close();
-
- if(!newConfig.renameTo(oldConfig)) {
- if(!oldConfig.delete()) {
- System.err.println("Cannot
delete "+oldConfig+" so cannot rename over it. Update failed.");
- Logger.error(this, "Cannot
delete "+oldConfig+" so cannot rename over it. Update failed.");
- return;
- }
- if(!newConfig.renameTo(oldConfig)) {
- String err = "CATASTROPHIC
ERROR: Deleted "+oldConfig+" but cannot rename "+newConfig+" to "+oldConfig+"
THEREFORE THE NODE WILL NOT START! Please resolve the problem by renaming
"+newConfig+" to "+oldConfig;
- System.err.println(err);
- Logger.error(this, err);
- node.exit("Updater error");
- return;
- }
- }
-
- // New config installed.
-
- } catch (IOException e) {
- Logger.error(this, "Not able to update because
of I/O error: "+e, e);
- System.err.println("Not able to update because
of I/O error: "+e);
- }
-
+ synchronized(this) {
+ writtenVersion = fetched;
}
- if(node.getNodeStarter()!=null) {
- System.err.println("Restarting because of update");
- node.getNodeStarter().restart();
- } else{
- System.out.println("New version has been downloaded:
please restart your node!");
- node.exit("New version ready but cannot auto-restart");
- }
- System.err.println("WTF? Restart returned!?");
}
-
- public synchronized void onSuccess(FetchResult result, ClientGetter
state) {
+
+ public void onSuccess(FetchResult result, ClientGetter state) {
logMINOR = Logger.shouldLog(Logger.MINOR, this);
- if(!state.getURI().equals(revocationURI)){
+ synchronized(this) {
if(result == null || result.asBucket() == null ||
result.asBucket().size() == 0) {
Logger.error(this, "Cannot update: result
either null or empty for "+availableVersion);
System.err.println("Cannot update: result
either null or empty for "+availableVersion);
@@ -437,131 +180,51 @@
this.fetchedVersion = fetchingVersion;
System.out.println("Found "+fetchingVersion);
Logger.normal(this, "Found a new version! (" +
fetchingVersion + ", setting up a new UpdatedVersionAvailableUserAlert");
- alert.set(availableVersion,fetchingVersion,true);
- alert.isValid(true);
this.cg = null;
if(this.result != null) this.result.asBucket().free();
this.result = result;
- if(this.isAutoUpdateAllowed) {
- scheduleUpdate();
- } else {
- isFetching = false;
- if(availableVersion > fetchingVersion) {
- node.ps.queueTimedJob(new Runnable() {
- public void run() {
maybeUpdate(); }
- }, 0);
- }
- }
- }else{
- // The key has been blown !
- // FIXME: maybe we need a bigger warning message.
- String msg = null;
- try {
- byte[] buf = result.asByteArray();
- msg = new String(buf);
- } catch (Throwable t) {
- try {
- Logger.error(this, "Failed to extract
result when key blown: "+t, t);
- System.err.println("Failed to extract
result when key blown: "+t);
- t.printStackTrace();
- msg = "Failed to extract result when
key blown: "+t;
- } catch (Throwable t1) {
- msg = "Internal error after retreiving
revocation key";
- }
- }
- blow(msg);
}
+ manager.onDownloadedNewJar(extUpdate);
}
public void onFailure(FetchException e, ClientGetter state) {
logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ if(!isRunning) return;
int errorCode = e.getMode();
- if(!state.getURI().equals(revocationURI)){
- if(logMINOR) Logger.minor(this, "onFailure("+e+ ','
+state+ ')');
- synchronized(this) {
- this.cg = null;
- isFetching=false;
- }
- state.cancel();
- if((errorCode == FetchException.DATA_NOT_FOUND) ||
- (errorCode ==
FetchException.ROUTE_NOT_FOUND) ||
- (errorCode ==
FetchException.PERMANENT_REDIRECT) ||
- (errorCode ==
FetchException.REJECTED_OVERLOAD)){
-
- Logger.normal(this, "Rescheduling new request");
- maybeUpdate();
+ if(logMINOR) Logger.minor(this, "onFailure("+e+ ',' +state+
')');
+ synchronized(this) {
+ this.cg = null;
+ isFetching=false;
+ }
+ if((errorCode == FetchException.DATA_NOT_FOUND) ||
+ (errorCode == FetchException.ROUTE_NOT_FOUND) ||
+ (errorCode ==
FetchException.PERMANENT_REDIRECT) ||
+ (errorCode == FetchException.REJECTED_OVERLOAD)
||
+ (errorCode == FetchException.CANCELLED)){
+
+ Logger.normal(this, "Rescheduling new request");
+ ticker.queueTimedJob(new Runnable() {
+ public void run() {
+ maybeUpdate();
+ }
+ }, 0);
+ maybeUpdate();
+ } else {
+ Logger.error(this, "Canceling fetch : "+
e.getMessage());
+ System.err.println("Unexpected error fetching update:
"+e.getMessage());
+ if(e.isFatal()) {
+ // Wait for the next version
} else {
- Logger.error(this, "Canceling fetch : "+
e.getMessage());
- System.err.println("Unexpected error fetching
update: "+e.getMessage());
+ ticker.queueTimedJob(new Runnable() {
+ public void run() {
+ maybeUpdate();
+ }
+ }, 60*60*1000);
}
- }else{
- Logger.minor(this, "Revocation fetch failed: "+e);
- synchronized(this) {
- if(errorCode == FetchException.DATA_NOT_FOUND){
- revocationDNFCounter++;
- Logger.minor(this, "Incremented DNF
counter to "+revocationDNFCounter);
- }
- }
- if(e.isFatal()) this.blow("Permanent error fetching
revocation (invalid data inserted?): "+e.toString());
- // Start it again
- synchronized(this) {
- if(this.finalCheck) {
- if(revocationDNFCounter < 3)
- queueFetchRevocation(1000);
- else
- notifyAll();
- } else {
- boolean pause = (revocationDNFCounter
== 3);
- if(pause) revocationDNFCounter = 0;
- queueFetchRevocation(pause ? 60*60*1000
: 5000);
- }
- }
}
}
- private void queueFetchRevocation(long delay) {
- if(logMINOR) Logger.minor(this, "Queueing fetch of revocation
key in "+delay+"ms", new Exception("debug"));
- node.ps.queueTimedJob(new Runnable() { // maybe a FastRunnable?
FIXME
-
- public void run() {
- try {
- // We've got the data, so speed things
up a bit.
- ClientGetter cg;
- synchronized(this) {
- if(revocationGetter != null &&
-
!(revocationGetter.isCancelled() || revocationGetter.isFinished())) {
- if(logMINOR)
Logger.minor(this, "Not queueing another revocation fetcher yet");
- return;
- } else {
- if(logMINOR)
Logger.minor(this, "fetcher="+revocationGetter);
- if(revocationGetter !=
null)
-
Logger.minor(this, "revocation fetcher:
cancelled="+revocationGetter.isCancelled()+",
finished="+revocationGetter.isFinished());
- cg = revocationGetter =
new ClientGetter(NodeUpdater.this, core.requestStarters.chkFetchScheduler,
core.requestStarters.sskFetchScheduler, revocationURI, ctxRevocation,
RequestStarter.MAXIMUM_PRIORITY_CLASS, NodeUpdater.this, null);
- if(logMINOR)
Logger.minor(this, "Queued another revocation fetcher");
- }
- }
- cg.start();
- if(logMINOR) Logger.minor(this,
"Started revocation fetcher");
- } catch (FetchException e) {
- Logger.error(this, "Not able to start
the revocation fetcher.");
- blow("Cannot fetch the auto-update
URI");
- }
- }
-
- }, delay);
- }
-
- private void scheduleUpdate() {
- node.ps.queueTimedJob(new Runnable() { // definitely needs its
own thread
-
- public void run() {
- Update();
- }
-
- }, 500); // give it time to get close-together next version
- }
-
public void onSuccess(BaseClientPutter state) {
// Impossible
}
@@ -574,49 +237,30 @@
// Impossible
}
- public synchronized boolean isUpdatable() throws
PrivkeyHasBeenBlownException{
- if(hasBeenBlown)
- throw new
PrivkeyHasBeenBlownException(revocationMessage);
- else
- return (currentVersion<availableVersion);
- }
-
public synchronized boolean isRunning(){
return isRunning;
}
- protected void kill(){
+ /** Called before kill(). Don't do anything that will involve taking
locks. */
+ public void preKill() {
+ isRunning = false;
+ }
+
+ void kill(){
try{
- ClientGetter c, r;
+ ClientGetter c;
synchronized(this) {
isRunning = false;
USK
myUsk=USK.create(URI.setSuggestedEdition(currentVersion));
ctx.uskManager.unsubscribe(myUsk, this, true);
c = cg;
- r = revocationGetter;
+ cg = null;
}
c.cancel();
- r.cancel();
}catch(Exception e){
}
}
- public synchronized void blow(String msg){
- if(hasBeenBlown){
- Logger.error(this, "The key has ALREADY been marked as
blown!");
- }else{
- this.revocationMessage = msg;
- this.hasBeenBlown = true;
- if(revocationAlert==null){
- revocationAlert = new
RevocationKeyFoundUserAlert(msg);
- core.alerts.register(revocationAlert);
- // we don't need to advertize updates : we are
not going to do them
- core.alerts.unregister(alert);
- }
- Logger.error(this, "The updater has acknoledged that it
knows the private key has been blown");
- }
- }
-
public FreenetURI getUpdateKey(){
return URI;
}
@@ -625,66 +269,42 @@
return revocationURI;
}
- protected synchronized void setAutoupdateAllowed(boolean b) {
- this.isAutoUpdateAllowed = b;
+ public boolean inFinalCheck() {
+ return finalCheck;
}
-
- public static NodeUpdater maybeCreate(Node node, Config config) throws
Exception {
- SubConfig updaterConfig = new SubConfig("node.updater", config);
-
- updaterConfig.register("enabled",
WrapperManager.isControlledByNativeWrapper(), 1, true, false, "Check for, and
download new versions",
- "Should your node automatically check for new versions
of Freenet. If yes, new versions will be automatically detected and downloaded,
but not necessarily installed. This setting resets itself always back to false
unless the node runs within the wrapper.",
- new UpdaterEnabledCallback(node, config));
-
- boolean enabled = updaterConfig.getBoolean("enabled");
- // is the auto-update allowed ?
- updaterConfig.register("autoupdate", false, 2, false, true,
"Automatically install new versions", "Should your node automatically update to
the newest version of Freenet, without asking?",
- new AutoUpdateAllowedCallback(node));
- boolean autoUpdateAllowed = updaterConfig.getBoolean("autoupdate");
+ public void onMajorProgress() {
+ // Ignore
+ }
- updaterConfig.register("URI", NodeUpdater.UPDATE_URI, 3,
- true, false, "Where should the node look for updates?",
- "Where should the node look for updates?",
- new UpdateURICallback(node));
+ public synchronized boolean canUpdateNow() {
+ return fetchedVersion > currentVersion;
+ }
+
+ public void onFetchable(BaseClientPutter state) {
+ // Ignore, we don't insert
+ }
- String URI = updaterConfig.getString("URI");
-
-
- updaterConfig.register("revocationURI",
NodeUpdater.REVOCATION_URI,4,
- true, false, "Where should the node look for revocation
?",
- "Where should the node look for revocation ?",
- new UpdateRevocationURICallback(node));
-
- String revURI = updaterConfig.getString("revocationURI");
-
- updaterConfig.finishedInitialization();
-
- if(enabled) {
- try{
- return new NodeUpdater(node , autoUpdateAllowed, new
FreenetURI(URI), new FreenetURI(revURI));
- }catch(Exception e){
- Logger.error(node, "Error starting the NodeUpdater:
"+e);
- throw new Exception("Unable to start the NodeUpdater
up");
- }
- } else {
- return null;
- }
+ /** Called when the fetch URI has changed. No major locks are held by
caller. */
+ public void onChangeURI() {
+ kill();
+ maybeUpdate();
}
- public boolean inFinalCheck() {
- return finalCheck;
+ public int getWrittenVersion() {
+ return writtenVersion;
}
- public void onMajorProgress() {
- // Ignore
+ public int getFetchedVersion() {
+ return fetchedVersion;
}
- public int getRevocationDNFCounter() {
- return revocationDNFCounter;
+ public boolean isFetching() {
+ return availableVersion > fetchedVersion && availableVersion >
currentVersion;
}
- public void onFetchable(BaseClientPutter state) {
- // Ignore, we don't insert
+ public int fetchingVersion() {
+ if(fetchingVersion == 0) return availableVersion;
+ else return fetchingVersion;
}
}
Added: trunk/freenet/src/freenet/node/updater/NodeUpdaterManager.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/NodeUpdaterManager.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/NodeUpdaterManager.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -0,0 +1,722 @@
+package freenet.node.updater;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import org.tanukisoftware.wrapper.WrapperManager;
+
+import freenet.config.BooleanCallback;
+import freenet.config.Config;
+import freenet.config.InvalidConfigValueException;
+import freenet.config.StringCallback;
+import freenet.config.SubConfig;
+import freenet.keys.FreenetURI;
+import freenet.node.Node;
+import freenet.node.NodeStarter;
+import freenet.node.Version;
+import freenet.node.updater.UpdateDeployContext.UpdateCatastropheException;
+import freenet.node.useralerts.RevocationKeyFoundUserAlert;
+import freenet.node.useralerts.SimpleUserAlert;
+import freenet.node.useralerts.UpdatedVersionAvailableUserAlert;
+import freenet.node.useralerts.UserAlert;
+import freenet.support.Logger;
+
+/**
+ * Supervises NodeUpdater's. Enables us to easily update multiple files,
+ * change the URI's on the fly, eliminates some messy code in the
+ * callbacks etc.
+ */
+public class NodeUpdaterManager {
+
+ public final static String UPDATE_URI = "freenet:USK at
SIDKS6l-eOU8IQqDo03d~3qqBd-69WG60aDgg4nWqss,CPFqYi95Is3GwzAdAKtAuFMCXDZFFWC3~uPoidCD67s,AQABAAE/update/"+Version.buildNumber();
+ public final static String REVOCATION_URI = "freenet:SSK at
VOfCZVTYPaatJ~eB~4lu2cPrWEmGyt4bfbB1v15Z6qQ,B6EynLhm7QE0se~rMgWWhl7wh3rFWjxJsEUcyohAm8A,AQABAAE/revoked";
+ public final static String EXT_URI = "freenet:USK at
SIDKS6l-eOU8IQqDo03d~3qqBd-69WG60aDgg4nWqss,CPFqYi95Is3GwzAdAKtAuFMCXDZFFWC3~uPoidCD67s,AQABAAE/ext/"+Version.buildNumber();
+
+ FreenetURI updateURI;
+ FreenetURI extURI;
+ FreenetURI revocationURI;
+
+ NodeUpdater mainUpdater;
+ NodeUpdater extUpdater;
+
+ boolean wasEnabledOnStartup;
+ /** Is auto-update enabled? */
+ boolean isAutoUpdateAllowed;
+ /** Has the user given the go-ahead? */
+ boolean armed;
+ /** Should we check for freenet-ext.jar updates?
+ * Normally set only when our freenet-ext.jar is known to be out of
date. */
+ final boolean shouldUpdateExt;
+ /** Currently deploying an update? */
+ boolean isDeployingUpdate;
+
+ Node node;
+
+ final RevocationChecker revocationChecker;
+ private String revocationMessage;
+ private boolean hasBeenBlown;
+
+ /** Is there a new main jar ready to deploy? */
+ private boolean hasNewMainJar;
+ /** Is there a new ext jar ready to deploy? */
+ private boolean hasNewExtJar;
+ /** If another main jar is being fetched, when did the fetch start? */
+ private long startedFetchingNextMainJar;
+ /** If another ext jar is being fetched, when did the fetch start? */
+ private long startedFetchingNextExtJar;
+
+ // Revocation alert
+ private RevocationKeyFoundUserAlert revocationAlert;
+ // Update alert
+ private final UpdatedVersionAvailableUserAlert alert;
+
+ private boolean logMINOR;
+
+ public NodeUpdaterManager(Node node, Config config) throws
InvalidConfigValueException {
+ logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ this.node = node;
+ this.hasBeenBlown = false;
+ shouldUpdateExt = NodeStarter.extBuildNumber <
NodeStarter.RECOMMENDED_EXT_BUILD_NUMBER;
+ this.alert= new UpdatedVersionAvailableUserAlert(this);
+ alert.isValid(false);
+
+ SubConfig updaterConfig = new SubConfig("node.updater", config);
+
+ updaterConfig.register("enabled",
WrapperManager.isControlledByNativeWrapper(), 1, true, false, "Check for, and
download new versions",
+ "Should your node automatically check for new versions
of Freenet. If yes, new versions will be automatically detected and downloaded,
but not necessarily installed. This setting resets itself always back to false
unless the node runs within the wrapper.",
+ new UpdaterEnabledCallback());
+
+ wasEnabledOnStartup = updaterConfig.getBoolean("enabled");
+
+ // is the auto-update allowed ?
+ updaterConfig.register("autoupdate", false, 2, false, true,
"Automatically install new versions", "Should your node automatically update to
the newest version of Freenet, without asking?",
+ new AutoUpdateAllowedCallback());
+ isAutoUpdateAllowed = updaterConfig.getBoolean("autoupdate");
+
+ updaterConfig.register("URI", UPDATE_URI, 3,
+ true, false, "Where should the node look for updates?",
+ "Where should the node look for updates?",
+ new UpdateURICallback(false));
+
+ try {
+ updateURI = new
FreenetURI(updaterConfig.getString("URI"));
+ } catch (MalformedURLException e) {
+ throw new InvalidConfigValueException("Invalid
updateURI: "+e);
+ }
+
+ if(updateURI.lastMetaString() != null &&
updateURI.lastMetaString().length() == 0) {
+ // FIXME remove this hack, put in because of bad default
+ System.err.println("Correcting auto-update URI:
Removing extra /");
+ updateURI = updateURI.popMetaString();
+ }
+
+ updaterConfig.register("revocationURI", REVOCATION_URI,4,
+ true, false, "Where should the node look for revocation
?",
+ "Where should the node look for revocation ?",
+ new UpdateRevocationURICallback());
+
+ try {
+ revocationURI = new
FreenetURI(updaterConfig.getString("revocationURI"));
+ } catch (MalformedURLException e) {
+ throw new InvalidConfigValueException("Invalid
revocationURI: "+e);
+ }
+
+ if(revocationURI.lastMetaString() != null &&
revocationURI.lastMetaString().length() == 0) {
+ // FIXME remove this hack, put in because of bad default
+ System.err.println("Correcting auto-update revocation
URI: Removing extra /");
+ revocationURI = revocationURI.popMetaString();
+ }
+
+ updaterConfig.register("extURI", EXT_URI, 5,
+ true, false, "Where should the node look for updates to
freenet-ext.jar?",
+ "Where should the node look for updates to
freenet-ext.jar?",
+ new UpdateURICallback(true));
+
+ try {
+ extURI = new
FreenetURI(updaterConfig.getString("extURI"));
+ } catch (MalformedURLException e) {
+ throw new InvalidConfigValueException("Invalid extURI:
"+e);
+ }
+
+ updaterConfig.finishedInitialization();
+
+ this.revocationChecker = new RevocationChecker(this);
+
+ }
+
+ public void start() throws InvalidConfigValueException {
+
+ node.clientCore.alerts.register(alert);
+
+ enable(wasEnabledOnStartup);
+
+ }
+
+ /**
+ * Is auto-update enabled?
+ */
+ public boolean isEnabled() {
+ synchronized(this) {
+ return mainUpdater != null && mainUpdater.isRunning();
+ }
+ }
+
+ /**
+ * Enable or disable auto-update.
+ * @param enable Whether auto-update should be enabled.
+ * @throws InvalidConfigValueException If enable=true and we are not
running under the wrapper.
+ */
+ void enable(boolean enable) throws InvalidConfigValueException {
+ logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ NodeUpdater main = null, ext = null;
+ synchronized(this) {
+ boolean enabled = (mainUpdater != null &&
mainUpdater.isRunning());
+ if(enabled == enable) return;
+ if(!enable) {
+ // Kill it
+ mainUpdater.preKill();
+ main = mainUpdater;
+ mainUpdater = null;
+ if(extUpdater != null)
+ extUpdater.preKill();
+ ext = extUpdater;
+ extUpdater = null;
+ } else {
+
if((!WrapperManager.isControlledByNativeWrapper()) ||
(NodeStarter.extBuildNumber == -1)) {
+ Logger.error(this, "Cannot update
because not running under wrapper");
+ throw new
InvalidConfigValueException("Cannot update because not running under wrapper");
+ }
+ // Start it
+ mainUpdater = new NodeUpdater(this, updateURI,
revocationURI, false, Version.buildNumber());
+ if(shouldUpdateExt)
+ extUpdater = new NodeUpdater(this,
extURI, revocationURI, true, NodeStarter.extBuildNumber);
+ }
+ }
+ if(!enable) {
+ if(main != null) main.kill();
+ if(ext != null) ext.kill();
+ revocationChecker.kill();
+ } else {
+ mainUpdater.start();
+ if(extUpdater != null)
+ extUpdater.start();
+ revocationChecker.start(false);
+ }
+ }
+
+ /**
+ * Create a NodeUpdaterManager. Called by node constructor.
+ * @param node The node object.
+ * @param config The global config object. Options will be added to a
subconfig called node.updater.
+ * @return A new NodeUpdaterManager
+ * @throws InvalidConfigValueException If there is an error in the
config.
+ */
+ public static NodeUpdaterManager maybeCreate(Node node, Config config)
throws InvalidConfigValueException {
+ return new NodeUpdaterManager(node, config);
+ }
+
+ /**
+ * Get the URI for either the freenet.jar updater or the
freenet-ext.jar updater.
+ * @param isExt If true, return the freenet-ext.jar update URI; if
false, return the freenet.jar URI.
+ * @return See above.
+ */
+ public synchronized FreenetURI getURI(boolean isExt) {
+ return isExt ? extURI : updateURI;
+ }
+
+ /**
+ * Set the URI for either the freenet.jar updater or the
freenet-ext.jar updater.
+ * @param isExt If true, set the freenet-ext.jar update URI; if false,
set the freenet.jar update URI.
+ * @param uri The URI to set.
+ */
+ public void setURI(boolean isExt, FreenetURI uri) {
+ NodeUpdater updater;
+ synchronized(this) {
+ if(isExt) {
+ if(extURI.equals(uri)) return;
+ extURI = uri;
+ updater = extUpdater;
+ } else {
+ if(updateURI.equals(uri)) return;
+ updateURI = uri;
+ updater = mainUpdater;
+ }
+ }
+ if(updater == null) return;
+ if(updater.isRunning()) return;
+ updater.onChangeURI();
+ }
+
+ /**
+ * @return Is auto-update currently enabled?
+ */
+ public synchronized boolean isAutoUpdateAllowed() {
+ return isAutoUpdateAllowed;
+ }
+
+ /**
+ * Enable or disable auto-update.
+ * @param val If true, enable auto-update (and immediately update if an
update is ready). If false, disable it.
+ */
+ public void setAutoUpdateAllowed(boolean val) {
+ synchronized(this) {
+ if(val == isAutoUpdateAllowed) return;
+ isAutoUpdateAllowed = val;
+ if(val) {
+ if(!isReadyToDeployUpdate(false)) return;
+ } else return;
+ }
+ deployOffThread(0);
+ }
+
+ private static final int WAIT_FOR_SECOND_FETCH_TO_COMPLETE = 240*1000;
+ private static final int RECENT_REVOCATION_INTERVAL = 120*1000;
+
+ /** Does the updater have an update ready to deploy? May be called
synchronized(this) */
+ private boolean isReadyToDeployUpdate(boolean ignoreRevocation) {
+ long now = System.currentTimeMillis();
+ long startedMillisAgo = -1;
+ synchronized(this) {
+ if(!(hasNewMainJar || hasNewExtJar)) return false; //
no jar
+ // Don't immediately deploy if still fetching
+ startedMillisAgo = now -
Math.max(startedFetchingNextMainJar, startedFetchingNextExtJar);
+ if(startedMillisAgo < WAIT_FOR_SECOND_FETCH_TO_COMPLETE)
+ return false; // Wait for running fetch to
complete
+ if(!ignoreRevocation) {
+ if(now - revocationChecker.lastSucceeded() <
RECENT_REVOCATION_INTERVAL)
+ return true;
+ }
+ }
+ revocationChecker.start(true);
+ if(ignoreRevocation) return true;
+ deployOffThread(WAIT_FOR_SECOND_FETCH_TO_COMPLETE -
startedMillisAgo);
+ return false;
+ }
+
+ /** Check whether there is an update to deploy. If there is, do it. */
+ private void deployUpdate() {
+ logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ try {
+ synchronized(this) {
+ if(hasBeenBlown) {
+ String msg = "Trying to update but key
has been blown! Message was "+revocationMessage;
+ Logger.error(this, msg);
+ System.err.println(msg);
+ return;
+ }
+ if(!isEnabled()) return;
+ if(!(isAutoUpdateAllowed || armed)) return;
+ if(!isReadyToDeployUpdate(false)) return;
+ if(isDeployingUpdate) return;
+ isDeployingUpdate = true;
+ }
+
+ innerDeployUpdate();
+ } catch (Throwable t) {
+ synchronized(this) {
+ isDeployingUpdate = false;
+ }
+ }
+ }
+
+ /**
+ * Deploy the update. Inner method. Doesn't check anything, just does
it.
+ */
+ private void innerDeployUpdate() {
+ // Write the jars, config etc.
+ // Then restart
+
+ UpdateDeployContext ctx;
+ try {
+ ctx = new UpdateDeployContext();
+ } catch (UpdaterParserException e) {
+ failUpdate("Could not determine which jars are in use:
"+e.getMessage());
+ return;
+ }
+
+ if(writeJars(ctx))
+ restart(ctx);
+ }
+
+ /**
+ * Write the updated jars, if necessary rewrite the wrapper.conf.
+ * @return True if this part of the update succeeded.
+ */
+ private boolean writeJars(UpdateDeployContext ctx) {
+ /**
+ * What do we want to do here?
+ * 1. If we have a new main jar:
+ * - If on Windows, write it to a new jar file, update the
wrapper.conf to point to it.
+ * - Otherwise, write to a new jar file, then move the new jar
file over the old jar file.
+ * 2. If we have a new ext jar:
+ * - Write it to a new jar file, update the wrapper.conf to
point to it.
+ *
+ * Then restart.
+ */
+
+ boolean writtenNewJar = false;
+ boolean writtenNewExt = false;
+
+ boolean tryEasyWay = File.pathSeparatorChar == '/' &&
!hasNewExtJar;
+
+ File mainJar = ctx.getMainJar();
+ File newMainJar = ctx.getNewMainJar();
+
+ if(hasNewMainJar) {
+ writtenNewJar = true;
+ boolean writtenToTempFile = false;
+ try {
+ if(newMainJar.exists()) {
+ if(!newMainJar.delete()) {
+ if(newMainJar.exists()) {
+
System.err.println("Cannot write to preferred new jar location "+newMainJar);
+ if(tryEasyWay) {
+ try {
+
newMainJar = File.createTempFile("freenet", ".jar", mainJar.getParentFile());
+ } catch
(IOException e) {
+
failUpdate("Cannot write to any other location either - disk full? "+e);
+ return
false;
+ }
+ // Try writing
to it
+ try {
+
mainUpdater.writeJarTo(newMainJar);
+
writtenToTempFile = true;
+ } catch
(IOException e) {
+
newMainJar.delete();
+
failUpdate("Cannot write new jar - disk full? "+e);
+ return
false;
+ }
+ } else {
+ // Try writing
it to the new one even though we can't delete it.
+
mainUpdater.writeJarTo(newMainJar);
+ }
+ } else {
+
mainUpdater.writeJarTo(newMainJar);
+ }
+ } else {
+ if(logMINOR) Logger.minor(this,
"Deleted old jar "+newMainJar);
+
mainUpdater.writeJarTo(newMainJar);
+ }
+ } else {
+ mainUpdater.writeJarTo(newMainJar);
+ }
+ } catch (IOException e) {
+ failUpdate("Cannot update: Cannot write to " +
(tryEasyWay ? " temp file " : "new jar ")+newMainJar);
+ return false;
+ }
+
+ if(tryEasyWay) {
+ // Do it the easy way. Just rewrite the main
jar.
+ if(!newMainJar.renameTo(mainJar)) {
+ Logger.error(this, "Cannot rename temp
file "+newMainJar+" over original jar "+mainJar);
+ if(writtenToTempFile) {
+ // Fail the update - otherwise
we will leak disk space
+ newMainJar.delete();
+ failUpdate("Cannot write to
preferred new jar location and cannot rename temp file over old jar, update
failed");
+ return false;
+ }
+ // Try the hard way
+ } else {
+ System.err.println("Written new freenet
jar: "+mainUpdater.getWrittenVersion());
+ return true;
+ }
+ }
+
+ }
+
+ // Easy way didn't work or we can't do the easy way. Try the
hard way.
+
+ if(hasNewExtJar) {
+
+ writtenNewExt = true;
+
+ // Write the new ext jar
+
+ File newExtJar = ctx.getNewExtJar();
+
+ try {
+ extUpdater.writeJarTo(newExtJar);
+ } catch (IOException e) {
+ failUpdate("Cannot write new ext jar to
"+newExtJar);
+ return false;
+ }
+
+ }
+
+ try {
+ ctx.rewriteWrapperConf(writtenNewJar, writtenNewExt);
+ } catch (IOException e) {
+ failUpdate("Cannot rewrite wrapper.conf: "+e);
+ return false;
+ } catch (UpdateCatastropheException e) {
+ failUpdate(e.getMessage());
+ node.clientCore.alerts.register(new
SimpleUserAlert(false, "Catastrophic update failure", e.getMessage(),
UserAlert.CRITICAL_ERROR));
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Restart the node. Does not return. */
+ private void restart(UpdateDeployContext ctx) {
+ node.getNodeStarter().restart();
+ try {
+ Thread.sleep(5*60*1000);
+ } catch (InterruptedException e) {
+ // Break
+ } // in case it's still restarting
+ System.err.println("Failed to restart. Exiting, please restart
the node.");
+ System.exit(Node.EXIT_RESTART_FAILED);
+ }
+
+ private void failUpdate(String reason) {
+ Logger.error(this, "Update failed: "+reason);
+ System.err.println("Update failed: "+reason);
+ this.killUpdateAlerts();
+ node.clientCore.alerts.register(new SimpleUserAlert(true,
"Update Failed!", "Update Failed: "+reason, UserAlert.ERROR));
+ }
+
+ /** @return The revocation URI. */
+ public synchronized FreenetURI getRevocationURI() {
+ return revocationURI;
+ }
+
+ /**
+ * Set the revocation URI.
+ * @param uri The new revocation URI.
+ */
+ public void setRevocationURI(FreenetURI uri) {
+ synchronized(this) {
+ if(revocationURI.equals(uri)) return;
+ this.revocationURI = uri;
+ }
+ revocationChecker.onChangeRevocationURI();
+ }
+
+ /**
+ * Called when a new jar has been downloaded.
+ * @param isExt If true, the new jar is the ext jar; if false, it is
the main jar.
+ */
+ void onDownloadedNewJar(boolean isExt) {
+ synchronized(this) {
+ if(isExt) {
+ hasNewExtJar = true;
+ startedFetchingNextExtJar = -1;
+ } else {
+ hasNewMainJar = true;
+ startedFetchingNextMainJar = -1;
+ }
+ }
+ revocationChecker.start(true);
+ }
+
+ /**
+ * Called when the NodeUpdater starts to fetch a new version of the jar.
+ * @param isExt If true, the new jar is the ext jar; if false, it is
the main jar.
+ */
+ void onStartFetching(boolean isExt) {
+ long now = System.currentTimeMillis();
+ synchronized(this) {
+ if(isExt) {
+ startedFetchingNextExtJar = now;
+ } else {
+ startedFetchingNextMainJar = now;
+ }
+ }
+ }
+
+ public void blow(String msg){
+ synchronized(this) {
+ if(hasBeenBlown){
+ Logger.error(this, "The key has ALREADY been
marked as blown! Message was "+revocationMessage+" new message "+msg);
+ return;
+ }else{
+ this.revocationMessage = msg;
+ this.hasBeenBlown = true;
+ // We must get to the lower part, and show the
user the message
+ try {
+ System.err.println("THE AUTO-UPDATING
SYSTEM HAS BEEN COMPROMIZED!");
+ System.err.println("The auto-updating
system revocation key has been inserted. It says: "+revocationMessage);
+ } catch (Throwable t) {
+ try {
+ Logger.error(this, "Caught "+t,
t);
+ } catch (Throwable t1) {}
+ }
+ }
+ }
+ if(revocationAlert==null){
+ revocationAlert = new RevocationKeyFoundUserAlert(msg);
+ node.clientCore.alerts.register(revocationAlert);
+ // we don't need to advertize updates : we are not
going to do them
+ killUpdateAlerts();
+ }
+ }
+
+ /**
+ * Kill all UserAlerts asking the user whether he wants to update.
+ */
+ private void killUpdateAlerts() {
+ node.clientCore.alerts.unregister(alert);
+ }
+
+ /** Called when the RevocationChecker has got 3 DNFs on the revocation
key */
+ public void noRevocationFound() {
+ deployUpdate(); // May have been waiting for the revocation.
+ // If we're still here, we didn't update.
+ node.ps.queueTimedJob(new Runnable() {
+ public void run() {
+ revocationChecker.start(false);
+ }
+ }, node.random.nextInt(24*60*60*1000));
+ }
+
+ public void arm() {
+ synchronized(this) {
+ armed = true;
+ }
+ deployOffThread(0);
+ }
+
+ void deployOffThread(long delay) {
+ node.ps.queueTimedJob(new Runnable() {
+ public void run() {
+ deployUpdate();
+ }
+ }, delay);
+ }
+
+ /**
+ * Has the private key been revoked?
+ */
+ public boolean isBlown() {
+ return hasBeenBlown;
+ }
+
+ // Config callbacks
+
+ class UpdaterEnabledCallback implements BooleanCallback {
+
+ public boolean get() {
+ return isEnabled();
+ }
+
+ public void set(boolean val) throws InvalidConfigValueException
{
+ enable(val);
+ }
+ }
+
+ class AutoUpdateAllowedCallback implements BooleanCallback {
+
+ public boolean get() {
+ return isAutoUpdateAllowed();
+ }
+
+ public void set(boolean val) throws InvalidConfigValueException
{
+ setAutoUpdateAllowed(val);
+ }
+ }
+
+ class UpdateURICallback implements StringCallback {
+
+ boolean isExt;
+
+ UpdateURICallback(boolean isExt) {
+ this.isExt = isExt;
+ }
+
+ public String get() {
+ return getURI(isExt).toString(false);
+ }
+
+ public void set(String val) throws InvalidConfigValueException {
+ FreenetURI uri;
+ try {
+ uri = new FreenetURI(val);
+ } catch (MalformedURLException e) {
+ throw new InvalidConfigValueException("Invalid
key: "+val+" : "+e);
+ }
+ setURI(isExt, uri);
+ }
+
+ }
+
+ public class UpdateRevocationURICallback implements StringCallback {
+
+ public String get() {
+ return getRevocationURI().toString(false);
+ }
+
+ public void set(String val) throws InvalidConfigValueException {
+ FreenetURI uri;
+ try {
+ uri = new FreenetURI(val);
+ } catch (MalformedURLException e) {
+ throw new InvalidConfigValueException("Invalid
key: "+val+" : "+e);
+ }
+ setRevocationURI(uri);
+ }
+
+ }
+
+ public synchronized boolean hasNewMainJar() {
+ return hasNewMainJar;
+ }
+
+ public synchronized boolean hasNewExtJar() {
+ return hasNewExtJar;
+ }
+
+ public int newMainJarVersion() {
+ return mainUpdater.getFetchedVersion();
+ }
+
+ public int newExtJarVersion() {
+ return extUpdater.getFetchedVersion();
+ }
+
+ public boolean fetchingNewMainJar() {
+ return mainUpdater.isFetching();
+ }
+
+ public boolean fetchingNewExtJar() {
+ return extUpdater != null && extUpdater.isFetching();
+ }
+
+ public int fetchingNewMainJarVersion() {
+ return mainUpdater.fetchingVersion();
+ }
+
+ public int fetchingNewExtJarVersion() {
+ return extUpdater.fetchingVersion();
+ }
+
+ public boolean inFinalCheck() {
+ return isReadyToDeployUpdate(true) &&
!isReadyToDeployUpdate(false);
+ }
+
+ public int getRevocationDNFCounter() {
+ return revocationChecker.getRevocationDNFCounter();
+ }
+
+ public int getMainVersion() {
+ return Version.buildNumber();
+ }
+
+ public int getExtVersion() {
+ return NodeStarter.extBuildNumber;
+ }
+
+ public boolean isArmed() {
+ return armed || isAutoUpdateAllowed;
+ }
+
+ public boolean canUpdateNow() {
+ return isReadyToDeployUpdate(true);
+ }
+
+ public boolean canUpdateImmediately() {
+ return isReadyToDeployUpdate(false);
+ }
+
+}
Added: trunk/freenet/src/freenet/node/updater/RevocationChecker.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/RevocationChecker.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/RevocationChecker.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -0,0 +1,192 @@
+package freenet.node.updater;
+
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.client.FetcherContext;
+import freenet.client.InserterException;
+import freenet.client.async.BaseClientPutter;
+import freenet.client.async.ClientCallback;
+import freenet.client.async.ClientGetter;
+import freenet.keys.FreenetURI;
+import freenet.node.NodeClientCore;
+import freenet.node.RequestStarter;
+import freenet.support.Logger;
+
+/**
+ * Fetches the revocation key. Each time it starts, it will try to fetch it
until it has 3 DNFs. If it ever finds it, it will
+ * be immediately fed to the NodeUpdaterManager.
+ */
+public class RevocationChecker implements ClientCallback {
+
+ public final static int REVOCATION_DNF_MIN = 3;
+
+ private boolean logMINOR;
+
+ private NodeUpdaterManager manager;
+ private NodeClientCore core;
+ private int revocationDNFCounter;
+ private FetcherContext ctxRevocation;
+ private ClientGetter revocationGetter;
+ private boolean wasAggressive;
+ /** Last time at which we got 3 DNFs on the revocation key */
+ private long lastSucceeded;
+
+ public RevocationChecker(NodeUpdaterManager manager) {
+ this.manager = manager;
+ core = manager.node.clientCore;
+ this.revocationDNFCounter = 0;
+ this.logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ ctxRevocation = core.makeClient((short)0,
true).getFetcherContext();
+ ctxRevocation.allowSplitfiles = false;
+ ctxRevocation.cacheLocalRequests = false;
+ ctxRevocation.maxArchiveLevels = 1;
+ // big enough ?
+ ctxRevocation.maxOutputLength = 4096;
+ ctxRevocation.maxTempLength = 4096;
+ ctxRevocation.maxSplitfileBlockRetries = -1; // if we find
content, try forever to get it.
+ ctxRevocation.maxNonSplitfileRetries = 0; // but return quickly
normally
+ }
+
+ public int getRevocationDNFCounter() {
+ return revocationDNFCounter;
+ }
+
+ public void start(boolean aggressive) {
+ start(aggressive, true);
+ }
+
+ /** Start a fetch.
+ * @param aggressive If set to true, then we have just fetched an
update, and therefore can increase the priority of the
+ * fetch to maximum.
+ * */
+ public void start(boolean aggressive, boolean reset) {
+
+ logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ try {
+ ClientGetter cg = null;
+ ClientGetter toCancel = null;
+ synchronized(this) {
+ if(aggressive && !wasAggressive) {
+ // Ignore old one.
+ toCancel = revocationGetter;
+ if(logMINOR) Logger.minor(this,
"Ignoring old request, because was low priority");
+ revocationGetter = null;
+ }
+ wasAggressive = aggressive;
+ if(revocationGetter != null &&
+
!(revocationGetter.isCancelled() || revocationGetter.isFinished())) {
+ if(logMINOR) Logger.minor(this, "Not
queueing another revocation fetcher yet, old one still running");
+ } else {
+ if(reset)
+ revocationDNFCounter = 0;
+ if(logMINOR) Logger.minor(this,
"fetcher="+revocationGetter);
+ if(revocationGetter != null)
+ Logger.minor(this, "revocation
fetcher: cancelled="+revocationGetter.isCancelled()+",
finished="+revocationGetter.isFinished());
+ cg = revocationGetter = new
ClientGetter(this, core.requestStarters.chkFetchScheduler,
+
core.requestStarters.sskFetchScheduler, manager.revocationURI, ctxRevocation,
+ aggressive ?
RequestStarter.MAXIMUM_PRIORITY_CLASS :
RequestStarter.IMMEDIATE_SPLITFILE_PRIORITY_CLASS,
+ this, null);
+ if(logMINOR) Logger.minor(this, "Queued
another revocation fetcher");
+ }
+ }
+ if(toCancel != null)
+ toCancel.cancel();
+ if(cg != null) {
+ cg.start();
+ if(logMINOR) Logger.minor(this, "Started
revocation fetcher");
+ }
+ } catch (FetchException e) {
+ Logger.error(this, "Not able to start the revocation
fetcher.");
+ manager.blow("Cannot fetch the auto-update URI");
+ }
+ }
+
+ long lastSucceeded() {
+ return lastSucceeded;
+ }
+
+ /** Called when the revocation URI changes. */
+ public void onChangeRevocationURI() {
+ kill();
+ start(wasAggressive);
+ }
+
+ public void onSuccess(FetchResult result, ClientGetter state) {
+ // The key has been blown !
+ // FIXME: maybe we need a bigger warning message.
+ String msg = null;
+ try {
+ byte[] buf = result.asByteArray();
+ msg = new String(buf);
+ } catch (Throwable t) {
+ try {
+ Logger.error(this, "Failed to extract result
when key blown: "+t, t);
+ System.err.println("Failed to extract result
when key blown: "+t);
+ t.printStackTrace();
+ msg = "Failed to extract result when key blown:
"+t;
+ } catch (Throwable t1) {
+ msg = "Internal error after retreiving
revocation key";
+ }
+ }
+ manager.blow(msg);
+ }
+
+ public void onFailure(FetchException e, ClientGetter state) {
+ Logger.minor(this, "Revocation fetch failed: "+e);
+ logMINOR = Logger.shouldLog(Logger.MINOR, this);
+ int errorCode = e.getMode();
+ boolean completed = false;
+ long now = System.currentTimeMillis();
+ if(errorCode == FetchException.CANCELLED) return; // cancelled
by us above, or killed; either way irrelevant and doesn't need to be restarted
+ if(e.isFatal()) {
+ manager.blow("Permanent error fetching revocation
(error inserting the revocation key?): "+e.toString());
+ return;
+ }
+ synchronized(this) {
+ if(errorCode == FetchException.DATA_NOT_FOUND){
+ revocationDNFCounter++;
+ Logger.minor(this, "Incremented DNF counter to
"+revocationDNFCounter);
+ }
+ if(revocationDNFCounter >= 3) {
+ lastSucceeded = now;
+ completed = true;
+ }
+ revocationGetter = null;
+ }
+ if(completed)
+ manager.noRevocationFound();
+ else
+ start(wasAggressive, false);
+ }
+
+ public void onSuccess(BaseClientPutter state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onFailure(InserterException e, BaseClientPutter state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onMajorProgress() {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void onFetchable(BaseClientPutter state) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void kill() {
+ if(revocationGetter != null)
+ revocationGetter.cancel();
+ }
+
+}
Added: trunk/freenet/src/freenet/node/updater/UpdateDeployContext.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateDeployContext.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/UpdateDeployContext.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -0,0 +1,187 @@
+package freenet.node.updater;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.Properties;
+
+import org.tanukisoftware.wrapper.WrapperManager;
+
+/**
+ * Handles the wrapper.conf, essentially.
+ */
+class UpdateDeployContext {
+
+ public class UpdateCatastropheException extends Exception {
+
+ File oldConfig;
+ File newConfig;
+
+ UpdateCatastropheException(File oldConfig, File newConfig) {
+ super("CATASTROPHIC ERROR: Deleted "+oldConfig+" but
cannot rename "+newConfig+" to "+oldConfig+
+ " THEREFORE THE NODE WILL NOT START!
Please resolve the problem by renaming "+newConfig+" to "+oldConfig);
+ this.oldConfig = oldConfig;
+ this.newConfig = newConfig;
+ }
+
+ }
+
+ File mainJar;
+ File extJar;
+ int mainClasspathNo;
+ int extClasspathNo;
+ File newMainJar;
+ File newExtJar;
+ boolean mainJarAbsolute;
+ boolean extJarAbsolute;
+
+ UpdateDeployContext() throws UpdaterParserException {
+ Properties p = WrapperManager.getProperties();
+
+ for(int propNo=1;true;propNo++) {
+ String prop =
p.getProperty("wrapper.java.classpath."+propNo);
+ if(prop == null) break;
+ File f = new File(prop);
+ boolean isAbsolute = f.isAbsolute();
+ String name = f.getName().toLowerCase();
+ if(extJar == null) {
+ if(name.equals("freenet-ext.jar.new")) {
+ extJar = f;
+ newExtJar = new
File(extJar.getParent(), "freenet-ext.jar");
+ extJarAbsolute = isAbsolute;
+ extClasspathNo = propNo;
+ continue;
+ } else if(name.equals("freenet-ext.jar")) {
+ extJar = f;
+ newExtJar = new
File(extJar.getParent(), "freenet-ext.jar.new");
+ extClasspathNo = propNo;
+ continue;
+ }
+ }
+ if(mainJar == null) {
+ // Try to match it
+
if(name.equals("freenet-stable-latest.jar.new")) {
+ mainJar = f;
+ newMainJar = new
File(mainJar.getParent(), "freenet-stable-latest.jar");
+ mainJarAbsolute = isAbsolute;
+ mainClasspathNo = propNo;
+ continue;
+ } else
if(name.equals("freenet-stable-latest.jar")) {
+ mainJar = f;
+ newMainJar = new
File(mainJar.getParent(), "freenet-stable-latest.jar.new");
+ mainJarAbsolute = isAbsolute;
+ mainClasspathNo = propNo;
+ continue;
+ } else if(!(name.equals("freenet.jar") ||
name.equals("freenet.jar.new") ||
+
name.equals("freenet-cvs-snapshot.jar") ||
name.equals("freenet-cvs-snapshot.jar.new") ||
+
name.startsWith("freenet") && (name.endsWith(".jar") ||
name.endsWith(".jar.new")))) {
+ mainJar = f;
+ newMainJar = new
File(mainJar.getParent(), "freenet-stable-latest.jar");
+ mainJarAbsolute = isAbsolute;
+ mainClasspathNo = propNo;
+ continue;
+ }
+ }
+ }
+
+ if(mainJar == null && extJar == null)
+ throw new UpdaterParserException("Could not find
freenet jars in wrapper.conf");
+ if(mainJar == null)
+ throw new UpdaterParserException("Could not find
freenet jar in wrapper.conf (did find freenet-ext.jar: "+extJar+')');
+ if(extJar == null)
+ throw new UpdaterParserException("Could not find
freenet-ext.jar in wrapper.conf (did find freenet.jar: "+mainJar+')');
+ }
+
+ File getMainJar() {
+ return mainJar;
+ }
+
+ File getNewMainJar() {
+ return newMainJar;
+ }
+
+ File getExtJar() {
+ return extJar;
+ }
+
+ File getNewExtJar() {
+ return newExtJar;
+ }
+
+ void rewriteWrapperConf(boolean writtenNewJar, boolean writtenNewExt)
throws IOException, UpdateCatastropheException {
+
+ // Rewrite wrapper.conf
+ // Don't just write it out from properties; we want to keep it
as close to what it was as possible.
+
+ File oldConfig = new File("wrapper.conf");
+ File newConfig = new File("wrapper.conf.new");
+
+ FileInputStream fis = new FileInputStream(oldConfig);
+ BufferedInputStream bis = new BufferedInputStream(fis);
+ InputStreamReader isr = new InputStreamReader(bis);
+ BufferedReader br = new BufferedReader(isr);
+
+ FileOutputStream fos = new FileOutputStream(newConfig);
+ OutputStreamWriter osw = new OutputStreamWriter(fos);
+ BufferedWriter bw = new BufferedWriter(osw);
+
+ String line;
+
+ boolean writtenMain = false;
+ boolean writtenExt = false;
+ boolean writtenReload = false;
+
+ String newMain = mainJarAbsolute ? newMainJar.getAbsolutePath()
: newMainJar.getPath();
+ String newExt = extJarAbsolute ? newExtJar.getAbsolutePath() :
newExtJar.getPath();
+
+ while((line = br.readLine()) != null) {
+
+ if(line.startsWith("wrapper.java.classpath.")) {
+ if(writtenNewJar &&
line.startsWith("wrapper.java.classpath."+mainClasspathNo+'=')) {
+
bw.write("wrapper.java.classpath."+mainClasspathNo+'='+newMain+'\n');
+ writtenMain = true;
+ } else if(writtenNewExt &&
line.startsWith("wrapper.java.classpath."+extClasspathNo+'=')) {
+
bw.write("wrapper.java.classpath."+extClasspathNo+'='+newExt+'\n');
+ writtenExt = true;
+ } else {
+ bw.write(line+'\n');
+ }
+ } else
if(line.equalsIgnoreCase("wrapper.restart.reload_configuration=TRUE")) {
+ writtenReload = true;
+ bw.write(line+'\n');
+ } else
+ bw.write(line+'\n');
+ }
+ br.close();
+
+ if(!(writtenMain && writtenExt)) {
+ throw new IOException("Not able to update because of
non-standard config: written main="+writtenMain+" ext="+writtenExt+" - should
not happen! Report this to the devs, include your wrapper.conf");
+ }
+
+ if(!writtenReload) {
+ // Add it.
+ bw.write("wrapper.restart.reload_configuration=TRUE");
+ }
+
+ bw.close();
+
+ if(!newConfig.renameTo(oldConfig)) {
+ if(!oldConfig.delete()) {
+ throw new IOException("Cannot delete
"+oldConfig+" so cannot rename over it. Update failed.");
+ }
+ if(!newConfig.renameTo(oldConfig)) {
+ throw new UpdateCatastropheException(oldConfig,
newConfig);
+ }
+ }
+
+ // New config installed.
+
+ }
+
+}
Deleted: trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/UpdateRevocationURICallback.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -1,32 +0,0 @@
-/* This code is part of Freenet. It is distributed under the GNU General
- * Public License, version 2 (or at your option any later version). See
- * http://www.gnu.org/ for further details of the GPL. */
-package freenet.node.updater;
-
-import freenet.config.StringCallback;
-import freenet.node.Node;
-import freenet.support.Logger;
-
-public class UpdateRevocationURICallback implements StringCallback{
- private final Node node;
-
- public UpdateRevocationURICallback(Node node) {
- this.node = node;
- }
-
- public String get() {
- NodeUpdater nu = node.getNodeUpdater();
- if (nu != null)
- return nu.getRevocationKey().toString(true);
- else
- return NodeUpdater.REVOCATION_URI;
- }
-
- public void set(String val) {
- if(val!=null && val.equals(get())) return;
- // Good idea to prevent it ?
- //
- // Maybe it NEEDS to be implemented
- Logger.error(this, "Node's updater revocationURI can't be
updated on the fly");
- }
-}
Deleted: trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/UpdateURICallback.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -1,32 +0,0 @@
-/* This code is part of Freenet. It is distributed under the GNU General
- * Public License, version 2 (or at your option any later version). See
- * http://www.gnu.org/ for further details of the GPL. */
-package freenet.node.updater;
-
-import freenet.config.StringCallback;
-import freenet.node.Node;
-import freenet.support.Logger;
-
-public class UpdateURICallback implements StringCallback{
- private final Node node;
-
- public UpdateURICallback(Node node) {
- this.node = node;
- }
-
- public String get() {
- NodeUpdater nu = node.getNodeUpdater();
- if (nu != null)
- return nu.getUpdateKey().toString(true);
- else
- return NodeUpdater.UPDATE_URI;
- }
-
- public void set(String val) {
- if(val!=null && val.equals(get())) return;
- // Good idea to prevent it ?
- //
- // Maybe it NEEDS to be implemented
- Logger.error(this, "Node's updater URI can't be updated on the
fly");
- }
-}
\ No newline at end of file
Deleted: trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/UpdaterEnabledCallback.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -1,62 +0,0 @@
-/* This code is part of Freenet. It is distributed under the GNU General
- * Public License, version 2 (or at your option any later version). See
- * http://www.gnu.org/ for further details of the GPL. */
-package freenet.node.updater;
-
-import org.tanukisoftware.wrapper.WrapperManager;
-
-import freenet.config.BooleanCallback;
-import freenet.config.Config;
-import freenet.config.InvalidConfigValueException;
-import freenet.keys.FreenetURI;
-import freenet.node.Node;
-import freenet.node.NodeStarter;
-import freenet.support.Logger;
-
-public class UpdaterEnabledCallback implements BooleanCallback {
-
- final Node node;
- final Config nodeConfig;
-
- UpdaterEnabledCallback(Node n, Config nc) {
- this.node = n;
- this.nodeConfig = nc;
- }
-
- public boolean get() {
- if((node.nodeUpdater==null) ||
(!WrapperManager.isControlledByNativeWrapper()) || (NodeStarter.extBuildNumber
== -1))
- return false;
- else
- return node.nodeUpdater.isRunning();
- }
-
- public void set(boolean val) throws InvalidConfigValueException {
- if((val == true) &&
(!WrapperManager.isControlledByNativeWrapper()) || (NodeStarter.extBuildNumber
== -1)) {
- Logger.error(this, "Cannot update because not running
under wrapper");
- if(node.nodeUpdater != null){
- node.nodeUpdater.kill();
- Logger.normal(this, "Shutting down the node
updater");
- }
- set(false);
- throw new InvalidConfigValueException("Cannot update
because not running under wrapper");
- }
- synchronized (node) {
- if(val == get()) return;
- if(val){
- try{
- if(node.nodeUpdater != null)
- node.nodeUpdater.kill();
- node.nodeUpdater = new NodeUpdater(node
, false, new FreenetURI(NodeUpdater.UPDATE_URI), new
FreenetURI(NodeUpdater.REVOCATION_URI));
- Logger.normal(this, "Starting up the
node updater");
- }catch (Exception e){
- Logger.error(this, "unable to start the
node updater up "+e);
- e.printStackTrace();
- throw new
InvalidConfigValueException("Unable to enable the Node Updater "+e);
- }
- }else{
- node.nodeUpdater.kill();
- Logger.normal(this, "Shutting down the node
updater");
- }
- }
- }
-}
\ No newline at end of file
Added: trunk/freenet/src/freenet/node/updater/UpdaterParserException.java
===================================================================
--- trunk/freenet/src/freenet/node/updater/UpdaterParserException.java
2006-11-17 23:15:28 UTC (rev 10978)
+++ trunk/freenet/src/freenet/node/updater/UpdaterParserException.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -0,0 +1,14 @@
+/* This code is part of Freenet. It is distributed under the GNU General
+ * Public License, version 2 (or at your option any later version). See
+ * http://www.gnu.org/ for further details of the GPL. */
+package freenet.node.updater;
+
+public class UpdaterParserException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public UpdaterParserException(String msg) {
+ super(msg);
+ }
+
+}
Modified:
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
===================================================================
---
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
2006-11-17 23:15:28 UTC (rev 10978)
+++
trunk/freenet/src/freenet/node/useralerts/UpdatedVersionAvailableUserAlert.java
2006-11-18 02:04:03 UTC (rev 10979)
@@ -1,84 +1,184 @@
package freenet.node.useralerts;
-import freenet.node.updater.NodeUpdater;
+import freenet.node.updater.NodeUpdaterManager;
+import freenet.node.updater.RevocationChecker;
import freenet.support.HTMLNode;
public class UpdatedVersionAvailableUserAlert implements UserAlert {
- private boolean isValid, isReady;
- private final NodeUpdater updater;
- private int version;
- private int readyVersion;
+ private final NodeUpdaterManager updater;
- public UpdatedVersionAvailableUserAlert(int version, NodeUpdater
updater){
- this.version=version;
- isValid=false;
- isReady=false;
+ public UpdatedVersionAvailableUserAlert(NodeUpdaterManager updater){
this.updater = updater;
}
- public synchronized void set(int availableVersion, int readyVersion,
boolean ready){
- version = availableVersion;
- this.readyVersion = readyVersion;
- isReady = ready;
- }
-
public boolean userCanDismiss() {
return false;
}
public String getTitle() {
- return "A new stable version of Freenet is available
("+version+ ')';
+ StringBuffer sb = new StringBuffer();
+ sb.append("A new stable version of Freenet is available (");
+ appendVersionSummary(sb);
+ sb.append(')');
+ return sb.toString();
}
+ private void appendVersionSummary(StringBuffer sb) {
+ boolean b = false;
+ boolean main = false;
+ boolean ext = false;
+ if(updater.hasNewMainJar()) {
+ sb.append("main jar
").append(updater.newMainJarVersion());
+ b = true;
+ main = true;
+ }
+ if(updater.fetchingNewMainJar()) {
+ if(b) sb.append(", ");
+ sb.append("fetching main jar
").append(updater.fetchingNewMainJarVersion());
+ b = true;
+ main = true;
+ }
+ if(main)
+ sb.append(" from ").append(updater.getMainVersion());
+ if(updater.hasNewExtJar()) {
+ if(b) sb.append(", ");
+ sb.append("extra jar
").append(updater.newExtJarVersion());
+ b = true;
+ ext = true;
+ }
+ if(updater.fetchingNewExtJar()) {
+ if(b) sb.append(", ");
+ sb.append("fetching extra jar
").append(updater.fetchingNewExtJarVersion());
+ ext = true;
+ }
+ if(ext)
+ sb.append(" from ").append(updater.getExtVersion());
+ }
+
public String getText() {
- String s ="It seems that your node isn't running the latest
version of the software. ";
- if(updater.inFinalCheck()) {
- return s + "Your node is currently doing a final check
to verify the security of the update"+
- (version == readyVersion ? "" : (" to "+readyVersion)) +
- ". ("+updater.getRevocationDNFCounter()+ '/'
+NodeUpdater.REVOCATION_DNF_MIN+ ')';
- } else {
- s+="Updating to "+version+" is advised. ";
- if(isReady) s += " <form action=\"/\"
method=\"post\"><input type=\"submit\" name=\"update\" value=\"Update to
"+readyVersion+" Now\" /></form>";
- if(readyVersion < version || !isReady)
- s += "Your node is currently fetching the
update over Freenet. Once this is complete, you will be prompted to install the
new version. Please be patient, this could take up to half an hour.";
- return s;
+
+ UpdateThingy ut = createUpdateThingy();
+
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(ut.firstBit);
+
+ if(ut.formText != null) {
+ sb.append(" <form action=\"/\" method=\"post\"><input
type=\"submit\" name=\"update\" value=\"");
+ sb.append(ut.formText);
+ sb.append("\" /></form>");
}
+
+ return sb.toString();
}
+ class UpdateThingy {
+ public UpdateThingy(String first, String form) {
+ this.firstBit = first;
+ this.formText = form;
+ }
+ String firstBit;
+ String formText;
+ }
+
public HTMLNode getHTMLText() {
+
+ UpdateThingy ut = createUpdateThingy();
+
HTMLNode alertNode = new HTMLNode("div");
- alertNode.addChild("#", "It seems that your node isn't running
the latest version of the software.");
- if (updater.inFinalCheck()) {
- alertNode.addChild("#", " Your node is currently doing
a final check to verify the security of the update " + (version == readyVersion
? "" : (" to " + readyVersion)) + ". (Finished checks: " +
updater.getRevocationDNFCounter() + " of " + NodeUpdater.REVOCATION_DNF_MIN +
')');
+
+ alertNode.addChild("#", ut.firstBit);
+
+ if(ut.formText != null) {
+ alertNode.addChild("form", new String[] { "action",
"method" }, new String[] { "/", "post" }).addChild("input", new String[] {
"type", "name", "value" }, new String[] { "submit", "update", ut.formText });
+ }
+
+ return alertNode;
+ }
+
+ private UpdateThingy createUpdateThingy() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("It seems that your node isn't running the latest
version of the software. ");
+
+ if(updater.isArmed() && updater.inFinalCheck()) {
+ sb.append("Your node is currently doing a final check
to verify the security of the update (");
+ sb.append(updater.getRevocationDNFCounter());
+ sb.append('/');
+ sb.append(RevocationChecker.REVOCATION_DNF_MIN);
+ sb.append("). ");
+ } else if(updater.isArmed()) {
+ sb.append("Your node will automatically restart as soon
as it has finished downloading and verifying the new version of Freenet.");
} else {
- if (isReady) {
- alertNode.addChild("#", " Updating to " +
version + " is advised.");
- alertNode.addChild("form", new String[] {
"action", "method" }, new String[] { "/", "post" }).addChild("input", new
String[] { "type", "name", "value" }, new String[] { "submit", "update",
"Update to " + readyVersion + " now" });
- if(readyVersion < version)
- alertNode.addChild("#", "The node is
currently fetching version "+version+" but you can update to "+readyVersion+"
now, or wait for the node to fetch "+version+ '.');
+ String formText;
+ if(updater.canUpdateNow()) {
+ boolean b = false;
+ if(updater.hasNewMainJar()) {
+ sb.append("Your node has downloaded a
new version of Freenet, version ");
+ sb.append(updater.newMainJarVersion());
+ sb.append(". ");
+ b = true;
+ }
+ if(updater.hasNewExtJar()) {
+ if(b) {
+ sb.append("Your node has also
downloaded a new version of the freenet extra jar, version ");
+ } else {
+ sb.append("Your node has
downloaded a new version of the freenet extra jar, version ");
+ }
+ sb.append(updater.newExtJarVersion());
+ sb.append(". ");
+ }
+ sb.append("Click below to update your node");
+ if(updater.canUpdateImmediately()) {
+ sb.append(" immediately");
+ formText = "Update Now!";
+ } else {
+ sb.append(" as soon as the update has
been verified");
+ formText = "Update ASAP";
+ }
+ sb.append('.');
} else {
- if(updater.isAutoUpdateAllowed)
- alertNode.addChild("#", " Your node is
currently fetching the update over Freenet and will automatically restart when
it's ready (as configured).");
- else
- alertNode.addChild("#", " Your node is
currently fetching the update over Freenet. Once this is complete, you will be
prompted to install the new version. Please be patient, this could take up to
half an hour.");
+ sb.append("Your node is currently downloading a
new version of Freenet");
+ boolean fetchingNew =
updater.fetchingNewMainJar();
+ boolean fetchingNewExt =
updater.fetchingNewExtJar();
+ if(fetchingNew || fetchingNewExt)
+ sb.append(" (");
+ if(fetchingNew) {
+ sb.append("node version ");
+
sb.append(updater.fetchingNewMainJarVersion());
+ }
+ if(fetchingNewExt) {
+ if(fetchingNew)
+ sb.append(", ");
+ sb.append("ext version ");
+
sb.append(updater.fetchingNewExtJarVersion());
+ }
+ if(fetchingNew)
+ sb.append(')');
+ sb.append(". ");
+ sb.append("Would you like the node to
automatically restart as soon as it has downloaded the update?");
+ formText = "Update ASAP";
}
+
+ return new UpdateThingy(sb.toString(), formText);
}
- return alertNode;
+
+ return new UpdateThingy(sb.toString(), null);
}
-
+
public short getPriorityClass() {
- if(isReady || updater.inFinalCheck())
+ if(updater.inFinalCheck())
return UserAlert.WARNING;
else
return UserAlert.MINOR;
}
public boolean isValid() {
- return isValid;
+ return updater.isEnabled() && (!updater.isBlown()) &&
updater.fetchingNewExtJar() || updater.fetchingNewMainJar() ||
+ updater.hasNewExtJar() || updater.hasNewMainJar();
}
public void isValid(boolean b){
- isValid=b;
+ // Ignore
}
public String dismissButtonText(){
@@ -90,6 +190,6 @@
}
public void onDismiss() {
- // do nothing on alert dismissal
+ // Ignore
}
}