Coverage Report - org.crosswire.jsword.book.sword.SwordBookPath
 
Classes in this File Line Coverage Branch Coverage Complexity
SwordBookPath
0%
0/127
0%
0/62
3.389
SwordBookPath$CustomFilenameFilter
0%
0/2
0%
0/4
3.389
 
 1  
 /**
 2  
  * Distribution License:
 3  
  * JSword is free software; you can redistribute it and/or modify it under
 4  
  * the terms of the GNU Lesser General Public License, version 2.1 or later
 5  
  * as published by the Free Software Foundation. This program is distributed
 6  
  * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 7  
  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 8  
  * See the GNU Lesser General Public License for more details.
 9  
  *
 10  
  * The License is available on the internet at:
 11  
  *      http://www.gnu.org/copyleft/lgpl.html
 12  
  * or by writing to:
 13  
  *      Free Software Foundation, Inc.
 14  
  *      59 Temple Place - Suite 330
 15  
  *      Boston, MA 02111-1307, USA
 16  
  *
 17  
  * © CrossWire Bible Society, 2005 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.book.sword;
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.FilenameFilter;
 25  
 import java.io.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.net.URI;
 28  
 import java.util.ArrayList;
 29  
 import java.util.List;
 30  
 
 31  
 import org.crosswire.common.util.CWProject;
 32  
 import org.crosswire.common.util.OSType;
 33  
 import org.crosswire.common.util.PropertyMap;
 34  
 import org.crosswire.common.util.StringUtil;
 35  
 import org.crosswire.jsword.book.BookException;
 36  
 import org.crosswire.jsword.book.Books;
 37  
 import org.slf4j.Logger;
 38  
 import org.slf4j.LoggerFactory;
 39  
 
 40  
 /**
 41  
  * This represents all of the Sword Books (aka modules).
 42  
  * 
 43  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 44  
  * @author Joe Walker
 45  
  * @author DM Smith
 46  
  */
 47  
 public final class SwordBookPath {
 48  
     /**
 49  
      * Some basic name initialization
 50  
      */
 51  0
     private SwordBookPath() {
 52  0
     }
 53  
 
 54  
     /**
 55  
      * Establish additional locations that Sword may hold books.
 56  
      * 
 57  
      * @param theNewDirs
 58  
      *            The new Sword directories
 59  
      * @throws BookException
 60  
      */
 61  
     public static void setAugmentPath(File[] theNewDirs) throws BookException {
 62  0
         File[] newDirs = theNewDirs;
 63  0
         if (newDirs == null) {
 64  0
             return;
 65  
         }
 66  
 
 67  0
         SwordBookPath.augmentPath = newDirs.clone();
 68  
 
 69  
         // Now we need to (re)register ourselves
 70  0
         Books.installed().registerDriver(SwordBookDriver.instance());
 71  0
     }
 72  
 
 73  
     /**
 74  
      * Retrieve the additional locations that Sword may hold Books.
 75  
      * 
 76  
      * @return The new Sword directory
 77  
      */
 78  
     public static File[] getAugmentPath() {
 79  0
         return augmentPath.clone();
 80  
     }
 81  
 
 82  
     /**
 83  
      * Obtain a prioritized path of Book locations. This contains the download
 84  
      * dir as the first location, the user's augment path and finally all the
 85  
      * discovered standard locations.
 86  
      * 
 87  
      * @return the array of Book locations.
 88  
      */
 89  
     public static File[] getSwordPath() {
 90  0
         ArrayList<File> swordPath = new ArrayList<File>();
 91  
 
 92  
         // The first place to look for Books
 93  0
         swordPath.add(getSwordDownloadDir());
 94  
 
 95  
         // Then all the user's augments
 96  0
         if (augmentPath != null) {
 97  0
             for (int i = 0; i < augmentPath.length; i++) {
 98  0
                 File path = augmentPath[i];
 99  0
                 if (!swordPath.contains(path)) {
 100  0
                     swordPath.add(path);
 101  
                 }
 102  
             }
 103  
         }
 104  
 
 105  0
         File[] defaultPath = getDefaultPaths();
 106  
         // Then all the user's bookDirs
 107  0
         if (defaultPath != null) {
 108  0
             for (int i = 0; i < defaultPath.length; i++) {
 109  0
                 File path = defaultPath[i];
 110  0
                 if (!swordPath.contains(path)) {
 111  0
                     swordPath.add(path);
 112  
                 }
 113  
             }
 114  
         }
 115  
 
 116  0
         return swordPath.toArray(new File[swordPath.size()]);
 117  
     }
 118  
 
 119  
     /**
 120  
      * Get a list of books in a given location.
 121  
      * 
 122  
      * @param bookDir
 123  
      *            the directory in which to look
 124  
      * @return the list of books in that location
 125  
      */
 126  
     public static String[] getBookList(File bookDir) {
 127  0
         return bookDir.list(new CustomFilenameFilter());
 128  
     }
 129  
 
 130  
     /**
 131  
      * Search all of the "standard" Sword locations for Books. Remember all the
 132  
      * locations.
 133  
      */
 134  
     private static File[] getDefaultPaths() {
 135  
         // If possible migrate the old location to the new one
 136  0
         migrateBookDir();
 137  
 
 138  0
         List<File> bookDirs = new ArrayList<File>();
 139  
 
 140  0
         String home = System.getProperty(PROPERTY_USER_HOME);
 141  
 
 142  
         // Is sword.conf in the current directory?
 143  0
         readSwordConf(bookDirs, ".");
 144  
 
 145  
         // mods.d in the current directory?
 146  0
         testDefaultPath(bookDirs, ".");
 147  
 
 148  
         // how about in the library, just next door?
 149  0
         testDefaultPath(bookDirs, ".." + File.separator + DIR_SWORD_LIBRARY);
 150  
 
 151  
         // if there is a property set for the sword home directory
 152  
         // The Sword project defines SWORD_HOME, but JSword expects this to be
 153  
         // transformed into sword.home.
 154  0
         String swordhome = System.getProperty(PROPERTY_SWORD_HOME);
 155  0
         if (swordhome != null) {
 156  0
             testDefaultPath(bookDirs, swordhome);
 157  
 
 158  
             // how about in the library, just next door?
 159  0
             testDefaultPath(bookDirs, swordhome + File.separator + ".." + File.separator + DIR_SWORD_LIBRARY);
 160  
         }
 161  
 
 162  0
         if (System.getProperty("os.name").startsWith("Windows")) {
 163  0
             testDefaultPath(bookDirs, DIR_WINDOWS_DEFAULT);
 164  
             // how about in the library, just next door?
 165  0
             testDefaultPath(bookDirs, DIR_WINDOWS_DEFAULT + File.separator + ".." + File.separator + DIR_SWORD_LIBRARY);
 166  
         }
 167  
 
 168  
         // .sword in the users home directory?
 169  0
         readSwordConf(bookDirs, home + File.separator + DIR_SWORD_CONF);
 170  
 
 171  
         // Check for sword.conf in the usual places
 172  0
         String[] sysconfigPaths = StringUtil.split(DIR_SWORD_GLOBAL_CONF, ':');
 173  0
         for (int i = 0; i < sysconfigPaths.length; i++) {
 174  0
             readSwordConf(bookDirs, sysconfigPaths[i]);
 175  
         }
 176  
 
 177  0
         URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
 178  
 
 179  
         // Check look for mods.d in the sword user data area
 180  0
         testDefaultPath(bookDirs, new File(userDataArea.getPath()));
 181  
 
 182  
         // JSword used to hold books in ~/.jsword (or its equivalent) but has
 183  
         // code that will
 184  
         // migrate it to ~/.sword (or its equivalent)
 185  
         // If the migration did not work then use the old area
 186  0
         testDefaultPath(bookDirs, new File(CWProject.instance().getWritableProjectDir().getPath()));
 187  
 
 188  0
         return bookDirs.toArray(new File[bookDirs.size()]);
 189  
     }
 190  
 
 191  
     private static void readSwordConf(List<File> bookDirs, File swordConfDir) {
 192  0
         File sysconfig = new File(swordConfDir, SWORD_GLOBAL_CONF);
 193  0
         if (sysconfig.canRead()) {
 194  0
             InputStream is = null;
 195  
             try {
 196  0
                 PropertyMap prop = new PropertyMap();
 197  0
                 is = new FileInputStream(sysconfig);
 198  0
                 prop.load(is);
 199  0
                 String datapath = prop.get(DATA_PATH);
 200  0
                 testDefaultPath(bookDirs, datapath);
 201  0
                 datapath = prop.get(AUGMENT_PATH);
 202  0
                 testDefaultPath(bookDirs, datapath);
 203  0
             } catch (IOException ex) {
 204  0
                 log.warn("Failed to read system config file", ex);
 205  
             } finally {
 206  0
                 if (is != null) {
 207  
                     try {
 208  0
                         is.close();
 209  0
                     } catch (IOException e) {
 210  0
                         log.warn("Failed to close system config file", e);
 211  0
                     }
 212  
                 }
 213  
             }
 214  
         }
 215  0
     }
 216  
 
 217  
     private static void readSwordConf(List<File> bookDirs, String swordConfDir) {
 218  0
         readSwordConf(bookDirs, new File(swordConfDir));
 219  0
     }
 220  
 
 221  
     /**
 222  
      * Check to see if the given directory is a Sword mods.d directory and then
 223  
      * add it to the list if it is.
 224  
      * 
 225  
      * @param bookDirs
 226  
      *            The list to add good paths
 227  
      * @param path
 228  
      *            the path to check
 229  
      */
 230  
     private static void testDefaultPath(List<File> bookDirs, File path) {
 231  0
         if (path == null) {
 232  0
             return;
 233  
         }
 234  
 
 235  0
         File mods = new File(path, SwordConstants.DIR_CONF);
 236  0
         if (mods.isDirectory() && mods.canRead()) {
 237  0
             bookDirs.add(path);
 238  
         }
 239  0
     }
 240  
 
 241  
     /**
 242  
      * Check to see if the given directory is a Sword mods.d directory and then
 243  
      * add it to the list if it is.
 244  
      * 
 245  
      * @param bookDirs
 246  
      *            The list to add good paths
 247  
      * @param path
 248  
      *            the path to check
 249  
      */
 250  
     private static void testDefaultPath(List<File> bookDirs, String path) {
 251  0
         if (path == null) {
 252  0
             return;
 253  
         }
 254  
 
 255  0
         testDefaultPath(bookDirs, new File(path));
 256  0
     }
 257  
 
 258  
     private static File getDefaultDownloadPath() {
 259  0
         File path = null;
 260  0
         File[] possiblePaths = getDefaultPaths();
 261  
 
 262  0
         if (possiblePaths != null) {
 263  0
             for (int i = 0; i < possiblePaths.length; i++) {
 264  0
                 File mods = new File(possiblePaths[i], SwordConstants.DIR_CONF);
 265  0
                 if (mods.canWrite()) {
 266  0
                     path = possiblePaths[i];
 267  0
                     break;
 268  
                 }
 269  
             }
 270  
         }
 271  
 
 272  
         // If it is not found on the path then it doesn't exist yet and needs to
 273  
         // be established
 274  0
         if (path == null) {
 275  0
             URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
 276  0
             path = new File(userDataArea.getPath());
 277  
         }
 278  
 
 279  0
         return path;
 280  
     }
 281  
 
 282  
     private static void migrateBookDir() {
 283  
         // Books should be on this path
 284  0
         URI userDataArea = OSType.getOSType().getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT);
 285  
 
 286  0
         File swordBookPath = new File(userDataArea.getPath());
 287  
 
 288  
         // The "old" Book location might be in one of two locations
 289  
         // It might be ~/.jsword or the new project dir
 290  0
         File oldPath = new File(CWProject.instance().getDeprecatedWritableProjectDir().getPath());
 291  
 
 292  0
         if (oldPath.isDirectory()) {
 293  0
             migrateBookDir(oldPath, swordBookPath);
 294  0
             return;
 295  
         }
 296  
 
 297  
         // now trying the new project dir
 298  0
         oldPath = new File(CWProject.instance().getWritableProjectDir().getPath());
 299  
 
 300  0
         if (oldPath.isDirectory()) {
 301  0
             migrateBookDir(oldPath, swordBookPath);
 302  0
             return;
 303  
         }
 304  
 
 305  
         // Finally, it might be ~/.sword
 306  0
         oldPath = new File(OSType.DEFAULT.getUserAreaFolder(DIR_SWORD_CONF, DIR_SWORD_CONF_ALT).getPath());
 307  0
         if (oldPath.isDirectory()) {
 308  0
             migrateBookDir(oldPath, swordBookPath);
 309  
         }
 310  0
     }
 311  
 
 312  
     private static void migrateBookDir(File oldPath, File newPath) {
 313  
         // move the modules and confs
 314  0
         File oldDataDir = new File(oldPath, SwordConstants.DIR_DATA);
 315  0
         File newDataDir = new File(newPath, SwordConstants.DIR_DATA);
 316  0
         File oldConfDir = new File(oldPath, SwordConstants.DIR_CONF);
 317  0
         File newConfDir = new File(newPath, SwordConstants.DIR_CONF);
 318  
 
 319  
         // move the modules
 320  0
         if (!migrate(oldDataDir, newDataDir)) {
 321  0
             return;
 322  
         }
 323  
 
 324  
         // move the confs
 325  0
         if (!migrate(oldConfDir, newConfDir)) {
 326  
             // oops, restore the modules
 327  0
             migrate(newDataDir, oldDataDir);
 328  
         }
 329  0
     }
 330  
 
 331  
     private static boolean migrate(File oldPath, File newPath) {
 332  0
         if (oldPath.equals(newPath) || !oldPath.exists()) {
 333  0
             return true;
 334  
         }
 335  
 
 336  
         // make sure the parent exists
 337  0
         File parent = newPath.getParentFile();
 338  0
         if (!parent.exists() && !parent.mkdirs()) {
 339  0
             return false;
 340  
         }
 341  
 
 342  0
         return oldPath.renameTo(newPath);
 343  
     }
 344  
 
 345  
     /**
 346  
      * Get the download directory, which is either the one that the user chose
 347  
      * or that JSword picked for the user.
 348  
      * 
 349  
      * @return Returns the download directory.
 350  
      */
 351  
     public static File getSwordDownloadDir() {
 352  0
         if (overrideDownloadDir != null) {
 353  0
             return overrideDownloadDir;
 354  
         }
 355  0
         return defaultDownloadDir;
 356  
     }
 357  
 
 358  
     /**
 359  
      * @return Returns the download directory that the user chose.
 360  
      */
 361  
     public static File getDownloadDir() {
 362  0
         return overrideDownloadDir;
 363  
     }
 364  
 
 365  
     /**
 366  
      * @param dlDir
 367  
      *            The download directory that the user specifies.
 368  
      */
 369  
     public static void setDownloadDir(File dlDir) {
 370  0
         if (!"".equals(dlDir.getPath())) {
 371  0
             overrideDownloadDir = dlDir;
 372  0
             log.debug("Setting sword download directory to: {}", dlDir);
 373  
         }
 374  0
     }
 375  
 
 376  
     /**
 377  
      * Check that the directories in the version directory really represent
 378  
      * versions.
 379  
      */
 380  0
     static class CustomFilenameFilter implements FilenameFilter {
 381  
         /*
 382  
          * (non-Javadoc)
 383  
          * 
 384  
          * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
 385  
          */
 386  
         public boolean accept(File parent, String name) {
 387  0
             return !name.startsWith(PREFIX_GLOBALS) && name.endsWith(SwordConstants.EXTENSION_CONF);
 388  
         }
 389  
     }
 390  
 
 391  
     /**
 392  
      * Default windows installation directory
 393  
      */
 394  
     private static final String DIR_WINDOWS_DEFAULT = "C:\\Program Files\\CrossWire\\The SWORD Project";
 395  
 
 396  
     /**
 397  
      * Library may be a sibling of DIR_WINDOWS_DEFAULT or SWORD_HOME or CWD
 398  
      */
 399  
     private static final String DIR_SWORD_LIBRARY = "library";
 400  
 
 401  
     /**
 402  
      * Users config directory for Sword in Unix
 403  
      */
 404  
     private static final String DIR_SWORD_CONF = ".sword";
 405  
 
 406  
     /**
 407  
      * Users config directory for Sword in Unix
 408  
      */
 409  
     private static final String DIR_SWORD_CONF_ALT = "Sword";
 410  
 
 411  
     /**
 412  
      * Sword global config file
 413  
      */
 414  
     private static final String SWORD_GLOBAL_CONF = "sword.conf";
 415  
 
 416  
     /**
 417  
      * Sword global config file locations
 418  
      */
 419  
     private static final String DIR_SWORD_GLOBAL_CONF = "/etc:/usr/local/etc";
 420  
 
 421  
     /**
 422  
      * Sword global config file's path to where mods can be found
 423  
      */
 424  
     private static final String DATA_PATH = "DataPath";
 425  
 
 426  
     /**
 427  
      * Sword global config file's path to where mods can be found
 428  
      */
 429  
     private static final String AUGMENT_PATH = "AugmentPath";
 430  
 
 431  
     /**
 432  
      * System property for sword home directory
 433  
      */
 434  
     private static final String PROPERTY_SWORD_HOME = "sword.home";
 435  
 
 436  
     /**
 437  
      * Java system property for users home directory
 438  
      */
 439  
     private static final String PROPERTY_USER_HOME = "user.home";
 440  
 
 441  
     /**
 442  
      * File prefix for config file
 443  
      */
 444  
     private static final String PREFIX_GLOBALS = "globals.";
 445  
 
 446  
     /**
 447  
      * The directory URL
 448  
      */
 449  0
     private static File[] augmentPath = new File[0];
 450  
 
 451  
     /**
 452  
      * The directory URL
 453  
      */
 454  0
     private static File defaultDownloadDir = getDefaultDownloadPath();
 455  
 
 456  
     /**
 457  
      * The directory URL
 458  
      */
 459  
     private static File overrideDownloadDir;
 460  
 
 461  
     /**
 462  
      * The log stream
 463  
      */
 464  0
     private static final Logger log = LoggerFactory.getLogger(SwordBookPath.class);
 465  
 
 466  
 }