diff --git a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java index 98d4214f14b2f4cb8094bc22f339bc9576ace4e0..91f0da2355b267336fcb201a1c882b36455e3464 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/ConsoleUpdateManager.java @@ -62,6 +62,7 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { private final Log _log; private final Collection<RegisteredUpdater> _registeredUpdaters; private final Collection<RegisteredChecker> _registeredCheckers; + private final Map<Integer, UpdatePostProcessor> _registeredPostProcessors; /** active checking tasks */ private final Collection<UpdateTask> _activeCheckers; /** active updating tasks, pointing to the next ones to try */ @@ -95,6 +96,7 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { _log = ctx.logManager().getLog(ConsoleUpdateManager.class); _registeredUpdaters = new ConcurrentHashSet<RegisteredUpdater>(); _registeredCheckers = new ConcurrentHashSet<RegisteredChecker>(); + _registeredPostProcessors = new ConcurrentHashMap<Integer, UpdatePostProcessor>(2); _activeCheckers = new ConcurrentHashSet<UpdateTask>(); _downloaders = new ConcurrentHashMap<UpdateTask, List<RegisteredUpdater>>(); _available = new ConcurrentHashMap<UpdateItem, VersionAvailable>(); @@ -756,6 +758,20 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { _log.info("Unregistering " + rc); _registeredCheckers.remove(rc); } + + /** + * Register a post-processor for this UpdateType and SU3File file type. + * + * @param type only ROUTER_SIGNED_SU3 and ROUTER_DEV_SU3 are currently supported + * @param fileType a SU3File TYPE_xxx constant, 1-255, TYPE_ZIP not supported. + * @since 0.9.51 + */ + public void register(UpdatePostProcessor upp, UpdateType type, int fileType) { + Integer key = Integer.valueOf(type.toString().hashCode() ^ fileType); + UpdatePostProcessor old = _registeredPostProcessors.put(key, upp); + if (old != null && _log.shouldLog(Log.WARN)) + _log.warn("Duplicate registration " + upp); + } /** * Called by the Updater, either after check() was called, or it found out on its own. @@ -1102,7 +1118,8 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { if (_log.shouldLog(Log.INFO)) _log.info("Updater " + task + " for " + task.getType() + " complete"); boolean rv = false; - switch (task.getType()) { + UpdateType utype = task.getType(); + switch (utype) { case TYPE_DUMMY: case NEWS: case NEWS_SU3: @@ -1110,13 +1127,13 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { break; case ROUTER_SIGNED: - rv = handleSudFile(task.getURI(), actualVersion, file); + rv = handleRouterFile(task.getURI(), actualVersion, file, utype); if (rv) notifyDownloaded(task.getType(), task.getID(), actualVersion); break; case ROUTER_SIGNED_SU3: - rv = handleSu3File(task.getURI(), actualVersion, file); + rv = handleRouterFile(task.getURI(), actualVersion, file, utype); if (rv) notifyDownloaded(task.getType(), task.getID(), actualVersion); break; @@ -1130,7 +1147,7 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { break; case ROUTER_DEV_SU3: - rv = handleSu3File(task.getURI(), actualVersion, file); + rv = handleRouterFile(task.getURI(), actualVersion, file, utype); if (rv) { _context.router().saveConfig(PROP_DEV_SU3_AVAILABLE, null); notifyDownloaded(task.getType(), task.getID(), actualVersion); @@ -1325,47 +1342,48 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { } /** + * Process sud, su2, or su3. + * Only for router updates. * * @return success - */ - private boolean handleSudFile(URI uri, String actualVersion, File f) { - return handleRouterFile(uri, actualVersion, f, false); - } - - /** - * @return success - * @since 0.9.9 - */ - private boolean handleSu3File(URI uri, String actualVersion, File f) { - return handleRouterFile(uri, actualVersion, f, true); - } - - /** - * Process sud, su2, or su3 - * @return success * @since 0.9.9 */ - private boolean handleRouterFile(URI uri, String actualVersion, File f, boolean isSU3) { + private boolean handleRouterFile(URI uri, String actualVersion, File f, UpdateType updateType) { + boolean isSU3 = updateType == ROUTER_SIGNED_SU3 || updateType == ROUTER_DEV_SU3; String url = uri.toString(); updateStatus("<b>" + _t("Update downloaded") + "</b>"); File to = new File(_context.getRouterDir(), Router.UPDATE_FILE); - String err; + String err = null; // Process the file if (isSU3) { SU3File up = new SU3File(_context, f); - File temp = new File(_context.getTempDir(), "su3out-" + _context.random().nextLong() + ".zip"); + File temp = new File(_context.getTempDir(), "su3out-" + _context.random().nextLong()); try { if (up.verifyAndMigrate(temp)) { String ver = up.getVersionString(); int type = up.getContentType(); - if (ver == null || VersionComparator.comp(RouterVersion.VERSION, ver) >= 0) + if (ver == null || VersionComparator.comp(RouterVersion.VERSION, ver) >= 0) { err = "Old version " + ver; - else if (type != SU3File.CONTENT_ROUTER) + } else if (type != SU3File.CONTENT_ROUTER) { err = "Bad su3 content type " + type; - else if (!FileUtil.copy(temp, to, true, false)) - err = "Failed copy to " + to; - else - err = null; // success + } else { + int ftype = up.getFileType(); + if (ftype == SU3File.TYPE_ZIP) { + // standard update, copy to i2pupdate.zip in config dir + if (!FileUtil.copy(temp, to, true, false)) + err = "Failed copy to " + to; + } else if ((ftype == SU3File.TYPE_DMG && SystemVersion.isMac()) || + (ftype == SU3File.TYPE_EXE && SystemVersion.isWindows())) { + Integer key = Integer.valueOf(updateType.toString().hashCode() ^ ftype); + UpdatePostProcessor upp = _registeredPostProcessors.get(key); + if (upp != null) + upp.updateDownloadedandVerified(updateType, ftype, actualVersion, temp); + else + err = "Unsupported su3 file type " + ftype; + } else { + err = "Unsupported su3 file type " + ftype; + } + } } else { err = "Signature failed, signer " + DataHelper.stripHTML(up.getSignerString()) + ' ' + up.getSigType(); @@ -1406,6 +1424,8 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { } /** + * Only for router updates + * * @param Long.toString(timestamp) * @return success */ @@ -1755,6 +1775,10 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp { buf.append("<div class=\"debug_container\">"); toString(buf, _registeredUpdaters); buf.append("</div>"); + buf.append("<h3>Registered PostProcessors</h3>"); + buf.append("<div class=\"debug_container\">"); + toString(buf, _registeredPostProcessors.values()); + buf.append("</div>"); buf.append("<h3>Active Checkers</h3>"); buf.append("<div class=\"debug_container\">"); toString(buf, _activeCheckers); diff --git a/core/java/src/net/i2p/crypto/SU3File.java b/core/java/src/net/i2p/crypto/SU3File.java index ca3a9cca39f5d3edbc63be62efaa109da4cd9314..ad31f0a4cd385d912ab0aa3a05ccc1a843901ff9 100644 --- a/core/java/src/net/i2p/crypto/SU3File.java +++ b/core/java/src/net/i2p/crypto/SU3File.java @@ -81,6 +81,10 @@ public class SU3File { public static final int TYPE_XML_GZ = 3; /** @since 0.9.28 */ public static final int TYPE_TXT_GZ = 4; + /** @since 0.9.51 */ + public static final int TYPE_DMG = 5; + /** @since 0.9.51 */ + public static final int TYPE_EXE = 6; public static final int CONTENT_UNKNOWN = 0; public static final int CONTENT_ROUTER = 1; @@ -703,7 +707,9 @@ public class SU3File { " HTML\t(code: 2)\n" + " XML_GZ\t(code: 3)\n" + " TXT_GZ\t(code: 4)\n" + - " (user defined)\t(code: 5-255)\n"); + " DMG\t(code: 5)\n" + + " EXE\t(code: 6)\n" + + " (user defined)\t(code: 7-255)\n"); return buf.toString(); } @@ -761,6 +767,10 @@ public class SU3File { ftype = "HTML"; else if (file._fileType == TYPE_XML_GZ) ftype = "XML_GZ"; + else if (file._fileType == TYPE_DMG) + ftype = "DMG"; + else if (file._fileType == TYPE_EXE) + ftype = "EXE"; else ftype = Integer.toString(file._fileType); System.out.println("FileType: " + ftype); @@ -861,6 +871,10 @@ public class SU3File { ft = TYPE_HTML; } else if (ftype.equalsIgnoreCase("XML_GZ")) { ft = TYPE_XML_GZ; + } else if (ftype.equalsIgnoreCase("DMG")) { + ft = TYPE_DMG; + } else if (ftype.equalsIgnoreCase("EXE")) { + ft = TYPE_EXE; } else { try { ft = Integer.parseInt(ftype); @@ -975,6 +989,12 @@ public class SU3File { case TYPE_TXT_GZ: sfx = ".txt.gz"; break; + case TYPE_DMG: + sfx = ".dmg"; + break; + case TYPE_EXE: + sfx = ".exe"; + break; default: sfx = ".extracted"; break; diff --git a/core/java/src/net/i2p/update/UpdateManager.java b/core/java/src/net/i2p/update/UpdateManager.java index 716c2fbace1d20c10d7bd659b77c96e27a285aab..c7cb492e1ce419917b7ccca2a04226313771047a 100644 --- a/core/java/src/net/i2p/update/UpdateManager.java +++ b/core/java/src/net/i2p/update/UpdateManager.java @@ -34,7 +34,16 @@ public interface UpdateManager { public void unregister(Updater updater, UpdateType type, UpdateMethod method); public void unregister(Checker checker, UpdateType type, UpdateMethod method); - + + /** + * Register a post-processor for this UpdateType and SU3File file type. + * + * @param type only ROUTER_SIGNED_SU3 and ROUTER_DEV_SU3 are currently supported + * @param fileType a SU3File TYPE_xxx constant, 1-255, TYPE_ZIP not supported. + * @since 0.9.51 + */ + public void register(UpdatePostProcessor upp, UpdateType type, int fileType); + public void start(); public void shutdown(); diff --git a/core/java/src/net/i2p/update/UpdatePostProcessor.java b/core/java/src/net/i2p/update/UpdatePostProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..050a9aed04f12a1fc02e60f3197749c7c96ef19b --- /dev/null +++ b/core/java/src/net/i2p/update/UpdatePostProcessor.java @@ -0,0 +1,50 @@ +package net.i2p.update; + +import java.io.IOException; +import java.io.File; + +/** + * An external class to handle complex processing of update files, + * where necessary instead of simply copying i2pupdate.zip to the config dir. + * + * @since 0.9.51 + */ +public interface UpdatePostProcessor { + + /** + * Notify the post-processor that an update has been downloaded and verified. + * The version will be higher than the currently-installed version. + * + * This method MUST immediately postprocess, copy, or rename the file, which + * will be located in the temporary directory. + * Caller will delete the file if it remains, immediately after this method returns. + * + * This method MUST throw an IOException on all errors. The IOException will be + * displayed to the user in the console, so it should be clear. + * + * This method must not trigger the shutdown itself. + * Caller will trigger the shutdown if so configured. + * + * If the post-processor needs to perform any actions at shutdown, it should + * call I2PAppContext.addShutdownTask() or RouterContext.addFinalShutdownTask(). + * See javadocs for restrictions on final shutdown tasks. + * Note that the router's temporary directory is deleted at shutdown, + * BEFORE the final shutdown tasks are run. + * + * After this call, the router will do a graceful shutdown if so configured, + * or will notify the user in the console to manually shut down the router. + * Therefore, the shutdown may happen immediately, or be delayed for 10 minutes, + * or may be hours, days, or weeks later. + * + * In rare cases, a newer update may be downloaded before the shutdown + * for the first update, and this method may be called again with the newer version. + * Implementers must take care to properly handle multiple calls. + * + * @param type only ROUTER_SIGNED_SU3 and ROUTER_DEV_SU3 are currently supported + * @param fileType a TYPE_xxx file type code from the SU3File, 0-255 + * @param version the version string from the SU3File + * @param file in the temp directory, as extracted from the validated su3 file + * @throws IOException on all errors, message will be displayed to the user + */ + public void updateDownloadedandVerified(UpdateType type, int fileType, String version, File file) throws IOException; +}