/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.java.sip.communicator.util.launchutils;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import net.java.sip.communicator.util.*;
/**
* The LauncherArgHandler class handles invocation arguments that have
* been passed to us when running SIP Communicator. The class supports a fixed
* set of options and also allows for registration of delegates.
*
* @author Emil Ivov
*/
public class LaunchArgHandler
{
/**
* Our class logger.
*/
private static final net.java.sip.communicator.util.Logger logger =
net.java.sip.communicator.util.Logger.getLogger(LaunchArgHandler.class);
/**
* The name of the property that contains the location of the SC
* configuration directory.
*/
private static final String PNAME_SC_HOME_DIR_LOCATION =
"net.java.sip.communicator.SC_HOME_DIR_LOCATION";
/**
* The name of the property that stores the home dir for cache data, such
* as avatars or spelling dictionaries.
*/
private static final String PNAME_SC_CACHE_DIR_LOCATION =
"net.java.sip.communicator.SC_CACHE_DIR_LOCATION";
/**
* The name of the property that stores the home dir for application logs
* (not history).
*/
private static final String PNAME_SC_LOG_DIR_LOCATION =
"net.java.sip.communicator.SC_LOG_DIR_LOCATION";
/**
* The name of the property that contains the name of the SC configuration
* directory.
*/
private static final String PNAME_SC_HOME_DIR_NAME =
"net.java.sip.communicator.SC_HOME_DIR_NAME";
/**
* Returned by the handleArgs methods when the arguments that have
* been parsed do not require for SIP Communicator to be started and the
* Launcher is supposed to exit. That could happen when "SIP Communicator"
* is launched with a --version argument for example or when trying to
* run the application after an instance was already launched.
*/
public static final int ACTION_EXIT = 0;
/**
* Returned by the handleArgs methods when all arguments have been
* parsed and the SIP Communicator launch can continue.
*/
public static final int ACTION_CONTINUE = 1;
/**
* Returned by the handleArgs method when parsing the arguments
* has failed or if no arguments were passed and an instance of SC was
* already launched. If this is the code returned by handleArgs, then the
* getErrorCode method would return an error code indicating what
* the error was.
*/
public static final int ACTION_ERROR = 2;
/**
* Returned by the handleArgs methods when all arguments have been
* successfully parsed and one of them indicates that the user has requested
* a multi instance launch.
*/
public static final int ACTION_CONTINUE_LOCK_DISABLED = 3;
/**
* The error code returned when we couldn't parse one of the options.
*/
public static final int ERROR_CODE_UNKNOWN_ARG = 1;
/**
* The error code returned when we try to launch SIP Communicator while
* there is already a running instance and there were no arguments that we
* forward to that instance.
*/
public static final int ERROR_CODE_ALREADY_STARTED = 2;
/**
* The error code that we return when we fail to create a directory that has
* been specified with the -c|--config option.
*/
public static final int ERROR_CODE_CREATE_DIR_FAILED = 3;
/**
* The property name containing the name of the application
* (e.g. SIP Communicator)
*/
private static final String PNAME_APPLICATION_NAME = "APPLICATION_NAME";
/**
* The package name of the applications (e.g. jitsi).
*/
private static final String PNAME_PACKAGE_NAME = "PACKAGE_NAME";
/**
* The property name containing the current version.
*/
private static final String PNAME_VERSION = "APPLICATION_VERSION";
/**
* The name of the file containing version properties for use with the
* argument handler.
*/
private static final String VERSION_PROPERTIES = "version.properties";
/**
* The errorCode identifying the error that occurred last time
* handleArgs was called.
*/
private int errorCode = 0;
/**
* A reference to the instance of the
*/
private ArgDelegator argDelegator = new ArgDelegator();
/**
* The singleton instance of this handler.
*/
private static LaunchArgHandler argHandler = null;
/**
* The properties where we load version info from our update location.
*/
private Properties versionProperties = new Properties();
/**
* Creates the sole instance of this class;
*/
private LaunchArgHandler()
{
InputStream versionPropertiesStream =
getClass().getResourceAsStream(VERSION_PROPERTIES);
boolean versionPropertiesAreLoaded = false;
if (versionPropertiesStream != null)
{
try
{
try
{
versionProperties.load(versionPropertiesStream);
versionPropertiesAreLoaded = true;
}
finally
{
versionPropertiesStream.close();
}
}
catch (IOException exc)
{
// no need to worry the user, so only print if we're in FINEST
}
}
if (!versionPropertiesAreLoaded)
{
if (logger.isTraceEnabled())
logger.trace("Couldn't open version.properties");
}
// Start url handler for Mac OS X.
/*
* XXX The detection of the operating systems is the responsibility of
* OSUtils. It used to reside in the util.jar which is in the classpath
* but it is now in libjitsi.jar which is not in the classpath.
*/
String osName = System.getProperty("os.name");
if ((osName != null) && osName.startsWith("Mac"))
new AEGetURLEventHandler(this);
}
/**
* Creates a singleton instance of the LauncherArgHandler if necessary and
* returns a reference to it.
*
* @return the singleton instance of the LauncherArgHandler.
*/
public static LaunchArgHandler getInstance()
{
if(argHandler == null)
{
argHandler = new LaunchArgHandler();
}
return argHandler;
}
/**
* Does the actual argument handling.
*
* @param args the arguments the way we have received them from the main()
* method.
*
* @return one of the ACTION_XXX fields defined here, intended to indicate
* to the caller they action that they are supposed as a result of the arg
* handling.
*/
public int handleArgs(String[] args)
{
int returnAction = ACTION_CONTINUE;
for(int i = 0; i < args.length; i++)
{
if (logger.isTraceEnabled())
logger.trace("handling arg " + i);
if (args[i].equals("--version") || args[i].equals("-v"))
{
handleVersionArg();
//we're supposed to exit after printing version info
returnAction = ACTION_EXIT;
break;
}
else if (args[i].equals("--help") || args[i].equals("-h"))
{
handleHelpArg();
//we're supposed to exit after printing the help message
returnAction = ACTION_EXIT;
break;
}
else if (args[i].equals("--debug") || args[i].equals("-d"))
{
handleDebugArg(args[i]);
continue;
}
else if (args[i].equals("--ipv6") || args[i].equals("-6"))
{
handleIPv6Enforcement();
break;
}
else if (args[i].equals("--ipv4") || args[i].equals("-4"))
{
handleIPv4Enforcement();
break;
}
else if (args[i].startsWith("--config="))
{
returnAction = handleConfigArg(args[i]);
if(returnAction == ACTION_ERROR)
break;
else
continue;
}
else if (args[i].equals("-c"))
{
//make sure we have at least one more argument left.
if( i == args.length - 1)
{
System.out.println(
"The \"-c\" option expects a directory parameter.");
returnAction = ACTION_ERROR;
break;
}
handleConfigArg(args[++i]);
continue;
}
else if (args[i].equals("--multiple") || args[i].equals("-m"))
{
returnAction = ACTION_CONTINUE_LOCK_DISABLED;
continue;
}
else if (args[i].startsWith("--splash="))
{
// do nothing already handled by startup script/binary
continue;
}
else if (args[i].startsWith("--notray"))
{
System.setProperty("disable-tray", "true");
continue;
}
//if this is the last arg and it's not an option then it's probably
//an URI
else if ( i == args.length - 1
&& !args[i].startsWith("-"))
{
handleUri(args[i]);
}
else
{
handleUnknownArg(args[i]);
errorCode = ERROR_CODE_UNKNOWN_ARG;
returnAction = ACTION_ERROR;
break;
}
}
return returnAction;
}
/**
* Forces use of IPv6 addresses where possible. (This should one day
* become a default mode of operation.)
*/
private void handleIPv6Enforcement()
{
System.setProperty("java.net.preferIPv4Stack", "false");
System.setProperty("java.net.preferIPv6Addresses", "true");
}
/**
* Forces non-support for IPv6 and use of IPv4 only.
*/
private void handleIPv4Enforcement()
{
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
}
/**
* Passes uriArg to our uri manager for handling.
*
* @param uri the uri that we'd like to pass to
*/
private void handleUri(String uri)
{
if (logger.isTraceEnabled())
logger.trace("Handling uri "+ uri);
argDelegator.handleUri(uri);
}
/**
* Instructs SIP Communicator to print logging messages to the console.
*
* @param arg the debug arg which we are not really using in this method.
*/
private void handleDebugArg(String arg)
{
//first enable standard out printing
ScStdOut.setStdOutPrintingEnabled(true);
//then find a console handler (or create a new one) and set its level
//to FINEST
java.util.logging.Logger rootLogger
= java.util.logging.Logger.getAnonymousLogger().getParent();
ConsoleHandler conHan = null;
for (Handler handler : rootLogger.getHandlers())
{
if(handler instanceof ConsoleHandler)
{
conHan = (ConsoleHandler) handler;
break;
}
}
if(conHan == null)
{
conHan = new ConsoleHandler();
rootLogger.addHandler(conHan);
}
//conHan.setLevel(Level.SEVERE);
}
/**
* Instructs SIP Communicator change the location of its home dir.
*
* @param configArg the arg containing the location of the new dir.
*
* @return either ACTION_ERROR or ACTION_CONTINUE depending on whether or
* not parsing the option went fine.
*/
private int handleConfigArg(String configArg)
{
if (configArg.startsWith("--config="))
{
configArg = configArg.substring("--config=".length());
}
File configDir = new File(configArg);
configDir.mkdirs();
if(!configDir.isDirectory())
{
System.out.println("Failed to create directory " + configArg);
errorCode = ERROR_CODE_CREATE_DIR_FAILED;
return ACTION_ERROR;
}
System.setProperty(PNAME_SC_HOME_DIR_LOCATION, configDir.getParent());
System.setProperty(PNAME_SC_CACHE_DIR_LOCATION, configDir.getParent());
System.setProperty(PNAME_SC_LOG_DIR_LOCATION, configDir.getParent());
System.setProperty(PNAME_SC_HOME_DIR_NAME, configDir.getName());
//we instantiated our class logger before we had a chance to change
//the dir so we need to reset it now.
logger.reset();
return ACTION_CONTINUE;
}
/**
* Prints the name and the version of this application. This method uses the
* version.properties file which is created by ant during the build process.
* If this file does not exist the method would print a default name and
* version string.
*/
private void handleVersionArg()
{
String name = getApplicationName();
String version = getVersion();
if (name == null || name.trim().length() == 0)
{
name = "Jitsi";
}
if (version == null || version.trim().length() == 0)
{
version = "build.by.SVN";
}
System.out.println(name + " " + version);
}
/**
* Returns the version of the SIP Communicator instance that we are
* currently running.
*
* @return a String containing the version of the SC instance we are
* currently running.
*/
private String getVersion()
{
String version = versionProperties.getProperty(PNAME_VERSION);
return version == null
? "build.by.SVN"
: version;
}
/**
* Returns the name of the application. That should be Jitsi
* most of the time but who knows ..
*
* @return the name of the application (i.e. SIP Communicator until we
* change our name some day.)
*/
private String getApplicationName()
{
String name = versionProperties.getProperty(PNAME_APPLICATION_NAME);
return name == null
? "Jitsi"
: name;
}
/**
* Returns the package name of the application. That should be jitsi
* most of the time but who knows ..
*
* @return the package name of the application.
*/
private String getPackageName()
{
String name = versionProperties.getProperty(PNAME_PACKAGE_NAME);
return name == null
? "jitsi"
: name;
}
/**
* Prints an error message and then prints the help message.
*
* @param arg the unknown argument we need to print
*/
public void handleUnknownArg(String arg)
{
System.out.println("Unknown argument: " + arg);
handleHelpArg();
}
/**
* Prints a help message containing usage instructions and descriptions of
* all options currently supported by Jitsi.
*/
public void handleHelpArg()
{
handleVersionArg();
System.out.println("Usage: " + getPackageName() + " [OPTIONS] [uri-to-call]");
System.out.println("");
System.out.println(" -c, --config=DIR use DIR for config files");
System.out.println(" -d, --debug print debugging messages to stdout");
System.out.println(" -h, --help display this help message and exit");
System.out.println(" -m, --multiple do not ensure single instance");
System.out.println(" -6, --ipv6 prefer IPv6 addresses where possible only");
System.out.println(" -4, --ipv4 forces use of IPv4 only");
System.out.println(" -v, --version display the current version and exit");
System.out.println(" -n, --notray disable the tray icon and show the GUI");
}
/**
* Returns an error code that could help identify an error when
* handleArgs returns ACTION_ERROR or 0 if everything went fine.
*
* @return an error code that could help identify an error when
* handleArgs returns ACTION_ERROR or 0 if everything went fine.
*/
public int getErrorCode()
{
return errorCode;
}
/**
* Sets the delegationPeer that would be handling all URIs passed
* as command line arguments to SIP Communicator.
*
* @param delegationPeer the delegationPeer that should handle URIs
* or null if we'd like to unset a previously set peer.
*/
public void setDelegationPeer(ArgDelegationPeer delegationPeer)
{
this.argDelegator.setDelegationPeer(delegationPeer);
}
/**
* Called when the user has tried to launch a second instance of
* SIP Communicator while a first one was already running. This method
* only handles arguments that need to be handled by a running instance
* of SIP Communicator assuming that simple ones such as "--version" or
* "--help" have been handled by the calling instance.
*
* @param args the args that we need to handle.
*/
public void handleConcurrentInvocationRequestArgs(String[] args)
{
//if we have 1 or more args then we only care about the last one since
//the only interinstance arg we currently know how to handle are URIs.
//Change this if one day we implement fun stuff like inter instance
//command execution.
if(args.length >=1
&& !args[args.length -1].startsWith("-"))
{
this.argDelegator.handleUri(args[args.length -1]);
}
//otherwise, we simply notify SC of the request so that it could do
//stuff like showing the contact list for example.
else
{
this.argDelegator.handleConcurrentInvocationRequest();
}
}
}