OSDN Git Service

Add new FileManager class, accessible as Global.getFileManager
authorNick Clarke <memorius@gmail.com>
Mon, 19 Jul 2010 13:12:25 +0000 (01:12 +1200)
committerRandy Baumgarte <randy@fbn.cx>
Fri, 23 Jul 2010 09:31:05 +0000 (05:31 -0400)
Checks and creates dirs at startup, purges 'res' directory.

Moved some of the startup settings from NeverNote and Global into new
StartupConfig class for better separation of things that can't be changed
after startup.

.settings/org.eclipse.jdt.core.prefs
.settings/org.eclipse.jdt.ui.prefs
src/cx/fbn/nevernote/Global.java
src/cx/fbn/nevernote/NeverNote.java
src/cx/fbn/nevernote/config/FileManager.java [new file with mode: 0644]
src/cx/fbn/nevernote/config/InitializationException.java [new file with mode: 0644]
src/cx/fbn/nevernote/config/StartupConfig.java [new file with mode: 0644]

index 585ca3f..980bece 100644 (file)
@@ -1,4 +1,4 @@
-#Thu Jul 15 16:56:27 NZST 2010
+#Tue Jul 20 01:42:00 NZST 2010
 eclipse.preferences.version=1
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
index cee40d6..097ea36 100644 (file)
@@ -1,54 +1,55 @@
-#Mon Feb 01 13:37:03 EST 2010\r
-eclipse.preferences.version=1\r
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true\r
-sp_cleanup.add_default_serial_version_id=true\r
-sp_cleanup.add_generated_serial_version_id=false\r
-sp_cleanup.add_missing_annotations=true\r
-sp_cleanup.add_missing_deprecated_annotations=true\r
-sp_cleanup.add_missing_methods=false\r
-sp_cleanup.add_missing_nls_tags=false\r
-sp_cleanup.add_missing_override_annotations=true\r
-sp_cleanup.add_serial_version_id=false\r
-sp_cleanup.always_use_blocks=true\r
-sp_cleanup.always_use_parentheses_in_expressions=false\r
-sp_cleanup.always_use_this_for_non_static_field_access=false\r
-sp_cleanup.always_use_this_for_non_static_method_access=false\r
-sp_cleanup.convert_to_enhanced_for_loop=true\r
-sp_cleanup.correct_indentation=false\r
-sp_cleanup.format_source_code=false\r
-sp_cleanup.format_source_code_changes_only=false\r
-sp_cleanup.make_local_variable_final=false\r
-sp_cleanup.make_parameters_final=false\r
-sp_cleanup.make_private_fields_final=true\r
-sp_cleanup.make_type_abstract_if_missing_method=false\r
-sp_cleanup.make_variable_declarations_final=true\r
-sp_cleanup.never_use_blocks=false\r
-sp_cleanup.never_use_parentheses_in_expressions=true\r
-sp_cleanup.on_save_use_additional_actions=true\r
-sp_cleanup.organize_imports=true\r
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false\r
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true\r
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true\r
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false\r
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false\r
-sp_cleanup.remove_private_constructors=true\r
-sp_cleanup.remove_trailing_whitespaces=false\r
-sp_cleanup.remove_trailing_whitespaces_all=true\r
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false\r
-sp_cleanup.remove_unnecessary_casts=true\r
-sp_cleanup.remove_unnecessary_nls_tags=false\r
-sp_cleanup.remove_unused_imports=false\r
-sp_cleanup.remove_unused_local_variables=false\r
-sp_cleanup.remove_unused_private_fields=true\r
-sp_cleanup.remove_unused_private_members=false\r
-sp_cleanup.remove_unused_private_methods=true\r
-sp_cleanup.remove_unused_private_types=true\r
-sp_cleanup.sort_members=false\r
-sp_cleanup.sort_members_all=false\r
-sp_cleanup.use_blocks=false\r
-sp_cleanup.use_blocks_only_for_return_and_throw=false\r
-sp_cleanup.use_parentheses_in_expressions=false\r
-sp_cleanup.use_this_for_non_static_field_access=false\r
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true\r
-sp_cleanup.use_this_for_non_static_method_access=false\r
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true\r
+#Tue Jul 20 01:42:00 NZST 2010
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_settings_version=11
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
index 11f2106..328d5cb 100644 (file)
@@ -39,6 +39,9 @@ import com.trolltech.qt.core.QMutex;
 import com.trolltech.qt.core.QSettings;\r
 import com.trolltech.qt.gui.QPalette;\r
 \r
