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