/*
* Created on 25-Jul-2005
* Created by Paul Gardner
* Copyright (C) 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.launch;
import java.io.*;
import java.net.*;
import java.util.*;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.SystemProperties;
import org.gudy.azureus2.plugins.LaunchablePlugin;
import org.gudy.azureus2.plugins.Plugin;
import org.gudy.azureus2.plugins.logging.LoggerChannel;
import org.gudy.azureus2.plugins.logging.LoggerChannelListener;
import org.gudy.azureus2.pluginsimpl.PluginUtils;
import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.launcher.Launcher;
public class
PluginLauncherImpl
{
private static Map preloaded_plugins = new HashMap();
// used as callback (via reflection) by Launcher
private static void main(String[] args)
{
launch(args);
}
public static void
launch(
String[] args )
{
if(Launcher.checkAndLaunch(PluginLauncherImpl.class, args))
return;
// This *has* to be done first as it sets system properties that are read and cached by Java
COConfigurationManager.preInitialise();
final LoggerChannelListener listener =
new LoggerChannelListener()
{
public void
messageLogged(
int type,
String content )
{
log( content, false );
}
public void
messageLogged(
String str,
Throwable error )
{
log( str, true );
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter( sw );
error.printStackTrace( pw );
pw.flush();
log( sw.toString(), true );
}
protected synchronized void
log(
String str,
boolean stdout )
{
File log_file = getApplicationFile("launch.log");
PrintWriter pw = null;
try{
pw = new PrintWriter(new FileWriter( log_file, true ));
if ( str.endsWith( "\n" )){
if ( stdout ){
System.err.print( "PluginLauncher: " + str );
}
pw.print( str );
}else{
if ( stdout ){
System.err.println( "PluginLauncher: " + str );
}
pw.println( str );
}
}catch( Throwable e ){
}finally{
if ( pw != null ){
pw.close();
}
}
}
};
LaunchablePlugin[] launchables = findLaunchablePlugins(listener);
if ( launchables.length == 0 ){
listener.messageLogged( LoggerChannel.LT_ERROR, "No launchable plugins found" );
return;
}else if ( launchables.length > 1 ){
listener.messageLogged( LoggerChannel.LT_ERROR, "Multiple launchable plugins found, running first" );
}
try{
// set default details for restarter
SystemProperties.setApplicationEntryPoint( "org.gudy.azureus2.plugins.PluginLauncher" );
launchables[0].setDefaults( args );
// see if we're a secondary instance
if ( PluginSingleInstanceHandler.process( listener, args )){
return;
}
// we have to run the core startup on a separate thread and then effectively pass "this thread"
// through to the launchable "process" method
Thread core_thread =
new Thread( "PluginLauncher" )
{
public void
run()
{
try{
// give 'process' call below some time to start up
Thread.sleep(500);
AzureusCore azureus_core = AzureusCoreFactory.create();
azureus_core.start();
}catch( Throwable e ){
listener.messageLogged( "PluginLauncher: launch fails", e );
}
}
};
core_thread.setDaemon( true );
core_thread.start();
boolean restart = false;
boolean process_succeeded = false;
try{
restart = launchables[0].process();
process_succeeded = true;
}finally{
try{
if ( restart ){
AzureusCoreFactory.getSingleton().restart();
}else{
AzureusCoreFactory.getSingleton().stop();
}
}catch( Throwable e ){
// only report this exception if we're not already failing
if ( process_succeeded ){
throw( e );
}
}
}
}catch( Throwable e ){
listener.messageLogged( "PluginLauncher: launch fails", e );
}
}
private static LaunchablePlugin[]
findLaunchablePlugins(
LoggerChannelListener listener )
{
// CAREFUL - this is called BEFORE any AZ initialisation has been performed and must
// therefore NOT use anything that relies on this (such as logging, debug....)
List res = new ArrayList();
File app_dir = getApplicationFile("plugins");
if ( !( app_dir.exists()) && app_dir.isDirectory()){
listener.messageLogged( LoggerChannel.LT_ERROR, "Application dir '" + app_dir + "' not found" );
return( new LaunchablePlugin[0] );
}
File[] plugins = app_dir.listFiles();
if ( plugins == null || plugins.length == 0 ){
listener.messageLogged( LoggerChannel.LT_ERROR, "Application dir '" + app_dir + "' empty" );
return( new LaunchablePlugin[0] );
}
for ( int i=0;i<plugins.length;i++ ) {
File plugin_dir = plugins[i];
if( !plugin_dir.isDirectory()){
continue;
}
try{
ClassLoader classLoader = PluginLauncherImpl.class.getClassLoader();
ClassLoader root_cl = classLoader;
File[] contents = plugin_dir.listFiles();
if ( contents == null || contents.length == 0){
continue;
}
// take only the highest version numbers of jars that look versioned
String[] plugin_version = {null};
String[] plugin_id = {null};
contents = getHighestJarVersions( contents, plugin_version, plugin_id, true );
for( int j = 0 ; j < contents.length ; j++){
classLoader = addFileToClassPath( root_cl, classLoader, contents[j]);
}
Properties props = new Properties();
File properties_file = new File( plugin_dir, "plugin.properties");
// if properties file exists on its own then override any properties file
// potentially held within a jar
if ( properties_file.exists()){
FileInputStream fis = null;
try{
fis = new FileInputStream( properties_file );
props.load( fis );
}finally{
if ( fis != null ){
fis.close();
}
}
}else{
if ( classLoader instanceof URLClassLoader ){
URLClassLoader current = (URLClassLoader)classLoader;
URL url = current.findResource("plugin.properties");
if ( url != null ){
props.load(url.openStream());
}
}
}
String plugin_class = (String)props.get( "plugin.class");
// don't support multiple launchable plugins
if ( plugin_class == null || plugin_class.indexOf(';') != -1 ){
continue;
}
Class c = classLoader.loadClass(plugin_class);
Plugin plugin = (Plugin) c.newInstance();
if ( plugin instanceof LaunchablePlugin ){
preloaded_plugins.put( plugin_class, plugin );
res.add( plugin );
}
}catch( Throwable e ){
listener.messageLogged( "Load of plugin in '" + plugin_dir + "' fails", e );
}
}
LaunchablePlugin[] x = new LaunchablePlugin[res.size()];
res.toArray( x );
return( x );
}
public static Plugin
getPreloadedPlugin(
String cla )
{
return((Plugin)preloaded_plugins.get( cla ));
}
private static File
getApplicationFile(
String filename)
{
String path = SystemProperties.getApplicationPath();
if (Constants.isOSX ){
path = path + "/" + SystemProperties.getApplicationName() + ".app/Contents/";
}
return new File(path, filename);
}
public static File[]
getHighestJarVersions(
File[] files,
String[] version_out ,
String[] id_out, // currently the version of last versioned jar found...
boolean discard_non_versioned_when_versioned_found )
{
// WARNING!!!!
// don't use Debug/lglogger here as we can be called before AZ has been initialised
List res = new ArrayList();
Map version_map = new HashMap();
for (int i=0;i<files.length;i++){
File f = files[i];
String name = f.getName().toLowerCase();
if ( name.endsWith(".jar")){
int cvs_pos = name.lastIndexOf("_cvs");
int sep_pos;
if (cvs_pos <= 0)
sep_pos = name.lastIndexOf("_");
else
sep_pos = name.lastIndexOf("_", cvs_pos - 1);
if ( sep_pos == -1 ||
sep_pos == name.length()-1 ||
!Character.isDigit(name.charAt(sep_pos+1))){
// not a versioned jar
res.add( f );
}else{
String prefix = name.substring(0,sep_pos);
String version = name.substring(sep_pos+1, (cvs_pos <= 0) ? name.length()-4 : cvs_pos);
String prev_version = (String)version_map.get(prefix);
if ( prev_version == null ){
version_map.put( prefix, version );
}else{
if ( PluginUtils.comparePluginVersions( prev_version, version ) < 0 ){
version_map.put( prefix, version );
}
}
}
}
}
// If any of the jars are versioned then the assumption is that all of them are
// For migration purposes (i.e. on the first real introduction of the update versioning
// system) we drop all non-versioned jars from the set
if ( version_map.size() > 0 && discard_non_versioned_when_versioned_found ){
res.clear();
}
// fix a problem we had with the rating plugin. It went out as rating_x.jar when it should
// have been azrating_x.jar. If there are any azrating entries then we remove any rating ones
// to avoid load problems
if ( version_map.containsKey( "azrating" )){
version_map.remove( "rating" );
}
Iterator it = version_map.keySet().iterator();
while(it.hasNext()){
String prefix = (String)it.next();
String version = (String)version_map.get(prefix);
String target = prefix + "_" + version;
version_out[0] = version;
id_out[0] = prefix;
for (int i=0;i<files.length;i++){
File f = files[i];
String lc_name = f.getName().toLowerCase();
if ( lc_name.equals( target + ".jar" ) ||
lc_name.equals( target + "_cvs.jar" )){
res.add( f );
break;
}
}
}
File[] res_array = new File[res.size()];
res.toArray( res_array );
return( res_array );
}
public static ClassLoader
addFileToClassPath(
ClassLoader root,
ClassLoader classLoader,
File f)
{
if ( f.exists() &&
(!f.isDirectory())&&
f.getName().endsWith(".jar")){
try {
classLoader = extendClassLoader( root, classLoader, f.toURL());
}catch( Exception e){
// don't use Debug/lglogger here as we can be called before AZ has been initialised
e.printStackTrace();
}
}
return( classLoader );
}
public static ClassLoader
extendClassLoader(
ClassLoader root,
ClassLoader classLoader,
URL url )
{
// URL classloader doesn't seem to delegate to parent classloader properly
// so if you get a chain of them then it fails to find things. Here we
// make sure that all of our added URLs end up within a single URLClassloader
// with its parent being the one that loaded this class itself
if ( classLoader instanceof URLClassLoader ){
URL[] old = ((URLClassLoader)classLoader).getURLs();
URL[] new_urls = new URL[old.length+1];
System.arraycopy( old, 0, new_urls, 1, old.length );
new_urls[0]= url;
classLoader = new URLClassLoader(
new_urls,
classLoader==root?
classLoader:
classLoader.getParent());
}else{
classLoader = new URLClassLoader(new URL[]{ url },classLoader);
}
return( classLoader );
}
}