package cx.fbn.nevernote.config; import java.io.File; import java.io.FileFilter; import java.util.regex.Pattern; /** * Provides access to NeverNote standard runtime directories. * * @author Nick Clarke */ public class FileManager { private static final Pattern ALL_PATH_SEPARATORS_REGEX = Pattern.compile("[/\\\\]"); private final String programDirPath; private final File programDir; private final String homeDirPath; private final File homeDir; private final String dbDirPath; private final File dbDir; private final File logsDir; private final String imagesDirPath; private final File imagesDir; private final String spellDirPath; private final File spellDir; private final String spellDirPathUser; private final File spellDirUser; private final String qssDirPath; private final File qssDir; private final String qssDirPathUser; private final File qssDirUser; private final String resDirPath; private final File resDir; private final File xmlDir; private final String translateDirPath; private final File translateDir; /** * Check or create the db, log and res directories. * * @param homeDirPath the installation dir containing db/log/res directories, must exist * @throws InitializationException for missing directories or file permissions problems */ public FileManager(String homeDirPath, String programDirPath) throws InitializationException { if (homeDirPath == null) { throw new IllegalArgumentException("homeDirPath must not be null"); } if (programDirPath == null) { throw new IllegalArgumentException("programDirPath must not be null"); } this.homeDir = new File(toPlatformPathSeparator(homeDirPath)); this.programDir = new File(toPlatformPathSeparator(programDirPath)); createDirOrCheckWriteable(homeDir); this.homeDirPath = slashTerminatePath(homeDir.getPath()); this.programDirPath = slashTerminatePath(programDir.getPath()); // Read-only imagesDir = new File(programDir, "images"); checkExistingReadableDir(imagesDir); imagesDirPath = slashTerminatePath(imagesDir.getPath()); qssDir = new File(programDir, "qss"); checkExistingReadableDir(qssDir); qssDirPath = slashTerminatePath(qssDir.getPath()); qssDirUser = new File(homeDir, "qss"); createDirOrCheckWriteable(qssDirUser); qssDirPathUser = slashTerminatePath(qssDirUser.getPath()); spellDir = new File(programDir, "spell"); checkExistingReadableDir(spellDir); spellDirPath = slashTerminatePath(spellDir.getPath()); spellDirUser = new File(homeDir, "spell"); createDirOrCheckWriteable(spellDirUser); spellDirPathUser = slashTerminatePath(spellDirUser.getPath()); xmlDir = new File(programDir, "xml"); checkExistingReadableDir(xmlDir); translateDir = new File(programDir, "translations"); checkExistingReadableDir(translateDir); translateDirPath= slashTerminatePath(translateDir.getPath()); // Read-write dbDir = new File(homeDir, "db"); createDirOrCheckWriteable(dbDir); dbDirPath = slashTerminatePath(dbDir.getPath()); logsDir = new File(homeDir, "logs"); createDirOrCheckWriteable(logsDir); resDir = new File(homeDir, "res"); createDirOrCheckWriteable(resDir); resDirPath = slashTerminatePath(resDir.getPath()); } /** * Get a file below the base user home directory. */ public File getProgramDirFile(String relativePath) { return new File(programDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the base user home directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getProgramDirPath(String relativePath) { return programDirPath + toPlatformPathSeparator(relativePath); } /** * Get a file below the base user home directory. */ public File getHomeDirFile(String relativePath) { return new File(homeDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the base user home directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getHomeDirPath(String relativePath) { return homeDirPath + toPlatformPathSeparator(relativePath); } /** * Get a file below the 'db' directory. */ public File getDbDirFile(String relativePath) { return new File(dbDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the 'spell' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getSpellDirPath(String relativePath) { return dbDirPath + toPlatformPathSeparator(relativePath); } /** * Get a file below the 'spell' directory. */ public File getSpellDirFile(String relativePath) { return new File(spellDir, toPlatformPathSeparator(relativePath)); } /** * Get the spell directory for the jazzy word list */ public String getSpellDirPath() { return spellDirPath; } /** * Get a file below the 'spell' directory for user dictionaries. */ public File getSpellDirFileUser(String relativePath) { return new File(spellDirUser, toPlatformPathSeparator(relativePath)); } /** * Get the spell directory for the jazzy word list (user dictionary). */ public String getSpellDirPathUser() { return spellDirPathUser; } /** * Get a path below the 'db' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getDbDirPath(String relativePath) { return dbDirPath + toPlatformPathSeparator(relativePath); } /** * Get a file below the 'images' directory. */ public File getImageDirFile(String relativePath) { return new File(imagesDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the 'images' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getImageDirPath(String relativePath) { return imagesDirPath + toPlatformPathSeparator(relativePath); } /** * Get a file below the 'logs' directory. */ public File getLogsDirFile(String relativePath) { return new File(logsDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the 'qss' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getQssDirPath(String relativePath) { return qssDirPath + toPlatformPathSeparator(relativePath); } /** * Get a path below the 'qss' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getQssDirPathUser(String relativePath) { return qssDirPathUser + toPlatformPathSeparator(relativePath); } /** * Get a path to the 'res' directory, terminated with native {@link File#separator}. * This will contain backslashes on Windows. */ public String getResDirPath() { return resDirPath; } /** * Get a path below the 'res' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getResDirPath(String relativePath) { return resDirPath + toPlatformPathSeparator(relativePath); } /** * Get a path below the 'res' directory, using native {@link File#separator}. * This will contain backslashes on Windows. This is different from the * one above in that it will encode the relative path */ public String getResDirPathSpecialChar(String relativePath) { return resDirPath + toPlatformPathSeparator(relativePath).replace("#", "%23"); } /** * Get a file below the 'xml' directory. */ public File getXMLDirFile(String relativePath) { return new File(xmlDir, toPlatformPathSeparator(relativePath)); } /** * Get a path below the 'translate' directory, using native {@link File#separator}. * This will contain backslashes on Windows. */ public String getTranslateFilePath(String relativePath) { return translateDirPath + toPlatformPathSeparator(relativePath); } public static String toPlatformPathSeparator(String relativePath) { // Sometimes a space in the file name comes across as a %20. This is to put it back as a space. relativePath = relativePath.replace("%20", " "); return ALL_PATH_SEPARATORS_REGEX.matcher(relativePath).replaceAll( // Must double-escape backslashes, // because they have special meaning in the replacement string of Matcher.replaceAll (File.separator.equals("\\") ? "\\\\" : File.separator)); } public static String slashTerminatePath(String path) { if (!path.substring(path.length() - 1).equals(File.separator)) { return path + File.separator; } return path; } /** * Delete first-level files (but not directories) from the directory. * * @throws InitializationException for file deletion failures */ private static void deleteTopLevelFiles(File dir, boolean exitOnFail) throws InitializationException { File[] toDelete = dir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isFile(); } }); for (File f : toDelete) { if (!f.delete() && exitOnFail) { throw new InitializationException("Failed to delete file: '" + f + "'"); } } } /** * @throws InitializationException for bad file permissions, or a file instead of a directory */ private static void createDirOrCheckWriteable(File dir) throws InitializationException { if (dir.isDirectory()) { // Dir exists, check permissions if (!dir.canRead()) { throw new InitializationException("Directory '" + dir + "' does not have read permission"); } if (!dir.canWrite()) { throw new InitializationException("Directory '" + dir + "' does not have write permission"); } } else if (!dir.exists()) { if (!dir.mkdirs()) { throw new InitializationException("Failed to create directory '" + dir + "'"); } } else { throw new InitializationException("Expected directory '" + dir + "' but found a file instead"); } } /** * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory */ private static void checkExistingReadableDir(File dir) throws InitializationException { if (dir.isDirectory()) { // Dir exists, check permissions if (!dir.canRead()) { throw new InitializationException("Directory '" + dir + "' does not have read permission"); } } else if (!dir.exists()) { throw new InitializationException("Directory '" + dir + "' does not exist"); } else { throw new InitializationException("Expected directory '" + dir + "' but found a file instead"); } } /** * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory */ @SuppressWarnings("unused") private static void checkExistingWriteableDir(File dir) throws InitializationException { checkExistingReadableDir(dir); if (!dir.canWrite()) { throw new InitializationException("Directory '" + dir + "' does not have write permission"); } } /** * Called at startup to purge files from 'res' directory. */ public void purgeResDirectory(boolean exitOnFail) throws InitializationException { deleteTopLevelFiles(resDir, exitOnFail); } }