/*
* Jajuk
* Copyright (C) The Jajuk Team
* http://jajuk.info
*
* 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 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.
*
*/
package org.jajuk.services.core;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.swing.SwingUtilities;
import org.jajuk.ui.wizard.FirstTimeWizard;
import org.jajuk.util.Const;
import org.jajuk.util.MD5Processor;
import org.jajuk.util.Messages;
import org.jajuk.util.UpgradeManager;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.JajukRuntimeException;
import org.jajuk.util.log.Log;
/**
* Multi-session and test/final mode facilities.
*/
public class SessionService {
/** Debug mode. */
private static boolean bIdeMode = false;
/** Test mode. */
private static boolean bTestMode = false;
/** Workspace PATH*. */
private static String workspace;
/** Forced workspace location (required for some special packaging like portableapps) *. */
private static String forcedWorkspacePath = null;
/** Lock used to trigger first time wizard window close*. */
private static short[] isFirstTimeWizardClosed = new short[0];
/** Bootstrap file content as key/value format. */
private static Properties bootstrapContent = new Properties();
/** For performances, store conf root path. */
private static String confRoot;
/** Boostrap file test workspace path key. */
private static final String KEY_TEST = "test";
/** Boostrap file final workspace path key. */
private static final String KEY_FINAL = "final";
/** First time wizard instance if required. */
private static FirstTimeWizard ftw;
/**
* private constructor for utility class with only static methods.
*/
private SessionService() {
super();
}
/**
* Checks if is ide mode.
*
* @return true, if is ide mode
*/
public static boolean isIdeMode() {
return bIdeMode;
}
/**
* Checks if is test mode.
*
* @return true, if is test mode
*/
public static boolean isTestMode() {
return bTestMode;
}
/**
* Gets the workspace.
*
* @return the workspace
*/
public static String getWorkspace() {
return workspace;
}
/**
* Sets the test mode.
*
* @param bTestMode the new test mode
*/
public static void setTestMode(boolean bTestMode) {
SessionService.bTestMode = bTestMode;
}
/**
* Sets the workspace.
*
* @param workspace the new workspace
*/
public static void setWorkspace(String workspace) {
SessionService.workspace = workspace;
if (isTestMode()) {
bootstrapContent.put(KEY_TEST, workspace);
} else {
bootstrapContent.put(KEY_FINAL, workspace);
}
// Make sure to set all paths
if (!bootstrapContent.containsKey(KEY_FINAL)) {
bootstrapContent.put(KEY_FINAL, workspace);
}
if (!bootstrapContent.containsKey(KEY_TEST)) {
bootstrapContent.put(KEY_TEST, workspace);
}
}
/**
* Walks through the command line arguments and sets flags for any one that we
* recognize.
*
* @param args The list of command line arguments that is passed to main()
*/
public static void handleCommandline(final String[] args) {
// walk through all arguments and check if there is one that we
// recognize
for (final String element : args) {
// Tells jajuk it is inside the IDE (useful to find right
// location for images and jar resources)
if (element.equals("-" + Const.CLI_IDE)) {
bIdeMode = true;
}
// Tells jajuk to use a .jajuk_test repository
// The information can be given from CLI using
// -test=[test|notest] option
if (element.equals("-" + Const.CLI_TEST)) {
bTestMode = true;
}
// Handle special workspace location
// Format : -workspace=<url of the workspace>
if (element.matches("-" + Const.CLI_WORKSPACE_LOCATION + "=.*")) {
String testedForcedWorkspace = null;
try {
StringTokenizer st = new StringTokenizer(element, "=");
st.nextToken();
testedForcedWorkspace = st.nextToken();
} catch (Exception e) {
throw new JajukRuntimeException("[BOOT] Wrong forced workspace location : "
+ testedForcedWorkspace);
}
if (testedForcedWorkspace == null || !new File(testedForcedWorkspace).canRead()) {
// Leave jajuk
throw new JajukRuntimeException("[BOOT] Wrong forced workspace location : "
+ testedForcedWorkspace);
} else {
forcedWorkspacePath = testedForcedWorkspace;
}
}
}
}
/**
* Load system properties provided when calling jvm (-Dxxx=yyy) <br>
* This is usefull for unit tests.
*/
public static void handleSystemProperties() {
// walk through all system properties and check if there is one that we
// recognize
for (final Object element : System.getProperties().keySet()) {
String key = (String) element;
String value = System.getProperty(key);
// Tells jajuk it is inside the IDE (useful to find right
// location for images and jar resources)
if (Const.CLI_IDE.equals(key) && Const.TRUE.equalsIgnoreCase(value)) {
bIdeMode = true;
}
// Tells jajuk to use a .jajuk_test repository
if (Const.CLI_TEST.equals(key) && Const.TRUE.equalsIgnoreCase(value)) {
bTestMode = true;
}
}
}
/**
* Discover the jajuk workspace by reading the bootstrap file.<br>
* Searched in this order :
* <ul>
* <li>Forced workspace path provided on command line (-workspace=...)</li>
* <li>Bootstrap file content</li>
* <li>Default path presence</li>
* <li>Human selection</li>
* </ul>
*
* @throws InterruptedException the interrupted exception
*/
public static void discoverWorkspace() throws InterruptedException {
try {
// Upgrade the bootstrap file if it exists (must be done here, not in upgrade step 1
// because of the boot sequence dependencies)
UpgradeManager.upgradeBootstrapFile();
// Use any forced workspace location given in CLI. Note that this path has
// already been validated in the handleCommand() method
if (forcedWorkspacePath != null) {
System.out.println("[BOOT] Forced workspace location : " + forcedWorkspacePath);
// If the workspace not yet exists, display the first time wizard with workspace
// location selection disabled
String forcedCollectionPath = forcedWorkspacePath + '/'
+ (isTestMode() ? ".jajuk_test_" + Const.TEST_VERSION : ".jajuk");
if (!new File(forcedCollectionPath).exists()) {
humanWorkspaceSelection();
} else {
setWorkspace(forcedWorkspacePath);
}
} else {
// Check for bootstrap file presence
final File bootstrap = new File(getBootstrapPath());
// Default collection path : ~/.jajuk
final File fDefaultCollectionPath = getDefaultCollectionPath();
// Try to find the workspace path in a bootstrap file
if (bootstrap.canRead()) {
try {
// Parse bootstrap file (XML format)
FileInputStream fis = new FileInputStream(bootstrap);
bootstrapContent.loadFromXML(fis);
String workspacePath = null;
// Compute the final workspace path
if (isTestMode()) {
workspacePath = (String) bootstrapContent.get(KEY_TEST);
} else {
workspacePath = (String) bootstrapContent.get(KEY_FINAL);
}
// Case where the file exist but doesn't contain the path lines
if (workspacePath == null) {
throw new IllegalStateException("the bootsrap file doesn't contain the path lines");
}
// Check if the repository can be found
File targetWorkspace = new File(workspacePath + '/'
+ (isTestMode() ? ".jajuk_test_" + Const.TEST_VERSION : ".jajuk"));
if (targetWorkspace.canRead()) {
setWorkspace(workspacePath);
} else {
// Use default directory but do not commit the bootstrap file because the workspace could
// be available again later, especially if located in a detachable device
System.out
.println("[BOOT] Workspace given in bootstrap file is not accessible, using home directory as a workspace");
if (!targetWorkspace.getAbsolutePath().equals(UtilSystem.getUserHome())) {
Messages.showErrorMessage(182, targetWorkspace.getAbsolutePath());
}
setWorkspace(UtilSystem.getUserHome());
}
// Bootstrap file corrupted
} catch (final Exception e) {
Log.error(e);
System.out
.println("[BOOT] Bootstrap file corrupted, using home directory as a workspace");
setWorkspace(UtilSystem.getUserHome());
// Commit the bootstrap file
commitBootstrapFile();
}
}
// No bootstrap file ? Try default directory
else if (fDefaultCollectionPath.canRead()) {
System.out
.println("[BOOT] Bootstrap file does not exist or is not readable, using home directory as a workspace");
setWorkspace(UtilSystem.getUserHome());
// Commit the bootstrap file
commitBootstrapFile();
}
// Not better ? Show a first time wizard and let user select
// the workspace (~/.jajuk by default)
else {
System.out
.println("[BOOT] Bootstrap file does not exist or is not readable and home directory is not readable neither");
humanWorkspaceSelection();
// Commit the bootstrap file
commitBootstrapFile();
}
}
} finally {
// In all cases, make sure to set a workspace
if (getWorkspace() == null) {
setWorkspace(UtilSystem.getUserHome());
}
}
}
/**
* Let user select himself the workspace path.
*/
private static void humanWorkspaceSelection() {
// If running in headless env, force /tmp directory
if (GraphicsEnvironment.isHeadless()) {
setWorkspace("/tmp");
} else {
// First time session ever
UpgradeManager.setFirstSession();
// display the first time wizard
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// default workspace displayed in the first time wizard is either the user home
// or the forced path if provided (can't be changed by the user from the wizard anyway)
String defaultWorkspacePath = UtilSystem.getUserHome();
if (forcedWorkspacePath != null) {
defaultWorkspacePath = forcedWorkspacePath;
}
ftw = new FirstTimeWizard(defaultWorkspacePath);
ftw.initUI();
}
});
// Lock until first time wizard is closed
synchronized (isFirstTimeWizardClosed) {
try {
isFirstTimeWizardClosed.wait();
} catch (InterruptedException e) {
Log.error(e);
}
}
// selection can be null if user postpone the configuration
if (ftw.getUserWorkspacePath() != null) {
setWorkspace(ftw.getUserWorkspacePath());
}
}
}
/**
* Write down the bootstrap file.
*
* @param prop : the properties to write to the file
*/
public static void commitBootstrapFile(Properties prop) {
File bootstrap = null;
try {
bootstrap = new File(getBootstrapPath());
FileOutputStream fos = new FileOutputStream(bootstrap);
// Write down the new bootstrap file
String comment = "Jajuk bootsrap file, do not edit manually, use Preference view / Advanced tab to set the workspace";
prop.storeToXML(fos, comment);
System.out.println("[BOOT] Bootstrap file written at : " + bootstrap.getAbsolutePath());
} catch (Exception e) {
// Log facilities not yet available
e.printStackTrace();
System.out.println("[BOOT] Cannot write down the boostrap file at : "
+ bootstrap.getAbsolutePath());
}
}
/**
* Write down the bootstrap file.
*/
public static void commitBootstrapFile() {
commitBootstrapFile(bootstrapContent);
}
/**
* Notify the system about the first time wizard being closed.
*/
public static void notifyFirstTimeWizardClosed() {
synchronized (isFirstTimeWizardClosed) {
isFirstTimeWizardClosed.notify();
}
}
/**
* Return destination file in cache for a given URL <br>
* We store the file using the URL's MD3 5 hash to ensure unicity and avoid
* unexpected characters in file names.
*
* @param url resource URL
*
* @return File in cache if any or null otherwise
*/
public static File getCachePath(final URL url) {
File out = null;
out = getConfFileByPath(Const.FILE_CACHE + '/' + MD5Processor.hash(url.toString()));
return out;
}
/**
* Gets the conf file by path.
*
* @param sPATH Configuration file or directory path
*
* @return the file relative to jajuk directory
*/
public static final File getConfFileByPath(final String sPATH) {
if (confRoot == null) {
String home = UtilSystem.getUserHome();
if ((getWorkspace() != null) && !getWorkspace().trim().equals("")) {
home = getWorkspace();
}
confRoot = home + '/' + (isTestMode() ? ".jajuk_test_" + Const.TEST_VERSION : ".jajuk") + '/';
}
return new File(confRoot + sPATH);
}
/**
* Return whether user provided a forced workspace on command line.
*
* @return whether user provided a forced workspace.
*/
public static boolean isForcedWorkspace() {
return (forcedWorkspacePath != null);
}
/**
* Return default workspace location.
*
* @return default workspace location
*/
public static final File getDefaultCollectionPath() {
String home = UtilSystem.getUserHome();
return new File(home + '/' + (isTestMode() ? ".jajuk_test_" + Const.TEST_VERSION : ".jajuk")
+ '/');
}
/**
* Clear locale images cache once a given size is reached.
*/
public static void clearCache() {
final File fCache = getConfFileByPath(Const.FILE_CACHE);
final File[] files = fCache.listFiles();
long totalSize = 0l;
for (final File element : files) {
totalSize += element.length();
}
if ((totalSize / 1048576) > Const.MAX_IMAGES_CACHE_SIZE) {
for (final File element : files) {
// note that this will not delete non-empty directories like last.fm cache in purpose
element.delete();
}
}
}
/**
* Return bootstrap file absolute path
*
* This bootstrap file location can be overridden by providing -bootstrap=<URL> CLI option.
*
* @param filename
*
* @return bootstrap file absolute path
*
* @filename filename of the bootstrap path
*/
public static String getBootstrapPath(String filename) {
return UtilSystem.getUserHome() + "/" + filename;
}
/**
* Return bootstrap file absolute path
*
* It also fixes #1473 by moving if required the bootstrap file (see See
* #1473)
*
*
* This bootstrap file location can be overridden by providing -bootstrap=<URL> CLI option
*
* @return bootstrap file absolute path
*/
public static String getBootstrapPath() {
return getBootstrapPath(Const.FILE_BOOTSTRAP);
}
}