2 * jSite - FileScanner.java - Copyright © 2006–2014 David Roden
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.
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.
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.
19 package de.todesbaum.jsite.gui;
22 import java.io.FileFilter;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.security.DigestOutputStream;
27 import java.security.MessageDigest;
28 import java.security.NoSuchAlgorithmException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Objects;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
36 import net.pterodactylus.util.io.Closer;
37 import net.pterodactylus.util.io.NullOutputStream;
38 import net.pterodactylus.util.io.StreamCopier;
39 import de.todesbaum.jsite.application.Project;
40 import de.todesbaum.jsite.i18n.I18n;
43 * Scans the local path of a project anychronously and returns the list of found
46 * @see Project#getLocalPath()
47 * @see FileScannerListener#fileScannerFinished(boolean, java.util.Collection)
48 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
50 public class FileScanner implements Runnable {
53 private final static Logger logger = Logger.getLogger(FileScanner.class.getName());
55 /** The list of listeners. */
56 private final FileScannerListener fileScannerListener;
58 /** The project to scan. */
59 private final Project project;
61 /** The list of found files. */
62 private List<ScannedFile> files;
64 /** Wether there was an error. */
65 private boolean error = false;
67 /** The name of the last file scanned. */
68 private String lastFilename;
71 * Creates a new file scanner for the given project.
74 * The project whose files to scan
76 public FileScanner(Project project, FileScannerListener fileScannerListener) {
77 this.project = project;
78 this.fileScannerListener = Objects.requireNonNull(fileScannerListener);
82 * Returns the name of the last file scanned.
84 * @return The name of the last file scanned, or {@code null} if there was
87 public String getLastFilename() {
91 public void startInBackground() {
92 new Thread(this).start();
98 * Scans all available files in the project’s local path and emits an event
101 * @see FileScannerListener#fileScannerFinished(boolean, java.util.Collection)
105 files = new ArrayList<ScannedFile>();
109 scanFiles(new File(project.getLocalPath()), files);
110 Collections.sort(files);
111 } catch (IOException ioe1) {
114 fileScannerListener.fileScannerFinished(error, files);
118 * Returns whether there was an error scanning for files.
120 * @return <code>true</code> if there was an error, <code>false</code>
123 public boolean isError() {
128 * Returns the list of found files.
130 * @return The list of found files
132 public List<ScannedFile> getFiles() {
137 * Recursively scans a directory and adds all found files to the given list.
140 * The directory to scan
142 * The list to which to add the found files
143 * @throws IOException
144 * if an I/O error occurs
146 private void scanFiles(File rootDir, List<ScannedFile> fileList) throws IOException {
147 File[] files = rootDir.listFiles(new FileFilter() {
150 @SuppressWarnings("synthetic-access")
151 public boolean accept(File file) {
152 return !project.isIgnoreHiddenFiles() || !file.isHidden();
156 throw new IOException(I18n.getMessage("jsite.file-scanner.can-not-read-directory"));
158 for (File file : files) {
159 if (file.isDirectory()) {
160 scanFiles(file, fileList);
163 String filename = project.shortenFilename(file).replace('\\', '/');
164 String hash = hashFile(project.getLocalPath(), filename);
165 fileList.add(new ScannedFile(filename, hash));
166 lastFilename = filename;
171 * Hashes the given file.
174 * The path of the project
176 * The name of the file, relative to the project path
177 * @return The hash of the file
179 private static String hashFile(String path, String filename) {
180 InputStream fileInputStream = null;
181 DigestOutputStream digestOutputStream = null;
182 File file = new File(path, filename);
184 fileInputStream = new FileInputStream(file);
185 digestOutputStream = new DigestOutputStream(new NullOutputStream(), MessageDigest.getInstance("SHA-256"));
186 StreamCopier.copy(fileInputStream, digestOutputStream, file.length());
187 return toHex(digestOutputStream.getMessageDigest().digest());
188 } catch (NoSuchAlgorithmException nsae1) {
189 logger.log(Level.WARNING, "Could not get SHA-256 digest!", nsae1);
190 } catch (IOException ioe1) {
191 logger.log(Level.WARNING, "Could not read file!", ioe1);
193 Closer.close(digestOutputStream);
194 Closer.close(fileInputStream);
196 return toHex(new byte[32]);
200 * Converts the given byte array into a hexadecimal string.
203 * The array to convert
204 * @return The hexadecimal string
206 private static String toHex(byte[] array) {
207 StringBuilder hexString = new StringBuilder(array.length * 2);
208 for (byte b : array) {
209 hexString.append("0123456789abcdef".charAt((b >>> 4) & 0x0f)).append("0123456789abcdef".charAt(b & 0xf));
211 return hexString.toString();