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