Add accessors for the configuration file location.
[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          * Returns the configuration directory.
117          *
118          * @return The configuration directory
119          */
120         public ConfigurationDirectory getConfigurationDirectory() {
121                 return configurationDirectory;
122         }
123
124         /**
125          * Sets the configuration directory.
126          *
127          * @param configurationDirectory
128          *            The configuration directory
129          */
130         public void setConfigurationDirectory(ConfigurationDirectory configurationDirectory) {
131                 this.configurationDirectory = configurationDirectory;
132         }
133
134         /**
135          * Creates the directory of the configuration file.
136          *
137          * @return <code>true</code> if the directory exists, or if it could be
138          *         created, <code>false</code> otherwise
139          */
140         private boolean createConfigDirectory() {
141                 File configDirectory = new File(filename).getAbsoluteFile().getParentFile();
142                 return (configDirectory.exists() && configDirectory.isDirectory()) || configDirectory.mkdirs();
143         }
144
145         /**
146          * Creates the lock file.
147          *
148          * @return <code>true</code> if the lock file did not already exist and
149          *         could be created, <code>false</code> otherwise
150          */
151         public boolean createLockFile() {
152                 if (!createConfigDirectory()) {
153                         return false;
154                 }
155                 File lockFile = new File(lockFilename);
156                 try {
157                         boolean fileLocked = lockFile.createNewFile();
158                         if (fileLocked) {
159                                 lockFile.deleteOnExit();
160                         }
161                         return fileLocked;
162                 } catch (IOException e) {
163                         /* ignore. */
164                 }
165                 return false;
166         }
167
168         /**
169          * Tells the VM to remove the lock file on program exit.
170          */
171         public void removeLockfileOnExit() {
172                 new File(lockFilename).deleteOnExit();
173         }
174
175         /**
176          * Reads the configuration from the file.
177          */
178         private void readConfiguration() {
179                 File configurationFile = new File(filename);
180                 if (configurationFile.exists()) {
181                         ByteArrayOutputStream fileByteOutputStream = null;
182                         FileInputStream fileInputStream = null;
183                         try {
184                                 fileByteOutputStream = new ByteArrayOutputStream();
185                                 fileInputStream = new FileInputStream(configurationFile);
186                                 StreamCopier.copy(fileInputStream, fileByteOutputStream, configurationFile.length());
187                                 fileByteOutputStream.close();
188                                 byte[] fileBytes = fileByteOutputStream.toByteArray();
189                                 rootNode = SimpleXML.fromDocument(XML.transformToDocument(fileBytes));
190                                 return;
191                         } catch (FileNotFoundException e) {
192                                 /* ignore. */
193                         } catch (IOException e) {
194                                 /* ignore. */
195                         } finally {
196                                 Closer.close(fileInputStream);
197                                 Closer.close(fileByteOutputStream);
198                         }
199                 }
200                 rootNode = new SimpleXML("configuration");
201         }
202
203         /**
204          * Saves the configuration.
205          *
206          * @return <code>true</code> if the configuration could be saved,
207          *         <code>false</code> otherwise
208          */
209         public boolean save() {
210                 File configurationFile = new File(filename);
211                 if (!configurationFile.exists()) {
212                         File configurationFilePath = configurationFile.getAbsoluteFile().getParentFile();
213                         if (!configurationFilePath.exists() && !configurationFilePath.mkdirs()) {
214                                 return false;
215                         }
216                 }
217                 FileOutputStream fileOutputStream = null;
218                 ByteArrayInputStream configurationInputStream = null;
219                 try {
220                         byte[] configurationBytes = XML.transformToByteArray(rootNode.getDocument());
221                         configurationInputStream = new ByteArrayInputStream(configurationBytes);
222                         fileOutputStream = new FileOutputStream(configurationFile);
223                         StreamCopier.copy(configurationInputStream, fileOutputStream, configurationBytes.length);
224                         return true;
225                 } catch (IOException ioe1) {
226                         /* ignore. */
227                 } finally {
228                         Closer.close(configurationInputStream);
229                         Closer.close(fileOutputStream);
230                 }
231                 return false;
232         }
233
234         /**
235          * Returns the value of a node.
236          *
237          * @param nodeNames
238          *            The name of all nodes in the chain
239          * @param defaultValue
240          *            The default value to return if the node could not be found
241          * @return The value of the node, or the default value if the node could not
242          *         be found
243          */
244         private String getNodeValue(String[] nodeNames, String defaultValue) {
245                 SimpleXML node = rootNode;
246                 int nodeIndex = 0;
247                 while ((node != null) && (nodeIndex < nodeNames.length)) {
248                         node = node.getNode(nodeNames[nodeIndex++]);
249                 }
250                 if (node == null) {
251                         return defaultValue;
252                 }
253                 return node.getValue();
254         }
255
256         /**
257          * Returns the integer 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 integer value, or the default value if the node can
264          *         not be found or the value can not be parsed into an integer
265          */
266         private int getNodeIntValue(String[] nodeNames, int defaultValue) {
267                 try {
268                         return Integer.parseInt(getNodeValue(nodeNames, String.valueOf(defaultValue)));
269                 } catch (NumberFormatException nfe1) {
270                         /* ignore. */
271                 }
272                 return defaultValue;
273         }
274
275         /**
276          * Returns the boolean value of a node.
277          *
278          * @param nodeNames
279          *            The names of all nodes in the chain
280          * @param defaultValue
281          *            The default value to return if the node can not be found
282          * @return The parsed boolean value, or the default value if the node can
283          *         not be found
284          */
285         private boolean getNodeBooleanValue(String[] nodeNames, boolean defaultValue) {
286                 String nodeValue = getNodeValue(nodeNames, null);
287                 if (nodeValue == null) {
288                         return defaultValue;
289                 }
290                 return Boolean.parseBoolean(nodeValue);
291         }
292
293         /**
294          * Returns the hostname of the node.
295          *
296          * @return The hostname of the node
297          * @deprecated Use {@link #getSelectedNode()} instead
298          */
299         @Deprecated
300         public String getNodeAddress() {
301                 return getNodeValue(new String[] { "node-address" }, "localhost");
302         }
303
304         /**
305          * Sets the hostname of the node.
306          *
307          * @param nodeAddress
308          *            The hostname of the node
309          * @deprecated Use {@link #setSelectedNode(Node)} instead
310          */
311         @Deprecated
312         public void setNodeAddress(String nodeAddress) {
313                 rootNode.replace("node-address", nodeAddress);
314         }
315
316         /**
317          * The port number of the node
318          *
319          * @return The port number of the node
320          * @deprecated Use {@link #getSelectedNode()} instead.
321          */
322         @Deprecated
323         public int getNodePort() {
324                 return getNodeIntValue(new String[] { "node-port" }, 9481);
325         }
326
327         /**
328          * Sets the port number of the node.
329          *
330          * @param nodePort
331          *            The port number of the node
332          * @deprecated Use {@link #setSelectedNode(Node)} instead
333          */
334         @Deprecated
335         public void setNodePort(int nodePort) {
336                 rootNode.replace("node-port", String.valueOf(nodePort));
337         }
338
339         /**
340          * Returns whether the node configuration page should be skipped on startup.
341          *
342          * @return <code>true</code> to skip the node configuration page on startup,
343          *         <code>false</code> to show it
344          */
345         public boolean isSkipNodePage() {
346                 return getNodeBooleanValue(new String[] { "skip-node-page" }, false);
347         }
348
349         /**
350          * Sets whether the node configuration page should be skipped on startup.
351          *
352          * @param skipNodePage
353          *            <code>true</code> to skip the node configuration page on
354          *            startup, <code>false</code> to show it
355          */
356         public void setSkipNodePage(boolean skipNodePage) {
357                 rootNode.replace("skip-node-page", String.valueOf(skipNodePage));
358         }
359
360         /**
361          * Returns all configured projects.
362          *
363          * @return A list of all projects
364          */
365         public Project[] getProjects() {
366                 List<Project> projects = new ArrayList<Project>();
367                 SimpleXML projectsNode = rootNode.getNode("project-list");
368                 if (projectsNode != null) {
369                         SimpleXML[] projectNodes = projectsNode.getNodes("project");
370                         for (SimpleXML projectNode : projectNodes) {
371                                 try {
372                                         Project project = new Project();
373                                         projects.add(project);
374                                         project.setDescription(projectNode.getNode("description").getValue(""));
375                                         String indexFile = projectNode.getNode("index-file").getValue("");
376                                         if (indexFile.indexOf('/') > -1) {
377                                                 indexFile = "";
378                                         }
379                                         project.setIndexFile(indexFile);
380                                         project.setLastInsertionTime(Long.parseLong(projectNode.getNode("last-insertion-time").getValue("0")));
381                                         project.setLocalPath(projectNode.getNode("local-path").getValue(""));
382                                         project.setName(projectNode.getNode("name").getValue(""));
383                                         project.setPath(projectNode.getNode("path").getValue(""));
384                                         if ((project.getPath() != null) && (project.getPath().indexOf("/") != -1)) {
385                                                 project.setPath(project.getPath().replaceAll("/", ""));
386                                         }
387                                         project.setEdition(Integer.parseInt(projectNode.getNode("edition").getValue("0")));
388                                         project.setInsertURI(projectNode.getNode("insert-uri").getValue(""));
389                                         project.setRequestURI(projectNode.getNode("request-uri").getValue(""));
390                                         if (projectNode.getNode("ignore-hidden-files") != null) {
391                                                 project.setIgnoreHiddenFiles(Boolean.parseBoolean(projectNode.getNode("ignore-hidden-files").getValue("true")));
392                                         } else {
393                                                 project.setIgnoreHiddenFiles(true);
394                                         }
395                                         SimpleXML fileOptionsNode = projectNode.getNode("file-options");
396                                         Map<String, FileOption> fileOptions = new HashMap<String, FileOption>();
397                                         if (fileOptionsNode != null) {
398                                                 SimpleXML[] fileOptionNodes = fileOptionsNode.getNodes("file-option");
399                                                 for (SimpleXML fileOptionNode : fileOptionNodes) {
400                                                         String filename = fileOptionNode.getNode("filename").getValue();
401                                                         FileOption fileOption = project.getFileOption(filename);
402                                                         fileOption.setInsert(Boolean.parseBoolean(fileOptionNode.getNode("insert").getValue()));
403                                                         if (fileOptionNode.getNode("insert-redirect") != null) {
404                                                                 fileOption.setInsertRedirect(Boolean.parseBoolean(fileOptionNode.getNode("insert-redirect").getValue()));
405                                                         }
406                                                         fileOption.setCustomKey(fileOptionNode.getNode("custom-key").getValue(""));
407                                                         if (fileOptionNode.getNode("changed-name") != null) {
408                                                                 fileOption.setChangedName(fileOptionNode.getNode("changed-name").getValue());
409                                                         }
410                                                         fileOption.setMimeType(fileOptionNode.getNode("mime-type").getValue(""));
411                                                         fileOption.setContainer(fileOptionNode.getNode("container").getValue());
412                                                         if (fileOptionNode.getNode("replace-edition") != null) {
413                                                                 fileOption.setReplaceEdition(Boolean.parseBoolean(fileOptionNode.getNode("replace-edition").getValue()));
414                                                                 fileOption.setEditionRange(Integer.parseInt(fileOptionNode.getNode("edition-range").getValue()));
415                                                         }
416                                                         fileOptions.put(filename, fileOption);
417                                                 }
418                                         }
419                                         project.setFileOptions(fileOptions);
420                                 } catch (NumberFormatException nfe1) {
421                                         nfe1.printStackTrace();
422                                 }
423                         }
424                 }
425                 return projects.toArray(new Project[projects.size()]);
426         }
427
428         /**
429          * Sets the list of all projects.
430          *
431          * @param projects
432          *            The list of all projects
433          */
434         public void setProjects(Project[] projects) {
435                 SimpleXML projectsNode = new SimpleXML("project-list");
436                 for (Project project : projects) {
437                         SimpleXML projectNode = projectsNode.append("project");
438                         projectNode.append("edition", String.valueOf(project.getEdition()));
439                         projectNode.append("description", project.getDescription());
440                         projectNode.append("index-file", project.getIndexFile());
441                         projectNode.append("last-insertion-time", String.valueOf(project.getLastInsertionTime()));
442                         projectNode.append("local-path", project.getLocalPath());
443                         projectNode.append("name", project.getName());
444                         projectNode.append("path", project.getPath());
445                         projectNode.append("insert-uri", project.getInsertURI());
446                         projectNode.append("request-uri", project.getRequestURI());
447                         projectNode.append("ignore-hidden-files", String.valueOf(project.isIgnoreHiddenFiles()));
448                         SimpleXML fileOptionsNode = projectNode.append("file-options");
449                         Iterator<Entry<String, FileOption>> entries = project.getFileOptions().entrySet().iterator();
450                         while (entries.hasNext()) {
451                                 Entry<String, FileOption> entry = entries.next();
452                                 FileOption fileOption = entry.getValue();
453                                 if (fileOption.isCustom()) {
454                                         SimpleXML fileOptionNode = fileOptionsNode.append("file-option");
455                                         fileOptionNode.append("filename", entry.getKey());
456                                         fileOptionNode.append("insert", String.valueOf(fileOption.isInsert()));
457                                         fileOptionNode.append("insert-redirect", String.valueOf(fileOption.isInsertRedirect()));
458                                         fileOptionNode.append("custom-key", fileOption.getCustomKey());
459                                         fileOptionNode.append("changed-name", fileOption.getChangedName());
460                                         fileOptionNode.append("mime-type", fileOption.getMimeType());
461                                         fileOptionNode.append("container", fileOption.getContainer());
462                                         fileOptionNode.append("replace-edition", String.valueOf(fileOption.getReplaceEdition()));
463                                         fileOptionNode.append("edition-range", String.valueOf(fileOption.getEditionRange()));
464                                 }
465                         }
466                 }
467                 rootNode.replace(projectsNode);
468         }
469
470         /**
471          * Returns the stored locale.
472          *
473          * @return The stored locale
474          */
475         public Locale getLocale() {
476                 String language = getNodeValue(new String[] { "i18n", "language" }, "en");
477                 String country = getNodeValue(new String[] { "i18n", "country" }, null);
478                 if (country != null) {
479                         return new Locale(language, country);
480                 }
481                 return new Locale(language);
482         }
483
484         /**
485          * Sets the locale to store.
486          *
487          * @param locale
488          *            The locale to store
489          */
490         public void setLocale(Locale locale) {
491                 SimpleXML i18nNode = new SimpleXML("i18n");
492                 if (locale.getCountry().length() != 0) {
493                         i18nNode.append("country", locale.getCountry());
494                 }
495                 i18nNode.append("language", locale.getLanguage());
496                 rootNode.replace(i18nNode);
497                 return;
498         }
499
500         /**
501          * Returns a list of configured nodes.
502          *
503          * @return The list of the configured nodes
504          */
505         public Node[] getNodes() {
506                 SimpleXML nodesNode = rootNode.getNode("nodes");
507                 if (nodesNode == null) {
508                         String hostname = getNodeAddress();
509                         int port = getNodePort();
510                         if (hostname == null) {
511                                 hostname = "127.0.0.1";
512                                 port = 9481;
513                         }
514                         return new Node[] { new Node(hostname, port, "Node") };
515                 }
516                 SimpleXML[] nodeNodes = nodesNode.getNodes("node");
517                 Node[] returnNodes = new Node[nodeNodes.length];
518                 int nodeIndex = 0;
519                 for (SimpleXML nodeNode : nodeNodes) {
520                         String name = nodeNode.getNode("name").getValue();
521                         String hostname = nodeNode.getNode("hostname").getValue();
522                         int port = Integer.parseInt(nodeNode.getNode("port").getValue());
523                         Node node = new Node(hostname, port, name);
524                         returnNodes[nodeIndex++] = node;
525                 }
526                 return returnNodes;
527         }
528
529         /**
530          * Sets the list of configured nodes.
531          *
532          * @param nodes
533          *            The list of configured nodes
534          */
535         public void setNodes(Node[] nodes) {
536                 SimpleXML nodesNode = new SimpleXML("nodes");
537                 for (Node node : nodes) {
538                         SimpleXML nodeNode = nodesNode.append("node");
539                         nodeNode.append("name", node.getName());
540                         nodeNode.append("hostname", node.getHostname());
541                         nodeNode.append("port", String.valueOf(node.getPort()));
542                 }
543                 rootNode.replace(nodesNode);
544                 rootNode.remove("node-address");
545                 rootNode.remove("node-port");
546         }
547
548         /**
549          * Sets the selected node.
550          *
551          * @param selectedNode
552          *            The selected node
553          */
554         public void setSelectedNode(Node selectedNode) {
555                 SimpleXML selectedNodeNode = new SimpleXML("selected-node");
556                 selectedNodeNode.append("name", selectedNode.getName());
557                 selectedNodeNode.append("hostname", selectedNode.getHostname());
558                 selectedNodeNode.append("port", String.valueOf(selectedNode.getPort()));
559                 rootNode.replace(selectedNodeNode);
560         }
561
562         /**
563          * Returns the selected node.
564          *
565          * @return The selected node
566          */
567         public Node getSelectedNode() {
568                 SimpleXML selectedNodeNode = rootNode.getNode("selected-node");
569                 if (selectedNodeNode == null) {
570                         String hostname = getNodeAddress();
571                         int port = getNodePort();
572                         if (hostname == null) {
573                                 hostname = "127.0.0.1";
574                                 port = 9481;
575                         }
576                         return new Node(hostname, port, "Node");
577                 }
578                 String name = selectedNodeNode.getNode("name").getValue();
579                 String hostname = selectedNodeNode.getNode("hostname").getValue();
580                 int port = Integer.valueOf(selectedNodeNode.getNode("port").getValue());
581                 return new Node(hostname, port, name);
582         }
583
584         /**
585          * Returns the temp directory to use.
586          *
587          * @return The temp directoy, or {@code null} to use the default temp
588          *         directory
589          */
590         public String getTempDirectory() {
591                 return getNodeValue(new String[] { "temp-directory" }, null);
592         }
593
594         /**
595          * Sets the temp directory to use.
596          *
597          * @param tempDirectory
598          *            The temp directory to use, or {@code null} to use the default
599          *            temp directory
600          */
601         public void setTempDirectory(String tempDirectory) {
602                 if (tempDirectory != null) {
603                         SimpleXML tempDirectoryNode = new SimpleXML("temp-directory");
604                         tempDirectoryNode.setValue(tempDirectory);
605                         rootNode.replace(tempDirectoryNode);
606                 } else {
607                         rootNode.remove("temp-directory");
608                 }
609         }
610
611 }