+import cx.fbn.nevernote.config.FileManager;\r
+import cx.fbn.nevernote.config.InitializationException;\r
+import cx.fbn.nevernote.config.StartupConfig;\r
 import cx.fbn.nevernote.gui.ContainsAttributeFilterTable;\r
 import cx.fbn.nevernote.gui.DateAttributeFilterTable;\r
 import cx.fbn.nevernote.gui.ShortcutKeys;\r
@@ -117,7 +120,6 @@ public class Global {
     private static String wordRegex;\r
     public static boolean enableCarriageReturnFix = false;\r
     \r
-    public static String name = null;\r
     public static QSettings    settings;\r
     public static boolean isConnected;\r
     public static boolean showDeleted = false;\r
@@ -139,7 +141,7 @@ public class Global {
        PrintStream stdoutStream;\r
        public static QPalette                          originalPalette;\r
        public static ShortcutKeys                      shortcutKeys;\r
-       public static boolean                           disableViewing = false;\r
+       private static boolean                          disableViewing;\r
        \r
        public static List<String>                              invalidElements = new ArrayList<String>();\r
        public static HashMap<String, ArrayList<String>>        invalidAttributes = new HashMap<String, ArrayList<String>>();\r
@@ -149,13 +151,17 @@ public class Global {
        \r
        static Calendar startTraceTime;\r
        static Calendar intervalTraceTime;\r
-       \r
+\r
+       private static FileManager fileManager;\r
+\r
     // Do initial setup \r
-    public static void setup()  {\r
-               if (name == null)\r
-                       name = "NeverNote";\r
-               settings = new QSettings("fbn.cx", name);\r
-                   currentDir = getDirectoryPath();\r
+    public static void setup(StartupConfig startupConfig) throws InitializationException  {\r
+        settings = new QSettings("fbn.cx", startupConfig.getName());\r
+        disableViewing = startupConfig.getDisableViewing();\r
+\r
+        fileManager = new FileManager(startupConfig.getHomeDirPath());\r
+        currentDir = fileManager.getHomeDirPath();\r
+\r
                        getServer();\r
                        settings.beginGroup("General");\r
                        String regex = (String) settings.value("regex", "[,\\s]+");\r
@@ -181,11 +187,7 @@ public class Global {
                        resourceMap = new HashMap<String,String>();\r
                                \r
     }\r
-    public static void setName(String n) {\r
-       if (!n.trim().equals("")) {\r
-               name = "NeverNote-"+n;\r
-       }\r
-    }\r
+\r
     public static String getDirectoryPath() {\r
                if (currentDir == null) {\r
                        currentDir = System.getProperty("user.dir");\r
@@ -195,13 +197,7 @@ public class Global {
                }\r
                return currentDir;\r
     }\r
-    public static void setDirectoryPath(String path) {\r
-       if (path.trim().equals(""))\r
-               path = System.getProperty("user.dir");\r
-       if (!path.substring(path.length()-1).equals(File.separator)) \r
-                       path = path+File.separator;\r
-       currentDir = path;\r
-    }\r
+\r
     public static String getWordRegex() {\r
        return wordRegex;\r
     }\r
@@ -1133,5 +1129,12 @@ public class Global {
                startTraceTime = null;\r
        }\r
 \r
+    public static FileManager getFileManager() {\r
+        return fileManager;\r
+    }\r
+\r
+    public static boolean getDisableViewing() {\r
+        return disableViewing;\r
+    }\r
 }\r
 \r
index 24eadb2..792870c 100644 (file)
@@ -63,7 +63,6 @@ import com.trolltech.qt.core.QFile;
 import com.trolltech.qt.core.QFileInfo;
 import com.trolltech.qt.core.QFileSystemWatcher;
 import com.trolltech.qt.core.QIODevice;
-import com.trolltech.qt.core.QIODevice.OpenModeFlag;
 import com.trolltech.qt.core.QModelIndex;
 import com.trolltech.qt.core.QSize;
 import com.trolltech.qt.core.QTemporaryFile;
@@ -72,22 +71,19 @@ import com.trolltech.qt.core.QThreadPool;
 import com.trolltech.qt.core.QTimer;
 import com.trolltech.qt.core.QUrl;
 import com.trolltech.qt.core.Qt;
+import com.trolltech.qt.core.QIODevice.OpenModeFlag;
 import com.trolltech.qt.core.Qt.SortOrder;
 import com.trolltech.qt.core.Qt.WidgetAttribute;
 import com.trolltech.qt.gui.QAbstractItemView;
-import com.trolltech.qt.gui.QAbstractItemView.ScrollHint;
 import com.trolltech.qt.gui.QAction;
 import com.trolltech.qt.gui.QApplication;
 import com.trolltech.qt.gui.QCloseEvent;
 import com.trolltech.qt.gui.QColor;
 import com.trolltech.qt.gui.QComboBox;
-import com.trolltech.qt.gui.QComboBox.InsertPolicy;
 import com.trolltech.qt.gui.QCursor;
 import com.trolltech.qt.gui.QDesktopServices;
 import com.trolltech.qt.gui.QDialog;
 import com.trolltech.qt.gui.QFileDialog;
-import com.trolltech.qt.gui.QFileDialog.AcceptMode;
-import com.trolltech.qt.gui.QFileDialog.FileMode;
 import com.trolltech.qt.gui.QGridLayout;
 import com.trolltech.qt.gui.QHBoxLayout;
 import com.trolltech.qt.gui.QIcon;
@@ -97,13 +93,11 @@ import com.trolltech.qt.gui.QListWidget;
 import com.trolltech.qt.gui.QMainWindow;
 import com.trolltech.qt.gui.QMenu;
 import com.trolltech.qt.gui.QMessageBox;
-import com.trolltech.qt.gui.QMessageBox.StandardButton;
 import com.trolltech.qt.gui.QPixmap;
 import com.trolltech.qt.gui.QPrintDialog;
 import com.trolltech.qt.gui.QPrinter;
 import com.trolltech.qt.gui.QProgressBar;
 import com.trolltech.qt.gui.QSizePolicy;
-import com.trolltech.qt.gui.QSizePolicy.Policy;
 import com.trolltech.qt.gui.QSpinBox;
 import com.trolltech.qt.gui.QSplashScreen;
 import com.trolltech.qt.gui.QSplitter;
@@ -113,13 +107,21 @@ import com.trolltech.qt.gui.QTableWidgetItem;
 import com.trolltech.qt.gui.QTextEdit;
 import com.trolltech.qt.gui.QToolBar;
 import com.trolltech.qt.gui.QTreeWidgetItem;
-import com.trolltech.qt.webkit.QWebPage.WebAction;
+import com.trolltech.qt.gui.QAbstractItemView.ScrollHint;
+import com.trolltech.qt.gui.QComboBox.InsertPolicy;
+import com.trolltech.qt.gui.QFileDialog.AcceptMode;
+import com.trolltech.qt.gui.QFileDialog.FileMode;
+import com.trolltech.qt.gui.QMessageBox.StandardButton;
+import com.trolltech.qt.gui.QSizePolicy.Policy;
 import com.trolltech.qt.webkit.QWebSettings;
+import com.trolltech.qt.webkit.QWebPage.WebAction;
 import com.trolltech.qt.xml.QDomAttr;
 import com.trolltech.qt.xml.QDomDocument;
 import com.trolltech.qt.xml.QDomElement;
 import com.trolltech.qt.xml.QDomNodeList;
 
+import cx.fbn.nevernote.config.InitializationException;
+import cx.fbn.nevernote.config.StartupConfig;
 import cx.fbn.nevernote.dialog.AccountDialog;
 import cx.fbn.nevernote.dialog.ConfigDialog;
 import cx.fbn.nevernote.dialog.DatabaseLoginDialog;
@@ -615,17 +617,14 @@ public class NeverNote extends QMainWindow{
                QApplication.initialize(args);
                QPixmap pixmap = new QPixmap("classpath:cx/fbn/nevernote/icons/splash_logo.png");
                QSplashScreen splash = new QSplashScreen(pixmap);
-                       
-               for (String arg : args) {
-                       String lower = arg.toLowerCase();
-                       if (lower.startsWith("--name="))
-                               Global.setName(arg.substring(arg.indexOf('=')+1));
-                       if (lower.startsWith("--home="))
-                               Global.currentDir = arg.substring(arg.indexOf('=')+1);
-                       if (lower.startsWith("--disable-viewing"))
-                               Global.disableViewing = true;
+
+               try {
+                   initializeGlobalSettings(args);
+               } catch (InitializationException e) {
+                       QMessageBox.critical(null, "Startup error", "Aborting: " + e.getMessage());
+                       return;
                }
-               Global.setup();
+
                boolean showSplash = Global.isWindowVisible("SplashScreen");
                if (showSplash) 
                        splash.show();
@@ -641,7 +640,24 @@ public class NeverNote extends QMainWindow{
                System.out.println("Goodbye.");
                QApplication.exit();
        }
-       // Exit point
+
+       private static void initializeGlobalSettings(String[] args) throws InitializationException {
+                StartupConfig startupConfig = new StartupConfig();
+
+                for (String arg : args) {
+                        String lower = arg.toLowerCase();
+                        if (lower.startsWith("--name="))
+                                startupConfig.setName(arg.substring(arg.indexOf('=') + 1));
+                        if (lower.startsWith("--home="))
+                                startupConfig.setHomeDirPath(arg.substring(arg.indexOf('=') + 1));
+                        if (lower.startsWith("--disable-viewing"))
+                                startupConfig.setDisableViewing(true);
+                }
+
+                Global.setup(startupConfig);
+        }
+
+    // Exit point
        @Override
        public void closeEvent(QCloseEvent event) {     
                logger.log(logger.HIGH, "Entering NeverNote.closeEvent");
@@ -2989,7 +3005,7 @@ public class NeverNote extends QMainWindow{
     // Get a note from Evernote (and put it in the browser)
        private void refreshEvernoteNote(boolean reload) {
                logger.log(logger.HIGH, "Entering NeverNote.refreshEvernoteNote");
-               if (Global.disableViewing) {
+               if (Global.getDisableViewing()) {
                        browserWindow.setEnabled(false);
                        return;
                }
diff --git a/src/cx/fbn/nevernote/config/FileManager.java b/src/cx/fbn/nevernote/config/FileManager.java
new file mode 100644 (file)
index 0000000..74b474e
--- /dev/null
@@ -0,0 +1,242 @@
+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 homeDirPath;
+    private final File homeDir;
+
+    private final String dbDirPath;
+    private final File dbDir;
+    private final String logsDirPath;
+    private final File logsDir;
+    private final String imagesDirPath;
+    private final File imagesDir;
+    private final String qssDirPath;
+    private final File qssDir;
+    private final String resDirPath;
+    private final File resDir;
+    private final String xmlDirPath;
+    private final File xmlDir;
+
+    /**
+     * Check or create the db, log and res directories, and purge files from 'res' .
+     *
+     * @param homeDirPath the installation dir containing db/log/res directories, must exist
+     */
+    public FileManager(String homeDirPath) throws InitializationException {
+        if (homeDirPath == null) {
+            throw new IllegalArgumentException("homeDirPath must not be null");
+        }
+
+        this.homeDir = new File(toPlatformPathSeparator(homeDirPath));
+        checkExistingWriteableDir(homeDir);
+        this.homeDirPath = slashTerminatePath(homeDir.getPath());
+
+        // Read-only
+        imagesDir = new File(homeDir, "images");
+        checkExistingReadableDir(imagesDir);
+        imagesDirPath = slashTerminatePath(imagesDir.getPath());
+
+        qssDir = new File(homeDir, "qss");
+        checkExistingReadableDir(qssDir);
+        qssDirPath = slashTerminatePath(qssDir.getPath());
+
+        xmlDir = new File(homeDir, "xml");
+        checkExistingReadableDir(xmlDir);
+        xmlDirPath = slashTerminatePath(xmlDir.getPath());
+
+        // Read-write
+        dbDir = new File(homeDir, "db");
+        createDirOrCheckWriteable(dbDir);
+        dbDirPath = slashTerminatePath(dbDir.getPath());
+
+        logsDir = new File(homeDir, "logs");
+        createDirOrCheckWriteable(logsDir);
+        logsDirPath = slashTerminatePath(logsDir.getPath());
+
+        resDir = new File(homeDir, "res");
+        createDirOrCheckWriteable(resDir);
+        resDirPath = slashTerminatePath(resDir.getPath());
+
+        deleteTopLevelFiles(resDir);
+    }
+
+    /**
+     * Get the path to the base installation directory, terminated with {@link File#separator}.
+     * This will contain backslashes on Windows.
+     */
+    public String getHomeDirPath() {
+        return homeDirPath;
+    }
+
+    /**
+     * Get a file below the 'db' directory.
+     */
+    public File getDbDirFile(String relativePath) {
+        return new File(dbDir, toPlatformPathSeparator(relativePath));
+    }
+
+    /**
+     * Get a file below the base installation directory.
+     */
+    public File getHomeDirFile(String relativePath) {
+        return new File(homeDir, toPlatformPathSeparator(relativePath));
+    }
+
+    /**
+     * Get a path below the base installation directory, using native {@link File#separator}.
+     * This will contain backslashes on Windows.
+     */
+    public String getHomeDirPath(String relativePath) {
+        return homeDirPath + toPlatformPathSeparator(relativePath);
+    }
+
+    /**
+     * 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 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 file below the 'xml' directory.
+     */
+    public File getXMLDirFile(String relativePath) {
+        return new File(xmlDir, toPlatformPathSeparator(relativePath));
+    }
+
+    private static String toPlatformPathSeparator(String relativePath) {
+        return ALL_PATH_SEPARATORS_REGEX.matcher(relativePath).replaceAll(File.separator);
+    }
+
+    private 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) throws InitializationException {
+        File[] toDelete = dir.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return pathname.isFile();
+            }
+        });
+        for (File f : toDelete) {
+            if (!f.delete()) {
+                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
+     */
+    private static void checkExistingWriteableDir(File dir) throws InitializationException {
+        checkExistingReadableDir(dir);
+        if (!dir.canWrite()) {
+            throw new InitializationException("Directory '" + dir + "' does not have write permission");
+        }
+    }
+}
diff --git a/src/cx/fbn/nevernote/config/InitializationException.java b/src/cx/fbn/nevernote/config/InitializationException.java
new file mode 100644 (file)
index 0000000..f41ad3a
--- /dev/null
@@ -0,0 +1,15 @@
+package cx.fbn.nevernote.config;
+
+/**
+ * Thrown for startup config errors
+ *
+ * @author Nick Clarke
+ */
+public class InitializationException extends Exception {
+    private static final long serialVersionUID = 0L;
+    
+    public InitializationException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/cx/fbn/nevernote/config/StartupConfig.java b/src/cx/fbn/nevernote/config/StartupConfig.java
new file mode 100644 (file)
index 0000000..487497a
--- /dev/null
@@ -0,0 +1,49 @@
+package cx.fbn.nevernote.config;
+
+
+/**
+ * Things that can only be changed at startup
+ *
+ * @author Nick Clarke
+ */
+public class StartupConfig {
+
+    // Init to default values
+    private String name = "NeverNote";
+    private String homeDirPath = System.getProperty("user.dir");
+    private boolean disableViewing = false;
+
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String n) {
+        if (isNonEmpty(n)) {
+            name = "NeverNote-" + n;
+        }
+    }
+
+    public String getHomeDirPath() {
+        return homeDirPath;
+    }
+
+    public void setHomeDirPath(String path) {
+        if (isNonEmpty(path)) {
+            homeDirPath = path;
+        }
+    }
+
+    public boolean getDisableViewing() {
+        return disableViewing;
+    }
+
+    public void setDisableViewing(boolean disableViewing) {
+        this.disableViewing = disableViewing;
+    }
+
+    private static boolean isNonEmpty(String n) {
+        return n != null && !n.trim().equals("");
+    }
+
+}