001    /*
002     Copyright (C) 2003 Adam Olsen
003    
004     This program is free software; you can redistribute it and/or modify
005     it under the terms of the GNU General Public License as published by
006     the Free Software Foundation; either version 1, or (at your option)
007     any later version.
008    
009     This program is distributed in the hope that it will be useful,
010     but WITHOUT ANY WARRANTY; without even the implied warranty of
011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012     GNU General Public License for more details.
013    
014     You should have received a copy of the GNU General Public License
015     along with this program; if not, write to the Free Software
016     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
017     */
018    
019    package com.valhalla.pluginmanager;
020    
021    import java.awt.BorderLayout;
022    import java.awt.Dimension;
023    import java.awt.event.ActionEvent;
024    import java.awt.event.ActionListener;
025    import java.io.BufferedReader;
026    import java.io.File;
027    import java.io.InputStreamReader;
028    import java.io.PrintWriter;
029    import java.net.Socket;
030    import java.net.URL;
031    import java.util.ArrayList;
032    import java.util.Hashtable;
033    import java.util.Locale;
034    import java.util.Properties;
035    import java.util.ResourceBundle;
036    
037    import javax.swing.*;
038    import javax.swing.SwingUtilities;
039    import javax.swing.event.ListSelectionEvent;
040    import javax.swing.event.ListSelectionListener;
041    
042    import com.valhalla.gui.DialogTracker;
043    import com.valhalla.gui.Standard;
044    
045    /**
046     * Allows the user to download, install, and upgrade plugins all from within the
047     * application that the plugins are used in
048     *
049     * @author Adam Olsen
050     * @version 1.0
051     */
052    public class PluginManager extends JFrame {
053        private ResourceBundle resources = ResourceBundle.getBundle(
054                "PluginManager", Locale.getDefault());
055    
056        private String mirror;
057    
058        private String script;
059    
060        private String installDir;
061    
062        private ArrayList pluginList = null;
063    
064        private ArrayList installedPlugins = null;
065    
066        private JLabel statusLabel = new JLabel("<html><b>"
067                + resources.getString("status") + "</b>: ");
068    
069        private JTabbedPane pane = new JTabbedPane();
070    
071        private JButton closeButton = new JButton(resources
072                .getString("closeButton"));
073    
074        private JPanel mainPanel;
075    
076        private JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
077    
078        private PluginManagerPanel listPanel = new PluginManagerPanel(this, false);
079    
080        private PluginManagerPanel managePanel = new PluginManagerPanel(this, true);
081    
082        private JButton unloadButton = new JButton(resources.getString("unload"));
083    
084        private JButton loadButton = new JButton(resources.getString("load"));
085    
086        /**
087         * Constructs the Plugin Manager
088         *
089         * @param mirror
090         *            the mirror to download the plugins from
091         * @param script
092         *            the script to get after connecting
093         * @param installDir
094         *            the location to install the plugins to
095         */
096        public PluginManager(String mirror, String script, String installDir) {
097            setTitle(resources.getString("pluginManager"));
098            this.mirror = mirror;
099            this.script = script;
100            this.installDir = installDir;
101    
102            initComponents();
103            DialogTracker.addDialog(this, false, true);
104            downloadPluginList();
105        }
106    
107        /**
108         * @return the download mirror
109         */
110        String getMirror() {
111            return mirror;
112        }
113    
114        /**
115         * @return the download script
116         */
117        String getScript() {
118            return script;
119        }
120    
121        /**
122         * @return the install dir
123         */
124        String getInstallDir() {
125            return installDir;
126        }
127    
128        /**
129         * Sets up UI components
130         */
131        private void initComponents() {
132            closeButton.addActionListener(new ActionListener() {
133                public void actionPerformed(ActionEvent e) {
134                    DialogTracker.removeDialog(PluginManager.this);
135                }
136            });
137    
138            mainPanel = (JPanel) getContentPane();
139            mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
140            mainPanel.setLayout(new BorderLayout(5, 5));
141            loadButton.setEnabled(false);
142            unloadButton.setEnabled(false);
143    
144            ListActionListener listener = new ListActionListener();
145    
146            managePanel.getButton().addActionListener(listener);
147            managePanel.getTable().getSelectionModel().addListSelectionListener(
148                    new HighlightListener());
149            unloadButton.addActionListener(listener);
150            loadButton.addActionListener(listener);
151    
152            listPanel.getButton().addActionListener(listener);
153    
154            // set up the manage panel
155            managePanel.setButtonText(resources.getString("removePlugins"));
156            JPanel bPanel = managePanel.getButtonPanel();
157            bPanel.add(Box.createRigidArea(new Dimension(5, 0)));
158            bPanel.add(unloadButton);
159            bPanel.add(loadButton);
160            bPanel.validate();
161    
162            pane.add(managePanel, resources.getString("managePlugins"));
163            pane.add(listPanel, resources.getString("installUpgradePlugins"));
164    
165            mainPanel.add(pane, BorderLayout.CENTER);
166            mainPanel.add(bottomPanel, BorderLayout.SOUTH);
167    
168            bottomPanel.add(statusLabel, BorderLayout.CENTER);
169            bottomPanel.add(closeButton, BorderLayout.EAST);
170    
171            pack();
172            setSize(new Dimension(520, 470));
173            setLocationRelativeTo(null);
174        }
175    
176        class HighlightListener implements ListSelectionListener {
177            public void valueChanged(ListSelectionEvent e) {
178                int rows[] = managePanel.getTable().getSelectedRows();
179    
180                boolean selected = (rows.length > 0);
181                managePanel.getButton().setEnabled(selected);
182                loadButton.setEnabled(selected);
183                unloadButton.setEnabled(selected);
184            }
185        }
186    
187        /**
188         * Listens for a button to be clicked in one of the list panels
189         */
190        class ListActionListener implements ActionListener {
191            public void actionPerformed(ActionEvent e) {
192                if (e.getSource() == managePanel.getButton())
193                    removeHandler();
194                else if (e.getSource() == listPanel.getButton())
195                    installHandler(pluginList);
196                else if (e.getSource() == unloadButton) {
197                    unloadPluginHandler(installedPlugins, true, managePanel);
198                    managePanel.setPlugins(installedPlugins);
199                } else if (e.getSource() == loadButton) {
200                    loadPluginHandler(installedPlugins, managePanel);
201                    managePanel.setPlugins(installedPlugins);
202                }
203            }
204        }
205    
206        private boolean checkSelectedRow(PluginManagerPanel panel, int current) {
207            if (panel == null)
208                return false;
209            JTable table = panel.getTable();
210    
211            int rows[] = table.getSelectedRows();
212            for (int i = 0; i < rows.length; i++) {
213                if (current == rows[i])
214                    return true;
215            }
216    
217            return false;
218        }
219    
220        /**
221         * loads the checked plugins
222         */
223        private void loadPluginHandler(ArrayList list, PluginManagerPanel panel) {
224            for (int i = 0; i < list.size(); i++) {
225                Properties props = (Properties) list.get(i);
226                if (props.getProperty("selected") != null
227                        || checkSelectedRow(panel, i)) {
228                    Hashtable loadedPlugins = PluginLoader.getInstance()
229                            .getLoadedPlugins();
230                    PluginJAR jar = PluginLoader.getInstance().getPlugin(
231                            props.getProperty("name"));
232    
233                    if (jar != null && !jar.getLoaded()) {
234                        jar.loadPlugin();
235                        loadedPlugins.put(props.getProperty("name"), jar);
236                        props.remove("selected");
237                    }
238                }
239            }
240        }
241    
242        /**
243         * Unloads the checked plugins
244         */
245        private void unloadPluginHandler(ArrayList list, boolean mark,
246                PluginManagerPanel panel) {
247            for (int i = 0; i < list.size(); i++) {
248                Properties props = (Properties) list.get(i);
249                if (props.getProperty("selected") != null
250                        || checkSelectedRow(panel, i)) {
251                    Hashtable loadedPlugins = PluginLoader.getInstance()
252                            .getLoadedPlugins();
253    
254                    PluginJAR jar = PluginLoader.getInstance().getPlugin(
255                            props.getProperty("name"));
256    
257                    if (jar != null) {
258                        if (jar.getLoaded())
259                            jar.unloadPlugin();
260                        loadedPlugins.remove(props.getProperty("name"));
261    
262                        jar.close();
263    
264                        jar = null;
265                        System.gc();
266                    }
267    
268                    if (mark)
269                        props.remove("selected");
270                }
271            }
272    
273        }
274    
275        /**
276         * Installs the plugins selected in the install panel
277         *
278         * @param list
279         *            the list to download
280         */
281        private void installHandler(ArrayList list) {
282            setStatusText(resources.getString("downloading"));
283            Thread thread = new Thread(new PluginDownloaderThread(this, list));
284            thread.start();
285        }
286    
287        /**
288         * Removes the plugins that are selected in the manage panel
289         */
290        private void removeHandler() {
291            int result = JOptionPane.showConfirmDialog(null, resources
292                    .getString("pluginRemoveConfirmation"), resources
293                    .getString("pluginManager"), JOptionPane.YES_NO_OPTION);
294    
295            if (result != 0)
296                return;
297    
298            int rows[] = managePanel.getTable().getSelectedRows();
299            ArrayList remove = new ArrayList();
300            for (int i = 0; i < rows.length; i++) {
301                Properties props = (Properties) installedPlugins.get(rows[i]);
302                props.setProperty("selected", "true");
303    
304                remove.add(props);
305            }
306    
307            unloadPluginHandler(remove, false, null);
308            System.gc();
309            remove = new ArrayList();
310    
311            for (int i = 0; i < rows.length; i++) {
312                Properties props = (Properties) installedPlugins.get(rows[i]);
313                Hashtable loadedPlugins = PluginLoader.getInstance()
314                        .getLoadedPlugins();
315    
316                String name = props.getProperty("fileName");
317                File file = new File(name);
318    
319                if (file.delete()) {
320                    remove.add(props);
321                } else {
322                    throwError("Could not unload plugin!", false);
323                    return;
324                }
325            }
326    
327            for (int i = 0; i < remove.size(); i++) {
328                installedPlugins.remove(remove.get(i));
329            }
330    
331            managePanel.setPlugins(installedPlugins);
332            downloadPluginList();
333        }
334    
335        /**
336         * Downloads a list of plugins
337         */
338        private void downloadPluginList() {
339            PluginLoader loader = PluginLoader.getInstance();
340            loader.findPlugins(installDir + File.separatorChar + "plugins");
341            installedPlugins = loader.getInstalledPlugins();
342    
343            managePanel.setPlugins(installedPlugins);
344    
345            managePanel.getTable().setEnabled(false);
346            listPanel.getTable().setEnabled(false);
347    
348            setStatusText(resources.getString("gettingPluginList"));
349            Thread thread = new Thread(new DownloadListThread());
350            thread.start();
351        }
352    
353        /**
354         * Called when the download thread is done downloading selected plugins
355         */
356        protected void doneDownloadingPlugins(ArrayList list) {
357            File cacheDir = new File(installDir, "downloadcache");
358            File pluginDir = new File(installDir, "plugins");
359            if (!pluginDir.isDirectory() && !pluginDir.mkdirs()) {
360                throwError("pluginDirectoryCreationError", true);
361                return;
362            }
363    
364            unloadPluginHandler(list, false, null);
365            System.gc();
366    
367            for (int i = 0; i < list.size(); i++) {
368                Properties props = (Properties) list.get(i);
369                if (props.getProperty("selected") != null) {
370                    File file = new File(cacheDir, props.getProperty("fileName"));
371                    File newFile = new File(pluginDir, props
372                            .getProperty("fileName"));
373                    if (newFile.exists() && !newFile.delete()) {
374                        com.valhalla.Logger.debug("Could not delete old plugin!");
375                    }
376    
377                    if (!file.renameTo(newFile)) {
378                        com.valhalla.Logger.debug("Could not rename to new plugin");
379                    }
380                }
381            }
382    
383            PluginLoader.getNewInstance();
384            System.gc();
385    
386            PluginLoader.getInstance().findPlugins(
387                    installDir + File.separatorChar + "plugins");
388            loadPluginHandler(list, null);
389    
390            downloadPluginList();
391        }
392    
393        /**
394         * Called when the downloader thread is done downloading the list of
395         * available plugins
396         */
397        protected void doneDownloadingList() {
398            listPanel.setPlugins(pluginList);
399            managePanel.getTable().setEnabled(true);
400            listPanel.getTable().setEnabled(true);
401    
402            setStatusText(resources.getString("doneDownloading"));
403        }
404    
405        /**
406         * Sets the text in the status label
407         *
408         * @param text
409         *            the text to set
410         */
411        private void setStatusText(String text) {
412            statusLabel.setText("<html><b>" + resources.getString("status")
413                    + "</b>: " + text);
414        }
415    
416        /**
417         * Returns true if a plugin is already installed
418         *
419         * @return <tt>true</tt> if a plugin is already installed
420         */
421        private boolean pluginInstalled(Properties props) {
422            boolean check = false;
423            if (props.getProperty("name") == null)
424                return check;
425            for (int i = 0; i < installedPlugins.size(); i++) {
426                Properties p = (Properties) installedPlugins.get(i);
427    
428                if (p.getProperty("name") != null
429                        && p.getProperty("name").equals(props.getProperty("name")))
430                    check = true;
431            }
432    
433            return check;
434        }
435    
436        /**
437         * Returns true if a plugin is upgradable
438         *
439         * @return <tt>true</tt> if a plugin is already upgradable
440         */
441        private boolean pluginUpgradable(Properties props) {
442            boolean check = false;
443            if (props.getProperty("name") == null)
444                return check;
445            for (int i = 0; i < installedPlugins.size(); i++) {
446                Properties p = (Properties) installedPlugins.get(i);
447    
448                if (p.getProperty("name") != null
449                        && p.getProperty("name").equals(props.getProperty("name"))) {
450                    try {
451                        String installedVersion = p.getProperty("version");
452                        String remoteVersion = props.getProperty("version");
453    
454                        int comp = remoteVersion.compareTo(installedVersion);
455    
456                        if (comp > 0)
457                            check = true;
458    
459                        String installedAPI = p.getProperty("APIVersion");
460                        String apiVersion = PluginLoader.getAPIVersion() + "";
461    
462                        comp = apiVersion.compareTo(installedAPI);
463                        if( comp > 0 ) check = true;
464    
465                    } catch (Exception e) {
466                    }
467                }
468            }
469    
470            return check;
471        }
472    
473        /**
474         * Collects the list of available plugins in their versions
475         */
476        class DownloadListThread implements Runnable {
477            public void run() {
478                BufferedReader in = null;
479                pluginList = new ArrayList();
480    
481                try {
482                    URL url = new URL("http://" + mirror + script +
483                        "?command=pluginList&apiVersion=" + PluginLoader.getAPIVersion());
484                    in = new BufferedReader(new InputStreamReader(url.openStream()));
485    
486                    String line = null;
487                    String names[] = null;
488                    int lineCount = 0;
489    
490                    while ((line = in.readLine()) != null) {
491                        // the first line will indicate if we've connected
492                        // successfully
493                        if (lineCount == 0) {
494                            if (!line.equals("Plugin list will follow")) {
495                                throwError("invalidResponse", true);
496                                break;
497                            }
498                        }
499    
500                        // second line gets a list of available arguments for each
501                        // plugin
502                        else if (lineCount == 1)
503                            names = line.split("\t");
504    
505                        // all lines after are plugin information lines
506                        else {
507                            String de[] = line.split("\t");
508                            //com.valhalla.Logger.debug( line );
509                            if (de.length == names.length) {
510                                Properties props = new Properties();
511                                for (int i = 0; i < names.length; i++) {
512                                    props.setProperty(names[i], de[i]);
513                                }
514    
515                                // make sure the plugin platform is compatible and
516                                // the plugin api version is the same,
517                                // otherwise
518                                // ignore this plugin
519                                if (PluginLoader.checkPlatform (props)
520                                        && props.getProperty("APIVersion").equals(
521                                                PluginLoader.getAPIVersion() + "")) {
522    
523                                    com.valhalla.Logger.debug( props.getProperty("APIVersion"));
524                                    if (!pluginInstalled(props))
525                                        pluginList.add(props);
526                                    else if (pluginUpgradable(props))
527                                        pluginList.add(props);
528                                }
529                            } else {
530                                com.valhalla.Logger.debug("Invalid plugin found.");
531                            }
532                        }
533    
534                        lineCount++;
535                    }
536    
537                } catch (Exception e) {
538                    throwError(e.getMessage(), false);
539                    e.printStackTrace();
540                }
541    
542                doneDownloadingList();
543            }
544        }
545    
546        /**
547         * Displays an error dialog
548         *
549         * @param message
550         *            the message to display
551         */
552        void throwError(String message, boolean useResources) {
553            if (useResources)
554                message = resources.getString(message);
555            final String tempMessage = message;
556    
557            SwingUtilities.invokeLater(new Runnable() {
558                public void run() {
559                    Standard.warningMessage(PluginManager.this, resources
560                            .getString("pluginManager"), tempMessage);
561                    setStatusText(tempMessage);
562                }
563            });
564        }
565    }