forked from I2P_Developers/i2p.i2p
Plugins: Add form to browse for local plugin file to install,
easy since we have multipart in console now Better status feedback from update manager to console
This commit is contained in:
@ -188,6 +188,7 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp {
|
||||
PluginUpdateHandler puh = new PluginUpdateHandler(_context, this);
|
||||
register((Checker)puh, PLUGIN, HTTP, 0);
|
||||
register((Updater)puh, PLUGIN, HTTP, 0);
|
||||
register((Updater)puh, PLUGIN, FILE, 0);
|
||||
// Don't do this until we can prevent it from retrying the same thing again...
|
||||
// handled inside P.U.H. for now
|
||||
//register((Updater)puh, PLUGIN, FILE, 0);
|
||||
@ -523,7 +524,8 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp {
|
||||
UpdateItem item = new UpdateItem(PLUGIN, name);
|
||||
VersionAvailable va = _available.get(item);
|
||||
if (va == null) {
|
||||
va = new VersionAvailable("", "", HTTP, uris);
|
||||
UpdateMethod method = "file".equals(uri.getScheme()) ? FILE : HTTP;
|
||||
va = new VersionAvailable("", "", method, uris);
|
||||
_available.putIfAbsent(item, va);
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -971,8 +973,8 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp {
|
||||
* @param t may be null
|
||||
*/
|
||||
public void notifyTaskFailed(UpdateTask task, String reason, Throwable t) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed " + task + " for " + task.getType() + ": " + reason, t);
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed " + task + " for " + task.getType() + ": " + reason, t);
|
||||
List<RegisteredUpdater> toTry = _downloaders.get(task);
|
||||
if (toTry != null) {
|
||||
UpdateItem ui = new UpdateItem(task.getType(), task.getID());
|
||||
@ -988,8 +990,27 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp {
|
||||
_downloaders.remove(task);
|
||||
_activeCheckers.remove(task);
|
||||
// any other types that shouldn't display?
|
||||
if (task.getURI() != null && task.getType() != TYPE_DUMMY)
|
||||
finishStatus("<b>" + _("Transfer failed from {0}", linkify(task.getURI().toString())) + "</b>");
|
||||
if (task.getURI() != null && task.getType() != TYPE_DUMMY) {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append("<b>");
|
||||
String uri = task.getURI().toString();
|
||||
if (uri.startsWith("file:") || task.getMethod() == FILE) {
|
||||
uri = DataHelper.stripHTML(task.getURI().getPath());
|
||||
buf.append(_("Install failed from {0}", uri));
|
||||
} else {
|
||||
buf.append(_("Transfer failed from {0}"));
|
||||
}
|
||||
if (reason != null && reason.length() > 0) {
|
||||
buf.append("<br>");
|
||||
buf.append(reason);
|
||||
}
|
||||
if (t != null && t.getMessage() != null && t.getMessage().length() > 0) {
|
||||
buf.append("<br>");
|
||||
buf.append(DataHelper.stripHTML(t.getMessage()));
|
||||
}
|
||||
buf.append("</b>");
|
||||
finishStatus(buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,8 @@ class PluginUpdateHandler implements Checker, Updater {
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String appName, String newVersion, long maxTime) {
|
||||
if (type != UpdateType.PLUGIN ||
|
||||
method != UpdateMethod.HTTP || updateSources.isEmpty())
|
||||
(method != UpdateMethod.HTTP && method != UpdateMethod.FILE) ||
|
||||
updateSources.isEmpty())
|
||||
return null;
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
|
@ -82,16 +82,16 @@ class PluginUpdateRunner extends UpdateRunner {
|
||||
protected void update() {
|
||||
|
||||
_updated = false;
|
||||
if(_xpi2pURL.startsWith("file://")) {
|
||||
updateStatus("<b>" + _("Attempting to install from file {0}", _xpi2pURL) + "</b>");
|
||||
// strip off "file://"
|
||||
String xpi2pfile = _xpi2pURL.substring(7);
|
||||
if(xpi2pfile.length() == 0) {
|
||||
statusDone("<b>" + _("No file specified {0}", _xpi2pURL) + "</b>");
|
||||
if (_xpi2pURL.startsWith("file:") || _method == UpdateMethod.FILE) {
|
||||
// strip off file:// or just file:
|
||||
String xpi2pfile = _uri.getPath();
|
||||
if(xpi2pfile == null || xpi2pfile.length() == 0) {
|
||||
statusDone("<b>" + _("Bad URL {0}", _xpi2pURL) + "</b>");
|
||||
} else {
|
||||
// copy the contents of from to _updateFile
|
||||
long alreadyTransferred = (new File(xpi2pfile)).getAbsoluteFile().length();
|
||||
if(FileUtil.copy((new File(xpi2pfile)).getAbsolutePath(), _updateFile, true, false)) {
|
||||
updateStatus("<b>" + _("Attempting to install from file {0}", _xpi2pURL) + "</b>");
|
||||
transferComplete(alreadyTransferred, alreadyTransferred, 0L, _xpi2pURL, _updateFile, false);
|
||||
} else {
|
||||
statusDone("<b>" + _("Failed to install from file {0}, copy failed.", _xpi2pURL) + "</b>");
|
||||
|
@ -1,6 +1,10 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
@ -13,11 +17,15 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.crypto.SU3File;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.router.update.ConsoleUpdateManager;
|
||||
import static net.i2p.update.UpdateType.*;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
|
||||
|
||||
@ -66,6 +74,15 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
addFormError("Plugins disabled");
|
||||
return;
|
||||
}
|
||||
if (_action.equals(_("Install Plugin from File"))) {
|
||||
if (pluginsEnabled &&
|
||||
(_context.getBooleanPropertyDefaultTrue(ConfigClientsHelper.PROP_ENABLE_PLUGIN_INSTALL) ||
|
||||
isAdvanced()))
|
||||
installPluginFromFile();
|
||||
else
|
||||
addFormError("Plugins disabled");
|
||||
return;
|
||||
}
|
||||
if (_action.equals(_("Update All Installed Plugins"))) {
|
||||
if (pluginsEnabled)
|
||||
updateAllPlugins();
|
||||
@ -388,6 +405,73 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
installPlugin(null, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.19
|
||||
*/
|
||||
private void installPluginFromFile() {
|
||||
InputStream in = _requestWrapper.getInputStream("pluginFile");
|
||||
// go to some trouble to verify it's an su3 or xpi2p file before
|
||||
// passing it along, so we can display a good error message
|
||||
byte[] su3Magic = DataHelper.getASCII(SU3File.MAGIC);
|
||||
byte[] zipMagic = new byte[] { 0x50, 0x4b, 0x03, 0x04 };
|
||||
byte[] magic = new byte[TrustedUpdate.HEADER_BYTES + zipMagic.length];
|
||||
File tmp = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
// non-null but zero bytes if no file entered, don't know why
|
||||
if (in == null || in.available() <= 0) {
|
||||
addFormError(_("You must enter a file"));
|
||||
return;
|
||||
}
|
||||
DataHelper.read(in, magic);
|
||||
boolean isSU3 = DataHelper.eq(magic, 0, su3Magic, 0, su3Magic.length);
|
||||
if (!isSU3) {
|
||||
if (!DataHelper.eq(magic, TrustedUpdate.HEADER_BYTES, zipMagic, 0, zipMagic.length)) {
|
||||
String name = _requestWrapper.getFilename("pluginFile");
|
||||
if (name == null)
|
||||
name = "File";
|
||||
throw new IOException(name + " is not an xpi2p or su3 plugin");
|
||||
}
|
||||
}
|
||||
tmp = new File(_context.getTempDir(), "plugin-" + _context.random().nextInt() + (isSU3 ? ".su3" : ".xpi2p"));
|
||||
out = new BufferedOutputStream(new SecureFileOutputStream(tmp));
|
||||
out.write(magic);
|
||||
byte buf[] = new byte[16*1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
out.close();
|
||||
String url = tmp.toURI().toString();
|
||||
// threaded... TODO inline to get better result to UI?
|
||||
installPlugin(null, url);
|
||||
// above sleeps 1000, give it some more time?
|
||||
// or check for complete?
|
||||
ConsoleUpdateManager mgr = UpdateHandler.updateManager(_context);
|
||||
if (mgr == null)
|
||||
return;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (!mgr.isUpdateInProgress(PLUGIN)) {
|
||||
tmp.delete();
|
||||
break;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
String status = mgr.getStatus();
|
||||
if (status != null && status.length() > 0)
|
||||
addFormNoticeNoEscape(status);
|
||||
} catch (IOException ioe) {
|
||||
addFormError(_("Install from file failed") + " - " + ioe.getMessage());
|
||||
} finally {
|
||||
// it's really a ByteArrayInputStream but we'll play along...
|
||||
if (in != null)
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlugin(String app) {
|
||||
Properties props = PluginStarter.pluginProperties(_context, app);
|
||||
String url = props.getProperty("updateURL.su3");
|
||||
@ -434,10 +518,14 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
addFormError(_("Bad URL {0}", url));
|
||||
return;
|
||||
}
|
||||
if (mgr.installPlugin(app, uri))
|
||||
addFormNotice(_("Downloading plugin from {0}", url));
|
||||
else
|
||||
if (mgr.installPlugin(app, uri)) {
|
||||
if (url.startsWith("file:"))
|
||||
addFormNotice(_("Installing plugin from {0}", uri.getPath()));
|
||||
else
|
||||
addFormNotice(_("Downloading plugin from {0}", url));
|
||||
} else {
|
||||
addFormError("Cannot install, check logs");
|
||||
}
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
|
@ -130,6 +130,7 @@ class ReseedBundler {
|
||||
entry.setTime(ri.getPublished());
|
||||
zip.putNextEntry(entry);
|
||||
ri.writeBytes(zip);
|
||||
zip.closeEntry();
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
rv.delete();
|
||||
|
@ -115,7 +115,7 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
<form action="" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<jsp:getProperty name="clientshelper" property="form3" />
|
||||
<hr><div class="formaction">
|
||||
<div class="formaction">
|
||||
<input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
|
||||
<input type="submit" name="action" class="accept" value="<%=intl._("Save Plugin Configuration")%>" />
|
||||
</div></form></div>
|
||||
@ -123,43 +123,44 @@ input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
} // pluginUpdateEnabled
|
||||
if (clientshelper.isPluginInstallEnabled()) {
|
||||
%>
|
||||
<h3><a name="plugin"></a><%=intl._("Plugin Installation")%></h3><p>
|
||||
<h3><a name="plugin"></a><%=intl._("Plugin Installation from URL")%></h3><p>
|
||||
<%=intl._("Look for available plugins on {0}.", "<a href=\"http://plugins.i2p\">plugins.i2p</a>")%>
|
||||
<%=intl._("To install a plugin, enter the download URL:")%>
|
||||
</p>
|
||||
<%
|
||||
} // pluginInstallEnabled
|
||||
if (clientshelper.isPluginInstallEnabled() || clientshelper.isPluginUpdateEnabled()) {
|
||||
%>
|
||||
<div class="wideload">
|
||||
<form action="configclients" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<%
|
||||
if (clientshelper.isPluginInstallEnabled()) {
|
||||
%>
|
||||
<p>
|
||||
<input type="text" size="60" name="pluginURL" >
|
||||
</p><hr><div class="formaction">
|
||||
<input type="submit" name="action" class="default" value="<%=intl._("Install Plugin")%>" />
|
||||
<input type="submit" class="cancel" name="foo" value="<%=intl._("Cancel")%>" />
|
||||
<input type="submit" name="action" class="download" value="<%=intl._("Install Plugin")%>" />
|
||||
</div>
|
||||
</div></form></div>
|
||||
|
||||
|
||||
<div class="wideload">
|
||||
<h3><a name="plugin"></a><%=intl._("Plugin Installation from File")%></h3>
|
||||
<form action="configclients" method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<p><%=intl._("Install plugin from file.")%>
|
||||
<br><%=intl._("Select xpi2p or su3 file")%> :
|
||||
<input type="file" name="pluginFile" >
|
||||
</p><hr><div class="formaction">
|
||||
<input type="submit" name="action" class="download" value="<%=intl._("Install Plugin from File")%>" />
|
||||
</div></form></div>
|
||||
<%
|
||||
} // pluginInstallEnabled
|
||||
} // pluginInstallEnabled
|
||||
if (clientshelper.isPluginUpdateEnabled()) {
|
||||
%>
|
||||
</div>
|
||||
<%
|
||||
if (clientshelper.isPluginUpdateEnabled()) {
|
||||
%>
|
||||
<hr><div class="formaction">
|
||||
<h3><a name="plugin"></a><%=intl._("Update All Plugins")%></h3>
|
||||
<div class="formaction">
|
||||
<form action="configclients" method="POST">
|
||||
<input type="hidden" name="nonce" value="<%=pageNonce%>" >
|
||||
<input type="submit" name="action" class="reload" value="<%=intl._("Update All Installed Plugins")%>" />
|
||||
</div>
|
||||
<%
|
||||
} // pluginUpdateEnabled
|
||||
%>
|
||||
</form></div>
|
||||
<%
|
||||
} // pluginInstallEnabled || pluginUpdateEnabled
|
||||
} // pluginUpdateEnabled
|
||||
} // showPlugins
|
||||
%>
|
||||
</div></div></body></html>
|
||||
|
@ -1829,6 +1829,7 @@ public class WebMail extends HttpServlet
|
||||
ZipEntry entry = new ZipEntry( name );
|
||||
zip.putNextEntry( entry );
|
||||
zip.write( content.content, content.offset, content.length );
|
||||
zip.closeEntry();
|
||||
zip.finish();
|
||||
shown = true;
|
||||
} catch (IOException e) {
|
||||
|
Reference in New Issue
Block a user