Override toString() for simple displaying it in a List.
[jSite.git] / src / de / todesbaum / jsite / gui / FileScanner.java
1 /*
2  * jSite - FileScanner.java - Copyright © 2006–2011 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.gui;
20
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.security.DigestOutputStream;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35
36 import de.todesbaum.jsite.application.Project;
37 import de.todesbaum.jsite.i18n.I18n;
38 import de.todesbaum.util.io.Closer;
39 import de.todesbaum.util.io.StreamCopier;
40
41 /**
42  * Scans the local path of a project anychronously and returns the list of found
43  * files as an event.
44  *
45  * @see Project#getLocalPath()
46  * @see FileScannerListener#fileScannerFinished(FileScanner)
47  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
48  */
49 public class FileScanner implements Runnable {
50
51         /** The logger. */
52         private final static Logger logger = Logger.getLogger(FileScanner.class.getName());
53
54         /** The list of listeners. */
55         private final List<FileScannerListener> fileScannerListeners = new ArrayList<FileScannerListener>();
56
57         /** The project to scan. */
58         private final Project project;
59
60         /** The list of found files. */
61         private List<ScannedFile> files;
62
63         /** Wether there was an error. */
64         private boolean error = false;
65
66         /**
67          * Creates a new file scanner for the given project.
68          *
69          * @param project
70          *            The project whose files to scan
71          */
72         public FileScanner(Project project) {
73                 this.project = project;
74         }
75
76         /**
77          * Adds the given listener to the list of listeners.
78          *
79          * @param fileScannerListener
80          *            The listener to add
81          */
82         public void addFileScannerListener(FileScannerListener fileScannerListener) {
83                 fileScannerListeners.add(fileScannerListener);
84         }
85
86         /**
87          * Removes the given listener from the list of listeners.
88          *
89          * @param fileScannerListener
90          *            The listener to remove
91          */
92         public void removeFileScannerListener(FileScannerListener fileScannerListener) {
93                 fileScannerListeners.remove(fileScannerListener);
94         }
95
96         /**
97          * Notifies all listeners that the file scan finished.
98          */
99         protected void fireFileScannerFinished() {
100                 for (FileScannerListener fileScannerListener : new ArrayList<FileScannerListener>(fileScannerListeners)) {
101                         fileScannerListener.fileScannerFinished(this);
102                 }
103         }
104
105         /**
106          * {@inheritDoc}
107          * <p>
108          * Scans all available files in the project’s local path and emits an event
109          * when finished.
110          *
111          * @see FileScannerListener#fileScannerFinished(FileScanner)
112          */
113         public void run() {
114                 files = new ArrayList<ScannedFile>();
115                 error = false;
116                 try {
117                         scanFiles(new File(project.getLocalPath()), files);
118                         Collections.sort(files);
119                 } catch (IOException ioe1) {
120                         error = true;
121                 }
122                 fireFileScannerFinished();
123         }
124
125         /**
126          * Returns whether there was an error scanning for files.
127          *
128          * @return <code>true</code> if there was an error, <code>false</code>
129          *         otherwise
130          */
131         public boolean isError() {
132                 return error;
133         }
134
135         /**
136          * Returns the list of found files.
137          *
138          * @return The list of found files
139          */
140         public List<ScannedFile> getFiles() {
141                 return files;
142         }
143
144         /**
145          * Recursively scans a directory and adds all found files to the given list.
146          *
147          * @param rootDir
148          *            The directory to scan
149          * @param fileList
150          *            The list to which to add the found files
151          * @throws IOException
152          *             if an I/O error occurs
153          */
154         private void scanFiles(File rootDir, List<ScannedFile> fileList) throws IOException {
155                 File[] files = rootDir.listFiles(new FileFilter() {
156
157                         @SuppressWarnings("synthetic-access")
158                         public boolean accept(File file) {
159                                 return !project.isIgnoreHiddenFiles() || !file.isHidden();
160                         }
161                 });
162                 if (files == null) {
163                         throw new IOException(I18n.getMessage("jsite.file-scanner.can-not-read-directory"));
164                 }
165                 for (File file : files) {
166                         if (file.isDirectory()) {
167                                 scanFiles(file, fileList);
168                                 continue;
169                         }
170                         String filename = project.shortenFilename(file).replace('\\', '/');
171                         String hash = hashFile(project.getLocalPath(), filename);
172                         fileList.add(new ScannedFile(filename, hash));
173                 }
174         }
175
176         /**
177          * Hashes the given file.
178          *
179          * @param path
180          *            The path of the project
181          * @param filename
182          *            The name of the file, relative to the project path
183          * @return The hash of the file
184          */
185         @SuppressWarnings("synthetic-access")
186         private static String hashFile(String path, String filename) {
187                 InputStream fileInputStream = null;
188                 DigestOutputStream digestOutputStream = null;
189                 File file = new File(path, filename);
190                 try {
191                         fileInputStream = new FileInputStream(file);
192                         digestOutputStream = new DigestOutputStream(new NullOutputStream(), MessageDigest.getInstance("SHA-256"));
193                         StreamCopier.copy(fileInputStream, digestOutputStream, file.length());
194                         return toHex(digestOutputStream.getMessageDigest().digest());
195                 } catch (NoSuchAlgorithmException nsae1) {
196                         logger.log(Level.WARNING, "Could not get SHA-256 digest!", nsae1);
197                 } catch (IOException ioe1) {
198                         logger.log(Level.WARNING, "Could not read file!", ioe1);
199                 } finally {
200                         Closer.close(digestOutputStream);
201                         Closer.close(fileInputStream);
202                 }
203                 return toHex(new byte[32]);
204         }
205
206         /**
207          * Converts the given byte array into a hexadecimal string.
208          *
209          * @param array
210          *            The array to convert
211          * @return The hexadecimal string
212          */
213         private static String toHex(byte[] array) {
214                 StringBuilder hexString = new StringBuilder(array.length * 2);
215                 for (byte b : array) {
216                         hexString.append("0123456789abcdef".charAt((b >>> 4) & 0x0f)).append("0123456789abcdef".charAt(b & 0xf));
217                 }
218                 return hexString.toString();
219         }
220
221         /**
222          * {@link OutputStream} that discards all written bytes.
223          *
224          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
225          */
226         private static class NullOutputStream extends OutputStream {
227
228                 /**
229                  * {@inheritDoc}
230                  */
231                 @Override
232                 public void write(int b) {
233                         /* do nothing. */
234                 }
235
236                 /**
237                  * {@inheritDoc}
238                  */
239                 @Override
240                 public void write(byte[] b) {
241                         /* do nothing. */
242                 }
243
244                 /**
245                  * {@inheritDoc}
246                  */
247                 @Override
248                 public void write(byte[] b, int off, int len) {
249                         /* do nothing. */
250                 }
251
252         }
253
254         /**
255          * Container for a scanned file, consisting of the name of the file and its
256          * hash.
257          *
258          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
259          */
260         public static class ScannedFile implements Comparable<ScannedFile> {
261
262                 /** The name of the file. */
263                 private final String filename;
264
265                 /** The hash of the file. */
266                 private final String hash;
267
268                 /**
269                  * Creates a new scanned file.
270                  *
271                  * @param filename
272                  *            The name of the file
273                  * @param hash
274                  *            The hash of the file
275                  */
276                 public ScannedFile(String filename, String hash) {
277                         this.filename = filename;
278                         this.hash = hash;
279                 }
280
281                 //
282                 // ACCESSORS
283                 //
284
285                 /**
286                  * Returns the name of the file.
287                  *
288                  * @return The name of the file
289                  */
290                 public String getFilename() {
291                         return filename;
292                 }
293
294                 /**
295                  * Returns the hash of the file.
296                  *
297                  * @return The hash of the file
298                  */
299                 public String getHash() {
300                         return hash;
301                 }
302
303                 //
304                 // OBJECT METHODS
305                 //
306
307                 /**
308                  * {@inheritDoc}
309                  */
310                 @Override
311                 public int hashCode() {
312                         return filename.hashCode();
313                 }
314
315                 /**
316                  * {@inheritDoc}
317                  */
318                 @Override
319                 public boolean equals(Object obj) {
320                         return filename.equals(obj);
321                 }
322
323                 /**
324                  * {@inheritDoc}
325                  */
326                 @Override
327                 public String toString() {
328                         return filename;
329                 }
330
331                 //
332                 // COMPARABLE METHODS
333                 //
334
335                 /**
336                  * {@inheritDoc}
337                  */
338                 public int compareTo(ScannedFile scannedFile) {
339                         return filename.compareTo(scannedFile.filename);
340                 }
341
342         }
343
344 }