001    package org.LiveGraph.bootstrap;
002    
003    
004    import static org.LiveGraph.bootstrap.UpgradeManager.AutoCheckFrequency.*;
005    
006    import java.awt.BorderLayout;
007    import java.awt.Dimension;
008    import java.awt.FlowLayout;
009    import java.awt.GridBagLayout;
010    import java.awt.Toolkit;
011    import java.awt.event.ActionEvent;
012    import java.awt.event.ActionListener;
013    import java.awt.event.WindowAdapter;
014    import java.awt.event.WindowEvent;
015    import java.io.File;
016    import java.io.FileInputStream;
017    import java.io.FileOutputStream;
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.net.SocketTimeoutException;
021    import java.net.URL;
022    import java.net.URLConnection;
023    import java.text.DateFormat;
024    import java.text.SimpleDateFormat;
025    import java.util.Calendar;
026    import java.util.Properties;
027    
028    import javax.swing.Box;
029    import javax.swing.JButton;
030    import javax.swing.JComboBox;
031    import javax.swing.JDialog;
032    import javax.swing.JLabel;
033    import javax.swing.JOptionPane;
034    import javax.swing.JPanel;
035    import javax.swing.JScrollPane;
036    import javax.swing.JTextPane;
037    import javax.swing.WindowConstants;
038    import javax.swing.text.BadLocationException;
039    import javax.swing.text.Document;
040    
041    import org.LiveGraph.LiveGraph;
042    import org.LiveGraph.gui.GUIManager;
043    import org.LiveGraph.gui.Tools;
044    
045    import com.softnetConsult.utils.exceptions.Bug;
046    import com.softnetConsult.utils.exceptions.UnexpectedSwitchCase;
047    import com.softnetConsult.utils.files.FileTools;
048    import com.softnetConsult.utils.mutableWrappers.MutableInt;
049    import com.softnetConsult.utils.sys.SystemTools;
050    
051    public class UpgradeManager {
052    
053    public static final String upgradeOptionsFileName = "autoUpdate.lguds";
054    
055    public enum AutoCheckFrequency {
056    
057            DEFAULT(1),
058            DAILY(1),
059            WEEKLY(7),
060            MONTHLY(30),
061            MANUAL_ONLY(Integer.MAX_VALUE);
062            
063            private int freq = 1;
064            private AutoCheckFrequency(int freq) { this.freq = freq; }
065            public int getFrequency()                        { return freq; }
066    }
067    
068    private AutoCheckFrequency frequency = DEFAULT;
069    private long lastCheckTS = 0L;
070    private volatile boolean checkRunning = false;
071    private volatile boolean checkAbort = false;
072    
073    public void autoUpdate() {
074            
075            if (!LiveGraph.application().standalone())
076                    return;
077            
078            loadSettings();
079            
080            if (MANUAL_ONLY == frequency)
081                    return;
082            
083            Calendar now = Calendar.getInstance();
084            Calendar last = Calendar.getInstance();
085            last.setTimeInMillis(lastCheckTS);
086            
087            last.add(Calendar.DATE, frequency.getFrequency());
088            if (last.after(now))
089                    return;
090            
091            final Object[] options = new Object[] { "Check now", "Remind me later", "Auto-update options..."};
092            Object sel = JOptionPane.showOptionDialog(
093                                            null,
094                                            "Would you like to allow LiveGraph to check whether a software update is available?",
095                                            "Auto-Update",
096                                            JOptionPane.DEFAULT_OPTION,
097                                            JOptionPane.QUESTION_MESSAGE,
098                                            null,
099                                            options,
100                                            options[0]);
101            
102            if (null == sel)
103                    return;
104            
105            if (! (sel instanceof Integer)) {
106                    throw new Bug("Unecperted type: " + sel.getClass().getName());
107            } else {
108                    int opt = ((Integer) sel).intValue();
109                    switch(opt) {
110                            case 0:
111                                    checkForUpdates(true);
112                                    break;
113                                    
114                            case 1:
115                                    break;
116                                    
117                            case 2:
118                                    upgradeOptionsDialog();
119                                    break;
120                            
121                            default:
122                                    throw new UnexpectedSwitchCase(opt);
123                    }
124            }
125    }
126    
127    private String formatUpdateCheckURL(boolean automatic) {
128            final String template = "http://live-graph.org/update-check/?version=%s&requestType=%s";
129            return String.format(template,
130                                                     LiveGraph.version,
131                                                     automatic ? frequency.name() : "UserInitiated");
132    }
133    
134    private void checkForUpdates(final boolean automatic) {
135            
136            Runnable checker = new Runnable() {
137                    public void run() {
138                            doCheckForUpdates(automatic);
139                    }
140            };
141            
142            Thread worker = new Thread(checker, "LiveGRaph Software Update Checker");
143            worker.start();
144    }
145    
146    private synchronized void doCheckForUpdates(boolean automatic) {
147            
148            // Create dialog window:
149            final JDialog dialog = new JDialog((JDialog) null, "LiveGraph auto update", false);
150            dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
151            dialog.getContentPane().setLayout(new BorderLayout());
152            
153            // Create close button:
154            JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
155            final JButton closeButton = new JButton("Close");
156            closeButton.addActionListener(new ActionListener() {
157                    public void actionPerformed(ActionEvent e) {
158                            closeButton.setText("Closing...");
159                            closeButton.setEnabled(false);
160                            if (checkRunning)       checkAbort = true;
161                            else                            dialog.dispose();
162            }
163            });
164            panel.add(closeButton, BorderLayout.CENTER);
165            dialog.getContentPane().add(panel, BorderLayout.SOUTH);
166    
167            // Create text pane:
168            JTextPane textPane = new JTextPane();
169            textPane.setEditable(false);
170            Document doc = textPane.getDocument();
171            
172            JScrollPane scrollPane = new JScrollPane();
173            scrollPane.getViewport().add(textPane);
174            dialog.getContentPane().add(scrollPane);
175            dialog.pack();
176            
177            // Set dialog size and display:
178            final int WIDTH = 620;
179            final int HEIGHT = 600;
180            Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
181            dialog.setBounds((scr.width - WIDTH) / 2, (scr.height - HEIGHT) / 2, WIDTH, HEIGHT);
182            dialog.addWindowListener(new WindowAdapter() {
183                    @Override public void windowClosing(WindowEvent e) {
184                            closeButton.setText("Closing...");
185                            closeButton.setEnabled(false);
186                            if (checkRunning)       checkAbort = true;
187                            else                            dialog.dispose();
188                    }
189            });
190            checkRunning = true;
191            dialog.setVisible(true);
192            SystemTools.sleep(100);
193            
194            // Check for update availability:
195            final String urlStr = formatUpdateCheckURL(automatic);
196            try {
197                    URL url = new URL(urlStr);
198                    URLConnection conn = url.openConnection();
199                    conn.setConnectTimeout(1000);
200                    conn.setReadTimeout(10000);
201                    InputStream ins = null;
202                    try {
203                            doc.insertString(doc.getLength(), "\nAttempting to connect to server... ", null);
204                            dialog.repaint();
205                            ins = conn.getInputStream();
206                            doc.insertString(doc.getLength(), "Connected. Loading info...", null);
207                    } catch(SocketTimeoutException e) {
208                            doc.insertString(doc.getLength(), "Could not connect. Will retry shortly.", null);
209                            ins = null;
210                    }
211                    
212                    for (int attempt = 1; !checkAbort && null == ins && attempt <= 3; attempt++) {
213                            SystemTools.sleep(5000);
214                            if (checkAbort)
215                                    break;
216                            url = new URL(urlStr);
217                            conn = url.openConnection();
218                            conn.setConnectTimeout(10000);
219                            conn.setReadTimeout(10000);
220                            doc.insertString(doc.getLength(), "\nReconnection attempt " + attempt + " of 3."
221                                                                                            + " Attempting to connect to server... ", null);
222                            try {
223                                    conn.connect();
224                                    ins = conn.getInputStream();
225                                    doc.insertString(doc.getLength(), "Connected. Loading info...", null);
226                            } catch(SocketTimeoutException e) {
227                                    ins = null;
228                                    doc.insertString(doc.getLength(), "Could not connect. Will retry shortly.", null);
229                            }
230                    }
231                    
232                    if (null == ins) {
233                            doc.insertString(doc.getLength(), "\n\nCould not connect to server.", null);
234                            doc.insertString(doc.getLength(), "\nVisit http://www.live-graph.org/downloads.html to"
235                                                                                            + " check whether you have the latest version.", null);
236                    } else if (!checkAbort) {
237                            try {
238                                    try {
239                                            String oldBtnTxt = closeButton.getText();
240                                            boolean oldBtnEnbl = closeButton.isEnabled();
241                                            closeButton.setEnabled(false);
242                                            closeButton.setText("READING...");
243                                            
244                                            textPane.setContentType("text/html");
245                                            textPane.read(ins, textPane.getEditorKit().createDefaultDocument());
246                                            
247                                            closeButton.setText(oldBtnTxt);
248                                            closeButton.setEnabled(oldBtnEnbl);
249                                            
250                                            lastCheckTS = System.currentTimeMillis();
251                                            saveSettings();
252                                    } finally {
253                                            ins.close();
254                                    }
255                            } catch(SocketTimeoutException e) {
256                                    doc = textPane.getDocument();
257                                    doc.insertString(doc.getLength(), "\n\nCould not read from the server.", null);
258                                    doc.insertString(doc.getLength(), "\nVisit http://www.live-graph.org/downloads.html to"
259                                                                                                    + " check whether you have the latest version.", null);
260                            }
261                    }
262            } catch (IOException ioe) {
263                    dialog.dispose();
264                    // Exceptions other than handled above can be treated by the default exception handler:
265                    throw new RuntimeException(ioe);
266            } catch (BadLocationException ble) {
267                    dialog.dispose();
268                    throw new Bug("This should never happen!", ble);
269            }
270            
271            checkRunning = false;
272            if (checkAbort)
273                    dialog.dispose();
274    }
275    
276    public synchronized void upgradeOptionsDialog() {
277            
278            if (!LiveGraph.application().standalone()) {
279                    JOptionPane.showMessageDialog(
280                                    null,
281                                    "Automatic update settings are not available since LiveGraph is not running is stand-alone mode.",
282                                    "LiveGraph is not running in stand-alone mode",
283                                    JOptionPane.INFORMATION_MESSAGE);
284                    return;
285            }
286            
287            loadSettings();
288            
289            // Create dialog window:
290            final JDialog dialog = new JDialog((JDialog) null, "LiveGraph auto update settings", true);
291            dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
292            dialog.getContentPane().setLayout(new GridBagLayout());
293            dialog.addWindowListener(new WindowAdapter() {
294                    @Override public void windowClosing(WindowEvent e) {
295                            dialog.dispose();
296                    }
297            });
298            
299            // Last update info:
300            dialog.getContentPane().add(new JLabel("Last update check:"), Tools.createGridBagConstraints(0, 0, 1, 1));
301            String lastCheckStr = "never";
302            if (0L < lastCheckTS) {
303                    DateFormat format = new SimpleDateFormat("dd/MM/yyyy, HH:mm:ss");
304                    Calendar cal = Calendar.getInstance();
305                    cal.setTimeInMillis(lastCheckTS);
306                    lastCheckStr = format.format(cal.getTime());
307            }
308            dialog.getContentPane().add(new JLabel(lastCheckStr), Tools.createGridBagConstraints(1, 0, 1, 1));
309            
310            // Update chooser:
311            dialog.getContentPane().add(new JLabel("Auto-update frequency:"), Tools.createGridBagConstraints(0, 1, 1, 1));
312            final JComboBox freqCombo = new JComboBox();
313            int i = 0, selected = -1;
314            for (AutoCheckFrequency f : AutoCheckFrequency.values()) {
315                    if (DEFAULT == f)
316                            continue;
317                    freqCombo.addItem(f.toString());
318                    if (DEFAULT == frequency && DAILY == f)
319                            selected = i;
320                    if (DEFAULT != frequency && f == frequency)
321                            selected = i;
322                    i++;
323            }
324            freqCombo.setSelectedIndex(selected);
325            dialog.getContentPane().add(freqCombo, Tools.createGridBagConstraints(1, 1, 1, 1));
326            
327            // Spacer:
328            dialog.getContentPane().add(Box.createVerticalStrut(10), Tools.createGridBagConstraints(0, 2, 2, 1));
329            
330            // Buttons:
331            JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
332            dialog.getContentPane().add(panel, Tools.createGridBagConstraints(0, 3, 2, 1));
333            
334            JButton button = new JButton("Save");
335            button.addActionListener(new ActionListener() {
336                    public void actionPerformed(ActionEvent e) {
337                            String sel = (null == freqCombo.getSelectedItem() ? "" : freqCombo.getSelectedItem().toString()); 
338                            for (AutoCheckFrequency f : AutoCheckFrequency.values()) {
339                                    if (f.toString().equals(sel)) {
340                                            frequency = f;
341                                            break;
342                                    }
343                            }
344                            saveSettings();
345                            dialog.dispose();
346            }
347            });
348            panel.add(button);
349            
350            button = new JButton("Cancel");
351            button.addActionListener(new ActionListener() {
352                    public void actionPerformed(ActionEvent e) {
353                            dialog.dispose();
354            }
355            });
356            panel.add(button);
357            
358            button = new JButton("Check now");
359            final MutableInt needToCheckNow = new MutableInt(0);
360            button.addActionListener(new ActionListener() {
361                    public void actionPerformed(ActionEvent e) {
362                            dialog.dispose();
363                            needToCheckNow.value = 1;
364            }
365            });
366            panel.add(button);
367            
368            // Set dialog size and display:
369            dialog.pack();
370            Dimension ds = dialog.getSize();
371            ds.width = ds.width + 10;
372            ds.height = ds.height + 10; 
373            Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
374            dialog.setBounds((scr.width - ds.width) / 2, (scr.height - ds.height) / 2, ds.width, ds.height);
375            dialog.setVisible(true);
376            
377            if (1 == needToCheckNow.value)
378                    checkForUpdates(false);
379    }
380    
381    private void loadSettings() {
382            
383            GUIManager gui = LiveGraph.application().guiManager();
384            
385            if (!LiveGraph.application().standalone()) {
386                    gui.logInfoLn("Will not try loading auto-update options as LiveGraph is not"
387                                            + " running in stand-alone mode. Will use default settings.");
388                    return;
389            }
390            
391            String fn = "";
392            try {
393                    fn = FileTools.concatDirFile(System.getProperty("user.dir"), upgradeOptionsFileName);
394            } catch(SecurityException e) {
395                    try {
396                            fn = FileTools.concatDirFile(System.getProperty("user.home"), upgradeOptionsFileName);
397                    } catch(SecurityException ew) {
398                            // If we have so few permissions, we should not try to read the update settings:
399                            return;
400                    }
401            }
402            
403            File file = new File(fn);
404            
405            if (!file.exists() || !file.isFile()) {
406                    gui.logInfoLn("Auto-update options file not found. Will use default settings. (" + fn + ")");
407                    frequency = DEFAULT;
408                    lastCheckTS = 0L;
409                    return;
410            }
411            
412            Properties props = new Properties();
413            try {
414                    FileInputStream ins = new FileInputStream(fn);
415                    try {
416                            props.loadFromXML(ins);
417                    } finally {
418                            ins.close();
419                    }
420            } catch(IOException e) {
421                    gui.logErrorLn("Error while loading auto-update options from \"" + fn + "\": " + e.getMessage());
422                    frequency = DEFAULT;
423                    lastCheckTS = 0L;
424                    return;
425            }
426    
427            lastCheckTS = Long.parseLong(props.getProperty("lastCheckTS", "0"));
428            frequency = DEFAULT;
429            for (AutoCheckFrequency v : AutoCheckFrequency.values()) {
430                    if (v.name().equals(props.getProperty("frequency", "DEFAULT"))) {
431                            frequency = v;
432                            break;
433                    }
434            }
435            
436            gui.logSuccessLn("Auto-update options loaded from \"" + fn + "\".");
437    }
438    
439    private void saveSettings() {
440            
441            GUIManager gui = LiveGraph.application().guiManager();
442            
443            if (!LiveGraph.application().standalone()) {
444                    gui.logInfoLn("Will not try saving auto-update options as LiveGraph is not"
445                                            + " running in stand-alone mode.");
446                    return;
447            }
448            
449            String fn = "";
450            try {
451                    fn = FileTools.concatDirFile(System.getProperty("user.dir"), upgradeOptionsFileName);
452            } catch(SecurityException e) {
453                    try {
454                            fn = FileTools.concatDirFile(System.getProperty("user.home"), upgradeOptionsFileName);
455                    } catch(SecurityException ew) {
456                            // If we have so few permissions, we should not try to write the update settings:
457                            return;
458                    }
459            }
460            
461            Properties props = new Properties();
462            props.setProperty("lastCheckTS", Long.toString(lastCheckTS));
463            props.setProperty("frequency", frequency.name());
464            
465            try {
466                    FileOutputStream outs = new FileOutputStream(fn);
467                    try {
468                            props.storeToXML(outs,
469                                                             "LiveGraph version " + LiveGraph.version + ". Automatic software update settings.");
470                    } finally {
471                            outs.close();
472                    }
473                    gui.logSuccessLn("Auto-update options saved to \"" + fn + "\".");
474            } catch(IOException e) {
475                    gui.logErrorLn("Error while saving auto-update options to \"" + fn + "\": " + e.getMessage());
476                    return;
477            }
478    }
479    
480    } // public class UpgradeManager