8f1cb8a09c9e3d6c0d6baff75f260e7a15aa17e8
[jSite2.git] / src / net / pterodactylus / jsite / project / ProjectManager.java
1 /*
2  * jSite2 - ProjectManager.java -
3  * Copyright © 2008 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 net.pterodactylus.jsite.project;
21
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.Properties;
35 import java.util.Random;
36 import java.util.logging.Logger;
37
38 import net.pterodactylus.jsite.core.JSiteException;
39 import net.pterodactylus.jsite.core.NodeManager;
40 import net.pterodactylus.util.io.Closer;
41 import net.pterodactylus.util.logging.Logging;
42 import net.pterodactylus.util.number.Hex;
43
44 /**
45  * Manages projects, taking care of persistence, lifetime statistics, and other
46  * things.
47  *
48  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
49  */
50 public class ProjectManager implements PropertyChangeListener {
51
52         /** Logger. */
53         private static final Logger logger = Logging.getLogger(ProjectManager.class.getName());
54
55         /** The RNG used to create project IDs. */
56         private static final Random random = new Random();
57
58         /** The directory the projects are stored in. */
59         private final String directory;
60
61         /** The node manager. */
62         private NodeManager nodeManager;
63
64         /** All projects. */
65         private final List<Project> projects = Collections.synchronizedList(new ArrayList<Project>());
66
67         /**
68          * Creates a new project manager that saves and restores its state to/from
69          * the given directory.
70          *
71          * @param directory
72          *            The directory to save and restore states to/from
73          */
74         public ProjectManager(String directory) {
75                 this.directory = directory;
76         }
77
78         //
79         // ACCESSORS
80         //
81
82         /**
83          * Returns the directory the projects are loaded from and saved to.
84          *
85          * @return The directory for storing the projects
86          */
87         public String getDirectory() {
88                 return directory;
89         }
90
91         /**
92          * Returns a list of all projects.
93          *
94          * @return A list of all projects
95          */
96         public List<Project> getProjects() {
97                 return Collections.unmodifiableList(new ArrayList<Project>(projects));
98         }
99
100         /**
101          * Sets the node manager to use.
102          *
103          * @param nodeManager
104          *            The node manager to use
105          */
106         public void setNodeManager(NodeManager nodeManager) {
107                 this.nodeManager = nodeManager;
108         }
109
110         //
111         // ACTIONS
112         //
113
114         /**
115          * Loads projects and statistics.
116          *
117          * @throws IOException
118          *             if an I/O error occurs
119          */
120         public void load() throws IOException {
121                 File directoryFile = new File(directory);
122                 File projectFile = new File(directoryFile, "projects.properties");
123                 if (!projectFile.exists() || !projectFile.isFile() || !projectFile.canRead()) {
124                         return;
125                 }
126                 Properties projectProperties = new Properties();
127                 InputStream projectInputStream = null;
128                 try {
129                         projectInputStream = new FileInputStream(projectFile);
130                         projectProperties.load(projectInputStream);
131                 } finally {
132                         Closer.close(projectInputStream);
133                 }
134                 int projectIndex = 0;
135                 while (projectProperties.containsKey("projects." + projectIndex + ".name")) {
136                         String projectPrefix = "projects." + projectIndex;
137                         String projectId = projectProperties.getProperty(projectPrefix + ".id");
138                         String projectName = projectProperties.getProperty(projectPrefix + ".name");
139                         String projectDescription = projectProperties.getProperty(projectPrefix + ".description");
140                         String projectPrivateKey = projectProperties.getProperty(projectPrefix + ".privateKey");
141                         String projectPublicKey = projectProperties.getProperty(projectPrefix + ".publicKey");
142                         String projectBasePath = projectProperties.getProperty(projectPrefix + ".basePath");
143                         String projectDefaultFile = projectProperties.getProperty(projectPrefix + ".defaultFile");
144                         Project project = new Project();
145                         project.setId(projectId);
146                         project.setName(projectName);
147                         project.setDescription(projectDescription);
148                         project.setPrivateKey(projectPrivateKey);
149                         project.setPublicKey(projectPublicKey);
150                         project.setBasePath(projectBasePath);
151                         project.setDefaultFile(projectDefaultFile);
152                         int overrideIndex = 0;
153                         while (projectProperties.containsKey(projectPrefix + ".overrides." + overrideIndex)) {
154                                 String filePath = projectProperties.getProperty(projectPrefix + ".overrides." + overrideIndex + ".filePath");
155                                 FileOverride override = FileOverride.valueOf(projectProperties.getProperty(projectPrefix + ".overrides." + overrideIndex + ".override"));
156                                 project.addFileOverride(filePath, override);
157                                 overrideIndex++;
158                         }
159                         projects.add(project);
160                         logger.fine("loaded project “" + project.getName() + "”.");
161                         projectIndex++;
162                 }
163         }
164
165         /**
166          * Saves projects and statistics.
167          *
168          * @throws IOException
169          *             if an I/O error occurs
170          */
171         public void save() throws IOException {
172                 File directoryFile = new File(directory);
173                 if (!directoryFile.exists()) {
174                         if (!directoryFile.mkdirs()) {
175                                 throw new IOException("could not create directory: " + directory);
176                         }
177                 }
178                 Properties projectProperties = new Properties();
179                 int projectIndex = 0;
180                 for (Project project: projects) {
181                         String projectPrefix = "projects." + projectIndex;
182                         projectProperties.setProperty(projectPrefix + ".id", project.getId());
183                         projectProperties.setProperty(projectPrefix + ".name", project.getName());
184                         projectProperties.setProperty(projectPrefix + ".description", project.getDescription());
185                         projectProperties.setProperty(projectPrefix + ".privateKey", project.getPrivateKey());
186                         projectProperties.setProperty(projectPrefix + ".publicKey", project.getPublicKey());
187                         projectProperties.setProperty(projectPrefix + ".basePath", project.getBasePath());
188                         projectProperties.setProperty(projectPrefix + ".defaultFile", project.getDefaultFile());
189                         int overrideIndex = 0;
190                         for (Entry<String, FileOverride> overrideEntry: project.getFileOverrides().entrySet()) {
191                                 projectProperties.setProperty(projectPrefix + ".overrides." + overrideIndex + ".filePath", overrideEntry.getKey());
192                                 projectProperties.setProperty(projectPrefix + ".overrides." + overrideIndex + ".override", overrideEntry.getValue().toString());
193                                 overrideIndex++;
194                         }
195                         projectIndex++;
196                 }
197                 File projectFile = new File(directoryFile, "projects.properties");
198                 OutputStream projectOutputStream = null;
199                 try {
200                         projectOutputStream = new FileOutputStream(projectFile);
201                         projectProperties.store(projectOutputStream, "jSite projects");
202                 } finally {
203                         Closer.close(projectOutputStream);
204                 }
205         }
206
207         /**
208          * Creates a new project. The returned {@link Project} will contain a newly
209          * generated key pair.
210          *
211          * @return A newly created project
212          * @throws IOException
213          *             if an I/O error occured communicating with the node
214          * @throws JSiteException
215          *             if there is a problem with the node
216          */
217         public Project createProject() throws IOException, JSiteException {
218                 Project project = new Project();
219                 String[] keyPair = nodeManager.generateKeyPair();
220                 project.setId(generateId());
221                 project.setName("");
222                 project.setDescription("");
223                 project.setPrivateKey(keyPair[0]);
224                 project.setPublicKey(keyPair[1]);
225                 project.setBasePath("");
226                 project.setDefaultFile("");
227                 projects.add(project);
228                 project.addPropertyChangeListener(this);
229                 try {
230                         save();
231                 } catch (IOException ioe1) {
232                         /* ignore. */
233                 }
234                 return project;
235         }
236
237         /**
238          * Clones the given project and returns the clone. The clone will be
239          * identical in all user-exposed fields, except for the project’s
240          * {@link Project#getId ID}.
241          *
242          * @param project
243          *            The project to clone
244          * @return The cloned project
245          */
246         public Project cloneProject(Project project) {
247                 Project projectClone = new Project(project);
248                 projects.add(projectClone);
249                 projectClone.setId(generateId());
250                 projectClone.addPropertyChangeListener(this);
251                 try {
252                         save();
253                 } catch (IOException ioe1) {
254                         /* ignore. */
255                 }
256                 return projectClone;
257         }
258
259         /**
260          * Removes the given project.
261          *
262          * @param project
263          *            The project to remove
264          */
265         public void removeProject(Project project) {
266                 projects.remove(project);
267                 try {
268                         save();
269                 } catch (IOException ioe1) {
270                         /* ignore. */
271                 }
272         }
273
274         //
275         // PRIVATE METHODS
276         //
277
278         /**
279          * Generates a new random ID, consisting of 16 random bytes converted to a
280          * hexadecimal number.
281          *
282          * @return The new ID
283          */
284         private static String generateId() {
285                 byte[] idBytes = new byte[16];
286                 random.nextBytes(idBytes);
287                 return Hex.toHex(idBytes);
288         }
289
290         //
291         // INTERFACE PropertyChangeListener
292         //
293
294         /**
295          * {@inheritDoc}
296          */
297         public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
298                 try {
299                         save();
300                 } catch (IOException ioe1) {
301                         /* ignore. */
302                 }
303         }
304
305 }