Add enumeration for location of the configuration file.
[jSite.git] / src / de / todesbaum / jsite / main / Configuration.java
1 /*
2  * jSite - a tool for uploading websites into Freenet
3  * Copyright (C) 2006 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package de.todesbaum.jsite.main;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Map.Entry;
36
37 import de.todesbaum.jsite.application.FileOption;
38 import de.todesbaum.jsite.application.Node;
39 import de.todesbaum.jsite.application.Project;
40 import de.todesbaum.util.io.Closer;
41 import de.todesbaum.util.io.StreamCopier;
42 import de.todesbaum.util.xml.SimpleXML;
43 import de.todesbaum.util.xml.XML;
44
45 /**
46  * The configuration.
47  *
48  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
49  */
50 public class Configuration {
51
52         /**
53          * The location of the configuration directory.
54          *
55          * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
56          */
57         public enum ConfigurationDirectory {
58
59                 /** The configuration is in the same directory as the JAR file. */
60                 NEXT_TO_JAR_FILE,
61
62                 /**
63                  * The configuration is in the user’s home directory. This is the
64                  * pre-0.9.3 default.
65                  */
66                 HOME_DIRECTORY;
67
68         }
69
70         /** The configuration directory. */
71         private ConfigurationDirectory configurationDirectory = ConfigurationDirectory.HOME_DIRECTORY;
72
73         /** The name of the file the configuration is stored to. */
74         private String filename;
75
76         /** The name of the lock file. */
77         private String lockFilename;
78
79         /** The root node of the configuration. */
80         private SimpleXML rootNode;
81
82         /**
83          * Creates a new configuration with the default name of the configuration
84          * file.
85          */
86         public Configuration() {
87                 this(System.getProperty("user.home") + "/.jSite/config7");
88         }
89
90         /**
91          * Creates a new configuration that is read from the given file.
92          *
93          * @param filename
94          *            The name of the configuration file
95          */
96         public Configuration(String filename) {
97                 this(filename, filename + ".lock");
98         }
99
100         /**
101          * Creates a new configuration that is read from the given file and uses the
102          * given lock file.
103          *
104          * @param filename
105          *            The name of the configuration file
106          * @param lockFilename
107          *            The name of the lock file
108          */
109         public Configuration(String filename, String lockFilename) {
110                 this.filename = filename;
111                 this.lockFilename = lockFilename;
112                 readConfiguration();
113         }
114
115         /**
116          * Creates the directory of the configuration file.
117          *
118          * @return <code>true</code> if the directory exists, or if it could be
119          *         created, <code>false</code> otherwise
120          */
121         private boolean createConfigDirectory() {
122                 File configDirectory = new File(filename).getAbsoluteFile().getParentFile();
123                 return (configDirectory.exists() && configDirectory.isDirectory()) || configDirectory.mkdirs();
124         }
125
126         /**
127          * Creates the lock file.
128          *
129          * @return <code>true</code> if the lock file did not already exist and
130          *         could be created, <code>false</code> otherwise
131          */
132         public boolean createLockFile() {
133                 if (!createConfigDirectory()) {
134                         return false;
135                 }
136                 File lockFile = new File(lockFilename);
137                 try {
138                         boolean fileLocked = lockFile.createNewFile();
139                         if (fileLocked) {
140                                 lockFile.deleteOnExit();
141                         }
142                         return fileLocked;
143                 } catch (IOException e) {
144                         /* ignore. */
145                 }
146                 return false;
147         }
148
149         /**
150          * Tells the VM to remove the lock file on program exit.
151          */
152         public void removeLockfileOnExit() {
153                 new File(lockFilename).deleteOnExit();
154         }
155
156         /**
157          * Reads the configuration from the file.
158          */
159         private void readConfiguration() {
160                 File configurationFile = new File(filename);
161                 if (configurationFile.exists()) {
162                         ByteArrayOutputStream fileByteOutputStream = null;
163                         FileInputStream fileInputStream = null;
164                         try {
165                                 fileByteOutputStream = new ByteArrayOutputStream();
166                                 fileInputStream = new FileInputStream(configurationFile);
167                                 StreamCopier.copy(fileInputStream, fileByteOutputStream, configurationFile.length());
168                                 fileByteOutputStream.close();
169                                 byte[] fileBytes = fileByteOutputStream.toByteArray();
170                                 rootNode = SimpleXML.fromDocument(XML.transformToDocument(fileBytes));
171                                 return;
172                         } catch (FileNotFoundException e) {
173                                 /* ignore. */
174                         } catch (IOException e) {
175                                 /* ignore. */
176                         } finally {
177                                 Closer.close(fileInputStream);
178                                 Closer.close(fileByteOutputStream);
179                         }
180                 }
181                 rootNode = new SimpleXML("configuration");
182         }
183
184         /**
185          * Saves the configuration.
186          *
187          * @return <code>true</code> if the configuration could be saved,
188          *         <code>false</code> otherwise
189          */
190         public boolean save() {
191                 File configurationFile = new File(filename);
192                 if (!configurationFile.exists()) {
193                         File configurationFilePath = configurationFile.getAbsoluteFile().getParentFile();
194                         if (!configurationFilePath.exists() && !configurationFilePath.mkdirs()) {
195                                 return false;
196                         }
197                 }
198                 FileOutputStream fileOutputStream = null;
199                 ByteArrayInputStream configurationInputStream = null;
200                 try {
201                         byte[] configurationBytes = XML.transformToByteArray(rootNode.getDocument());
202                         configurationInputStream = new ByteArrayInputStream(configurationBytes);
203                         fileOutputStream = new FileOutputStream(configurationFile);
204                         StreamCopier.copy(configurationInputStream, fileOutputStream, configurationBytes.length);
205                         return true;
206                 } catch (IOException ioe1) {
207                         /* ignore. */
208                 } finally {
209                         Closer.close(configurationInputStream);
210                         Closer.close(fileOutputStream);
211                 }
212                 return false;
213         }
214
215         /**
216          * Returns the value of a node.
217          *
218          * @param nodeNames
219          *            The name of all nodes in the chain
220          * @param defaultValue
221          *            The default value to return if the node could not be found
222          * @return The value of the node, or the default value if the node could not
223          *         be found
224          */
225         private String getNodeValue(String[] nodeNames, String defaultValue) {
226                 SimpleXML node = rootNode;
227                 int nodeIndex = 0;
228                 while ((node != null) && (nodeIndex < nodeNames.length)) {
229                         node = node.getNode(nodeNames[nodeIndex++]);
230                 }
231                 if (node == null) {
232                         return defaultValue;
233                 }
234                 return node.getValue();
235         }
236
237         /**
238          * Returns the integer value of a node.
239          *
240          * @param nodeNames
241          *            The names of all nodes in the chain
242          * @param defaultValue
243          *            The default value to return if the node can not be found
244          * @return The parsed integer value, or the default value if the node can
245          *         not be found or the value can not be parsed into an integer
246          */
247         private int getNodeIntValue(String[] nodeNames, int defaultValue) {
248                 try {
249                         return Integer.parseInt(getNodeValue(nodeNames, String.valueOf(defaultValue)));
250                 } catch (NumberFormatException nfe1) {
251                         /* ignore. */
252                 }
253                 return defaultValue;
254         }
255
256         /**
257          * Returns the boolean value of a node.
258          *
259          * @param nodeNames
260          *            The names of all nodes in the chain
261          * @param defaultValue
262          *            The default value to return if the node can not be found
263          * @return The parsed boolean value, or the default value if the node can
264          *         not be found
265          */
266         private boolean getNodeBooleanValue(String[] nodeNames, boolean defaultValue) {
267                 String nodeValue = getNodeValue(nodeNames, null);
268                 if (nodeValue == null) {
269                         return defaultValue;
270                 }
271                 return Boolean.parseBoolean(nodeValue);
272         }
273
274         /**
275          * Returns the hostname of the node.
276          *
277          * @return The hostname of the node
278          * @deprecated Use {@link #getSelectedNode()} instead
279          */
280         @Deprecated
281         public String getNodeAddress() {
282                 return getNodeValue(new String[] { "node-address" }, "localhost");
283         }
284
285         /**
286          * Sets the hostname of the node.
287          *
288          * @param nodeAddress
289          *            The hostname of the node
290          * @deprecated Use {@link #setSelectedNode(Node)} instead
291          */
292         @Deprecated
293         public void setNodeAddress(String nodeAddress) {
294                 rootNode.replace("node-address", nodeAddress);
295         }
296
297         /**
298          * The port number of the node
299          *
300          * @return The port number of the node
301          * @deprecated Use {@link #getSelectedNode()} instead.
302          */
303         @Deprecated
304         public int getNodePort() {
305                 return getNodeIntValue(new String[] { "node-port" }, 9481);
306         }
307
308         /**
309          * Sets the port number of the node.
310          *
311          * @param nodePort
312          *            The port number of the node
313          * @deprecated Use {@link #setSelectedNode(Node)} instead
314          */
315         @Deprecated
316         public void setNodePort(int nodePort) {
317                 rootNode.replace("node-port", String.valueOf(nodePort));
318         }
319
320         /**
321          * Returns whether the node configuration page should be skipped on startup.
322          *
323          * @return <code>true</code> to skip the node configuration page on startup,
324          *         <code>false</code> to show it
325          */
326         public boolean isSkipNodePage() {
327                 return getNodeBooleanValue(new String[] { "skip-node-page" }, false);
328         }
329
330         /**
331          * Sets whether the node configuration page should be skipped on startup.
332          *
333          * @param skipNodePage
334          *            <code>true</code> to skip the node configuration page on
335          *            startup, <code>false</code> to show it
336          */
337         public void setSkipNodePage(boolean skipNodePage) {
338                 rootNode.replace("skip-node-page", String.valueOf(skipNodePage));
339         }
340
341         /**
342          * Returns all configured projects.
343          *
344          * @return A list of all projects
345          */
346         public Project[] getProjects() {
347                 List<Project> projects = new ArrayList<Project>();
348                 SimpleXML projectsNode = rootNode.getNode("project-list");
349                 if (projectsNode != null) {
350                         SimpleXML[] projectNodes = projectsNode.getNodes("project");
351                         for (SimpleXML projectNode : projectNodes) {
352                                 try {
353                                         Project project = new Project();
354                                         projects.add(project);
355                                         project.setDescription(projectNode.getNode("description").getValue(""));
356                                         String indexFile = projectNode.getNode("index-file").getValue("");
357                                         if (indexFile.indexOf('/') > -1) {
358                                                 indexFile = "";
359                                         }
360                                         project.setIndexFile(indexFile);
361                                         project.setLastInsertionTime(Long.parseLong(projectNode.getNode("last-insertion-time").getValue("0")));
362                                         project.setLocalPath(projectNode.getNode("local-path").getValue(""));
363                                         project.setName(projectNode.getNode("name").getValue(""));
364                                         project.setPath(projectNode.getNode("path").getValue(""));
365                                         if ((project.getPath() != null) && (project.getPath().indexOf("/") != -1)) {
366                                                 project.setPath(project.getPath().replaceAll("/", ""));
367                                         }
368                                         project.setEdition(Integer.parseInt(projectNode.getNode("edition").getValue("0")));
369                                         project.setInsertURI(projectNode.getNode("insert-uri").getValue(""));
370                                         project.setRequestURI(projectNode.getNode("request-uri").getValue(""));
371                                         if (projectNode.getNode("ignore-hidden-files") != null) {
372                                                 project.setIgnoreHiddenFiles(Boolean.parseBoolean(projectNode.getNode("ignore-hidden-files").getValue("true")));
373                                         } else {
374                                                 project.setIgnoreHiddenFiles(true);
375                                         }
376                                         SimpleXML fileOptionsNode = projectNode.getNode("file-options");
377                                         Map<String, FileOption> fileOptions = new HashMap<String, FileOption>();
378                                         if (fileOptionsNode != null) {
379                                                 SimpleXML[] fileOptionNodes = fileOptionsNode.getNodes("file-option");
380                                                 for (SimpleXML fileOptionNode : fileOptionNodes) {
381                                                         String filename = fileOptionNode.getNode("filename").getValue();
382                                                         FileOption fileOption = project.getFileOption(filename);
383                                                         fileOption.setInsert(Boolean.parseBoolean(fileOptionNode.getNode("insert").getValue()));
384                                                         if (fileOptionNode.getNode("insert-redirect") != null) {
385                                                                 fileOption.setInsertRedirect(Boolean.parseBoolean(fileOptionNode.getNode("insert-redirect").getValue()));
386                                                         }
387                                                         fileOption.setCustomKey(fileOptionNode.getNode("custom-key").getValue(""));
388                                                         if (fileOptionNode.getNode("changed-name") != null) {
389                                                                 fileOption.setChangedName(fileOptionNode.getNode("changed-name").getValue());
390                                                         }
391                                                         fileOption.setMimeType(fileOptionNode.getNode("mime-type").getValue(""));
392                                                         fileOption.setContainer(fileOptionNode.getNode("container").getValue());
393                                                         if (fileOptionNode.getNode("replace-edition") != null) {
394                                                                 fileOption.setReplaceEdition(Boolean.parseBoolean(fileOptionNode.getNode("replace-edition").getValue()));
395                                                                 fileOption.setEditionRange(Integer.parseInt(fileOptionNode.getNode("edition-range").getValue()));
396                                                         }
397                                                         fileOptions.put(filename, fileOption);
398                                                 }
399                                         }
400                                         project.setFileOptions(fileOptions);
401                                 } catch (NumberFormatException nfe1) {
402                                         nfe1.printStackTrace();
403                                 }
404                         }
405                 }
406                 return projects.toArray(new Project[projects.size()]);
407         }
408
409         /**
410          * Sets the list of all projects.
411          *
412          * @param projects
413          *            The list of all projects
414          */
415         public void setProjects(Project[] projects) {
416                 SimpleXML projectsNode = new SimpleXML("project-list");
417                 for (Project project : projects) {
418                         SimpleXML projectNode = projectsNode.append("project");
419                         projectNode.append("edition", String.valueOf(project.getEdition()));
420                         projectNode.append("description", project.getDescription());
421                         projectNode.append("index-file", project.getIndexFile());
422                         projectNode.append("last-insertion-time", String.valueOf(project.getLastInsertionTime()));
423                         projectNode.append("local-path", project.getLocalPath());
424                         projectNode.append("name", project.getName());
425                         projectNode.append("path", project.getPath());
426                         projectNode.append("insert-uri", project.getInsertURI());
427                         projectNode.append("request-uri", project.getRequestURI());
428                         projectNode.append("ignore-hidden-files", String.valueOf(project.isIgnoreHiddenFiles()));
429                         SimpleXML fileOptionsNode = projectNode.append("file-options");
430                         Iterator<Entry<String, FileOption>> entries = project.getFileOptions().entrySet().iterator();
431                         while (entries.hasNext()) {
432                                 Entry<String, FileOption> entry = entries.next();
433                                 FileOption fileOption = entry.getValue();
434                                 if (fileOption.isCustom()) {
435                                         SimpleXML fileOptionNode = fileOptionsNode.append("file-option");
436                                         fileOptionNode.append("filename", entry.getKey());
437                                         fileOptionNode.append("insert", String.valueOf(fileOption.isInsert()));
438                                         fileOptionNode.append("insert-redirect", String.valueOf(fileOption.isInsertRedirect()));
439                                         fileOptionNode.append("custom-key", fileOption.getCustomKey());
440                                         fileOptionNode.append("changed-name", fileOption.getChangedName());
441                                         fileOptionNode.append("mime-type", fileOption.getMimeType());
442                                         fileOptionNode.append("container", fileOption.getContainer());
443                                         fileOptionNode.append("replace-edition", String.valueOf(fileOption.getReplaceEdition()));
444                                         fileOptionNode.append("edition-range", String.valueOf(fileOption.getEditionRange()));
445                                 }
446                         }
447                 }
448                 rootNode.replace(projectsNode);
449         }
450
451         /**
452          * Returns the stored locale.
453          *
454          * @return The stored locale
455          */
456         public Locale getLocale() {
457                 String language = getNodeValue(new String[] { "i18n", "language" }, "en");
458                 String country = getNodeValue(new String[] { "i18n", "country" }, null);
459                 if (country != null) {
460                         return new Locale(language, country);
461                 }
462                 return new Locale(language);
463         }
464
465         /**
466          * Sets the locale to store.
467          *
468          * @param locale
469          *            The locale to store
470          */
471         public void setLocale(Locale locale) {
472                 SimpleXML i18nNode = new SimpleXML("i18n");
473                 if (locale.getCountry().length() != 0) {
474                         i18nNode.append("country", locale.getCountry());
475                 }
476                 i18nNode.append("language", locale.getLanguage());
477                 rootNode.replace(i18nNode);
478                 return;
479         }
480
481         /**
482          * Returns a list of configured nodes.
483          *
484          * @return The list of the configured nodes
485          */
486         public Node[] getNodes() {
487                 SimpleXML nodesNode = rootNode.getNode("nodes");
488                 if (nodesNode == null) {
489                         String hostname = getNodeAddress();
490                         int port = getNodePort();
491                         if (hostname == null) {
492                                 hostname = "127.0.0.1";
493                                 port = 9481;
494                         }
495                         return new Node[] { new Node(hostname, port, "Node") };
496                 }
497                 SimpleXML[] nodeNodes = nodesNode.getNodes("node");
498                 Node[] returnNodes = new Node[nodeNodes.length];
499                 int nodeIndex = 0;
500                 for (SimpleXML nodeNode : nodeNodes) {
501                         String name = nodeNode.getNode("name").getValue();
502                         String hostname = nodeNode.getNode("hostname").getValue();
503                         int port = Integer.parseInt(nodeNode.getNode("port").getValue());
504                         Node node = new Node(hostname, port, name);
505                         returnNodes[nodeIndex++] = node;
506                 }
507                 return returnNodes;
508         }
509
510         /**
511          * Sets the list of configured nodes.
512          *
513          * @param nodes
514          *            The list of configured nodes
515          */
516         public void setNodes(Node[] nodes) {
517                 SimpleXML nodesNode = new SimpleXML("nodes");
518                 for (Node node : nodes) {
519                         SimpleXML nodeNode = nodesNode.append("node");
520                         nodeNode.append("name", node.getName());
521                         nodeNode.append("hostname", node.getHostname());
522                         nodeNode.append("port", String.valueOf(node.getPort()));
523                 }
524                 rootNode.replace(nodesNode);
525                 rootNode.remove("node-address");
526                 rootNode.remove("node-port");
527         }
528
529         /**
530          * Sets the selected node.
531          *
532          * @param selectedNode
533          *            The selected node
534          */
535         public void setSelectedNode(Node selectedNode) {
536                 SimpleXML selectedNodeNode = new SimpleXML("selected-node");
537                 selectedNodeNode.append("name", selectedNode.getName());
538                 selectedNodeNode.append("hostname", selectedNode.getHostname());
539                 selectedNodeNode.append("port", String.valueOf(selectedNode.getPort()));
540                 rootNode.replace(selectedNodeNode);
541         }
542
543         /**
544          * Returns the selected node.
545          *
546          * @return The selected node
547          */
548         public Node getSelectedNode() {
549                 SimpleXML selectedNodeNode = rootNode.getNode("selected-node");
550                 if (selectedNodeNode == null) {
551                         String hostname = getNodeAddress();
552                         int port = getNodePort();
553                         if (hostname == null) {
554                                 hostname = "127.0.0.1";
555                                 port = 9481;
556                         }
557                         return new Node(hostname, port, "Node");
558                 }
559                 String name = selectedNodeNode.getNode("name").getValue();
560                 String hostname = selectedNodeNode.getNode("hostname").getValue();
561                 int port = Integer.valueOf(selectedNodeNode.getNode("port").getValue());
562                 return new Node(hostname, port, name);
563         }
564
565         /**
566          * Returns the temp directory to use.
567          *
568          * @return The temp directoy, or {@code null} to use the default temp
569          *         directory
570          */
571         public String getTempDirectory() {
572                 return getNodeValue(new String[] { "temp-directory" }, null);
573         }
574
575         /**
576          * Sets the temp directory to use.
577          *
578          * @param tempDirectory
579          *            The temp directory to use, or {@code null} to use the default
580          *            temp directory
581          */
582         public void setTempDirectory(String tempDirectory) {
583                 if (tempDirectory != null) {
584                         SimpleXML tempDirectoryNode = new SimpleXML("temp-directory");
585                         tempDirectoryNode.setValue(tempDirectory);
586                         rootNode.replace(tempDirectoryNode);
587                 } else {
588                         rootNode.remove("temp-directory");
589                 }
590         }
591
592 }