// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// 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
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: ApplicationLauncher.java,v 1.240 2008/12/25 07:49:29 spyromus Exp $
//
package com.salas.bb.core;
import com.jgoodies.looks.*;
import com.jgoodies.looks.plastic.PlasticLookAndFeel;
import com.jgoodies.uif.AbstractFrame;
import com.jgoodies.uif.application.Application;
import com.jgoodies.uif.application.ApplicationConfiguration;
import com.jgoodies.uif.application.ApplicationDescription;
import com.jgoodies.uif.application.ResourceIDs;
import com.jgoodies.uif.splash.ImageSplash;
import com.jgoodies.uif.splash.Splash;
import com.jgoodies.uif.splash.SplashProvider;
import com.jgoodies.uif.util.ResourceUtils;
import com.jgoodies.uif.util.SystemUtils;
import com.jgoodies.uifextras.convenience.DefaultApplicationStarter;
import com.jgoodies.uifextras.convenience.SetupManager;
import com.limegroup.gnutella.gui.GURLHandler;
import com.salas.bb.core.actions.ActionsTable;
import com.salas.bb.core.actions.EDTLockupHandler;
import com.salas.bb.core.actions.EDTOverloadReporter;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.installation.Installer;
import com.salas.bb.installation.wizard.WelcomePage;
import com.salas.bb.persistence.IPersistenceManager;
import com.salas.bb.persistence.PersistenceException;
import com.salas.bb.persistence.PersistenceManagerConfig;
import com.salas.bb.plugins.Manager;
import com.salas.bb.service.ServicePreferences;
import com.salas.bb.utils.*;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.ipc.IPC;
import com.salas.bb.utils.locker.Locker;
import com.salas.bb.utils.net.auth.CachingAuthenticator;
import com.salas.bb.utils.net.auth.IPasswordsRepository;
import com.salas.bb.utils.osx.OSXSupport;
import com.salas.bb.utils.uif.NoFlickerSplashWrapper;
import com.salas.bb.utils.uif.TipOfTheDay;
import com.salas.bb.utils.uif.UifUtilities;
import com.salas.bb.utils.uif.images.Cache;
import com.salas.bb.utils.uif.images.ImageFetcher;
import com.salas.bb.utils.watchdogs.EventQueueWithWD;
import com.salas.bb.views.mainframe.MainFrame;
import com.salas.bb.views.stylesheets.StylesheetManager;
import com.salas.bbutilities.VersionUtils;
import org.apache.xmlrpc.XmlRpc;
import sun.net.NetworkClient;
import javax.net.ssl.*;
import javax.swing.*;
import javax.swing.plaf.DimensionUIResource;
import javax.swing.plaf.FontUIResource;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.ProxySelector;
import java.net.URL;
import java.nio.channels.FileLock;
import java.security.Permission;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
/**
* This is the class that starts and runs everything in Blog Bridge.
*/
public class ApplicationLauncher extends DefaultApplicationStarter
{
public static boolean proVersion = false;
private static final Logger LOG = Logger.getLogger(ApplicationLauncher.class.getName());
private static final String MINIMAL_COMPATIBLE_VERSION = "1.9";
/** Current version. */
private static final String CURRENT_VERSION = "6.8";
private static final int DEFAULT_ICON_WIDTH = 18;
private static final int DEFAULT_ICON_HEIGHT = 18;
private static final int TOOLBAR_SEP_WIDTH = 6;
private static final int TOOLBAR_SEP_HEIGHT = 18;
private static final int LOGGING_LIMIT = 50000;
private static final int LOGGING_LOOP_COUNT = 5;
/**
* Properties key for installation ID.
* @noinspection HardCodedStringLiteral
*/
public static final String KEY_INSTALLATION_ID = "InstallationID";
/**
* Properties key for installation version.
* @noinspection HardCodedStringLiteral
*/
public static final String KEY_INSTALLATION_VERSION = "InstallationVersion";
/**
* Properties key for number of runs.
* @noinspection HardCodedStringLiteral
*/
private static final String KEY_RUNS = "Runs";
/**
* Report errors flag. System property key.
* @noinspection HardCodedStringLiteral
*/
private static final String KEY_REPORT_ERRORS = "report.errors";
/**
* Logging config file.
* @noinspection HardCodedStringLiteral
*/
private static final String KEY_LOGGING_CONFIG_FILE = "java.util.logging.config.file";
/**
* Bundled logging properties resource.
* @noinspection HardCodedStringLiteral
*/
private static final String RESOURCE_LOGGING_PROPERTIES = "logging.properties";
/**
* Weekly release type name.
* @noinspection HardCodedStringLiteral
*/
private static final String RELEASE_TYPE_WEEKLY = "weekly";
/** Name of backups directory within user's BB directory. */
private static final String BACKUPS_DIR_NAME = "backups";
private static final String MSG_SPLASH_ENSURE_ERROR = "Wasn't able to manipulate splash screen visiblity.";
private static final String MSG_PARENT_APP_LAUNCHER_ERROR = "Failed to call parent app launcher.";
private static ApplicationLauncher globalAppLauncher;
private static String releaseType;
private static String prefix;
private static boolean usingFinalData;
private static ApplicationDescription description;
private static ApplicationConfiguration configuration;
private static ConnectionState connectionState;
private static String contextPath;
private static FileLock instanceLock;
private static IPC ipc;
private static File socketFile;
private static String urlToOpen;
static
{
connectionState = new ConnectionState();
}
/**
* Returns connection state object.
*
* @return connection state object.
*/
public static ConnectionState getConnectionState()
{
return connectionState;
}
/**
* Returns the URL to open when the initialization is finished.
*
* @return URL specified in the command-line or <code>NULL</code>.
*/
public static String getURLToOpen()
{
return urlToOpen;
}
/**
* Configure LOG handling to send all Log messages of Warning or worse to the default LOG file.
* 1. if -D java.util.logging.config.file is set, use that as the log configuration
* 2. if not then locate logging.properties in classpath otherwise error.
* <p/>
* Keep LOGGING_LOOP_COUNT console.* files around
*/
protected void configureLogging()
{
// Read configuration from specified file or default resource
LogManager lm = LogManager.getLogManager();
String fname = System.getProperty(KEY_LOGGING_CONFIG_FILE);
boolean inited = false;
if (fname != null && new File(fname).exists())
{
try
{
// default configuration reader will examine the property and load configuration
lm.readConfiguration();
inited = true;
} catch (IOException e)
{
System.err.println(Strings.error("logging.using.overriden.configuration.errored"));
e.printStackTrace();
}
}
// If configuration still not read use internal production configuration
if (!inited)
{
final InputStream url =
this.getClass().getClassLoader().getResourceAsStream(RESOURCE_LOGGING_PROPERTIES);
try
{
lm.readConfiguration(url);
} catch (IOException e)
{
System.err.println(Strings.error("logging.unable.to.use.production.configuration"));
e.printStackTrace();
}
}
Logger aLogger = Logger.getLogger("");
// Setup file logger
try
{
String pattern = getDefaultLogFilePattern();
ensureParentDirectoryExists(pattern);
FileHandler handler = new FileHandler(pattern, LOGGING_LIMIT, LOGGING_LOOP_COUNT);
handler.setFormatter(new TinyFormatter());
aLogger.addHandler(handler);
} catch (IOException e)
{
System.err.println(MessageFormat.format(Strings.error("logging.failed.to.configure"),
e.getLocalizedMessage()));
}
// Setup reporting logger (can't use logging.properties as this handler is not
// on the system path under JWS) :(
if (System.getProperty(KEY_REPORT_ERRORS) != null)
{
ReportingLogHandler reportingHandler = new ReportingLogHandler();
reportingHandler.setLevel(Level.SEVERE);
aLogger.addHandler(reportingHandler);
}
}
/**
* Returns <code>TRUE</code> if release type is weekly.
*
* @return <code>TRUE</code> if release type is weekly.
*/
public static boolean isWeekly()
{
return RELEASE_TYPE_WEEKLY.equalsIgnoreCase(releaseType);
}
/**
* Returns <code>TRUE</code> if this build uses final data directory.
*
* @return <code>TRUE</code> if this build uses final data directory.
*/
public static boolean isUsingFinalData()
{
return usingFinalData;
}
/**
* Returns <code>TRUE</code> if auto-updates features should be enabled.
*
* @return <code>TRUE</code> if auto-updates features should be enabled.
*/
public static boolean isAutoUpdatesEnabled()
{
return usingFinalData && !BrowserLauncher.isRunningUnderJWS();
}
/**
* Returns the type of release (weekly, stable, or final).
*
* @return the type of release.
*/
public static String getReleaseType()
{
return releaseType;
}
/**
* Returns prefix (working folder path relative to user home).
*
* @return prefix.
*/
public static String getPrefix()
{
return prefix;
}
/**
* Configure Log Handling so that we never display a Message Box via logger calls.
*/
protected void addMessageHandler()
{
}
/**
* UIF Framework call to create Main window frame during initialization.
*
* @return Main Window Frame.
*/
protected AbstractFrame createMainFrame()
{
MainFrame mainFrame = new MainFrame(connectionState);
Application.setDefaultParentFrame(mainFrame);
GlobalController.SINGLETON.setMainFrame(mainFrame);
return mainFrame;
}
/**
* Once only initialization for the whole application.
*/
protected void initializeActions()
{
Splash.setNote(Strings.message("startup.registering.actions"), 60);
ActionsTable.getInstance().registerActions(connectionState);
}
/**
* Configuring general networking.
*/
private void configureNetwork()
{
// Configure XML-RPC package to use Unicode
XmlRpc.setEncoding("UTF-8");
ApplicationDescription description = Application.getDescription();
System.setProperty("http.agent", description.getProductText() +
" (" + description.getVendorURL() + ") " + System.getProperty("java.version"));
System.setProperty("http.agent.discoverer", description.getProductText() + " Discoverer" +
" (" + description.getVendorURL() + ") " + System.getProperty("java.version"));
// Create and initialize caching authenticator
IPersistenceManager persistenceManager = PersistenceManagerConfig.getManager();
IPasswordsRepository passwordsRepository = persistenceManager.getPasswordsRepository();
Authenticator.setDefault(new CachingAuthenticator(passwordsRepository));
// This is a very kludgy way to propagate default reading and connection timeouts.
// It's the only way for now (JRE 1.4.x) to let this values be used when running under JWS.
// NOTE: NetworkClient is not part of public API and can be removed/changed in a future.
new NetworkClient()
{
{
Integer readTimeoutInt = Integer.getInteger("sun.net.client.defaultReadTimeout");
defaultSoTimeout = (readTimeoutInt == null) ? -1 : readTimeoutInt;
Integer connectTimeoutInt = Integer.getInteger("sun.net.client.defaultConnectTimeout");
defaultConnectTimeout = (connectTimeoutInt == null) ? -1 : connectTimeoutInt;
}
};
// Sets proxy selector to use for communication
ProxySelector.setDefault(CustomProxySelector.INSTANCE);
}
/**
* Disables verification of host name.
*/
private void disableSSLHostNameVerification()
{
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
{
public boolean verify(String string, SSLSession sslSession)
{
return true;
}
});
}
/**
* Disables verification of SSL certificate.
*/
private void disableSSLCertificates()
{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]
{
new X509TrustManager()
{
public java.security.cert.X509Certificate[] getAcceptedIssuers()
{
return null;
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs,
String authType)
{
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
String authType)
{
}
}
};
// Install the all-trusting trust manager
try
{
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e)
{
if (LOG.isLoggable(Level.WARNING))
{
LOG.log(Level.WARNING, Strings.error("ssl.probably.certificates.validation.was.not.disabled"), e);
}
}
}
// Install alternative event queue and necessary performance timers
private void installWatchdogs()
{
final String watchdogsDisable = System.getProperty("watchdogs.disable");
if (watchdogsDisable == null || !watchdogsDisable.equalsIgnoreCase("true"))
{
EventQueueWithWD queue = EventQueueWithWD.install();
queue.addWatchdog(2000, new EDTOverloadReporter(Level.WARNING), false);
queue.addWatchdog(3 * 60 * 1000, new EDTLockupHandler(), true);
}
}
/** Install our custom security manager. */
private void configureSecurityManager()
{
try
{
System.setSecurityManager(new SecurityManager()
{
/**
* Throws a <code>SecurityException</code> if the requested
* access, specified by the given permission, is not permitted based
* on the security policy currently in effect.
*
* @param perm the requested permission.
*/
public void checkPermission(Permission perm)
{
// This implementation allows all connections to any hosts.
// It's necessary to fix the unknown problem with SecurityException
// raising when trying to fetch some images (see BT #247 GUI: Gizmodo images)
}
/**
* Throws a <code>SecurityException</code> if the
* specified security context is denied access to the resource
* specified by the given permission.
*
* @param perm the specified permission
* @param context a system-dependent security context.
*/
public void checkPermission(Permission perm, Object context)
{
// This implementation allows all connections to any hosts.
// It's necessary to fix the unknown problem with SecurityException
// raising when trying to fetch some images (see BT #247 GUI: Gizmodo images)
}
});
} catch (SecurityException e)
{
// If we are unable to reinstall the SecurityManager, then it's better to report it
LOG.log(Level.SEVERE, "Unable to install the security manager.", e);
}
}
/** Configures GUI part. */
protected void configureUI()
{
if (LOG.isLoggable(Level.FINE)) LOG.fine("configureUI starting...");
// Configure displaying anti-aliased text (SHOULD BE RUN BEFORE SWING INIT)
configureAntiAliasing();
// Configure Mac OS X dock icon (we had problems with JWS dock icon: ugly and cannot be badged)
if (isMac()) OSXSupport.setApplicationIcon();
// Images cache
Cache imagesCache = new Cache(new File(getContextPath() + "cache"), 20000000);
ImageFetcher.setCache(imagesCache);
// Stylesheets
try
{
URL baseStylesheetURL = new URL("http://www.blogbridge.com/bbstyles/");
StylesheetManager.init(new File(getContextPath() + "stylesheets"), baseStylesheetURL);
} catch (MalformedURLException e)
{
// Impossible to have
}
Options.setDefaultIconSize(new Dimension(DEFAULT_ICON_WIDTH, DEFAULT_ICON_HEIGHT));
UIManager.put("ToolBar.separatorSize",
new DimensionUIResource(TOOLBAR_SEP_WIDTH, TOOLBAR_SEP_HEIGHT));
Options.setPopupDropShadowEnabled(true);
// In Java Web Start, indicate where to find the l&f classes. Set the Swing class loader.
UIManager.put("ClassLoader", LookUtils.class.getClassLoader());
// Make Swing update everything dynamically during resize operations.
Toolkit.getDefaultToolkit().setDynamicLayout(true);
super.configureUI();
configureFonts();
if (LOG.isLoggable(Level.FINE))
{
LOG.fine("configureUI done...");
}
OSXSupport.setupLAF();
// Custom icons
UIManager.getLookAndFeelDefaults().put("html.missingImage",
new UIDefaults.LazyValue() {
/**
* Creates the actual value retrieved from the <code>UIDefaults</code> table. When an object that implements
* this interface is retrieved from the table, this method is used to create the real value, which is then
* stored in the table and returned to the calling method.
*
* @param table a <code>UIDefaults</code> table
*
* @return the created <code>Object</code>
*/
public Object createValue(UIDefaults table)
{
return ResourceUtils.getIcon("application.icon");
}
});
}
/**
* Configures fonts.
*/
private void configureFonts()
{
Options.setUseSystemFonts(true);
if (SystemUtils.IS_OS_LINUX)
PlasticLookAndFeel.setFontPolicy(new SmallPlasticFontPolicy());
}
/**
* Configures the splash component: reads the splash image, then opens an ImageSplash.
*/
protected void configureSplash()
{
// Create image splash
Image image = ResourceUtils.getIcon(ResourceIDs.SPLASH_IMAGE).getImage();
ImageSplash imageSplash = new ImageSplash(image, true);
imageSplash.setNoteEnabled(true);
// Wrap with de-flicker "filter"
SplashProvider splashWrapper = new NoFlickerSplashWrapper(imageSplash);
Splash.setProvider(splashWrapper);
}
private static boolean isMac()
{
String osName = System.getProperty("os.name");
return osName != null && osName.startsWith("Mac OS");
}
/**
* Use the lovely JGoodies booting mechanism to get the thing launched.
*
* @param arguments standard main parameters.
*/
public static void main(String[] arguments)
{
if (isMac()) GURLHandler.getInstance().register();
initReleaseTypeAndPrefix(arguments, System.getProperty("release.type"),
System.getProperty("working.folder"));
overrideLocale();
String urlToOpen = checkCommandLineForURL(arguments);
socketFile = new File(getContextPath() + "/application.sock");
File lockerFile = new File(getContextPath() + "/application.lock");
instanceLock = Locker.tryLocking(lockerFile);
if (instanceLock != null)
{
proVersion = System.getProperty("pro") != null;
globalAppLauncher = new ApplicationLauncher();
globalAppLauncher.boot(getDescription(), getConfiguration());
// A few Mac Specific things that have to be done really early
OSXSupport.setAboutMenuName("BlogBridge");
ApplicationLauncher.urlToOpen = urlToOpen;
} else if (urlToOpen != null)
{
IPC.sendCommand(socketFile, "subscribe", new String[] { urlToOpen });
System.exit(0);
} else
{
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
Strings.message("startup.only.one.instance.allowed"),
"BlogBridge", JOptionPane.WARNING_MESSAGE);
System.exit(1);
}
}
/**
* Overrides system locale with English if the flag is set to use English.
*/
private static void overrideLocale()
{
boolean alwaysUseEnglish = Preferences.userRoot().node(prefix).getBoolean(UserPreferences.PROP_ALWAYS_USE_ENGLISH,
UserPreferences.DEFAULT_ALWAYS_USE_ENGLISH);
if (alwaysUseEnglish) Locale.setDefault(new Locale("en"));
}
/**
* Gets URL from the command line.
*
* @param arguments arguments.
*
* @return URL or <code>NULL</code>.
*/
static String checkCommandLineForURL(String[] arguments)
{
String urlStr = null;
for (int i = 0; urlStr == null && i < arguments.length; i++)
{
String arg = arguments[i];
if ("-open".equals(arg) && arguments.length > i)
{
arg = arguments[++i];
}
URL url = IPC.argToURL(StringUtils.cleanDraggedURL(arg));
if (url != null) urlStr = url.toString();
}
return urlStr;
}
static void initReleaseTypeAndPrefix(String[] arguments, String propReleaseType,
String propWorkingFolder)
{
String relType = propReleaseType;
String pref = propWorkingFolder;
if (pref != null)
{
pref = "bb/" + pref;
if (relType == null) relType = pref;
} else
{
if (arguments.length > 0)
{
pref = arguments[0];
int i = pref.indexOf('/');
if (i > -1) relType = pref.substring(i + 1).trim();
if (relType == null || relType.length() == 0) relType = "Final";
} else
{
pref = "bb/final";
relType = "Final";
}
}
releaseType = StringUtils.capitalise(relType);
prefix = pref;
usingFinalData = "bb/final".equalsIgnoreCase(prefix);
}
/**
* Transfers anti-aliasing property from user preferences to the Swing-known place.
*/
private static void configureAntiAliasing()
{
Preferences prefs = Application.getUserPreferences();
boolean aaText = prefs.getBoolean(UserPreferences.PROP_AA_TEXT, false);
System.getProperties().put("swing.aatext", Boolean.toString(aaText));
}
/**
* Creates application description object.
*
* @return application description.
*/
private static synchronized ApplicationDescription getDescription()
{
if (description == null)
{
description = new ApplicationDescription(
"BlogBridge", // Application short name
"BlogBridge", // Application long name
CURRENT_VERSION, // Version
CURRENT_VERSION + " - " + getMonthYear(), // Full version
Strings.message("appdescriptor.description"), // Description
"\u00a9 " + getYear() + " Salas Associates, All Rights Reserved.", // Copyright
"Salas Associates, Inc.", // Vendor
"http://www.blogbridge.com/", // Vendor URL
"info@blogbridge.com"); // Vendor email
}
return description;
}
/**
* Returns current month and year. For example, "Aug 2005".
*
* @return month and year.
*/
private static String getMonthYear()
{
return new SimpleDateFormat("MMM yyyy").format(new Date());
}
/**
* Returns current year.
*
* @return year.
*/
private static String getYear()
{
return new SimpleDateFormat("yyyy").format(new Date());
}
/**
* Return JGoodies UIF ApplicationConfiguration for specified prefix. The prefix is used as a
* subdirectory under the .bb directory to have separate persistent areas, and is also used as
* a prefix node in the Preferences (registery) to keep those separate as well.
*
* @return application configuration object.
*/
public static synchronized ApplicationConfiguration getConfiguration()
{
if (configuration == null)
{
configuration = new ApplicationConfiguration(
prefix, // Root node for prefs and logs
"", // resource.properties URL
"docs/Help.hs", // Helpset URL
"docs/tips/index.txt"); // Tips index path
}
return configuration;
}
/**
* Configures the <code>SetupManager</code>. The default does nothing. Sublcasses can, for
* example modify the welcome panel.
*/
protected void configureSetupManager()
{
SetupManager.setWelcomePanel(new WelcomePage(null, null));
}
/**
* Works that needs to be done *after* MainFrame has been displayed.
*/
protected void launchApplication()
{
incrementInstallationRuns();
com.salas.bbutilities.opml.export.Exporter.setGenerator("OPML generated by BlogBridge " + CURRENT_VERSION +
" (http://www.blogbridge.com/)");
configureSecurityManager();
// Start plug-ins
Splash.setNote(Strings.message("startup.loading.plugins"), 2);
File f = new File(getContextPath(), "plugins");
Manager.initialize(f, Application.getUserPreferences());
Manager.loadPackages();
disableSSLHostNameVerification();
disableSSLCertificates();
Splash.setNote(Strings.message("startup.starting.ipc"), 10);
configureIPC();
Splash.setNote(Strings.message("startup.configuring.network"), 12);
configureNetwork();
Splash.setNote(Strings.message("startup.installing.performance.timers"), 15);
installWatchdogs();
Splash.setNote(Strings.message("startup.checking.model"), 20);
GlobalModel newVersionModel = checkForNewVersion();
GlobalController.SINGLETON.restorePreferences();
UifUtilities.invokeAndWait(new Runnable()
{
public void run()
{
ApplicationLauncher.super.launchApplication();
}
}, MSG_PARENT_APP_LAUNCHER_ERROR, Level.SEVERE);
Splash.setNote(Strings.message("startup.opening.database"), 40);
IPersistenceManager manager = PersistenceManagerConfig.getManager();
try
{
manager.init();
} catch (PersistenceException e)
{
LOG.log(Level.SEVERE, Strings.error("failed.to.initialize.database"), e);
// It's not possible to continue working with database failing.
System.exit(1);
}
configureShutdownHook();
Splash.setNote(Strings.message("startup.restoring.state.from.database"), 80);
// Restore last saved persistent state. If none, then gen fake data.
GlobalController.SINGLETON.restorePersistentState(newVersionModel);
Splash.setNote(Strings.message("startup.starting.connection.checking"), 85);
startConnectionChecker();
Splash.setNote(Strings.message("startup.loading.tip.of.the.day"), 90);
checkForOpenTipOfTheDayDailog();
splashEnsureOpenClosed(false);
Splash.setProvider(null);
}
/**
* Enabled IPC by subscribing the model to the handler and enabling GURL handler on Mac.
* The handler is registered during the very start and may be already
* reported some URL. Now, when it's enabled, it will pass it through to
* the controller.
*/
public static void enableIPC()
{
ipc.addListener(GlobalController.SINGLETON);
// Enable GURL listener on Mac
if (isMac()) GURLHandler.getInstance().enable();
}
/**
* Configures IPC.
*/
private void configureIPC()
{
try
{
ipc = new IPC(socketFile);
} catch (IOException e)
{
LOG.log(Level.WARNING, "Failed to initialize IPC", e);
}
if (SystemUtils.IS_OS_MAC) OSXSupport.setupIPC(ipc);
}
/**
* Configures hook for application shutdown.
*/
private void configureShutdownHook()
{
Runtime.getRuntime().addShutdownHook(new Thread()
{
/**
* Invoked when shutdown of application is in progress.
*/
public void run()
{
// Closing database before termination
PersistenceManagerConfig.getManager().shutdown();
try
{
instanceLock.release();
} catch (IOException e)
{
// Not a big deal
}
// Close IPC channel
ipc.close();
}
});
}
private void checkForOpenTipOfTheDayDailog()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
TipOfTheDay.showRandomTip();
}
});
}
/**
* Initializes and starts connection checker.
*/
private void startConnectionChecker()
{
GlobalModel model = GlobalController.SINGLETON.getModel();
ServicePreferences prefs = model.getServicePreferences();
String version = globalAppLauncher.getApplicationDescription().getVersion();
long installationId = getInstallationId();
int runs = getInstallationRuns();
String accountEmail = prefs.getEmail();
String accountPassword = prefs.getPassword();
ConnectionChecker connectionChecker = new ConnectionChecker(
version, installationId, runs, accountEmail, accountPassword, connectionState);
connectionChecker.start();
}
/**
* Returns number of runs for current installation.
*
* @return runs count.
*/
public static int getInstallationRuns()
{
return Application.getUserPreferences().getInt(KEY_RUNS, 0);
}
/**
* Clears number of runs for installation.
*/
static void clearInstallationRuns()
{
Application.getUserPreferences().putInt(KEY_RUNS, 0);
}
/**
* Increments number of runs for installation.
*/
static void incrementInstallationRuns()
{
Application.getUserPreferences().putInt(KEY_RUNS, getInstallationRuns() + 1);
}
/**
* Returns installation ID. If ID is not currently defined defines it and stores.
*
* @return installation ID.
*/
public static long getInstallationId()
{
return Application.getUserPreferences().getLong(KEY_INSTALLATION_ID, -1);
}
/**
* Checks if migration procedures are necessary.
*
* @return newly created model in case of new installation or <code>null</code>.
*/
private GlobalModel checkForNewVersion()
{
long installationId = getInstallationId();
String forceInstallationVal = System.getProperty("force.installation");
String installationVersion = forceInstallationVal != null
? Constants.EMPTY_STRING
: Application.getUserPreferences().get(KEY_INSTALLATION_VERSION,
Constants.EMPTY_STRING);
GlobalModel model = null;
if (installationId == -1 ||
!globalAppLauncher.getApplicationDescription().getVersion().equals(installationVersion))
{
// If previous version is less than minimal version compatible with current
// then continue with full installation wizard.
if (VersionUtils.versionCompare(installationVersion, MINIMAL_COMPATIBLE_VERSION) < 0)
{
model = resetIncompatibleDatabase();
if (model == null) System.exit(0);
}
installationId = generateInstallationId();
Application.getUserPreferences().putLong(KEY_INSTALLATION_ID, installationId);
Application.getUserPreferences().put(KEY_INSTALLATION_VERSION,
globalAppLauncher.getApplicationDescription().getVersion());
clearInstallationRuns();
}
return model;
}
/**
* Calls installer in EDT and waits for it to finish.
*
* @return new model or <code>NULL</code> in case of failure.
*/
private GlobalModel resetIncompatibleDatabase()
{
splashEnsureOpenClosed(false);
Installer installer = new Installer();
GlobalModel model = installer.perform(getContextPath());
splashEnsureOpenClosed(true);
return model;
}
/**
* Hides / shows splash screen.
*
* @param open <code>TRUE</code> to ensure that the splash screen is shown.
*/
private static void splashEnsureOpenClosed(final boolean open)
{
UifUtilities.invokeAndWait(new Runnable()
{
public void run()
{
if (open) Splash.ensureOpen(); else Splash.ensureClosed();
}
}, MSG_SPLASH_ENSURE_ERROR, Level.WARNING);
}
/**
* Generates installation ID with high level of uniqueness.
*
* @return installation ID.
*/
private long generateInstallationId()
{
// Using current time in millis here as it has high level enough.
return System.currentTimeMillis();
}
/**
* Computes where on the user's machine we will place various state files.
*
* @return context path.
*/
public synchronized static String getContextPath()
{
if (contextPath == null)
{
String userHomePath = System.getProperty("user.home");
String nodePath = '.' + getConfiguration().getPreferencesRootName();
contextPath = userHomePath + File.separatorChar + nodePath + File.separatorChar;
}
return contextPath;
}
/**
* Returns path to backups directory.
*
* @return backups directory path.
*/
public static String getBackupsPath()
{
return getContextPath() + BACKUPS_DIR_NAME;
}
/**
* Returns current application version.
*
* @return current version.
*/
public static String getCurrentVersion()
{
return CURRENT_VERSION;
}
/**
* Small font plastic policy.
*/
private static class SmallPlasticFontPolicy implements FontPolicy
{
/**
* Returns fonts for a given LAF with given defaults.
*
* @param laf LAF name.
* @param uiDefaults defaults.
*
* @return fonts set.
*/
public FontSet getFontSet(String laf, UIDefaults uiDefaults)
{
FontPolicy policy = FontPolicies.getDefaultPlasticPolicy();
int size = LookUtils.IS_LOW_RESOLUTION ? 11 : 12;
FontSet set = policy.getFontSet(laf, uiDefaults);
set = FontSets.createDefaultFontSet(
ensureSize(set.getControlFont(), size),
ensureSize(set.getMenuFont(), size),
ensureSize(set.getTitleFont(), size));
return set;
}
/**
* Ensures that the size of the font equals to the given or derives the font.
*
* @param font font to check.
* @param size desired size.
*
* @return font.
*/
private static Font ensureSize(FontUIResource font, int size)
{
return font.getSize() == size ? font : font.deriveFont((float)size);
}
}
}