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