package com.pugh.sockso;
import com.pugh.sockso.cache.ObjectCacheGC;
import com.pugh.sockso.db.DBExporter;
import com.pugh.sockso.db.Database;
import com.pugh.sockso.gui.Splash;
import com.pugh.sockso.inject.SocksoModule;
import com.pugh.sockso.music.CollectionManager;
import com.pugh.sockso.music.DBCollectionManager;
import com.pugh.sockso.music.indexing.Indexer;
import com.pugh.sockso.music.scheduling.SchedulerRunner;
import com.pugh.sockso.resources.Locale;
import com.pugh.sockso.resources.LocaleFactory;
import com.pugh.sockso.resources.Resources;
import com.pugh.sockso.web.Dispatcher;
import com.pugh.sockso.web.HttpServer;
import com.pugh.sockso.web.IpFinder;
import com.pugh.sockso.web.Server;
import com.pugh.sockso.web.SessionCleaner;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.logging.LogManager;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
public class Main {
private static final Logger log = Logger.getLogger( Main.class );
private static volatile boolean shutdownStarted = false;
private static Dispatcher dispatcher;
private static Database db;
private static Properties p;
private static Server sv;
private static CollectionManager cm;
private static Manager manager;
private static Resources r;
private static Locale locale;
private static Indexer indexer;
private static SchedulerRunner sched;
private static Injector injector;
/**
* application entry point
*
* @param args the command line arguments
*
*/
public static void main( final String[] args ) throws Exception{
Runtime.getRuntime().addShutdownHook( new Shutdown() );
initLogger( getLogPropsFile("default") );
initJavaLogger();
//
// do initial setup, we're gonna need to parse the command line
// arguments, and make sure we're connected to the database
//
final OptionParser parser = Options.getParser();
OptionSet options = null;
try {
options = parser.parse(args);
} catch ( final Exception e ) {
System.err.println( "Invalid command line switch!" );
}
// check if user asked for basic things as early as we can, don't
// wanna go doing anything we don't need to.
if ( options == null || options.has(Options.OPT_HELP) ) {
parser.printHelpOn( System.out );
exit( false );
}
// print version info?
if ( options.has(Options.OPT_VERSION) ) {
System.out.println( "Sockso " +Sockso.VERSION );
exit( false );
}
// set a user-defined data directory?
if ( options.hasArgument(Options.OPT_DATADIR) ) {
final File dir = new File( (String) options.valueOf(Options.OPT_DATADIR) );
Utils.setApplicationDirectory( dir );
}
setupAppDirectory();
//
// final setup from command line options before we try and do
// something kinda useful
//
if ( options.hasArgument(Options.OPT_LOGTYPE) ) {
PropertyConfigurator.configure( getLogPropsFile(options.valueOf(Options.OPT_LOGTYPE).toString()) );
}
injector = Guice.createInjector( new SocksoModule(options) );
try {
db = injector.getInstance( Database.class );
db.connect( options );
}
catch ( final Exception e ) {
log.error( e );
exit( 1 );
}
//
// now decide exactly what we're gonna be doing, hmm...
//
// perform a database query
if ( options.has(Options.OPT_QUERY) ) {
actionQuery( options );
}
// default is start sockso normally
else {
actionDefault( options );
}
}
/**
* performs a database query and outputs the results
*
* @param options
*
*/
private static void actionQuery( final OptionSet options ) {
BufferedReader in = null;
try {
String sql = "", line = "";
// if we were given a filename use that as input, otherwise
// read from stdin
in = new BufferedReader(
options.hasArgument(Options.OPT_QUERY)
? new FileReader( options.valueOf(Options.OPT_QUERY).toString() )
: new InputStreamReader( System.in )
);
// read in query, then output xml
while ( (line = in.readLine()) != null )
sql += line + "\n";
final DBExporter exporter = new DBExporter( db );
System.out.print(
exporter.export( sql, DBExporter.Format.XML )
);
}
catch ( final IOException e ) {
log.error( e );
}
finally { Utils.close(in); }
}
/**
* starts sockso normally in either GUI or Console mode
*
* @param options
*
* @throws java.sql.SQLException
*
*/
private static void actionDefault( final OptionSet options ) throws Exception {
final boolean useGui = getUseGui( options );
final String localeString = getLocale( options );
log.info( "Initializing Resources (" + locale + ")" );
r = injector.getInstance( Resources.class );
r.init( localeString );
LocaleFactory localeFactory = injector.getInstance( LocaleFactory.class );
localeFactory.init( localeString );
if ( useGui ) {
Splash.start( r );
}
log.info( "Loading Properties" );
p = injector.getInstance( Properties.class );
p.init();
log.info( "Starting Scheduler" );
sched = injector.getInstance( SchedulerRunner.class );
sched.start();
indexer = injector.getInstance( Indexer.class );
log.info( "Starting Collection Manager" );
cm = injector.getInstance( CollectionManager.class );
indexer.addIndexListener( (DBCollectionManager) cm );
injector.getInstance( CommunityUpdater.class ).start();
injector.getInstance( SessionCleaner.class ).init();
injector.getInstance( ObjectCacheGC.class ).start();
final IpFinder ipFinder = injector.getInstance( IpFinder.class );
ipFinder.init();
final int port = getSavedPort( p );
final String protocol = getProtocol( options );
dispatcher = injector.getInstance( Dispatcher.class );
dispatcher.init( protocol, port );
log.info( "Starting Web Server" );
sv = injector.getInstance( Server.class );
sv.start( options, port );
if ( options.has(Options.OPT_UPNP) ) {
log.info( "Trying UPNP Magic" );
UPNP.tryPortForwarding( sv.getPort() );
}
manager = injector.getInstance( Manager.class );
final VersionChecker versionChecker = injector.getInstance( VersionChecker.class );
versionChecker.addLatestVersionListener( manager );
versionChecker.fetchLatestVersion();
manager.open();
}
/**
* returns a boolean indicating if we should start the GUI or not
*
* @param options
*
* @return
*
*/
protected static boolean getUseGui( final OptionSet options ) {
return !options.has( Options.OPT_NOGUI );
}
/**
* returns the locale to use (eg. "en", "nb", etc...)
*
* @param options
*
* @return
*
*/
protected static String getLocale( final OptionSet options ) {
return options.has( Options.OPT_LOCALE )
? options.valueOf(Options.OPT_LOCALE).toString()
: Resources.DEFAULT_LOCALE;
}
/**
* Returns the protocol to use for web serving
*
* @param options
*
* @return
*
*/
protected static String getProtocol( final OptionSet options ) {
return options.has( Options.OPT_SSL )
? "https"
: "http";
}
/**
* fetches the port stored in the application properties, if this isn't
* something acceptable then it'll return the DEFAULT_PORT
*
* @param p
*
* @return
*
*/
protected static int getSavedPort( final Properties props ) {
int thePort = HttpServer.DEFAULT_PORT;
try {
thePort = Integer.parseInt(props.get(Constants.SERVER_PORT));
}
catch ( final NumberFormatException e ) {
log.error( "Invalid port number: " + e );
}
return thePort;
}
/**
* returns name of logger properties file for given type
*
* @param type the logging type
* @return the path to the props file
*
*/
private static String getLogPropsFile( final String type ) {
return Constants.LOG_DIR + File.separator + type + ".properties";
}
/**
* returns name of test logger properties file for given type
*
* @param type the logging type
* @return the path to the props file
*
*/
private static String getTestLogPropsFile( final String type ) {
return Constants.TEST_LOG_DIR + File.separator + type + ".properties";
}
/**
* shuts down the application
*
*/
public static void exit() {
exit( 0, true );
}
public static void exit( int status ) {
exit( status, true );
}
public static void exit( boolean showOutput ) {
exit( 0, showOutput );
}
/**
* shuts down the application and exits with the specifed exit code
*
* @param status the exit code
*
*/
public static void exit( final int status, final boolean showOutput ) {
if ( showOutput ) {
log.info( "Shutting Down" );
}
shutdown();
if ( showOutput ) {
log.info( "Thank you for your attention, bye!" );
}
System.exit( status );
}
/**
* Shuts down application components
*
*/
protected static void shutdown() {
if ( !shutdownStarted ) {
shutdownStarted = true;
if ( indexer != null ) {
// @TODO
}
if ( sv != null ) {
sv.shutdown();
sv = null;
}
if ( manager != null) {
manager.close();
manager = null;
}
shutdownDatabase();
}
}
/**
* Shuts down the database connection if it's open
*
* @return
*
*/
public static void shutdownDatabase() {
// finally shutdown database (do this last so
// anything can still be written here from the
// other components)
if ( db != null ) {
db.close();
db = null;
}
}
/**
* creates the folder in the users home directory that is used
* to store application stuff (the database)
*
*/
private static void setupAppDirectory() {
final String[] dirs = {
Utils.getApplicationDirectory(),
Utils.getCoversDirectory()
};
for ( final String dir : dirs ) {
final File file = new File( dir );
if ( !file.exists() ) {
if ( !file.mkdir() ) {
log.fatal( "Unable to create directory: " + dir );
exit( 1 );
}
}
}
}
/**
* Initializes the logger for the test cases
*
*/
public static void initTestLogger() {
initLogger( getTestLogPropsFile("test") );
}
/**
* Inits the logging framework with the specified props file
*
* @param propsFile the properties file to use
*
*/
private static void initLogger( final String propsFile ) {
if ( new File(propsFile).exists() ) {
PropertyConfigurator.configure( propsFile );
}
}
/**
* Inits loggers using java.util.logging
*
*/
private static void initJavaLogger() throws IOException {
final File propsFile = new File( getLogPropsFile("javalogging") );
if ( propsFile.exists() ) {
final InputStream is = new FileInputStream( propsFile );
LogManager.getLogManager().readConfiguration(is);
Utils.close( is );
}
}
}