001: package tide.update;
002:
003: import snow.Basics;
004: import tide.editor.TideUtils;
005: import java.awt.EventQueue;
006: import java.awt.TrayIcon;
007: import java.awt.SystemTray;
008: import java.util.regex.Pattern;
009: import java.util.prefs.Preferences;
010: import snow.utils.NetUtils;
011: import java.io.FilenameFilter;
012: import javax.swing.border.EmptyBorder;
013: import java.awt.BorderLayout;
014: import java.awt.Frame;
015: import snow.utils.gui.*;
016: import java.util.*;
017: import java.awt.Component;
018: import snow.utils.DateUtils;
019: import java.awt.event.*;
020: import javax.swing.*;
021: import java.util.Date;
022: import java.util.zip.ZipEntry;
023: import java.util.zip.ZipFile;
024: import snow.utils.SysUtils;
025: import tide.export.JarCreation;
026: import tide.editor.MainEditorFrame;
027: import snow.utils.storage.FileUtils;
028: import java.net.*;
029: import java.io.File;
030:
031: public final class UpdaterUtils {
032: private static Thread replacerStarterShutdownHook = null;
033:
034: private UpdaterUtils() {
035: }
036:
037: public static boolean isNewVersionAvailable(final URL url)
038: throws Exception {
039: if (replacerStarterShutdownHook != null) {
040: throw new Exception(
041: "An update has already been made, the jar replacement is pending at application exit. Please close tide.");
042: }
043:
044: File tf = TideUtils.getTideJarCurrentlyRunning();
045: if (tf == null)
046: return false; // WebStart mode
047:
048: long[] dl = NetUtils.getDateAndSizeOnServer(url);
049: if (dl[0] - tf.lastModified() > 1000 * 60) //1000*3600) // one hour ?
050: {
051: return true;
052: }
053:
054: return false;
055: }
056:
057: public static void updateAndLaunchReplacerTask(final URL url,
058: final boolean calledFromStartupDialog) throws Exception {
059:
060: if (replacerStarterShutdownHook != null) {
061: throw new Exception(
062: "An update has already been made, the jar replacement is pending at application exit. Please close tIDE.");
063: }
064:
065: final File currentTideJar = TideUtils
066: .getTideJarCurrentlyRunning();
067: if (currentTideJar == null)
068: throw new Exception(
069: "Cannot update: there is no tide.jar in the classpath");
070:
071: ProgressModalDialog pmd = new ProgressModalDialog(
072: MainEditorFrame.instance, "Downloading the new tIDE",
073: true);
074:
075: final File tempDest = new File(currentTideJar.getAbsolutePath()
076: + ".new");
077:
078: if (url.getPath().toLowerCase().endsWith("tide.jar.pack.gz")) {
079: final File tempDestGZ = new File(currentTideJar
080: .getAbsolutePath()
081: + ".new.pack.gz");
082: tempDestGZ.deleteOnExit();
083: try {
084: NetUtils.downloadFileFromServer(url, tempDestGZ,
085: "tide", pmd);
086:
087: pmd.setProgressComment("Unpack200 to tide.jar.new");
088: JarCreation.replaceJarWithPack200Decompressed(tempDest,
089: tempDestGZ);
090: } finally {
091: pmd.closeDialog();
092: }
093: } else if (url.getPath().toLowerCase().endsWith("tide.jar")) {
094: try {
095: NetUtils.downloadFileFromServer(url, tempDest, "tide",
096: pmd);
097: } finally {
098: pmd.closeDialog();
099: }
100: } else {
101: pmd.closeDialog();
102: throw new Exception(
103: "I can only update from tide.jar or from tide.jar.pack.gz files. "
104: + url + " is a bad path.");
105: }
106:
107: // May2007: extract from the new one
108: extractReplacer(tempDest);
109:
110: // tricky: the replacement must be delayed at shutdown, because
111: // we are now running tide.jar !
112:
113: if (calledFromStartupDialog) {
114: JOptionPane
115: .showMessageDialog(
116: null,
117: "The tIDE jar file will be updated after it has been closed,"
118: + "\nplease wait until tIDE restarts in approximatively 10 seconds.",
119: "tIDE update",
120: JOptionPane.INFORMATION_MESSAGE);
121:
122: installShutdownHook(currentTideJar, tempDest, true, false); // restart but dont reopen the last project... let the dialog appear !
123:
124: System.exit(0);
125: }
126:
127: int rep = JOptionPane
128: .showConfirmDialog(
129: MainEditorFrame.instance,
130: "The new version has been successfully downloaded."
131: + "\nAnother JVM has been started that will replace"
132: + "\n\n "
133: + currentTideJar
134: + "\n\nwith the new version when tIDE is closed."
135: + "\n\nShould tIDE restart now ?",
136:
137: "Download successful",
138: JOptionPane.YES_NO_CANCEL_OPTION,
139: JOptionPane.INFORMATION_MESSAGE);
140:
141: if (rep == JOptionPane.YES_OPTION) {
142: installShutdownHook(currentTideJar, tempDest, true, true); // restart and reopen the last project
143:
144: MainEditorFrame.instance.terminateTIDE();
145:
146: // clean shut down...
147: } else {
148: installShutdownHook(currentTideJar, tempDest, false, false); // don't restart.
149: if (SystemTray.isSupported()) {
150: MainEditorFrame.instance.getTideTrayIcon()
151: .displayMessage("tIDE",
152: "tIDE will be updated on close",
153: TrayIcon.MessageType.INFO);
154: }
155: }
156: }
157:
158: private static void installShutdownHook(final File currentTideJar,
159: final File tempDest, final boolean restart,
160: final boolean reopenLastProj) {
161: if (reopenLastProj) {
162: try {
163: // we must store the current proj in this, because other tide started later may have overriden this global settin gwith their own
164: if (MainEditorFrame.instance.getActualProjectFile() != null) {
165: Preferences.userRoot().put(
166: "tide_last_opened_proj",
167: MainEditorFrame.instance
168: .getActualProjectFile()
169: .getAbsolutePath());
170: }
171: } catch (Exception ignored) {
172: } // occurs when called from startup
173: }
174:
175: Runtime.getRuntime().addShutdownHook(
176: replacerStarterShutdownHook = new Thread() {
177: // Called at VM shutdown
178: @Override
179: public void run() {
180: // START A NEW JVM
181: File javaExe = TideUtils
182: .getJavaToolUsedToLaunchTide();
183:
184: System.out.println(""
185: + javaExe.getAbsolutePath());
186: System.out.println("TideJarReplacer "
187: + currentTideJar.getAbsolutePath()
188: + " " + tempDest.getAbsolutePath());
189:
190: List<String> cmd = new ArrayList<String>();
191: cmd.add(javaExe.getAbsolutePath());
192: cmd.add("TideJarReplacer");
193: cmd.add(currentTideJar.getAbsolutePath()); // old (current), will be replaced with the tempDest
194: cmd.add(tempDest.getAbsolutePath()); // new
195:
196: // The command [March2008] to restart
197: if (restart) {
198: cmd.add(javaExe.getAbsolutePath());
199: cmd.add("-Xmx512m");
200: cmd.add("-jar");
201: cmd.add(currentTideJar.getAbsolutePath());
202: if (MainEditorFrame.debug)
203: cmd.add("-debug");
204: if (MainEditorFrame.enableExperimental)
205: cmd.add("-enableExperimental");
206: if (MainEditorFrame.redirectConsoleInTab)
207: cmd.add("-logtab");
208: if (reopenLastProj)
209: cmd.add("-openLastProj");
210: }
211:
212: ProcessBuilder pb = new ProcessBuilder(cmd);
213:
214: try {
215: Process pr = pb.start();
216: System.out.println("Replacer started");
217:
218: //String deb = ProcessUtils.readWholeProcessStack(pr, 10000);
219: //Thread.sleep(2000);
220: //System.out.println("ReplacerExecution:\n"+ );
221: //JOptionPane.showMessageDialog(null, "Replacer stack:\n"+deb);
222: } catch (Exception e) {
223: e.printStackTrace();
224: System.out
225: .println("Replacer start failed: "
226: + e.getMessage());
227: //JOptionPane.showMessageDialog(null, "Can't start replacer: "+e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
228: }
229: }
230: });
231:
232: }
233:
234: /** In the same folder as tide, only if date more recent on server.
235: */
236: private static void downloadDocOrSrc(URL url, String name)
237: throws Exception {
238: // in the same folder as tide
239: final File currentTideJar = TideUtils
240: .getTideJarCurrentlyRunning();
241: if (currentTideJar == null)
242: throw new Exception(
243: "Cannot update: there is no tide.jar in the classpath");
244:
245: ProgressModalDialog pmd = new ProgressModalDialog(
246: MainEditorFrame.instance,
247: "Downloading the new " + name, true);
248:
249: final File dest = new File(currentTideJar.getParent(), name);
250: long[] servDandS = NetUtils.getDateAndSizeOnServer(url);
251:
252: if (dest.exists()
253: && servDandS[0] < dest.lastModified() + 1000L * 60) {
254: // older
255: System.out.println("is up to date: " + dest);
256: return;
257: }
258:
259: try {
260: NetUtils.downloadFileFromServer(url, dest, dest.getName(),
261: pmd);
262: } finally {
263: pmd.closeDialog();
264: }
265: }
266:
267: /** Please call deletereplacer from the new started tide. We cannot delete them
268: * because they are used in another jvm later...
269: */
270: private static void extractReplacer(final File f) throws Exception {
271: ZipFile zf = new ZipFile(f);
272: try {
273: for (ZipEntry ze : getEntriesStartingWithName(zf,
274: "TideJarReplacer")) {
275: File dest = new File(f.getParentFile(), ze.getName());
276: FileUtils.writeToFile(zf.getInputStream(ze), dest);
277: dest.setLastModified(ze.getTime());
278: System.out.println(ze.getName()
279: + " extracted from jar, size=" + dest.length()
280: + ", zl=" + ze.getSize());
281: }
282: } catch (Exception e) {
283: throw e;
284: } finally {
285: FileUtils.closeIgnoringExceptions(zf);
286: }
287: }
288:
289: /** Deletes the previous replacer if any found.
290: */
291: public static void deleteReplacer() {
292: try {
293: File root = new File(System.getProperty("user.dir"));
294: FilenameFilter ff = new FilenameFilter() {
295: public boolean accept(File dir, String name) {
296: if (name.startsWith("TideJarReplacer")
297: && name.endsWith(".class"))
298: return true;
299: return false;
300: }
301: };
302: for (File it : root.listFiles(ff)) {
303: it.delete();
304: }
305: } catch (Exception ex) {
306: ex.printStackTrace();
307: }
308: }
309:
310: public static List<ZipEntry> getEntriesStartingWithName(ZipFile zf,
311: String n) {
312: List<ZipEntry> res = new ArrayList<ZipEntry>();
313: Enumeration<? extends ZipEntry> zes = zf.entries();
314: while (zes.hasMoreElements()) {
315: ZipEntry ze = zes.nextElement();
316: if (ze.getName().startsWith(n)) {
317: res.add(ze);
318: }
319: }
320: return res;
321: }
322:
323: public static ActionListener getUpdaterAction(
324: final Component parent,
325: final boolean calledFromStartupDialog) {
326:
327: return new ActionListener() {
328: public void actionPerformed(ActionEvent ae) {
329: if (TideUtils.getTideJarCurrentlyRunning() == null) {
330: JOptionPane
331: .showMessageDialog(
332: parent,
333: "No tide.jar was found on the classpath. I cannot update.",
334: "Cannot update",
335: JOptionPane.ERROR_MESSAGE);
336: return;
337: }
338:
339: final Object[] choice = chooseUpdateURL(parent);
340: if (choice == null)
341: return;
342:
343: final boolean downloadDoc = (Boolean) choice[1];
344: final boolean downloadSrc = (Boolean) choice[2];
345:
346: if (downloadDoc || downloadSrc) {
347: Thread t = new Thread() {
348: public void run() {
349:
350: String base = (String) choice[0];
351: if (base.toLowerCase().endsWith(
352: "tide.jar.pack.gz")) {
353: base = base.substring(0,
354: base.length() - 16);
355: } else if (base.toLowerCase().endsWith(
356: "tide.jar")) {
357: base = base.substring(0,
358: base.length() - 8);
359: }
360: final String fBase = base;
361: System.out.println("Base for download: "
362: + base);
363:
364: try {
365: if (downloadDoc) {
366: downloadDocOrSrc(new URL(fBase
367: + "tide_manual.pdf"),
368: "tide_manual.pdf");
369: }
370:
371: if (downloadSrc) {
372: downloadDocOrSrc(new URL(fBase
373: + "tide_src.zip"),
374: "tide_src.zip");
375: }
376: } catch (Exception e) {
377: e.printStackTrace();
378: JOptionPane
379: .showMessageDialog(
380: parent,
381: "" + e.getMessage(),
382: "Cannot download source and/or manual",
383: JOptionPane.ERROR_MESSAGE);
384: }
385: }
386: };
387: t.start();
388: }
389:
390: Thread t = new Thread() {
391: public void run() {
392: try {
393: final URL url = new URL((String) choice[0]);
394: long[] ds = NetUtils
395: .getDateAndSizeOnServer(url);
396: System.out
397: .println("date and size of tide on server: "
398: + new Date(ds[0])
399: + ", "
400: + ds[1]);
401:
402: if (UpdaterUtils.isNewVersionAvailable(url)) {
403: int rep = JOptionPane
404: .showConfirmDialog(
405: parent,
406: "A newer tIDE version is available:"
407: + "\n\nDate: "
408: + DateUtils
409: .formatDateAndTimeHuman(ds[0])
410: + " (released "
411: + DateUtils
412: .formatTimeDifference(System
413: .currentTimeMillis()
414: - ds[0])
415: + " ago)."
416: + "\n\nShould I download it for you ?",
417: "tIDE updater",
418: JOptionPane.YES_NO_CANCEL_OPTION);
419:
420: if (rep == JOptionPane.YES_OPTION) {
421: System.out
422: .println("starting launch replacer task");
423: UpdaterUtils
424: .updateAndLaunchReplacerTask(
425: url,
426: calledFromStartupDialog);
427: System.out
428: .println("launch replacer task done");
429: }
430: } else {
431: EventQueue.invokeLater(new Runnable() {
432: public void run() {
433: JOptionPane
434: .showMessageDialog(
435: parent,
436: "your version of tIDE seems up to date.",
437: "tIDE updater",
438: JOptionPane.INFORMATION_MESSAGE);
439: }
440: });
441: }
442: } catch (Exception e) {
443: e.printStackTrace();
444: JOptionPane
445: .showMessageDialog(
446: parent, // OK
447: ""
448: + e.getMessage()
449: + "\nDetails: "
450: + Basics
451: .exceptionToString(e),
452: "Cannot check for updates",
453: JOptionPane.ERROR_MESSAGE);
454:
455: }
456: }
457: };
458: t.start();
459: }
460: };
461: }
462:
463: /** Helper to choose the URL to update from (file or http).
464: * @return null if cancelled. {url, bool ddoc, bool dsrc}
465: */
466: private static Object[] chooseUpdateURL(Component parent) {
467: final JDialog d;
468: if (parent instanceof Frame) {
469: d = new JDialog((Frame) parent, "tIDE updater", true);
470: } else {
471: d = new JDialog((JDialog) parent, "tIDE updater", true);
472: }
473:
474: JPanel cp = new JPanel(new BorderLayout(5, 5));
475: d.setContentPane(cp);
476: cp.setBorder(new EmptyBorder(4, 4, 4, 4));
477:
478: Vector<String> urls = new Vector<String>(); // combobx requires vector
479:
480: urls.add(MainEditorFrame.OfficialTideJarPack200URL); // hardcoded
481: urls.add(MainEditorFrame.Alt1TideJarPack200URL);
482:
483: // ok, the props exists
484: urls.addAll(MainEditorFrame.instance.globalProperties
485: .getArrayProperty("tIDEUpdaterURLS", new String[0]));
486:
487: final JComboBox cb = new JComboBox(urls);
488: cb.setEditable(true);
489: cb.setMaximumRowCount(25);
490:
491: String lastUpd = Preferences.userRoot().get(
492: "last_tide_update_url", null);
493: if (lastUpd != null) {
494: cb.setSelectedItem(lastUpd);
495: }
496:
497: CloseControlPanel ccp = new CloseControlPanel(d, true, true,
498: "Ok");
499: d.add(ccp, BorderLayout.SOUTH);
500: d
501: .add(
502: GUIUtils
503: .createReadOnlyDescriptionArea("Please provide the URL location to update tIDE from."
504: + "\nYou can give http or file urls. For tide.jar or tide.jar.pack.gz"
505: + "\ndoc and src are only downloaded together with a new tIDE version"),
506: BorderLayout.NORTH);
507:
508: JPanel optsPan = new JPanel();
509: d.add(optsPan, BorderLayout.CENTER);
510: GridLayout3 gl3 = new GridLayout3(1, optsPan);
511: gl3.add(cb);
512: JCheckBox downDoc = new JCheckBox("Download manual (PDF)",
513: MainEditorFrame.instance.globalProperties.getBoolean(
514: "download_manual", true));
515: gl3.add(downDoc);
516: JCheckBox downSrc = new JCheckBox("Download source (zip)",
517: MainEditorFrame.instance.globalProperties.getBoolean(
518: "download_source", false));
519: gl3.add(downSrc);
520:
521: d.pack();
522: d.setLocationRelativeTo(parent);
523: d.setVisible(true); // MODAL
524:
525: if (ccp.getWasCancelled())
526: return null;
527:
528: String sel = "" + cb.getSelectedItem();
529: if (sel.length() == 0)
530: return null;
531: Preferences.userRoot().put("last_tide_update_url", sel);
532:
533: if (!urls.contains(sel)) {
534: urls.add(sel);
535: urls.remove(MainEditorFrame.OfficialTideJarPack200URL); // hardcoded
536: urls.remove(MainEditorFrame.Alt1TideJarPack200URL);
537: MainEditorFrame.instance.globalProperties.setArrayProperty(
538: "tIDEUpdaterURLS", urls.toArray(new String[urls
539: .size()]));
540: }
541:
542: MainEditorFrame.instance.globalProperties.setBoolean(
543: "download_manual", downDoc.isSelected());
544: MainEditorFrame.instance.globalProperties.setBoolean(
545: "download_source", downSrc.isSelected());
546:
547: return new Object[] { sel, downDoc.isSelected(),
548: downSrc.isSelected() };
549: }
550:
551: /*test*/
552: public static void main(String[] arguments) {
553: // ERROR: shows the constructor of ZipFile !
554: // new File(
555: JComboBox cb = new JComboBox(new Object[] { "A", "B" });
556: cb.setEditable(true);
557: JOptionPane.showMessageDialog(null, cb, "A",
558: JOptionPane.INFORMATION_MESSAGE);
559: /*new File("C:/proj/tide/published/snowmail.sn.funpic.de/tide/tide.jar").
560: setLastModified(100000000000l);*/
561: }
562:
563: }
|