29
29
30
30
package net .imagej .ui .swing .updater ;
31
31
32
- import java .io .DataInputStream ;
33
- import java .io .File ;
34
- import java .io .IOException ;
32
+ import java .io .*;
35
33
import java .lang .reflect .InvocationTargetException ;
36
- import java .net .Authenticator ;
37
- import java .net .HttpURLConnection ;
38
- import java .net .MalformedURLException ;
39
- import java .net .URL ;
40
- import java .net .URLClassLoader ;
41
- import java .net .URLConnection ;
42
- import java .net .UnknownHostException ;
43
- import java .util .ArrayList ;
44
- import java .util .List ;
34
+ import java .net .*;
35
+ import java .util .*;
36
+ import java .util .concurrent .ExecutionException ;
45
37
46
38
import net .imagej .ui .swing .updater .ViewOptions .Option ;
47
39
import net .imagej .updater .*;
48
40
import net .imagej .updater .Conflicts .Conflict ;
49
41
import net .imagej .updater .util .*;
50
42
43
+ import org .apache .commons .compress .archivers .ArchiveEntry ;
44
+ import org .apache .commons .compress .archivers .ArchiveInputStream ;
45
+ import org .apache .commons .compress .archivers .tar .TarArchiveInputStream ;
46
+ import org .apache .commons .compress .archivers .zip .ZipArchiveInputStream ;
47
+ import org .apache .commons .compress .compressors .gzip .GzipCompressorInputStream ;
51
48
import org .scijava .app .StatusService ;
52
49
import org .scijava .command .CommandService ;
50
+ import org .scijava .download .Download ;
51
+ import org .scijava .download .DownloadService ;
53
52
import org .scijava .event .ContextDisposingEvent ;
54
53
import org .scijava .event .EventHandler ;
54
+ import org .scijava .io .location .LocationService ;
55
55
import org .scijava .log .LogService ;
56
56
import org .scijava .log .Logger ;
57
57
import org .scijava .plugin .Menu ;
58
58
import org .scijava .plugin .Parameter ;
59
59
import org .scijava .plugin .Plugin ;
60
+ import org .scijava .ui .DialogPrompt ;
61
+ import org .scijava .ui .UIService ;
60
62
import org .scijava .util .AppUtils ;
63
+ import org .scijava .util .PropertiesHelper ;
61
64
62
65
import javax .swing .*;
63
66
67
+ import static org .scijava .ui .DialogPrompt .MessageType .QUESTION_MESSAGE ;
68
+ import static org .scijava .ui .DialogPrompt .OptionType .YES_NO_OPTION ;
69
+
64
70
/**
65
71
* The Updater. As a command.
66
72
* <p>
@@ -78,9 +84,18 @@ public class ImageJUpdater implements UpdaterUI {
78
84
@ Parameter
79
85
private StatusService statusService ;
80
86
87
+ @ Parameter
88
+ private DownloadService downloadService ;
89
+
90
+ @ Parameter
91
+ private LocationService locationService ;
92
+
81
93
@ Parameter
82
94
private LogService log ;
83
95
96
+ @ Parameter
97
+ private UIService uiService ;
98
+
84
99
@ Parameter
85
100
private UploaderService uploaderService ;
86
101
@@ -105,6 +120,19 @@ public void run() {
105
120
final File imagejRoot = imagejDirProperty != null ? new File (
106
121
imagejDirProperty ) : AppUtils .getBaseDirectory ("ij.dir" ,
107
122
FilesCollection .class , "updater" );
123
+
124
+ // -- Check for HTTPs support in Java --
125
+ HTTPSUtil .checkHTTPSSupport (log );
126
+ if (!HTTPSUtil .supportsHTTPS ()) {
127
+ main .warn (
128
+ "Your Java might be too old to handle updates via HTTPS. This is a security risk!\n " +
129
+ "Please download a recent version of this software.\n " );
130
+ }
131
+
132
+ // check if there is a new Java update available
133
+ updateJavaIfNecessary (imagejRoot );
134
+
135
+ // -- Determine which files are governed by the updater --
108
136
final FilesCollection files = new FilesCollection (log , imagejRoot );
109
137
110
138
UpdaterUserInterface .set (new SwingUserInterface (log , statusService ));
@@ -136,12 +164,6 @@ public void run() {
136
164
137
165
try {
138
166
files .tryLoadingCollection ();
139
- HTTPSUtil .checkHTTPSSupport (log );
140
- if (!HTTPSUtil .supportsHTTPS ()) {
141
- main .warn (
142
- "Your Java might be too old to handle updates via HTTPS. This is a security risk!\n " +
143
- "Please download a recent version of this software.\n " );
144
- }
145
167
refreshUpdateSites (files );
146
168
String warnings = files .reloadCollectionAndChecksum (progress );
147
169
main .checkWritable ();
@@ -264,6 +286,195 @@ protected void updateConflictList() {
264
286
main .updateFilesTable ();
265
287
}
266
288
289
+ /**
290
+ * Helper method to download and extract the appropriate JDK for this platform
291
+ * to the corresponding ImageJ java subdirectory.
292
+ */
293
+ private boolean updateJava (final Map <String , String > jdkVersions ,
294
+ final File imagejRoot )
295
+ {
296
+ // Download and unzip the new JDK
297
+ final String platform = UpdaterUtil .getPlatform ();
298
+ final String jdkUrl = jdkVersions .get (platform );
299
+ final String jdkName = jdkUrl .substring (jdkUrl .lastIndexOf ("/" ) + 1 );
300
+ final File jdkDir = new File (imagejRoot + File .separator + "java" +
301
+ File .separator + platform );
302
+
303
+ if (!jdkDir .exists () && !jdkDir .mkdirs ()) {
304
+ log .error ("Unable to create platform Java directory: " + jdkDir );
305
+ return false ;
306
+ }
307
+
308
+ // Download the JDK
309
+ final File jdkDlLoc = new File (jdkDir .getAbsolutePath () + File .separator +
310
+ jdkName );
311
+ jdkDlLoc .deleteOnExit ();
312
+ try {
313
+ log .debug ("Downloading " + locationService .resolve (jdkUrl ) + " to " +
314
+ locationService .resolve (jdkDlLoc .toURI ()));
315
+ Download download = downloadService .download (locationService .resolve (
316
+ jdkUrl ), locationService .resolve (jdkDlLoc .toURI ()));
317
+ download .task ().waitFor ();
318
+ }
319
+ catch (URISyntaxException | ExecutionException | InterruptedException e ) {
320
+ log .error (e );
321
+ return false ;
322
+ }
323
+
324
+ String javaLoc = jdkDlLoc .getAbsolutePath ();
325
+ int extensionLength = 0 ;
326
+
327
+ // Extract the JDK
328
+ if (jdkDlLoc .toString ().endsWith ("tar.gz" )) {
329
+ try (FileInputStream fis = new FileInputStream (jdkDlLoc );
330
+ GzipCompressorInputStream gzIn = new GzipCompressorInputStream (fis );
331
+ TarArchiveInputStream tarIn = new TarArchiveInputStream (gzIn ))
332
+ {
333
+ doExtraction (jdkDir , tarIn );
334
+ extensionLength = 7 ;
335
+ }
336
+ catch (IOException e ) {
337
+ log .error (e );
338
+ return false ;
339
+ }
340
+ }
341
+ else if (jdkDlLoc .toString ().endsWith ("zip" )) {
342
+ try (FileInputStream fis = new FileInputStream (jdkDlLoc );
343
+ ZipArchiveInputStream zis = new ZipArchiveInputStream (fis ))
344
+ {
345
+ doExtraction (jdkDir , zis );
346
+ extensionLength = 4 ;
347
+ }
348
+ catch (IOException e ) {
349
+ log .error (e );
350
+ return false ;
351
+ }
352
+ }
353
+
354
+ // Notify user of success
355
+ uiService .showDialog ("Java version updated!" +
356
+ " Please restart to take advantage of the new Java." ,
357
+ DialogPrompt .MessageType .INFORMATION_MESSAGE );
358
+
359
+ // Update the app configuration file to use the newly downloaded JDK
360
+ javaLoc = javaLoc .substring (0 , javaLoc .length () - extensionLength );
361
+ String exeName = System .getProperty ("ij.executable" );
362
+ if (exeName != null && !exeName .trim ().isEmpty ()) {
363
+ exeName = exeName .substring (exeName .lastIndexOf (File .separator ));
364
+ exeName = exeName .substring (0 , exeName .indexOf ("-" ));
365
+ final File appCfg = new File (imagejRoot + File .separator + exeName +
366
+ ".cfg" );
367
+ Map <String , String > appProps = appCfg .exists () ? PropertiesHelper .get (
368
+ appCfg ) : new HashMap <>();
369
+ appProps .put ("app-configured" , javaLoc );
370
+ PropertiesHelper .put (appProps , appCfg );
371
+ }
372
+ return true ;
373
+ }
374
+
375
+ /**
376
+ * Helper method to extract an archive
377
+ */
378
+ private void doExtraction (final File jdkDir , final ArchiveInputStream tarIn )
379
+ throws IOException
380
+ {
381
+ ArchiveEntry entry ;
382
+ while ((entry = tarIn .getNextEntry ()) != null ) {
383
+ if (entry .isDirectory ()) {
384
+ new File (jdkDir , entry .getName ()).mkdirs ();
385
+ }
386
+ else {
387
+ byte [] buffer = new byte [1024 ];
388
+ File outputFile = new File (jdkDir , entry .getName ());
389
+ OutputStream fos = new FileOutputStream (outputFile );
390
+ int len ;
391
+ while ((len = tarIn .read (buffer )) != -1 ) {
392
+ fos .write (buffer , 0 , len );
393
+ }
394
+ fos .close ();
395
+ }
396
+ }
397
+ }
398
+
399
+ /**
400
+ * Helper method that checks the remote JDK list and compares to a locally
401
+ * cached version. If the remote list is newer an available Java update is
402
+ * indicated. If the user agrees, the new JDK is downloaded and extracted to
403
+ * the appropriate directory.
404
+ */
405
+ private void updateJavaIfNecessary (final File imagejRoot ) {
406
+ final File jdkUrls = new File (imagejRoot .getAbsolutePath () +
407
+ File .separator + "jdk-urls.txt" );
408
+ final String modifiedKey = "LAST_MODIFIED" ;
409
+ final String jdkUrl = "https://downloads.imagej.net/java/jdk-urls.txt" ;
410
+ long lastModifiedRemote ;
411
+
412
+ // Get the last modified time on the remote JDK list
413
+ try {
414
+ HttpURLConnection connection = (HttpURLConnection ) new URL (jdkUrl )
415
+ .openConnection ();
416
+ connection .setRequestMethod ("HEAD" );
417
+ lastModifiedRemote = connection .getLastModified ();
418
+ }
419
+ catch (IOException e ) {
420
+ log .error ("Unable to read remote JDK list" , e );
421
+ return ;
422
+ }
423
+
424
+ // Check if we've already cached a local version of the JDK list
425
+ if (jdkUrls .exists ()) {
426
+ // check when the remote was last modified
427
+ Map <String , String > jdkVersionProps = PropertiesHelper .get (jdkUrls );
428
+ if (lastModifiedRemote == 0 ) { // 0 means "not provided"
429
+ log .error ("No modification date found in jdk-urls.txt" );
430
+ return ;
431
+ }
432
+ long lastModifiedLocal = Long .parseLong (jdkVersionProps .getOrDefault (
433
+ modifiedKey , "0" ));
434
+
435
+ // return if up to date
436
+ if (lastModifiedLocal == lastModifiedRemote ) return ;
437
+
438
+ // Otherwise delete the conf file and re-download
439
+ jdkUrls .delete ();
440
+ }
441
+
442
+ // Download the new properties file
443
+ try {
444
+ Download dl = downloadService .download (locationService .resolve (jdkUrl ),
445
+ locationService .resolve (jdkUrls .toURI ()));
446
+ dl .task ().waitFor ();
447
+ }
448
+ catch (URISyntaxException e ) {
449
+ log .error ("Failed to download the remote JDK url list: bad URI" );
450
+ return ;
451
+ }
452
+ catch (ExecutionException | InterruptedException e ) {
453
+ log .error (
454
+ "Failed to download the remote JDK url list: download task failed" );
455
+ return ;
456
+ }
457
+
458
+ // Inject the last modification date to the JDK list
459
+ Map <String , String > jdkUrlMap = PropertiesHelper .get (jdkUrls );
460
+ jdkUrlMap .put (modifiedKey , Long .toString (lastModifiedRemote ));
461
+
462
+ // Ask the user if they would like to proceed with a Java update
463
+ DialogPrompt .Result result = uiService .showDialog (
464
+ "A newer version of Java is recommended.\n " +
465
+ "Downloading this may take longer than normal updates, but will " +
466
+ "eventually be required for continued updates.\n " +
467
+ "Would you like to update now?" , QUESTION_MESSAGE , YES_NO_OPTION );
468
+
469
+ // Do the update, if desired
470
+ if (result == DialogPrompt .Result .YES_OPTION && updateJava (jdkUrlMap ,
471
+ imagejRoot ))
472
+ {
473
+ // Store the current url list if we updated Java
474
+ PropertiesHelper .put (jdkUrlMap , jdkUrls );
475
+ }
476
+ }
477
+
267
478
private void refreshUpdateSites (FilesCollection files )
268
479
throws InterruptedException , InvocationTargetException
269
480
{
0 commit comments