// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.updater; import java.awt.Dialog; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.IOException; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Proxy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.WindowConstants; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; /** * Provides a dialog for configuring update-relevant data. */ public class UpdaterSettings extends JDialog { private final JComboBox<String> cbUpdateInterval = new JComboBox<>(new String[]{ "Once per session", "Daily", "Once per week", "Once per month"}); private final JTextField tfProxyAddress = new JTextField(12); private final JTextField tfProxyPort = new JTextField(6); private final JCheckBox cbStableOnly = new JCheckBox("Consider stable releases only"); private final JCheckBox cbAutoUpdate = new JCheckBox("Automatically check for updates"); private final JCheckBox cbProxyEnabled = new JCheckBox("Enable Proxy"); private final JButton bOK = new JButton("OK"); private final JButton bCancel = new JButton("Cancel"); private final Listeners listeners = new Listeners(); private final Server server = new Server(Updater.getMaxServerCount()); private boolean retVal = false; public static boolean showDialog(Window owner) { UpdaterSettings dlg = new UpdaterSettings(owner); try { dlg.setVisible(true); return dlg.retVal; } finally { dlg = null; } } private UpdaterSettings(Window owner) { super(owner, "Update settings", Dialog.ModalityType.APPLICATION_MODAL); init(); loadSettings(); } private Server getServer() { return server; } private Listeners getListeners() { return listeners; } private void init() { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setResizable(true); setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); // ESC closes dialog getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE"); getRootPane().getActionMap().put("ESCAPE", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { cancel(); } }); JLabel lUpdateInterval = new JLabel("Update interval:"); JLabel lProxyAddress = new JLabel("Address:"); JLabel lProxyPort = new JLabel("Port:"); cbAutoUpdate.addActionListener(getListeners()); cbStableOnly.setToolTipText("Stable versions are released much less often and don't include " + "the latest features and bugfixes."); cbProxyEnabled.addActionListener(getListeners()); bOK.setPreferredSize(bCancel.getPreferredSize()); bOK.addActionListener(getListeners()); bCancel.addActionListener(getListeners()); // configuring update server panel JPanel pServer = new JPanel(new GridBagLayout()); pServer.setBorder(BorderFactory.createTitledBorder("Update servers")); for (int i = 0; i < server.getServerCount(); i++) { JLabel label = new JLabel(String.format("Server %1$d", i+1)); gbc = ViewerUtil.setGBC(gbc, 0, i, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pServer.add(label, gbc); gbc = ViewerUtil.setGBC(gbc, 1, i, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); pServer.add(server.getTextField(i), gbc); gbc = ViewerUtil.setGBC(gbc, 2, i, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); pServer.add(server.getCheckButton(i), gbc); } gbc = ViewerUtil.setGBC(gbc, 0, Updater.getMaxServerCount(), 3, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 4, 8), 0, 0); pServer.add(cbStableOnly, gbc); // configuring proxy server panel JPanel pProxy = new JPanel(new GridBagLayout()); pProxy.setBorder(BorderFactory.createTitledBorder("Proxy server")); gbc = ViewerUtil.setGBC(gbc, 0, 0, 4, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 8), 0, 0); pProxy.add(cbProxyEnabled, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 0), 0, 0); pProxy.add(lProxyAddress, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 0), 0, 0); pProxy.add(tfProxyAddress, gbc); gbc = ViewerUtil.setGBC(gbc, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 8, 0), 0, 0); pProxy.add(lProxyPort, gbc); gbc = ViewerUtil.setGBC(gbc, 3, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 8), 0, 0); pProxy.add(tfProxyPort, gbc); // configuring auto update panel JPanel pUpdate = new JPanel(new GridBagLayout()); pUpdate.setBorder(BorderFactory.createTitledBorder("Auto Update")); gbc = ViewerUtil.setGBC(gbc, 0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 8), 0, 0); pUpdate.add(cbAutoUpdate, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 8, 8, 0), 0, 0); pUpdate.add(lUpdateInterval, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 8, 8, 0), 0, 0); pUpdate.add(cbUpdateInterval, gbc); gbc = ViewerUtil.setGBC(gbc, 2, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(12, 0, 8, 0), 0, 0); pUpdate.add(new JPanel(), gbc); // configuring dialog buttons panel JPanel pButtons = new JPanel(new GridBagLayout()); gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(new JPanel(), gbc); gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(bOK, gbc); gbc = ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pButtons.add(bCancel, gbc); gbc = ViewerUtil.setGBC(gbc, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(new JPanel(), gbc); // putting all together gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(pServer, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(pProxy, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(pUpdate, gbc); gbc = ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 8), 0, 0); add(pButtons, gbc); pack(); setMinimumSize(getPreferredSize()); setSize(new Dimension(getPreferredSize().width * 3 / 2, getPreferredSize().height)); setLocationRelativeTo(getOwner()); } // Loads Updater settings into dialog fields private void loadSettings() { // getting server list List<String> serverList = Updater.getInstance().getServerList(); for (int i = 0; i < Updater.getMaxServerCount(); i++) { String url = (i < serverList.size()) ? serverList.get(i) : ""; server.getTextField(i).setText(url); server.getTextField(i).setCaretPosition(0); server.setServerValidated(i, true); } cbStableOnly.setSelected(Updater.getInstance().isStableOnly()); // getting auto update settings cbAutoUpdate.setSelected(Updater.getInstance().isAutoUpdateCheckEnabled()); cbUpdateInterval.setSelectedIndex(Updater.getInstance().getAutoUpdateCheckInterval()); cbUpdateInterval.setEnabled(cbAutoUpdate.isSelected()); // getting proxy settings Proxy proxy = Updater.getInstance().getProxy(true); if (proxy != null) { InetSocketAddress addr = (InetSocketAddress)proxy.address(); cbProxyEnabled.setSelected(Updater.getInstance().isProxyEnabled()); tfProxyAddress.setText(addr.getHostName()); tfProxyPort.setText(Integer.toString(addr.getPort())); } else { cbProxyEnabled.setSelected(false); tfProxyAddress.setText(""); tfProxyPort.setText(""); } tfProxyAddress.setEnabled(cbProxyEnabled.isSelected()); tfProxyPort.setEnabled(cbProxyEnabled.isSelected()); } // Applies dialog settings to Updater private void saveSettings() { // saving server list List<String> serverList = Updater.getInstance().getServerList(); serverList.clear(); for (int i = 0; i < Updater.getMaxServerCount(); i++) { String url = server.getServerUrl(i); if (Utils.isUrlValid(url)) { // skip duplicate server URLs boolean isSame = false; for (Iterator<String> iter = serverList.iterator(); iter.hasNext();) { if (Updater.isSameServer(url, iter.next())) { isSame = true; break; } } if (!isSame) { serverList.add(url); } } } if (cbStableOnly.isSelected() != Updater.getInstance().isStableOnly()) { // reset cached release info if release type changed Updater.getInstance().setStableOnly(cbStableOnly.isSelected()); Updater.getInstance().setCurrentHash(null); Updater.getInstance().setCurrentTimeStamp(null); } // saving auto update settings Updater.getInstance().setAutoUpdateCheckEnabled(cbAutoUpdate.isSelected()); Updater.getInstance().setAutoUpdateCheckInterval(cbUpdateInterval.getSelectedIndex()); // saving proxy settings String addr = tfProxyAddress.getText(); int port = Utils.toNumber(tfProxyPort.getText(), -1); Updater.getInstance().setProxyEnabled(cbProxyEnabled.isSelected()); Updater.getInstance().setProxy(addr, port); } // Action when clicking OK button private void accept() { if (validateSettings()) { saveSettings(); retVal = true; setVisible(false); } } // Action when closing dialog without accepting settings private void cancel() { retVal = false; setVisible(false); } // Throws an exception with message if input data contains errors. private boolean validateSettings() { // checking update servers for (int i = 0; i < Updater.getMaxServerCount(); i++) { if (!server.isValidated(i)) { if (!Utils.isSecureUrl(server.getServerUrl(i))) { String msg = String.format("Server %1$d does not specify a secure connection (https).\n", i+1) + "Do you still want to use it?"; if (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(this, msg, "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) { server.getTextField(i).requestFocusInWindow(); return false; } } if (!validateServer(i)) { server.getTextField(i).requestFocusInWindow(); return false; } } } // adding predefined default servers if no valid servers are available int numValidServers = 0; for (int i = 0; i < Updater.getMaxServerCount(); i++) { String s = server.getServerUrl(i).trim(); if (!s.isEmpty()) { numValidServers++; } } if (numValidServers == 0) { String[] servers = Updater.getDefaultServerList(); for (int i = 0; i < servers.length; i++) { server.setServerUrl(i, servers[i]); } } // checking proxy settings if (!tfProxyAddress.getText().trim().isEmpty()) { int port = Utils.toNumber(tfProxyPort.getText().trim(), -1); if (port >= 0 && port < 65536) { tfProxyPort.setText(Integer.toString(port)); } else { JOptionPane.showMessageDialog(this, "Invalid proxy server port.", "Error", JOptionPane.ERROR_MESSAGE); tfProxyPort.requestFocusInWindow(); return false; } String s = tfProxyAddress.getText().trim(); // protocol prefixes are not needed final String[] prefix = {"http://", "https://"}; for (int i = 0; i < prefix.length; i++) { if (s.startsWith(prefix[i])) { s = s.substring(prefix[i].length()); break; } } if (!s.isEmpty()) { String url = null; try { InetSocketAddress addr = new InetSocketAddress(s, port); url = addr.getHostName(); } catch (Exception e) { } if (url != null && !url.isEmpty()) { tfProxyAddress.setText(url); } else { JOptionPane.showMessageDialog(this, "Invalid proxy server address.", "Error", JOptionPane.ERROR_MESSAGE); tfProxyAddress.requestFocusInWindow(); return false; } } } return true; } // Returns true if the specified server is valid or does not exist, false otherwise. private boolean validateServer(int index) { try { WindowBlocker.blockWindow(this, true); if (index >= 0 && index < Updater.getMaxServerCount()) { String msg = null; try { if (!server.isValidated(index)) { String link = server.getServerUrl(index).trim(); String newLink = Updater.getInstance().getValidatedUpdateUrl(link); if (newLink != null && !newLink.isEmpty()) { server.setServerUrl(index, newLink); return true; } else { return false; } } } catch (MalformedURLException e) { msg = "Server URL is incorrect"; } catch (IOException e) { msg = "Server URL does not point to a valid update server"; } catch (Throwable t) { msg = "Unknown error"; } if (msg != null) { JOptionPane.showMessageDialog(this, String.format("Server %1$d: %2$s.", index+1, msg), "Error", JOptionPane.ERROR_MESSAGE); } return false; } } finally { WindowBlocker.blockWindow(this, false); } return true; } //-------------------------- INNER CLASSES -------------------------- // Manages all listener objects private class Listeners implements ActionListener, DocumentListener { public Listeners() {} @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == cbAutoUpdate) { cbUpdateInterval.setEnabled(cbAutoUpdate.isSelected()); } else if (e.getSource() == cbProxyEnabled) { boolean enabled = cbProxyEnabled.isSelected(); tfProxyAddress.setEnabled(enabled); tfProxyPort.setEnabled(enabled); } else if (e.getSource() == bOK) { accept(); } else if (e.getSource() == bCancel) { cancel(); } else if (e.getSource() instanceof JButton) { int index = getServer().indexOf((JButton)e.getSource()); if (index >= 0) { String url = getServer().getServerUrl(index); if (!Utils.isUrlValid(url)) { JOptionPane.showMessageDialog(UpdaterSettings.this, "The server URL is invalid.", "Error", JOptionPane.ERROR_MESSAGE); getServer().getTextField(index).requestFocusInWindow(); return; } if (!Utils.isSecureUrl(url)) { String msg = "The server address does not specify a secure connection (https).\n" + "Do you still want to use it?"; if (JOptionPane.showConfirmDialog(UpdaterSettings.this, msg, "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) { return; } } getServer().setServerValidated(index, validateServer(index)); } } } @Override public void insertUpdate(DocumentEvent e) { getServer().setServerValidated(getServer().indexOf(e.getDocument()), false); } @Override public void removeUpdate(DocumentEvent e) { getServer().setServerValidated(getServer().indexOf(e.getDocument()), false); } @Override public void changedUpdate(DocumentEvent e) { getServer().setServerValidated(getServer().indexOf(e.getDocument()), false); } } // Manages server URL entries private class Server { private final List<JTextField> listServer = new ArrayList<JTextField>(); private final List<JButton> listCheck = new ArrayList<JButton>(); private final List<Boolean> listValidated = new ArrayList<Boolean>(); private final int numServers; public Server(int count) { numServers = Math.max(0, count); for (int i = 0; i < numServers; i++) { JTextField tf = new JTextField(); tf.getDocument().addDocumentListener(getListeners()); listServer.add(tf); JButton b = new JButton("Check"); b.addActionListener(getListeners()); listCheck.add(b); listValidated.add(Boolean.valueOf(false)); } } // Returns number of available servers public int getServerCount() { return numServers; } // Returns the URL of the specified server public String getServerUrl(int index) { if (index >= 0 && index < getServerCount()) { return listServer.get(index).getText(); } return ""; } // Replaces the URL of the specified server public void setServerUrl(int index, String url) { if (index >= 0 && index < getServerCount()) { if (url == null) url = ""; listServer.get(index).setText(url); } } // Returns the text field of the specified server public JTextField getTextField(int index) { if (index >= 0 && index < getServerCount()) { return listServer.get(index); } return null; } // Returns the Check button of the specified server public JButton getCheckButton(int index) { if (index >= 0 && index < getServerCount()) { return listCheck.get(index); } return null; } // Returns whether the server URL has already been validated public boolean isValidated(int index) { if (index >= 0 && index < getServerCount()) { return listValidated.get(index).booleanValue(); } return false; } // Sets whether the server URL has been validated public void setValidated(int index, boolean set) { if (index >= 0 && index < getServerCount()) { listValidated.set(index, Boolean.valueOf(set)); } } // Marks specified server as valid or invalid and sets state of associated text field and button. public void setServerValidated(int index, boolean set) { if (index >= 0 && index < getServerCount()) { Document doc = getTextField(index).getDocument(); setValidated(index, set || doc.getLength() == 0); getCheckButton(index).setEnabled(!set && doc.getLength() > 0); } } // Returns the server index of the specified text field document public int indexOf(Document doc) { if (doc != null) { for (int i = 0, size = listServer.size(); i < size; i++) { if (listServer.get(i).getDocument() == doc) { return i; } } } return -1; } // Returns the server index of the specified Check button public int indexOf(JButton b) { return listCheck.indexOf(b); } // // Returns the server index of the specified text field // public int indexOf(JTextField tf) // { // return listServer.indexOf(tf); // } } }