001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2015 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029package edu.wisc.ssec.mcidasv; 030 031import java.awt.event.ActionEvent; 032import java.io.BufferedReader; 033import java.io.BufferedWriter; 034import java.io.File; 035import java.io.FileReader; 036import java.io.FileWriter; 037import java.util.Enumeration; 038import java.util.Hashtable; 039import java.util.Map; 040import java.util.Properties; 041import java.util.Set; 042 043import javax.swing.JEditorPane; 044import javax.swing.JLabel; 045import javax.swing.JOptionPane; 046import javax.swing.JPanel; 047import javax.swing.event.HyperlinkEvent; 048import javax.swing.event.HyperlinkListener; 049 050import ucar.unidata.idv.IdvObjectStore; 051import ucar.unidata.idv.IntegratedDataViewer; 052import ucar.unidata.util.FileManager; 053import ucar.unidata.util.IOUtil; 054import ucar.unidata.util.LogUtil; 055import ucar.unidata.util.Misc; 056import ucar.unidata.util.StringUtil; 057 058import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 059import edu.wisc.ssec.mcidasv.util.SystemState; 060 061public class StateManager extends ucar.unidata.idv.StateManager implements Constants, HyperlinkListener { 062 063 public static final String USERPATH_IS_BAD_MESSAGE = "<html>McIDAS-V is unable to create or write to the local user's directory.<br>Please select a directory.</html>"; 064 065 public static final String USERPATH_PICK = "Please select a directory to use as the McIDAS-V user path."; 066 067 /** Lazily-loaded VisAD build date. */ 068 private String visadDate; 069 070 /** Lazily-loaded VisAD SVN revision number. */ 071 private String visadVersion; 072 073 private String version; 074 private String versionAbout; 075 076 public StateManager(IntegratedDataViewer idv) { 077 super(idv); 078 } 079 080 /** 081 * Override to set the right user directory. 082 * 083 * @return Newly created object store. 084 */ 085 @Override protected IdvObjectStore doMakeObjectStore() { 086 IdvObjectStore store = new IdvObjectStore(getIdv(), 087 getStoreSystemName(), getStoreName(), 088 getIdv().getEncoderForRead(), 089 StartupManager.getInstance().getPlatform().getUserDirectory()); 090 initObjectStore(store); 091 return store; 092 } 093 094 /** 095 * Initialize the given object store. This mostly initializes the user's 096 * {@literal "userpath"} directory when it is first created. 097 * 098 * @param store Object store to initialize. Cannot be {@code null}. 099 */ 100 @Override protected void initObjectStore(IdvObjectStore store) { 101 while (!store.userDirectoryOk()) { 102 LogUtil.userMessage(USERPATH_IS_BAD_MESSAGE); 103 File dir = FileManager.getDirectory(null, USERPATH_PICK); 104 if (dir != null) { 105 store.setOverrideDirectory(dir); 106 } else { 107 System.exit(0); 108 } 109 } 110 111 if (store.getMadeUserDirectory()) { 112 initNewUserDirectory(store.getUserDirectory()); 113 } 114 initUserDirectory(store.getUserDirectory()); 115 } 116 117 /** 118 * Handle a change to a link 119 * 120 * @param e the link's event 121 */ 122 @Override public void hyperlinkUpdate(HyperlinkEvent e) { 123 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 124 if (e.getURL() == null) { 125 click(e.getDescription()); 126 } else { 127 click(e.getURL().toString()); 128 } 129 } 130 } 131 132 /** 133 * Handle a click on a link 134 * 135 * @param url the link definition 136 */ 137 public void click(String url) { 138 getIdv().actionPerformed(new ActionEvent(this, 0, url)); 139 } 140 141 public String getOSName() { 142 String os = System.getProperty("os.name"); 143 os = os.replaceAll(" ", "_"); 144 return os; 145 } 146 147 public String getMcIdasVersionAbout() { 148 getMcIdasVersion(); 149 150 versionAbout = IOUtil.readContents((String) getProperty(Constants.PROP_ABOUTTEXT), ""); 151 versionAbout = StringUtil.replace(versionAbout, MACRO_VERSION, version); 152 Properties props = Misc.readProperties( 153 (String) getProperty(Constants.PROP_VERSIONFILE), 154 null, 155 getClass() 156 ); 157 158 String value = getIdvVersion(); 159 versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_IDV_VERSION, value); 160 value = props.getProperty(PROP_COPYRIGHT_YEAR, ""); 161 versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_COPYRIGHT_YEAR, value); 162 value = props.getProperty(PROP_BUILD_DATE, "Unknown"); 163 versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_BUILDDATE, value); 164 versionAbout = StringUtil.replace(versionAbout, Constants.MACRO_VISAD_VERSION, getVisadVersion()); 165 166 return versionAbout; 167 } 168 169 public String getMcIdasVersion() { 170 if (version != null) { 171 return version; 172 } 173 174 Properties props = new Properties(); 175 props = Misc.readProperties((String) getProperty(Constants.PROP_VERSIONFILE), null, getClass()); 176 String maj = props.getProperty(PROP_VERSION_MAJOR, "0"); 177 String min = props.getProperty(PROP_VERSION_MINOR, "0"); 178 String rel = props.getProperty(PROP_VERSION_RELEASE, ""); 179 180 version = maj.concat(".").concat(min).concat(rel); 181 182 return version; 183 } 184 185 /** 186 * Returns the current Jython version. 187 * 188 * @return Jython's version information. 189 */ 190 @Override public String getJythonVersion() { 191 return org.python.Version.PY_VERSION; 192 } 193 194 /** 195 * Get a property. 196 * 197 * @param name Name of the property. Cannot be {@code null}. 198 * 199 * @return Value associated with {@code name} or {@code null}. 200 */ 201 @Override public Object getProperty(final String name) { 202 Object value = null; 203 if (McIDASV.isMac()) { 204 value = getProperties().get("mac."+name); 205 } 206 if (value == null) { 207 value = getProperties().get(name); 208 } 209 if (value == null) { 210 String fixedName = StateManager.fixIds(name); 211 if (!name.equals(fixedName)) { 212 return getProperties().get(fixedName); 213 } 214 } 215 return value; 216 } 217 218 /** 219 * Find the value associated with the given ID by checking the 220 * {@literal "properties"}, and if nothing was found, check the preferences. 221 * 222 * @param name Property or preference ID. Cannot be {@code null}. 223 * 224 * @return Either the value associated with {@code name} or {@code null}. 225 */ 226 public Object getPropertyOrPreference(String name) { 227 Object o = getProperty(name); 228 if (o == null) { 229 o = getPreference(name); 230 } 231 return o; 232 } 233 234 /** 235 * Find the {@link String} value associated with the given ID by checking 236 * the {@literal "properties"}, and if nothing was found, check the 237 * preferences. 238 * 239 * @param name Property or preference ID. Cannot be {@code null}. 240 * @param dflt Value to return if there is no property or preference 241 * associated with {@code name} 242 * 243 * @return Either the value associated with {@code name} or {@code dflt}. 244 */ 245 public String getPropertyOrPreference(String name, String dflt) { 246 String value = dflt; 247 Object o = getPropertyOrPreference(name); 248 if (o != null) { 249 value = o.toString(); 250 } 251 return value; 252 } 253 254 /** 255 * Find the {@link Integer} value associated with the given ID by checking 256 * the {@literal "properties"}, and if nothing was found, check the 257 * preferences. 258 * 259 * @param name Property or preference ID. Cannot be {@code null}. 260 * @param dflt Value to return if there is no property or preference 261 * associated with {@code name} 262 * 263 * @return Either the value associated with {@code name} or {@code dflt}. 264 */ 265 public int getPropertyOrPreference(String name, int dflt) { 266 int value = dflt; 267 Object o = getPropertyOrPreference(name); 268 if (o != null) { 269 value = Integer.valueOf(o.toString()); 270 } 271 return value; 272 } 273 274 /** 275 * Find the {@link Double} value associated with the given ID by checking 276 * the {@literal "properties"}, and if nothing was found, check the 277 * preferences. 278 * 279 * @param name Property or preference ID. Cannot be {@code null}. 280 * @param dflt Value to return if there is no property or preference 281 * associated with {@code name} 282 * 283 * @return Either the value associated with {@code name} or {@code dflt}. 284 */ 285 public double getPropertyOrPreference(String name, double dflt) { 286 double value = dflt; 287 Object o = getPropertyOrPreference(name); 288 if (o != null) { 289 value = Double.valueOf(o.toString()); 290 } 291 return value; 292 } 293 294 /** 295 * Find the {@link Boolean} value associated with the given ID by checking 296 * the {@literal "properties"}, and if nothing was found, check the 297 * preferences. 298 * 299 * @param name Property or preference ID. Cannot be {@code null}. 300 * @param dflt Value to return if there is no property or preference 301 * associated with {@code name} 302 * 303 * @return Either the value associated with {@code name} or {@code dflt}. 304 */ 305 public boolean getPropertyOrPreference(String name, boolean dflt) { 306 boolean value = dflt; 307 Object o = getPropertyOrPreference(name); 308 if (o != null) { 309 value = Boolean.valueOf(o.toString()); 310 } 311 return value; 312 } 313 314 /** 315 * Returns information about the current version of McIDAS-V and the IDV, 316 * along with their respective build dates. 317 * 318 * @return Hashtable containing versioning information. 319 */ 320 public Hashtable<String, String> getVersionInfo() { 321 Properties props = new Properties(); 322 props = Misc.readProperties((String) getProperty(Constants.PROP_VERSIONFILE), null, getClass()); 323 324 String mcvBuild = props.getProperty(PROP_BUILD_DATE, "Unknown"); 325 326 Hashtable<String, String> table = new Hashtable<String, String>(); 327 table.put("mcv.version.general", getMcIdasVersion()); 328 table.put("mcv.version.build", mcvBuild); 329 table.put("idv.version.general", getVersion()); 330 table.put("idv.version.build", getBuildDate()); 331 table.put("visad.version.general", getVisadVersion()); 332 table.put("visad.version.build", getVisadDate()); 333 return table; 334 } 335 336 /** 337 * Return the timestamp from visad.jar was created. 338 * 339 * @return {@code String} representation of the creation timestamp. Likely to change formatting over time. 340 */ 341 public String getVisadDate() { 342 if (visadDate == null) { 343 Map<String, String> props = SystemState.queryVisadBuildProperties(); 344 visadDate = props.get(Constants.PROP_VISAD_DATE); 345 visadVersion = props.get(Constants.PROP_VISAD_REVISION); 346 } 347 return visadDate; 348 } 349 350 /** 351 * Return the {@literal "version"} of VisAD. 352 * 353 * @return Currently returns whatever the SVN revision number was when 354 * visad.jar was built. 355 */ 356 public String getVisadVersion() { 357 if (visadVersion== null) { 358 Map<String, String> props = SystemState.queryVisadBuildProperties(); 359 visadDate = props.get(Constants.PROP_VISAD_DATE); 360 visadVersion = props.get(Constants.PROP_VISAD_REVISION); 361 } 362 return visadVersion; 363 } 364 365 public String getIdvVersion() { 366 return getVersion(); 367 } 368 369 /** 370 * Overridden to set default of McIDAS-V 371 */ 372 @Override public String getStoreSystemName() { 373 return StartupManager.getInstance().getPlatform().getUserDirectory(); 374 } 375 376 /** 377 * Overridden to get dir of the unnecessary second level directory. 378 */ 379 @Override public String getStoreName() { 380 return ""; 381 } 382 383 /** 384 * Connect to McIDAS website and look for latest stable version 385 */ 386 public String getMcIdasVersionStable() { 387 String offscreen = "0"; 388 if (super.getIdv().getArgsManager().getIsOffScreen()) { 389 offscreen = "1"; 390 } 391 392 String version = ""; 393 try { 394 version = IOUtil.readContents(Constants.HOMEPAGE_URL+"/"+Constants.VERSION_HANDLER_URL+"?v="+getMcIdasVersion()+"&os="+getOSName()+"&off="+offscreen, ""); 395 } catch (Exception e) {} 396 return version.trim(); 397 } 398 399 /** 400 * Connect to McIDAS website and look for latest prerelease version 401 */ 402 public String getMcIdasVersionPrerelease() { 403 String version = ""; 404 try { 405 String htmlList = IOUtil.readContents(Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL, ""); 406 String lines[] = htmlList.split("\n"); 407 for (int i=0; i<lines.length; i++) { 408 String line = lines[i].trim(); 409 if (line.matches(".*McIDAS-V_\\d+\\.\\d+.*")) { 410 line = line.substring(line.indexOf("McIDAS-V_")+9); 411 String aVersion = line.substring(0, line.indexOf("_")); 412 if (version == "") { 413 version = aVersion; 414 } 415 else { 416 int comp = compareVersions(version, aVersion); 417 if (comp > 0) { 418 version = aVersion; 419 } 420 } 421 } 422 } 423 } catch (Exception e) {} 424 return version.trim(); 425 } 426 427 /** 428 * Connect to McIDAS website and look for latest notice 429 */ 430 public String getNoticeLatest() { 431 String notice = ""; 432 try { 433 notice = IOUtil.readContents(Constants.HOMEPAGE_URL+"/"+Constants.NOTICE_URL+"?requesting="+getMcIdasVersion()+"&os="+getOSName(), ""); 434 } catch (Exception e) {} 435 if (notice.indexOf("<notice>")<0) notice=""; 436 notice = notice.replaceAll("<[/?]notice>",""); 437 return notice.trim(); 438 } 439 440 /** 441 * Compare version strings 442 * 0: equal 443 * <0: this version is greater 444 * >0: that version is greater 445 */ 446 public static int compareVersions(String thisVersion, String thatVersion) { 447 int thisInt = versionToInteger(thisVersion); 448 int thatInt = versionToInteger(thatVersion); 449 return thatInt - thisInt; 450 } 451 452 /** 453 * Turn version strings of the form #.#(a#) 454 * where # is one or two digits, a is one of alpha or beta, and () is optional 455 * Into an integer... (empty) > beta > alpha 456 */ 457 public static int versionToInteger(String version) { 458 int value = 0; 459 int p; 460 String part; 461 Character one = null; 462 463 try { 464 465 // Major version 466 p = version.indexOf('.'); 467 if (p > 0) { 468 part = version.substring(0,p); 469 value += Integer.parseInt(part) * 1000000; 470 version = version.substring(p+1); 471 } 472 473 // Minor version 474 int minor = 0; 475 int i=0; 476 for (i=0; i<2 && i<version.length(); i++) { 477 one = version.charAt(i); 478 if (Character.isDigit(one)) { 479 if (i>0) minor *= 10; 480 minor += Character.digit(one, 10) * 10000; 481 } 482 else { 483 break; 484 } 485 } 486 value += minor; 487 if (one!=null) version = version.substring(i); 488 489 // Alpha/beta/update/release status 490 if (version.length() == 0) value += 300; 491 else if (version.charAt(0) == 'b') value += 200; 492 else if (version.charAt(0) == 'a') value += 100; 493 else if (version.charAt(0) == 'u') value += 400; 494 else if (version.charAt(0) == 'r') value += 400; 495 for (i=0; i<version.length(); i++) { 496 one = version.charAt(i); 497 if (Character.isDigit(one)) break; 498 } 499 if (one!=null) version = version.substring(i); 500 501 // Alpha/beta version 502 if (version.length() > 0) 503 value += Integer.parseInt(version); 504 505 } catch (Exception e) {} 506 507 return value; 508 } 509 510 public boolean getIsPrerelease() { 511 boolean isPrerelease = false; 512 String version = getMcIdasVersion(); 513 if (version.indexOf("a") >= 0 || version.indexOf("b") >= 0) { 514 isPrerelease = true; 515 } 516 return isPrerelease; 517 } 518 519 public void checkForNewerVersion(boolean notifyDialog) { 520 checkForNewerVersionStable(notifyDialog); 521 if (getStore().get(Constants.PREF_PRERELEASE_CHECK, getIsPrerelease())) { 522 checkForNewerVersionPrerelease(notifyDialog); 523 } 524 } 525 526 public void checkForNewerVersionStable(boolean notifyDialog) { 527 528 /** Get the stable version from the website (for statistics recording) */ 529 String thatVersion = getMcIdasVersionStable(); 530 531 /** Shortcut the rest of the process if we are processing offscreen */ 532 if (super.getIdv().getArgsManager().getIsOffScreen()) { 533 return; 534 } 535 536 String thisVersion = getMcIdasVersion(); 537 String titleText = "Version Check"; 538 539 if (thisVersion.equals("") || thatVersion.equals("")) { 540 if (notifyDialog) { 541 JOptionPane.showMessageDialog(null, "Version check failed", titleText, 542 JOptionPane.WARNING_MESSAGE); 543 } 544 } 545 else if (compareVersions(thisVersion, thatVersion) > 0) { 546 String labelText = "<html>Version <b>" + thatVersion + "</b> is available<br><br>"; 547 labelText += "Visit <a href=\"" + Constants.HOMEPAGE_URL + "\">"; 548 labelText += Constants.HOMEPAGE_URL + "</a> to download</html>"; 549 550 JPanel backgroundColorGetterPanel = new JPanel(); 551 JEditorPane messageText = new JEditorPane("text/html", labelText); 552 messageText.setBackground(backgroundColorGetterPanel.getBackground()); 553 messageText.setEditable(false); 554 messageText.addHyperlinkListener(this); 555 556// JLabel message = new JLabel(labelText, JLabel.CENTER); 557 JOptionPane.showMessageDialog(null, messageText, titleText, 558 JOptionPane.INFORMATION_MESSAGE); 559 } 560 else { 561 if (notifyDialog) { 562 String labelText = "<html>This version (<b>" + thisVersion + "</b>) is up to date</html>"; 563 JLabel message = new JLabel(labelText, JLabel.CENTER); 564 JOptionPane.showMessageDialog(null, message, titleText, 565 JOptionPane.INFORMATION_MESSAGE); 566 } 567 } 568 569 } 570 571 public void checkForNewerVersionPrerelease(boolean notifyDialog) { 572 573 /** Shortcut the rest of the process if we are processing offscreen */ 574 if (super.getIdv().getArgsManager().getIsOffScreen()) { 575 return; 576 } 577 578 String thisVersion = getMcIdasVersion(); 579 String thatVersion = getMcIdasVersionPrerelease(); 580 String titleText = "Prerelease Check"; 581 582 if (thisVersion.equals("") || thatVersion.equals("")) { 583 if (notifyDialog) { 584 JOptionPane.showMessageDialog(null, "No prerelease version available", titleText, 585 JOptionPane.WARNING_MESSAGE); 586 } 587 } 588 else if (compareVersions(thisVersion, thatVersion) > 0) { 589 String labelText = "<html>Prerelease <b>" + thatVersion + "</b> is available<br><br>"; 590 labelText += "Visit <a href=\"" + Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL + "\">"; 591 labelText += Constants.HOMEPAGE_URL+'/'+Constants.PRERELEASE_URL + "</a> to download</html>"; 592 593 JPanel backgroundColorGetterPanel = new JPanel(); 594 JEditorPane messageText = new JEditorPane("text/html", labelText); 595 messageText.setBackground(backgroundColorGetterPanel.getBackground()); 596 messageText.setEditable(false); 597 messageText.addHyperlinkListener(this); 598 599// JLabel message = new JLabel(labelText, JLabel.CENTER); 600 JOptionPane.showMessageDialog(null, messageText, titleText, 601 JOptionPane.INFORMATION_MESSAGE); 602 } 603 else { 604 if (notifyDialog) { 605 String labelText = "<html>This version (<b>" + thisVersion + "</b>) is up to date</html>"; 606 JLabel message = new JLabel(labelText, JLabel.CENTER); 607 JOptionPane.showMessageDialog(null, message, titleText, 608 JOptionPane.INFORMATION_MESSAGE); 609 } 610 } 611 612 } 613 614 public void checkForNotice(boolean notifyDialog) { 615 616 /** Shortcut this whole process if we are processing offscreen */ 617 if (super.getIdv().getArgsManager().getIsOffScreen()) 618 return; 619 620 String thisNotice = getNoticeCached().trim(); 621 String thatNotice = getNoticeLatest().trim(); 622 String titleText = "New Notice"; 623 String labelText = thatNotice; 624 625 if (thatNotice.equals("")) { 626 setNoticeCached(thatNotice); 627 if (notifyDialog) { 628 titleText = "No Notice"; 629 JLabel message = new JLabel("There is no current notice", JLabel.CENTER); 630 JOptionPane.showMessageDialog(null, message, titleText, 631 JOptionPane.INFORMATION_MESSAGE); 632 } 633 return; 634 } 635 else if (!thisNotice.equals(thatNotice)) { 636 setNoticeCached(thatNotice); 637 638 JPanel backgroundColorGetterPanel = new JPanel(); 639 JEditorPane messageText = new JEditorPane("text/html", labelText); 640 messageText.setBackground(backgroundColorGetterPanel.getBackground()); 641 messageText.setEditable(false); 642 messageText.addHyperlinkListener(this); 643 644// JLabel message = new JLabel(labelText, JLabel.CENTER); 645 JOptionPane.showMessageDialog(null, messageText, titleText, 646 JOptionPane.INFORMATION_MESSAGE); 647 } 648 else { 649 if (notifyDialog) { 650 titleText = "Previous Notice"; 651 JLabel message = new JLabel(labelText, JLabel.CENTER); 652 JOptionPane.showMessageDialog(null, message, titleText, 653 JOptionPane.INFORMATION_MESSAGE); 654 } 655 } 656 657 } 658 659 private String getNoticePath() { 660 return StartupManager.getInstance().getPlatform().getUserFile("notice.txt"); 661 } 662 663 private String getNoticeCached() { 664 String notice = ""; 665 try{ 666 FileReader fstream = new FileReader(getNoticePath()); 667 BufferedReader in = new BufferedReader(fstream); 668 String line; 669 while ((line = in.readLine()) != null) { 670 notice += line + '\n'; 671 } 672 in.close(); 673 } catch (Exception e){ 674 System.err.println("Error: " + e.getMessage()); 675 } 676 return notice; 677 } 678 679 private void setNoticeCached(String notice) { 680 try{ 681 FileWriter fstream = new FileWriter(getNoticePath()); 682 BufferedWriter out = new BufferedWriter(fstream); 683 out.write(notice); 684 out.close(); 685 } catch (Exception e){ 686 System.err.println("Error: " + e.getMessage()); 687 } 688 } 689 690}