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