/**
* Distribution License:
* JSword is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License, version 2.1 as published by
* the Free Software Foundation. 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 Lesser General Public License for more details.
*
* The License is available on the internet at:
* http://www.gnu.org/copyleft/lgpl.html
* or by writing to:
* Free Software Foundation, Inc.
* 59 Temple Place - Suite 330
* Boston, MA 02111-1307, USA
*
* Copyright: 2005
* The copyright to this program is held by it's authors.
*
* ID: $Id$
*/
package org.crosswire.jsword.book.sword;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.crosswire.common.util.CWProject;
import org.crosswire.common.util.Logger;
import org.crosswire.common.util.OSType;
import org.crosswire.common.util.PropertyMap;
import org.crosswire.common.util.StringUtil;
import org.crosswire.jsword.book.BookException;
import org.crosswire.jsword.book.Books;
/**
* This represents all of the Sword Books (aka modules).
*
* @see gnu.lgpl.License for license details.
* The copyright to this program is held by it's authors.
* @author Joe Walker [joe at eireneh dot com]
* @author DM Smith [dmsmith555 at yahoo dot com]
*/
public class SwordBookPath {
/**
* Some basic name initialization
*/
private SwordBookPath() {
}
/**
* Establish additional locations that Sword may hold books.
*
* @param theNewDirs
* The new Sword directories
* @throws BookException
*/
public static void setAugmentPath(File[] theNewDirs) throws BookException {
File[] newDirs = theNewDirs;
if (newDirs == null) {
return;
}
SwordBookPath.augmentPath = newDirs.clone();
// Now we need to (re)register ourselves
Books.installed().registerDriver(SwordBookDriver.instance());
}
/**
* Retrieve the additional locations that Sword may hold Books.
*
* @return The new Sword directory
*/
public static File[] getAugmentPath() {
return augmentPath.clone();
}
/**
* Obtain a prioritized path of Book locations. This contains the download
* dir as the first location, the user's augment path and finally all the
* discovered standard locations.
*
* @return the array of Book locations.
*/
public static File[] getSwordPath() {
ArrayList swordPath = new ArrayList();
// The first place to look for Books
swordPath.add(getSwordDownloadDir());
// Then all the user's augments
if (augmentPath != null) {
for (int i = 0; i < augmentPath.length; i++) {
File path = augmentPath[i];
if (!swordPath.contains(path)) {
swordPath.add(path);
}
}
}
File[] defaultPath = getDefaultPaths();
// Then all the user's bookDirs
if (defaultPath != null) {
for (int i = 0; i < defaultPath.length; i++) {
File path = defaultPath[i];
if (!swordPath.contains(path)) {
swordPath.add(path);
}
}
}
return swordPath.toArray(new File[swordPath.size()]);
}
/**
* Get a list of books in a given location.
*
* @param bookDir
* the directory in which to look
* @return the list of books in that location
*/
public static String[] getBookList(File bookDir) {
return bookDir.list(new CustomFilenameFilter());
}
/**
* Search all of the "standard" Sword locations for Books. Remember all the
* locations.
*/
private static File[] getDefaultPaths() {
// If possible migrate the old location to the new one
migrateBookDir();
List bookDirs = new ArrayList();
String home = System.getProperty(PROPERTY_USER_HOME);
// Is sword.conf in the current directory?
readSwordConf(bookDirs, ".");
// mods.d in the current directory?
testDefaultPath(bookDirs, ".");
// how about in the library, just next door?
testDefaultPath(bookDirs, ".." + File.separator + DIR_SWORD_LIBRARY);
// if there is a property set for the sword home directory
// The Sword project defines SWORD_HOME, but JSword expects this to be
// transformed into sword.home.
String swordhome = System.getProperty(PROPERTY_SWORD_HOME);
if (swordhome != null) {
testDefaultPath(bookDirs, swordhome);
// how about in the library, just next door?
testDefaultPath(bookDirs, swordhome + File.separator + ".." + File.separator + DIR_SWORD_LIBRARY);
}
if (System.getProperty("os.name").startsWith("Windows")) {
testDefaultPath(bookDirs, DIR_WINDOWS_DEFAULT);
// how about in the library, just next door?
testDefaultPath(bookDirs, DIR_WINDOWS_DEFAULT + File.separator + ".." + File.separator + DIR_SWORD_LIBRARY);
}
// .sword in the users home directory?
readSwordConf(bookDirs, home + File.separator + DIR_SWORD_CONF);
// Check for sword.conf in the usual places
String[] sysconfigPaths = StringUtil.split(DIR_SWORD_GLOBAL_CONF, ':');
for (int i = 0; i < sysconfigPaths.length; i++) {
readSwordConf(bookDirs, sysconfigPaths[i]);
}
URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
// Check look for mods.d in the sword user data area
testDefaultPath(bookDirs, new File(userDataArea.getPath()));
// JSword used to hold books in ~/.jsword (or its equivalent) but has
// code that will
// migrate it to ~/.sword (or its equivalent)
// If the migration did not work then use the old area
testDefaultPath(bookDirs, new File(CWProject.instance().getWritableProjectDir().getPath()));
return bookDirs.toArray(new File[bookDirs.size()]);
}
private static void readSwordConf(List bookDirs, File swordConfDir) {
File sysconfig = new File(swordConfDir, SWORD_GLOBAL_CONF);
if (sysconfig.canRead()) {
InputStream is = null;
try {
PropertyMap prop = new PropertyMap();
is = new FileInputStream(sysconfig);
prop.load(is);
String datapath = prop.get(DATA_PATH);
testDefaultPath(bookDirs, datapath);
datapath = prop.get(AUGMENT_PATH);
testDefaultPath(bookDirs, datapath);
} catch (IOException ex) {
log.warn("Failed to read system config file", ex);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.warn("Failed to close system config file", e);
}
}
}
}
}
private static void readSwordConf(List bookDirs, String swordConfDir) {
readSwordConf(bookDirs, new File(swordConfDir));
}
/**
* Check to see if the given directory is a Sword mods.d directory and then
* add it to the list if it is.
*
* @param bookDirs
* The list to add good paths
* @param path
* the path to check
*/
private static void testDefaultPath(List bookDirs, File path) {
if (path == null) {
return;
}
File mods = new File(path, SwordConstants.DIR_CONF);
if (mods.isDirectory() && mods.canRead()) {
bookDirs.add(path);
}
}
/**
* Check to see if the given directory is a Sword mods.d directory and then
* add it to the list if it is.
*
* @param bookDirs
* The list to add good paths
* @param path
* the path to check
*/
private static void testDefaultPath(List bookDirs, String path) {
if (path == null) {
return;
}
testDefaultPath(bookDirs, new File(path));
}
private static File getDefaultDownloadPath() {
File path = null;
File[] possiblePaths = getDefaultPaths();
if (possiblePaths != null) {
for (int i = 0; i < possiblePaths.length; i++) {
File mods = new File(possiblePaths[i], SwordConstants.DIR_CONF);
if (mods.canWrite()) {
path = possiblePaths[i];
break;
}
}
}
// If it is not found on the path then it doesn't exist yet and needs to
// be established
if (path == null) {
URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
path = new File(userDataArea.getPath());
}
return path;
}
private static void migrateBookDir() {
// Books should be on this path
URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
File swordBookPath = new File(userDataArea.getPath());
// The "old" Book location might be in one of two locations
// It might be ~/.jsword or the new project dir
File oldPath = new File(CWProject.instance().getDeprecatedWritableProjectDir().getPath());
if (oldPath.isDirectory()) {
migrateBookDir(oldPath, swordBookPath);
return;
}
// now trying the new project dir
oldPath = new File(CWProject.instance().getWritableProjectDir().getPath());
if (oldPath.isDirectory()) {
migrateBookDir(oldPath, swordBookPath);
return;
}
// Finally, it might be ~/.sword
oldPath = new File(OSType.DEFAULT.getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT).getPath());
if (oldPath.isDirectory()) {
migrateBookDir(oldPath, swordBookPath);
}
}
private static void migrateBookDir(File oldPath, File newPath) {
// move the modules and confs
File oldDataDir = new File(oldPath, SwordConstants.DIR_DATA);
File newDataDir = new File(newPath, SwordConstants.DIR_DATA);
File oldConfDir = new File(oldPath, SwordConstants.DIR_CONF);
File newConfDir = new File(newPath, SwordConstants.DIR_CONF);
// move the modules
if (!migrate(oldDataDir, newDataDir)) {
return;
}
// move the confs
if (!migrate(oldConfDir, newConfDir)) {
// oops, restore the modules
migrate(newDataDir, oldDataDir);
}
}
private static boolean migrate(File oldPath, File newPath) {
if (oldPath.equals(newPath) || !oldPath.exists()) {
return true;
}
// make sure the parent exists
File parent = newPath.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
return false;
}
return oldPath.renameTo(newPath);
}
/**
* Get the download directory, which is either the one that the user chose
* or that JSword picked for the user.
*
* @return Returns the download directory.
*/
public static File getSwordDownloadDir() {
if (overrideDownloadDir != null) {
return overrideDownloadDir;
}
return defaultDownloadDir;
}
/**
* @return Returns the download directory that the user chose.
*/
public static File getDownloadDir() {
return overrideDownloadDir;
}
/**
* @param dlDir
* The download directory that the user specifies.
*/
public static void setDownloadDir(File dlDir) {
if (!"".equals(dlDir.getPath())) {
overrideDownloadDir = dlDir;
log.debug("Setting sword download directory to: " + dlDir);
}
}
/**
* Check that the directories in the version directory really represent
* versions.
*/
static class CustomFilenameFilter implements FilenameFilter {
/*
* (non-Javadoc)
*
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
public boolean accept(File parent, String name) {
return !name.startsWith(PREFIX_GLOBALS) && name.endsWith(SwordConstants.EXTENSION_CONF);
}
}
/**
* Default windows installation directory
*/
private static final String DIR_WINDOWS_DEFAULT = "C:\\Program Files\\CrossWire\\The SWORD Project";
/**
* Library may be a sibling of DIR_WINDOWS_DEFAULT or SWORD_HOME or CWD
*/
private static final String DIR_SWORD_LIBRARY = "library";
/**
* Users config directory for Sword in Unix
*/
private static final String DIR_SWORD_CONF = ".sword";
/**
* Users config directory for Sword in Unix
*/
private static final String DIR_SWORD_CONF_ALT = "Sword";
/**
* Sword global config file
*/
private static final String SWORD_GLOBAL_CONF = "sword.conf";
/**
* Sword global config file locations
*/
private static final String DIR_SWORD_GLOBAL_CONF = "/etc:/usr/local/etc";
/**
* Sword global config file's path to where mods can be found
*/
private static final String DATA_PATH = "DataPath";
/**
* Sword global config file's path to where mods can be found
*/
private static final String AUGMENT_PATH = "AugmentPath";
/**
* System property for sword home directory
*/
private static final String PROPERTY_SWORD_HOME = "sword.home";
/**
* Java system property for users home directory
*/
private static final String PROPERTY_USER_HOME = "user.home";
/**
* File prefix for config file
*/
private static final String PREFIX_GLOBALS = "globals.";
/**
* The directory URL
*/
private static File[] augmentPath = new File[0];
/**
* The directory URL
*/
private static File defaultDownloadDir = getDefaultDownloadPath();
/**
* The directory URL
*/
private static File overrideDownloadDir;
/**
* The log stream
*/
private static final Logger log = Logger.getLogger(SwordBookPath.class);
}