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}