package freenet.node.updater;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import freenet.client.FetchResult;
import freenet.clients.http.PproxyToadlet;
import freenet.keys.FreenetURI;
import freenet.l10n.NodeL10n;
import freenet.node.RequestClient;
import freenet.node.Version;
import freenet.node.useralerts.AbstractUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.pluginmanager.PluginInfoWrapper;
import freenet.pluginmanager.PluginManager;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.api.Bucket;
import freenet.support.io.BucketTools;
public class PluginJarUpdater extends NodeUpdater {
final String pluginName;
final PluginManager pluginManager;
private UserAlert alert;
private boolean deployOnNoRevocation;
private boolean deployOnNextNoRevocation;
private boolean readyToDeploy;
private FetchResult result;
private final Object writeJarSync = new Object();
private int writtenVersion;
/**
* @return True if the caller should restart the revocation checker.
*/
boolean onNoRevocation() {
synchronized(this) {
if(!readyToDeploy) return false;
if(deployOnNoRevocation) {
// Lets go ...
} else if(deployOnNextNoRevocation) {
deployOnNoRevocation = true;
deployOnNextNoRevocation = false;
System.out.println("Deploying "+pluginName+" after next revocation check");
return true;
} else
return false;
}
// Deploy it!
if (!pluginManager.isPluginLoaded(pluginName)) {
Logger.error(this, "Plugin is not loaded, so not deploying: "+pluginName);
tempBlobFile.delete();
return false;
}
System.out.println("Deploying new version of "+pluginName+" : unloading old version...");
// Write the new version of the plugin before shutting down, so if there is a deadlock in terminate, we will still get the new version after a restart.
try {
writeJar();
} catch (IOException e) {
Logger.error(this, "Cannot deploy: "+e, e);
System.err.println("Cannot deploy new version of "+pluginName+" : "+e);
e.printStackTrace();
return false; // Not much we can do ...
// FIXME post a useralert
}
pluginManager.killPluginByFilename(pluginName, Integer.MAX_VALUE, true);
pluginManager.startPluginAuto(pluginName, true);
UserAlert a;
synchronized(this) {
a = alert;
alert = null;
}
node.clientCore.alerts.unregister(a);
return false;
}
PluginJarUpdater(NodeUpdateManager manager, FreenetURI URI, int current, int min, int max, String blobFilenamePrefix, String pluginName, PluginManager pm, boolean autoDeployOnRestart) {
super(manager, URI, current, min, max, blobFilenamePrefix);
this.pluginName = pluginName;
this.pluginManager = pm;
}
@Override
public String jarName() {
return pluginName;
}
private int requiredNodeVersion;
private static final String REQUIRED_NODE_VERSION_PREFIX = "Required-Node-Version: ";
@Override
protected void maybeParseManifest(FetchResult result, int build) {
requiredNodeVersion = -1;
parseManifest(result);
if(requiredNodeVersion != -1) {
System.err.println("Required node version for plugin "+pluginName+": "+requiredNodeVersion);
Logger.normal(this, "Required node version for plugin "+pluginName+": "+requiredNodeVersion);
}
}
@Override
protected void parseManifestLine(String line) {
if(line.startsWith(REQUIRED_NODE_VERSION_PREFIX)) {
requiredNodeVersion = Integer.parseInt(line.substring(REQUIRED_NODE_VERSION_PREFIX.length()));
}
}
@Override
protected void onStartFetching() {
System.err.println("Starting to fetch plugin "+pluginName);
}
@Override
protected void processSuccess(int build, FetchResult result, File blob) {
Bucket oldResult = null;
synchronized(this) {
if(requiredNodeVersion > Version.buildNumber()) {
System.err.println("Found version "+fetchedVersion+" of "+pluginName+" but needs node version "+requiredNodeVersion);
// FIXME deploy it with the main jar
tempBlobFile.delete();
return;
}
if(this.result != null)
oldResult = this.result.asBucket();
this.result = result;
}
if(oldResult != null) oldResult.free();
PluginInfoWrapper loaded = pluginManager.getPluginInfo(pluginName);
if(loaded == null) {
if(!node.pluginManager.isPluginLoadedOrLoadingOrWantLoad(pluginName)) {
System.err.println("Don't want plugin: "+pluginName);
Logger.error(this, "Don't want plugin: "+pluginName);
tempBlobFile.delete();
return;
}
}
if(loaded.getPluginLongVersion() >= fetchedVersion) {
tempBlobFile.delete();
return;
}
// Create a useralert to ask the user to deploy the new version.
UserAlert toRegister = null;
synchronized(this) {
readyToDeploy = true;
if(alert != null) return;
toRegister = alert = new AbstractUserAlert(true, l10n("pluginUpdatedTitle", "name", pluginName), l10n("pluginUpdatedText", "name", pluginName), l10n("pluginUpdatedShortText", "name", pluginName), null, UserAlert.ERROR, true, NodeL10n.getBase().getString("UserAlert.hide"), true, this) {
@Override
public void onDismiss() {
synchronized(PluginJarUpdater.this) {
alert = null;
}
}
@Override
public HTMLNode getHTMLText() {
HTMLNode div = new HTMLNode("div");
// Text saying the plugin has been updated...
synchronized(this) {
if(deployOnNoRevocation || deployOnNextNoRevocation) {
div.addChild("#", l10n("willDeployAfterRevocationCheck", "name", pluginName));
} else {
div.addChild("#", l10n("pluginUpdatedText", new String[] { "name", "newVersion" }, new String[] { pluginName, Long.toString(fetchedVersion) }));
// Form to deploy the updated version.
// This is not the same as reloading because we haven't written it yet.
HTMLNode formNode = div.addChild("form", new String[] { "action", "method" }, new String[] { PproxyToadlet.PATH, "post" });
formNode.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "formPassword", node.clientCore.formPassword });
formNode.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "update", pluginName });
formNode.addChild("input", new String[] { "type", "value" }, new String[] { "submit", l10n("updatePlugin") });
}
}
return div;
}
};
}
if(toRegister != null)
node.clientCore.alerts.register(toRegister);
}
private String l10n(String key) {
return NodeL10n.getBase().getString("PluginJarUpdater."+key);
}
private String l10n(String key, String name, String value) {
return NodeL10n.getBase().getString("PluginJarUpdater."+key, name, value);
}
private String l10n(String key, String[] names, String[] values) {
return NodeL10n.getBase().getString("PluginJarUpdater."+key, names, values);
}
public void writeJarTo(FetchResult result, File fNew) throws IOException {
int fetched;
synchronized(this) {
fetched = fetchedVersion;
}
synchronized(writeJarSync) {
if (!fNew.delete() && fNew.exists()) {
System.err.println("Can't delete " + fNew + "!");
}
FileOutputStream fos;
fos = new FileOutputStream(fNew);
BucketTools.copyTo(result.asBucket(), fos, -1);
fos.flush();
fos.close();
}
synchronized(this) {
writtenVersion = fetched;
}
System.err.println("Written " + jarName() + " to " + fNew);
}
void writeJar() throws IOException {
writeJarTo(result, pluginManager.getPluginFilename(pluginName));
UserAlert a;
synchronized(this) {
a = alert;
alert = null;
}
if(a != null)
node.clientCore.alerts.unregister(a);
}
@Override
void kill() {
super.kill();
UserAlert a;
synchronized(this) {
a = alert;
alert = null;
}
if(a != null)
node.clientCore.alerts.unregister(a);
}
public synchronized void arm(boolean wasRunning) {
if(wasRunning) {
deployOnNextNoRevocation = true;
System.out.println("Deploying "+pluginName+" after next but one revocation check");
} else {
deployOnNoRevocation = true;
System.out.println("Deploying "+pluginName+" after next revocation check");
}
}
@Override
public RequestClient getRequestClient() {
return pluginManager.singleUpdaterRequestClient;
}
}