/* * Created on 28-Nov-2004 * Created by Paul Gardner * Copyright (C) 2004, 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.pluginsimpl.local.installer; /** * @author parg * */ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.util.*; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.AESemaphore; import org.gudy.azureus2.core3.util.AETemporaryFileHandler; import org.gudy.azureus2.core3.util.AEThread2; import org.gudy.azureus2.core3.util.AsyncDispatcher; import org.gudy.azureus2.core3.util.Constants; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.FileUtil; import org.gudy.azureus2.plugins.*; import org.gudy.azureus2.plugins.installer.*; import org.gudy.azureus2.plugins.ui.UIManager; import org.gudy.azureus2.plugins.ui.UIManagerEvent; import org.gudy.azureus2.plugins.update.*; import org.gudy.azureus2.plugins.utils.StaticUtilities; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloader; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderAdapter; import org.gudy.azureus2.plugins.utils.resourcedownloader.ResourceDownloaderException; import org.gudy.azureus2.pluginsimpl.local.FailedPlugin; import org.gudy.azureus2.pluginsimpl.local.PluginInitializer; import org.gudy.azureus2.pluginsimpl.local.update.UpdateCheckInstanceImpl; import org.gudy.azureus2.pluginsimpl.local.update.UpdateManagerImpl; import org.gudy.azureus2.pluginsimpl.update.sf.*; import org.gudy.azureus2.pluginsimpl.update.PluginUpdatePlugin; import com.aelitis.azureus.core.util.CopyOnWriteList; import com.aelitis.azureus.core.vuzefile.VuzeFile; import com.aelitis.azureus.core.vuzefile.VuzeFileComponent; import com.aelitis.azureus.core.vuzefile.VuzeFileHandler; import com.aelitis.azureus.core.vuzefile.VuzeFileProcessor; public class PluginInstallerImpl implements PluginInstaller { protected static PluginInstallerImpl singleton; public static synchronized PluginInstallerImpl getSingleton( PluginManager _manager ) { if ( singleton == null ){ singleton = new PluginInstallerImpl( _manager ); } return( singleton ); } private PluginManager manager; private CopyOnWriteList<PluginInstallerListener> listeners = new CopyOnWriteList<PluginInstallerListener>(); private AsyncDispatcher add_file_install_dispatcher; protected PluginInstallerImpl( PluginManager _manager ) { manager = _manager; VuzeFileHandler.getSingleton().addProcessor( new VuzeFileProcessor() { public void process( VuzeFile[] files, int expected_types ) { for (int i=0;i<files.length;i++){ VuzeFile vf = files[i]; VuzeFileComponent[] comps = vf.getComponents(); for (int j=0;j<comps.length;j++){ VuzeFileComponent comp = comps[j]; if ( comp.getType() == VuzeFileComponent.COMP_TYPE_PLUGIN ){ try{ Map content = comp.getContent(); String id = new String((byte[])content.get( "id" ), "UTF-8" ); String version = new String((byte[])content.get( "version" ), "UTF-8" ); String suffix = ((Long)content.get( "is_jar" )).longValue()==1?"jar":"zip"; byte[] plugin_file = (byte[])content.get( "file" ); File temp_dir = AETemporaryFileHandler.createTempDir(); File temp_file = new File( temp_dir, id + "_" + version + "." + suffix ); FileUtil.copyFile( new ByteArrayInputStream( plugin_file ), temp_file ); FilePluginInstaller installer = installFromFile( temp_file ); addFileInstallOperation( installer ); comp.setProcessed(); }catch( Throwable e ){ Debug.printStackTrace(e); } } } } } }); } protected void addFileInstallOperation( final FilePluginInstaller installer ) { synchronized( this ){ if ( add_file_install_dispatcher == null ){ add_file_install_dispatcher = new AsyncDispatcher(); } add_file_install_dispatcher.dispatch( new AERunnable() { public void runSupport() { try{ final AESemaphore done_sem = new AESemaphore( "PluginInstall:fio" ); final UIManager ui_manager = StaticUtilities.getUIManager( 120*1000 ); new AEThread2( "PluginInstall:fio", true ) { public void run() { if ( installer.isAlreadyInstalled()){ String details = MessageText.getString( "fileplugininstall.duplicate.desc", new String[]{ installer.getName(), installer.getVersion()}); ui_manager.showMessageBox( "fileplugininstall.duplicate.title", "!" + details + "!", UIManagerEvent.MT_OK ); done_sem.release(); }else{ String details = MessageText.getString( "fileplugininstall.install.desc", new String[]{ installer.getName(), installer.getVersion()}); long res = ui_manager.showMessageBox( "fileplugininstall.install.title", "!" + details + "!", UIManagerEvent.MT_YES | UIManagerEvent.MT_NO ); if ( res == UIManagerEvent.MT_YES ){ try{ install( new InstallablePlugin[]{ installer }, false, true, null, new PluginInstallationListener() { public void completed() { done_sem.release(); } public void cancelled() { done_sem.release(); } public void failed( PluginException e ) { done_sem.release(); Debug.out( "Installation failed", e ); } }); }catch( Throwable e ){ Debug.printStackTrace(e); done_sem.release(); } }else if ( res == UIManagerEvent.MT_NO ){ done_sem.release(); }else{ Debug.out( "Message box not handled" ); done_sem.release(); } } } }.start(); while( !done_sem.reserve( 60*1000 )){ if ( add_file_install_dispatcher.getQueueSize() > 0 ){ Debug.out( "File plugin install operation queued pending completion of previous" ); } } }catch( Throwable e ){ Debug.printStackTrace(e); } } }); } } protected PluginManager getPluginManager() { return( manager ); } public StandardPlugin[] getStandardPlugins() throws PluginException { try{ SFPluginDetailsLoader loader = SFPluginDetailsLoaderFactory.getSingleton(); SFPluginDetails[] details = loader.getPluginDetails(); List res = new ArrayList(); for (int i=0;i<details.length;i++){ SFPluginDetails detail = details[i]; String name = detail.getId(); String version = ""; if ( Constants.isCVSVersion()){ version = detail.getCVSVersion(); } if ( version == null || version.length() == 0 || !Character.isDigit(version.charAt(0))){ version = detail.getVersion(); }else{ // if cvs version and non-cvs version are the same then show the // non-cvs version String non_cvs_version = detail.getVersion(); if ( version.equals( non_cvs_version + "_CVS" )){ version = non_cvs_version; } } if ( name.startsWith( "azplatform" ) || name.equals( "azupdater" )){ // skip built in ones we don't want to let user install directly // not the cleanest of fixes, but it'll do for the moment }else if ( version == null || version.length() == 0 || !Character.isDigit(version.charAt(0))){ // dodgy version }else if ( detail.getCategory().equalsIgnoreCase("hidden")){ // not public }else{ res.add( new StandardPluginImpl( this, details[i], version )); } } StandardPlugin[] res_a = new StandardPlugin[res.size()]; res.toArray( res_a ); return( res_a ); }catch( SFPluginDetailsException e ){ throw( new PluginException("Failed to load standard plugin details", e )); } } public StandardPlugin getStandardPlugin( String id ) throws PluginException { try{ SFPluginDetailsLoader loader = SFPluginDetailsLoaderFactory.getSingleton(); SFPluginDetails[] details = loader.getPluginDetails(); for (int i=0;i<details.length;i++){ SFPluginDetails detail = details[i]; String name = detail.getId(); if ( name.equals( id )){ String version = ""; if ( Constants.isCVSVersion()){ version = detail.getCVSVersion(); } if ( version == null || version.length() == 0 || !Character.isDigit(version.charAt(0))){ version = detail.getVersion(); }else{ // if cvs version and non-cvs version are the same then show the // non-cvs version String non_cvs_version = detail.getVersion(); if ( version.equals( non_cvs_version + "_CVS" )){ version = non_cvs_version; } } if ( name.startsWith( "azplatform" ) || name.equals( "azupdater" )){ // skip built in ones we don't want to let user install directly // not the cleanest of fixes, but it'll do for the moment }else if ( version == null || version.length() == 0 || !Character.isDigit(version.charAt(0))){ // dodgy version }else{ return( new StandardPluginImpl( this, details[i], version )); } } } return( null ); }catch( SFPluginDetailsException e ){ throw( new PluginException("Failed to load standard plugin details", e )); } } public FilePluginInstaller installFromFile( File file ) throws PluginException { return( new FilePluginInstallerImpl(this,file)); } public void install( InstallablePlugin installable_plugin, boolean shared ) throws PluginException { install( new InstallablePlugin[]{installable_plugin}, shared ); } public void install( InstallablePlugin[] plugins, boolean shared ) throws PluginException { install( plugins, shared, false, null, null ); } public UpdateCheckInstance install( InstallablePlugin[] plugins, boolean shared, Map<Integer,Object> properties, final PluginInstallationListener listener ) throws PluginException { return( install( plugins, shared, false, properties, listener )); } protected UpdateCheckInstance install( InstallablePlugin[] plugins, boolean shared, boolean low_noise, Map<Integer,Object> properties, final PluginInstallationListener listener ) throws PluginException { PluginInterface pup_pi = manager.getPluginInterfaceByClass( PluginUpdatePlugin.class ); if ( pup_pi == null ){ throw( new PluginException( "Installation aborted, plugin-update plugin unavailable" )); } if ( !pup_pi.getPluginState().isOperational()){ throw( new PluginException( "Installation aborted, plugin-update plugin not operational" )); } PluginUpdatePlugin pup = (PluginUpdatePlugin)pup_pi.getPlugin(); UpdateManagerImpl uman = (UpdateManagerImpl)manager.getDefaultPluginInterface().getUpdateManager(); UpdateCheckInstanceImpl inst = uman.createEmptyUpdateCheckInstance( UpdateCheckInstance.UCI_INSTALL, "update.instance.install", low_noise ); if ( properties != null ){ for ( Map.Entry<Integer,Object> entry: properties.entrySet()){ inst.setProperty( entry.getKey(), entry.getValue()); } } if ( listener != null ){ inst.addListener( new UpdateCheckInstanceListener() { public void cancelled( UpdateCheckInstance instance ) { listener.cancelled(); } public void complete( UpdateCheckInstance instance ) { final Update[] updates = instance.getUpdates(); if ( updates.length == 0 ){ listener.failed( new PluginException( "No updates were added during check process" )); }else{ for (int i=0;i<updates.length;i++){ updates[i].addListener( new UpdateListener() { private boolean cancelled; public void cancelled( Update update) { cancelled = true; check(); } public void complete( Update update ) { check(); } protected void check() { Update failed_update = null; for ( Update update: updates ){ if ( !update.isCancelled() && !update.isComplete()){ return; } if ( !update.wasSuccessful()){ failed_update = update; } } if ( cancelled ){ listener.cancelled(); }else{ if ( failed_update == null ){ // flush plugin events through before reporting complete PluginInitializer.waitForPluginEvents(); listener.completed(); }else{ listener.failed( new PluginException( "Install of " + failed_update.getName() + " failed" )); } } } }); } } } }); } try{ for (int i=0;i<plugins.length;i++){ InstallablePlugin plugin = plugins[i]; final String plugin_id = plugin.getId(); PluginInterface existing_plugin_interface = manager.getPluginInterfaceByID( plugin_id, false ); Plugin existing_plugin = null; if ( existing_plugin_interface != null ){ existing_plugin = existing_plugin_interface.getPlugin(); // try to check that the new version is higher than the old one! String old_version = existing_plugin_interface.getPluginVersion(); if ( old_version != null ){ int res = Constants.compareVersions( plugin.getVersion(), old_version ); if ( res < 0 ){ throw( new PluginException( "A higher version (" + old_version + ") of Plugin '" + plugin_id + "' is already installed" )); }else if ( res == 0 ){ throw( new PluginException( "Version (" + old_version + ") of Plugin '" + plugin_id + "' is already installed" )); } } } String target_dir; if ( shared ){ target_dir = FileUtil.getApplicationFile( "plugins" ).toString(); }else{ target_dir = FileUtil.getUserFile( "plugins" ).toString(); } target_dir += File.separator + plugin_id; // this may fail on Vista but it doesn't matter as we recover this later // on. So *don't* check for success here new File( target_dir ).mkdir(); if ( existing_plugin == null ){ // create a dummy plugin at version 0.0 to trigger the "upgrade" to the new // installed version FailedPlugin dummy_plugin = new FailedPlugin( plugin_id, target_dir ); PluginManager.registerPlugin( dummy_plugin, plugin_id ); final PluginInterface dummy_plugin_interface = manager.getPluginInterfaceByID( plugin_id, false ); ((InstallablePluginImpl)plugin).addUpdate( inst, pup, dummy_plugin, dummy_plugin_interface ); inst.addListener( new UpdateCheckInstanceListener() { public void cancelled( UpdateCheckInstance instance ) { try{ dummy_plugin_interface.getPluginState().unload(); }catch( Throwable e ){ Debug.out( "Failed to unload plugin", e ); } } public void complete( UpdateCheckInstance instance ) { PluginInterface pi = manager.getPluginInterfaceByID( plugin_id, false ); if ( pi != null && pi.getPlugin() instanceof FailedPlugin ){ try{ pi.getPluginState().unload(); }catch( Throwable e ){ Debug.out( "Failed to unload plugin", e ); } } } }); }else{ ((InstallablePluginImpl)plugin).addUpdate( inst, pup, existing_plugin, existing_plugin_interface ); } } inst.start(); return( inst ); }catch( Throwable e ){ inst.cancel(); if ( e instanceof PluginException ){ throw((PluginException)e); }else{ throw( new PluginException( "Failed to create installer", e )); } } } public void uninstall( InstallablePlugin standard_plugin ) throws PluginException { PluginInterface pi = standard_plugin.getAlreadyInstalledPlugin(); if ( pi == null ){ throw( new PluginException(" Plugin '" + standard_plugin.getId() + "' is not installed")); } pi.getPluginState().uninstall(); } public void uninstall( final PluginInterface pi ) throws PluginException { uninstall( new PluginInterface[]{ pi }); } public void uninstall( final PluginInterface[] pis ) throws PluginException { uninstall( pis, null ); } public void uninstall( final PluginInterface[] pis, final PluginInstallationListener listener_maybe_null ) throws PluginException { uninstall( pis, listener_maybe_null, new HashMap<Integer,Object>()); } public UpdateCheckInstance uninstall( final PluginInterface[] pis, final PluginInstallationListener listener_maybe_null, final Map<Integer,Object> properties ) throws PluginException { properties.put( UpdateCheckInstance.PT_UNINSTALL_RESTART_REQUIRED, false ); for (int i=0;i<pis.length;i++){ PluginInterface pi = pis[i]; if ( pi.getPluginState().isMandatory()){ throw( new PluginException( "Plugin '" + pi.getPluginID() + "' is mandatory, can't uninstall" )); } if ( pi.getPluginState().isBuiltIn()){ throw( new PluginException( "Plugin '" + pi.getPluginID() + "' is built-in, can't uninstall" )); } String plugin_dir = pi.getPluginDirectoryName(); if ( plugin_dir == null || !new File(plugin_dir).exists()){ throw( new PluginException( "Plugin '" + pi.getPluginID() + "' is not loaded from the file system, can't uninstall" )); } } try{ UpdateManager uman = manager.getDefaultPluginInterface().getUpdateManager(); UpdateCheckInstance inst = uman.createEmptyUpdateCheckInstance( UpdateCheckInstance.UCI_UNINSTALL, "update.instance.uninstall"); final int[] rds_added = { 0 }; final AESemaphore rd_waiter_sem = new AESemaphore( "uninst:rd:wait" ); for (int i=0;i<pis.length;i++){ final PluginInterface pi = pis[i]; final String plugin_dir = pi.getPluginDirectoryName(); inst.addUpdatableComponent( new UpdatableComponent() { public String getName() { return( pi.getPluginName()); } public int getMaximumCheckTime() { return( 0 ); } public void checkForUpdate( final UpdateChecker checker ) { try{ ResourceDownloader rd = manager.getDefaultPluginInterface().getUtilities().getResourceDownloaderFactory().create( new File( plugin_dir )); // the plugin may have > 1 plugin interfaces, make the name up appropriately String update_name = ""; PluginInterface[] ifs = manager.getPluginInterfaces(); Arrays.sort( ifs, new Comparator() { public int compare( Object o1, Object o2) { return(((PluginInterface)o1).getPluginName().compareTo(((PluginInterface)o2).getPluginName())); } }); for (int i=0;i<ifs.length;i++){ if ( ifs[i].getPluginID().equals(pi.getPluginID())){ update_name += (update_name.length()==0?"":",") + ifs[i].getPluginName(); } } boolean unloadable = pi.getPluginState().isUnloadable(); if ( !unloadable ){ properties.put( UpdateCheckInstance.PT_UNINSTALL_RESTART_REQUIRED, true ); } final Update update = checker.addUpdate( update_name, new String[]{ "Uninstall: " + plugin_dir}, pi.getPluginVersion(), rd, unloadable?Update.RESTART_REQUIRED_NO:Update.RESTART_REQUIRED_YES ); synchronized( rds_added ){ rds_added[0]++; } rd.addListener( new ResourceDownloaderAdapter() { public boolean completed( ResourceDownloader downloader, InputStream data ) { try{ try{ if ( pi.getPluginState().isUnloadable()){ pi.getPluginState().unload(); if ( !FileUtil.recursiveDelete( new File( plugin_dir ))){ update.setRestartRequired( Update.RESTART_REQUIRED_YES ); properties.put( UpdateCheckInstance.PT_UNINSTALL_RESTART_REQUIRED, true ); checker.reportProgress( "Failed to remove plugin, restart will be required" ); } } UpdateInstaller installer = checker.createInstaller(); installer.addRemoveAction( new File( plugin_dir ).getCanonicalPath()); update.complete( true ); try{ PluginInitializer.fireEvent( PluginEvent.PEV_PLUGIN_UNINSTALLED, pi.getPluginID()); }catch( Throwable e ){ Debug.out( e ); } }catch( Throwable e ){ update.complete( false ); Debug.printStackTrace(e); Logger.log(new LogAlert(LogAlert.REPEATABLE, "Plugin uninstall failed", e)); } // don't close the stream as we process it later return( true ); }finally{ rd_waiter_sem.release(); } } public void failed( ResourceDownloader downloader, ResourceDownloaderException e ) { try{ update.complete( false ); if ( !downloader.isCancelled()){ Logger.log(new LogAlert(LogAlert.REPEATABLE, "Plugin uninstall failed", e)); } }finally{ rd_waiter_sem.release(); } } }); }finally{ checker.completed(); } } }, false ); } if ( listener_maybe_null != null ){ inst.addListener( new UpdateCheckInstanceListener() { public void cancelled( UpdateCheckInstance instance ) { listener_maybe_null.cancelled(); } public void complete( UpdateCheckInstance instance ) { // needs to be async in the case of the caller of the uninstall needing access to the // updatecheckinstance and this is a sync callback new AEThread2( "Uninstall:async" ) { public void run() { int wait_count; synchronized( rds_added ){ wait_count = rds_added[0]; } for ( int i=0;i<wait_count;i++ ){ rd_waiter_sem.reserve(); } listener_maybe_null.completed(); } }.start(); } }); } inst.start(); return( inst ); }catch( Throwable e ){ PluginException pe; if ( e instanceof PluginException ){ pe = (PluginException)e; }else{ pe = new PluginException( "Uninstall failed", e ); } if ( listener_maybe_null != null ){ listener_maybe_null.failed( pe ); } throw( pe ); } } protected PluginInterface getAlreadyInstalledPlugin( String id ) { return( getPluginManager().getPluginInterfaceByID(id, false )); } public void requestInstall( String reason, InstallablePlugin plugin ) throws PluginException { for ( PluginInstallerListener listener: listeners ){ try{ if ( listener.installRequest( reason, plugin )){ return; } }catch( Throwable e ){ Debug.out( e ); } } throw( new PluginException( "No listeners registered to perform installation of '" + plugin.getName() +" (" + reason + ")" )); } public void addListener( PluginInstallerListener l ) { listeners.add( l ); } public void removeListener( PluginInstallerListener l ) { listeners.remove( l ); } }