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