OSDN Git Service

Correct problem parsing notes where carriage returns did not separate some XML lines...
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / config / FileManager.java
1 package cx.fbn.nevernote.config;
2
3 import java.io.File;
4 import java.io.FileFilter;
5 import java.util.regex.Pattern;
6
7 /**
8  * Provides access to NeverNote standard runtime directories.
9  *
10  * @author Nick Clarke
11  */
12 public class FileManager {
13
14     private static final Pattern ALL_PATH_SEPARATORS_REGEX = Pattern.compile("[/\\\\]");
15
16     private final String programDirPath;
17     private final File programDir;
18     
19     private final String homeDirPath;
20     private final File homeDir;
21
22     private final String dbDirPath;
23     private final File dbDir;
24
25     private final File logsDir;
26
27     private final String imagesDirPath;
28     private final File imagesDir;
29
30     private final String spellDirPath;
31     private final File spellDir;
32     
33     private final String qssDirPath;
34     private final File qssDir;
35
36     private final String resDirPath;
37     private final File resDir;
38
39     private final File xmlDir;
40
41     /**
42      * Check or create the db, log and res directories.
43      *
44      * @param homeDirPath the installation dir containing db/log/res directories, must exist
45      * @throws InitializationException for missing directories or file permissions problems
46      */
47     public FileManager(String homeDirPath, String programDirPath) throws InitializationException {
48         if (homeDirPath == null) {
49             throw new IllegalArgumentException("homeDirPath must not be null");
50         }
51         if (programDirPath == null) {
52             throw new IllegalArgumentException("programDirPath must not be null");
53         }
54
55         this.homeDir = new File(toPlatformPathSeparator(homeDirPath));
56         this.programDir = new File(toPlatformPathSeparator(programDirPath));
57         createDirOrCheckWriteable(homeDir);
58         this.homeDirPath = slashTerminatePath(homeDir.getPath());
59         this.programDirPath = slashTerminatePath(programDir.getPath());
60         
61         // Read-only
62         imagesDir = new File(programDir, "images");
63         checkExistingReadableDir(imagesDir);
64         imagesDirPath = slashTerminatePath(imagesDir.getPath());
65
66         qssDir = new File(programDir, "qss");
67         checkExistingReadableDir(qssDir);
68         qssDirPath = slashTerminatePath(qssDir.getPath());
69
70         spellDir = new File(programDir, "spell");
71         checkExistingReadableDir(spellDir);
72         spellDirPath = slashTerminatePath(spellDir.getPath());
73         
74         xmlDir = new File(programDir, "xml");
75         checkExistingReadableDir(xmlDir);
76
77         // Read-write
78         dbDir = new File(homeDir, "db");
79         createDirOrCheckWriteable(dbDir);
80         dbDirPath = slashTerminatePath(dbDir.getPath());
81
82         logsDir = new File(homeDir, "logs");
83         createDirOrCheckWriteable(logsDir);
84
85         resDir = new File(homeDir, "res");
86         createDirOrCheckWriteable(resDir);
87         resDirPath = slashTerminatePath(resDir.getPath());
88     }
89
90     /**
91      * Get a file below the base user home directory.
92      */
93     public File getProgramDirFile(String relativePath) {
94         return new File(programDir, toPlatformPathSeparator(relativePath));
95     }
96
97     /**
98      * Get a path below the base user home directory, using native {@link File#separator}.
99      * This will contain backslashes on Windows.
100      */
101     public String getProgramDirPath(String relativePath) {
102         return programDirPath + toPlatformPathSeparator(relativePath);
103     }
104     
105     /**
106      * Get a file below the base user home directory.
107      */
108     public File getHomeDirFile(String relativePath) {
109         return new File(homeDir, toPlatformPathSeparator(relativePath));
110     }
111
112     /**
113      * Get a path below the base user home directory, using native {@link File#separator}.
114      * This will contain backslashes on Windows.
115      */
116     public String getHomeDirPath(String relativePath) {
117         return homeDirPath + toPlatformPathSeparator(relativePath);
118     }
119
120     /**
121      * Get a file below the 'db' directory.
122      */
123     public File getDbDirFile(String relativePath) {
124         return new File(dbDir, toPlatformPathSeparator(relativePath));
125     }
126
127     /**
128      * Get a path below the 'db' directory, using native {@link File#separator}.
129      * This will contain backslashes on Windows.
130      */
131     public String getSpellDirPath(String relativePath) {
132         return dbDirPath + toPlatformPathSeparator(relativePath);
133     }
134
135     /**
136      * Get a file below the 'spell' directory.
137      */
138     public File getSpellDirFile(String relativePath) {
139         return new File(dbDir, toPlatformPathSeparator(relativePath));
140     }
141     
142     /** 
143      * Get the spell directory for the jazzy word list
144      */
145     public String getSpellDirPath() {
146         return spellDirPath;
147     }
148
149     /**
150      * Get a path below the 'spell' directory, using native {@link File#separator}.
151      * This will contain backslashes on Windows.
152      */
153     public String getDbDirPath(String relativePath) {
154         return dbDirPath + toPlatformPathSeparator(relativePath);
155     }
156     
157     /**
158      * Get a file below the 'images' directory.
159      */
160     public File getImageDirFile(String relativePath) {
161         return new File(imagesDir, toPlatformPathSeparator(relativePath));
162     }
163
164     /**
165      * Get a path below the 'images' directory, using native {@link File#separator}.
166      * This will contain backslashes on Windows.
167      */
168     public String getImageDirPath(String relativePath) {
169         return imagesDirPath + toPlatformPathSeparator(relativePath);
170     }
171
172     /**
173      * Get a file below the 'logs' directory.
174      */
175     public File getLogsDirFile(String relativePath) {
176         return new File(logsDir, toPlatformPathSeparator(relativePath));
177     }
178
179     /**
180      * Get a path below the 'qss' directory, using native {@link File#separator}.
181      * This will contain backslashes on Windows.
182      */
183     public String getQssDirPath(String relativePath) {
184         return qssDirPath + toPlatformPathSeparator(relativePath);
185     }
186
187     /**
188      * Get a path to the 'res' directory, terminated with native {@link File#separator}.
189      * This will contain backslashes on Windows.
190      */
191     public String getResDirPath() {
192         return resDirPath;
193     }
194
195     /**
196      * Get a path below the 'res' directory, using native {@link File#separator}.
197      * This will contain backslashes on Windows.
198      */
199     public String getResDirPath(String relativePath) {
200         return resDirPath + toPlatformPathSeparator(relativePath);
201     }
202
203     /**
204      * Get a file below the 'xml' directory.
205      */
206     public File getXMLDirFile(String relativePath) {
207         return new File(xmlDir, toPlatformPathSeparator(relativePath));
208     }
209
210     private static String toPlatformPathSeparator(String relativePath) {
211         // Sometimes a space in the file name comes across as a %20.  This is to put it back as a space.
212         relativePath = relativePath.replace("%20", " ");
213                 return ALL_PATH_SEPARATORS_REGEX.matcher(relativePath).replaceAll(
214                                 // Must double-escape backslashes,
215                                 // because they have special meaning in the replacement string of Matcher.replaceAll
216                                 (File.separator.equals("\\") ? "\\\\" : File.separator));
217     }
218
219     private static String slashTerminatePath(String path) {
220         if (!path.substring(path.length() - 1).equals(File.separator)) {
221             return path + File.separator;
222         }
223         return path;
224     }
225
226     /**
227      * Delete first-level files (but not directories) from the directory.
228      *
229      * @throws InitializationException for file deletion failures
230      */
231     private static void deleteTopLevelFiles(File dir) throws InitializationException {
232         File[] toDelete = dir.listFiles(new FileFilter() {
233             @Override
234             public boolean accept(File pathname) {
235                 return pathname.isFile();
236             }
237         });
238         for (File f : toDelete) {
239             if (!f.delete()) {
240                 throw new InitializationException("Failed to delete file: '" + f + "'");
241             }
242         }
243     }
244
245     /**
246      * @throws InitializationException for bad file permissions, or a file instead of a directory
247      */
248     private static void createDirOrCheckWriteable(File dir) throws InitializationException {
249         if (dir.isDirectory()) {
250             // Dir exists, check permissions
251             if (!dir.canRead()) {
252                 throw new InitializationException("Directory '" + dir + "' does not have read permission");
253             }
254             if (!dir.canWrite()) {
255                 throw new InitializationException("Directory '" + dir + "' does not have write permission");
256             }
257         } else if (!dir.exists()) {
258             if (!dir.mkdirs()) {
259                 throw new InitializationException("Failed to create directory '" + dir + "'");
260             }
261         } else {
262             throw new InitializationException("Expected directory '" + dir + "' but found a file instead");
263         }
264     }
265
266     /**
267      * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory
268      */
269     private static void checkExistingReadableDir(File dir) throws InitializationException {
270         if (dir.isDirectory()) {
271             // Dir exists, check permissions
272             if (!dir.canRead()) {
273                 throw new InitializationException("Directory '" + dir + "' does not have read permission");
274             }
275         } else if (!dir.exists()) {
276             throw new InitializationException("Directory '" + dir + "' does not exist");
277         } else {
278             throw new InitializationException("Expected directory '" + dir + "' but found a file instead");
279         }
280     }
281
282     /**
283      * @throws InitializationException if non-existent, bad file permissions, or a file instead of a directory
284      */
285     private static void checkExistingWriteableDir(File dir) throws InitializationException {
286         checkExistingReadableDir(dir);
287         if (!dir.canWrite()) {
288             throw new InitializationException("Directory '" + dir + "' does not have write permission");
289         }
290     }
291
292     /**
293      * Called at startup to purge files from 'res' directory.
294      */
295     public void purgeResDirectory() throws InitializationException {
296         deleteTopLevelFiles(resDir);
297     }
298 }