Use better MIME type detection
[jSite.git] / src / main / java / de / todesbaum / jsite / application / Project.java
1 /*
2  * jSite - Project.java - Copyright © 2006–2014 David Roden
3  *
4  * This program is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License as published by the Free Software
6  * Foundation; either version 2 of the License, or (at your option) any later
7  * version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12  * details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16  * Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package de.todesbaum.jsite.application;
20
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28
29 import net.pterodactylus.util.io.MimeTypes;
30
31 /**
32  * Container for project information.
33  *
34  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
35  */
36 public class Project implements Comparable<Project> {
37
38         /** The name of the project. */
39         protected String name;
40
41         /** The description of the project. */
42         protected String description;
43
44         /** The insert URI of the project. */
45         protected String insertURI;
46
47         /** The request URI of the project. */
48         protected String requestURI;
49
50         /** The index file of the project. */
51         protected String indexFile;
52
53         /** The local path of the project. */
54         protected String localPath;
55
56         /** The remote path of the URI. */
57         protected String path;
58
59         /** The time of the last insertion. */
60         protected long lastInsertionTime;
61
62         /** The edition to insert to. */
63         protected int edition;
64
65         /** Whether to always force inserts. */
66         private boolean alwaysForceInserts;
67
68         /** Whether to ignore hidden directory. */
69         private boolean ignoreHiddenFiles;
70
71         /** Options for files. */
72         protected Map<String, FileOption> fileOptions = new HashMap<String, FileOption>();
73
74         /**
75          * Empty constructor.
76          */
77         public Project() {
78                 /* do nothing. */
79         }
80
81         /**
82          * Creates a new project from an existing one.
83          *
84          * @param project
85          *            The project to clone
86          */
87         public Project(Project project) {
88                 name = project.name;
89                 description = project.description;
90                 insertURI = project.insertURI;
91                 requestURI = project.requestURI;
92                 path = project.path;
93                 edition = project.edition;
94                 localPath = project.localPath;
95                 indexFile = project.indexFile;
96                 lastInsertionTime = project.lastInsertionTime;
97                 alwaysForceInserts = project.alwaysForceInserts;
98                 ignoreHiddenFiles = project.ignoreHiddenFiles;
99                 for (Entry<String, FileOption> fileOption : fileOptions.entrySet()) {
100                         fileOptions.put(fileOption.getKey(), new FileOption(fileOption.getValue()));
101                 }
102         }
103
104         /**
105          * Returns the name of the project.
106          *
107          * @return The name of the project
108          */
109         public String getName() {
110                 return name;
111         }
112
113         /**
114          * Sets the name of the project.
115          *
116          * @param name
117          *            The name of the project
118          */
119         public void setName(String name) {
120                 this.name = name;
121         }
122
123         /**
124          * Returns the description of the project.
125          *
126          * @return The description of the project
127          */
128         public String getDescription() {
129                 return description;
130         }
131
132         /**
133          * Sets the description of the project.
134          *
135          * @param description
136          *            The description of the project
137          */
138         public void setDescription(String description) {
139                 this.description = description;
140         }
141
142         /**
143          * Returns the local path of the project.
144          *
145          * @return The local path of the project
146          */
147         public String getLocalPath() {
148                 return localPath;
149         }
150
151         /**
152          * Sets the local path of the project.
153          *
154          * @param localPath
155          *            The local path of the project
156          */
157         public void setLocalPath(String localPath) {
158                 this.localPath = localPath;
159         }
160
161         /**
162          * Returns the name of the index file of the project, relative to the
163          * project’s local path.
164          *
165          * @return The name of the index file of the project
166          */
167         public String getIndexFile() {
168                 return indexFile;
169         }
170
171         /**
172          * Sets the name of the index file of the project, relative to the project’s
173          * local path.
174          *
175          * @param indexFile
176          *            The name of the index file of the project
177          */
178         public void setIndexFile(String indexFile) {
179                 this.indexFile = indexFile;
180         }
181
182         /**
183          * Returns the time the project was last inserted, in milliseconds since the
184          * epoch.
185          *
186          * @return The time of the last insertion
187          */
188         public long getLastInsertionTime() {
189                 return lastInsertionTime;
190         }
191
192         /**
193          * Sets the time the project was last inserted, in milliseconds since the
194          * last epoch.
195          *
196          * @param lastInserted
197          *            The time of the last insertion
198          */
199         public void setLastInsertionTime(long lastInserted) {
200                 lastInsertionTime = lastInserted;
201         }
202
203         /**
204          * Returns the remote path of the project. The remote path is the path that
205          * directly follows the request URI of the project.
206          *
207          * @return The remote path of the project
208          */
209         public String getPath() {
210                 return path;
211         }
212
213         /**
214          * Sets the remote path of the project. The remote path is the path that
215          * directly follows the request URI of the project.
216          *
217          * @param path
218          *            The remote path of the project
219          */
220         public void setPath(String path) {
221                 this.path = path;
222         }
223
224         /**
225          * Returns the insert URI of the project.
226          *
227          * @return The insert URI of the project
228          */
229         public String getInsertURI() {
230                 return insertURI;
231         }
232
233         /**
234          * Sets the insert URI of the project.
235          *
236          * @param insertURI
237          *            The insert URI of the project
238          */
239         public void setInsertURI(String insertURI) {
240                 this.insertURI = shortenURI(insertURI);
241         }
242
243         /**
244          * Returns the request URI of the project.
245          *
246          * @return The request URI of the project
247          */
248         public String getRequestURI() {
249                 return requestURI;
250         }
251
252         /**
253          * Sets the request URI of the project.
254          *
255          * @param requestURI
256          *            The request URI of the project
257          */
258         public void setRequestURI(String requestURI) {
259                 this.requestURI = shortenURI(requestURI);
260         }
261
262         /**
263          * Returns whether files for this project should always be inserted, even
264          * when unchanged.
265          *
266          * @return {@code true} to always force inserts on this project,
267          *         {@code false} otherwise
268          */
269         public boolean isAlwaysForceInsert() {
270                 return alwaysForceInserts;
271         }
272
273         /**
274          * Sets whether files for this project should always be inserted, even when
275          * unchanged.
276          *
277          * @param alwaysForceInsert
278          *            {@code true} to always force inserts on this project,
279          *            {@code false} otherwise
280          */
281         public void setAlwaysForceInsert(boolean alwaysForceInsert) {
282                 this.alwaysForceInserts = alwaysForceInsert;
283         }
284
285         /**
286          * Returns whether hidden files are ignored, i.e. not inserted.
287          *
288          * @return {@code true} if hidden files are not inserted, {@code false}
289          *         otherwise
290          */
291         public boolean isIgnoreHiddenFiles() {
292                 return ignoreHiddenFiles;
293         }
294
295         /**
296          * Sets whether hidden files are ignored, i.e. not inserted.
297          *
298          * @param ignoreHiddenFiles
299          *            {@code true} if hidden files are not inserted, {@code false}
300          *            otherwise
301          */
302         public void setIgnoreHiddenFiles(boolean ignoreHiddenFiles) {
303                 this.ignoreHiddenFiles = ignoreHiddenFiles;
304         }
305
306         /**
307          * {@inheritDoc}
308          * <p>
309          * This method returns the name of the project.
310          */
311         @Override
312         public String toString() {
313                 return name;
314         }
315
316         /**
317          * Shortens the given URI by removing scheme and key-type prefixes.
318          *
319          * @param uri
320          *            The URI to shorten
321          * @return The shortened URI
322          */
323         private static String shortenURI(String uri) {
324                 String shortUri = uri;
325                 if (shortUri.startsWith("freenet:")) {
326                         shortUri = shortUri.substring("freenet:".length());
327                 }
328                 if (shortUri.startsWith("SSK@")) {
329                         shortUri = shortUri.substring("SSK@".length());
330                 }
331                 if (shortUri.startsWith("USK@")) {
332                         shortUri = shortUri.substring("USK@".length());
333                 }
334                 if (shortUri.endsWith("/")) {
335                         shortUri = shortUri.substring(0, shortUri.length() - 1);
336                 }
337                 return shortUri;
338         }
339
340         /**
341          * Shortens the name of the given file by removing the local path of the
342          * project and leading file separators.
343          *
344          * @param file
345          *            The file whose name should be shortened
346          * @return The shortened name of the file
347          */
348         public String shortenFilename(File file) {
349                 String filename = file.getPath();
350                 if (filename.startsWith(localPath)) {
351                         filename = filename.substring(localPath.length());
352                         if (filename.startsWith(File.separator)) {
353                                 filename = filename.substring(1);
354                         }
355                 }
356                 return filename;
357         }
358
359         /**
360          * Returns the options for the file with the given name. If the file does
361          * not yet have any options, a new set of default options is created and
362          * returned.
363          *
364          * @param filename
365          *            The name of the file, relative to the project root
366          * @return The options for the file
367          */
368         public FileOption getFileOption(String filename) {
369                 FileOption fileOption = fileOptions.get(filename);
370                 String defaultMimeType = "application/octet-stream";
371                 if (fileOption == null) {
372                         List<String> suffixes = getSuffixes(filename);
373                         for (String suffix : suffixes) {
374                                 String mimeType = MimeTypes.getMimeType(suffix);
375                                 if (!mimeType.equals(defaultMimeType)) {
376                                         defaultMimeType = mimeType;
377                                         break;
378                                 }
379                         }
380                         fileOption = new FileOption(defaultMimeType);
381                 }
382                 fileOptions.put(filename, fileOption);
383                 return fileOption;
384         }
385
386         private List<String> getSuffixes(String filename) {
387                 List<String> suffixes = new ArrayList<>();
388                 int dot = filename.lastIndexOf(".");
389                 while (dot > -1) {
390                         String suffix = filename.substring(dot + 1);
391                         suffixes.add(0, suffix);
392                         dot = filename.lastIndexOf(".", dot - 1);
393                 }
394                 return suffixes;
395         }
396
397         /**
398          * Sets options for a file.
399          *
400          * @param filename
401          *            The filename to set the options for, relative to the project
402          *            root
403          * @param fileOption
404          *            The options to set for the file, or <code>null</code> to
405          *            remove the options for the file
406          */
407         public void setFileOption(String filename, FileOption fileOption) {
408                 if (fileOption != null) {
409                         fileOptions.put(filename, fileOption);
410                 } else {
411                         fileOptions.remove(filename);
412                 }
413         }
414
415         /**
416          * Returns all file options.
417          *
418          * @return All file options
419          */
420         public Map<String, FileOption> getFileOptions() {
421                 return Collections.unmodifiableMap(fileOptions);
422         }
423
424         /**
425          * Sets all file options.
426          *
427          * @param fileOptions
428          *            The file options
429          */
430         public void setFileOptions(Map<String, FileOption> fileOptions) {
431                 this.fileOptions.clear();
432                 this.fileOptions.putAll(fileOptions);
433         }
434
435         /**
436          * {@inheritDoc}
437          * <p>
438          * Projects are compared by their name only.
439          */
440         @Override
441         public int compareTo(Project project) {
442                 return name.compareToIgnoreCase(project.name);
443         }
444
445         /**
446          * Returns the edition of the project.
447          *
448          * @return The edition of the project
449          */
450         public int getEdition() {
451                 return edition;
452         }
453
454         /**
455          * Sets the edition of the project.
456          *
457          * @param edition
458          *            The edition to set
459          */
460         public void setEdition(int edition) {
461                 this.edition = edition;
462         }
463
464         /**
465          * Constructs the final request URI including the edition number.
466          *
467          * @param offset
468          *            The offset for the edition number
469          * @return The final request URI
470          */
471         public String getFinalRequestURI(int offset) {
472                 return "USK@" + requestURI + "/" + path + "/" + (edition + offset) + "/";
473         }
474
475         /**
476          * Performs some post-processing on the project after it was inserted
477          * successfully. At the moment it copies the current hashes of all file
478          * options to the last insert hashes, updating the hashes for the next
479          * insert.
480          */
481         public void onSuccessfulInsert() {
482                 for (Entry<String, FileOption> fileOptionEntry : fileOptions.entrySet()) {
483                         FileOption fileOption = fileOptionEntry.getValue();
484                         if ((fileOption.getCurrentHash() != null) && (fileOption.getCurrentHash().length() > 0) && (!fileOption.getCurrentHash().equals(fileOption.getLastInsertHash()) || fileOption.isForceInsert())) {
485                                 fileOption.setLastInsertEdition(edition);
486                                 fileOption.setLastInsertHash(fileOption.getCurrentHash());
487                                 fileOption.setLastInsertFilename(fileOption.getChangedName().orElse(fileOptionEntry.getKey()));
488                         }
489                         fileOption.setForceInsert(false);
490                 }
491         }
492
493 }