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