// // $Id: DefaultFileData.java 267 2007-06-08 13:42:02 +0000 (ven., 08 juin 2007) // felfert $ // // jupload - A file upload applet. // Copyright 2007 The JUpload Team // // Created: 2006-04-21 // Creator: etienne_sf // Last modified: $Date: 2010-06-28 10:20:25 -0300 (Seg, 28 Jun 2010) $ // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software // Foundation; either version 2 of the License, or (at your option) any later // version. This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more // details. You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software Foundation, Inc., // 675 Mass Ave, Cambridge, MA 02139, USA. package wjhk.jupload2.filedata; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import wjhk.jupload2.exception.JUploadException; import wjhk.jupload2.exception.JUploadExceptionTooBigFile; import wjhk.jupload2.exception.JUploadIOException; import wjhk.jupload2.policies.DefaultUploadPolicy; import wjhk.jupload2.policies.UploadPolicy; import wjhk.jupload2.upload.helper.ByteArrayEncoder; /** * This class contains all data and methods for a file to upload. The current * {@link wjhk.jupload2.policies.UploadPolicy} contains the necessary parameters * to personalize the way files must be handled.
*
* This class is the default FileData implementation. It gives the default * behaviour, and is used by {@link DefaultUploadPolicy}. It provides standard * control on the files choosen for upload. * * @see FileData * @author etienne_sf */ public class DefaultFileData implements FileData { /** * The current upload policy. */ UploadPolicy uploadPolicy; /** * Indicates whether the file is prepared for upload or not. * * @see FileData#isPreparedForUpload() */ boolean preparedForUpload = false; private final static int BUFLEN = 4096; // /////////////////////////////////////////////////////////////////////////////////////////////////////// // /////////////////////// Protected attributes // ///////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Mime type of the file. It will be written in the upload HTTP request. */ protected String mimeType = "application/octet-stream"; // /////////////////////////////////////////////////////////////////////////////////////////////////////// // /////////////////////// Private attributes // //////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////////////////////////////////// /** * file is the file about which this FileData contains data. */ protected File file; /** * Cached file size */ protected long fileSize; /** * Cached file directory */ protected String fileDir; /** * cached root of this file */ protected String fileRoot = ""; /** * Cached file modification time. */ protected Date fileModified; /** * The md5sum for the prepared file. Calculated in the * {@link #beforeUpload()}, and cleared in the {@link #afterUpload()}. */ protected String md5sum = null; /** * Indicates whether the applet can read this file or not. */ protected Boolean canRead = null; /** * Standard constructor * * @param file * The file whose data this instance will give. * @param root * The directory root, to be able to calculate the result of * {@link #getRelativeDir()} * @param uploadPolicy * The current upload policy. */ public DefaultFileData(File file, File root, UploadPolicy uploadPolicy) { this.file = file; this.uploadPolicy = uploadPolicy; this.fileSize = this.file.length(); this.fileDir = this.file.getAbsoluteFile().getParent(); this.fileModified = new Date(this.file.lastModified()); if (null != root) { this.fileRoot = root.getAbsolutePath(); uploadPolicy.displayDebug("Creation of the DefaultFileData for " + file.getAbsolutePath() + "(root: " + root.getAbsolutePath() + ")", 10); } else { uploadPolicy.displayDebug("Creation of the DefaultFileData for " + file.getAbsolutePath() + "(root: null)", 10); } // Let this.mimeType = this.uploadPolicy.getContext().getMimeType( getFileExtension()); } /** {@inheritDoc} */ public void appendFileProperties(ByteArrayEncoder bae, int index) throws JUploadIOException { bae.appendTextProperty("mimetype", getMimeType(), index); bae.appendTextProperty("pathinfo", getDirectory(), index); bae.appendTextProperty("relpathinfo", getRelativeDir(), index); // To add the file date/time, we first have to format this date. SimpleDateFormat dateformat = new SimpleDateFormat(this.uploadPolicy .getDateFormat()); String uploadFileModificationDate = dateformat .format(getLastModified()); bae.appendTextProperty("filemodificationdate", uploadFileModificationDate, index); } /** {@inheritDoc} */ public synchronized void beforeUpload() throws JUploadException { if (this.preparedForUpload) { // Maybe an upload was stopped. Let's log a resume, and resume the // job. this.uploadPolicy.displayWarn("The file " + getFileName() + " is already prepared for upload"); } else { // The file is now prepared for upload. this.preparedForUpload = true; // Should we calculate the MD5Sum for this file ? if (this.uploadPolicy.getSendMD5Sum()) { calculateMD5Sum(); } // Default : we check that the file is smaller than the maximum // upload size. if (getUploadLength() > this.uploadPolicy.getMaxFileSize()) { throw new JUploadExceptionTooBigFile(getFileName(), getUploadLength(), this.uploadPolicy); } } } /** {@inheritDoc} */ public long getUploadLength() { if (!this.preparedForUpload) { throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload"); } return this.fileSize; } /** {@inheritDoc} */ public synchronized void afterUpload() { if (!this.preparedForUpload) { throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload"); } // Let's free resources or temporary calculation in DefaultFileData this.md5sum = null; // Then, we change the preparation status. this.preparedForUpload = false; } /** {@inheritDoc} */ public synchronized InputStream getInputStream() throws JUploadException { if (!this.preparedForUpload) { throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload"); } // Standard FileData : we read the file. try { return new FileInputStream(this.file); } catch (FileNotFoundException e) { throw new JUploadIOException(e); } } /** {@inheritDoc} */ public String getFileName() { return this.file.getName(); } /** {@inheritDoc} */ public String getFileExtension() { return getExtension(this.file); } /** {@inheritDoc} */ public long getFileLength() { return this.fileSize; } /** {@inheritDoc} */ public Date getLastModified() { return this.fileModified; } /** {@inheritDoc} */ public String getDirectory() { return this.fileDir; } /** {@inheritDoc} */ public String getMD5() throws JUploadException { if (this.md5sum == null) { throw new JUploadException("The MD5Sum has not been calculated!"); } return this.md5sum; } /** * Calculate the MD5Sum for the transformed file, or the original if no * transformation should be done on the file, before upload. * * @throws JUploadException */ public void calculateMD5Sum() throws JUploadException { StringBuffer ret = new StringBuffer(); MessageDigest digest = null; byte md5Buffer[] = new byte[BUFLEN]; int nbBytes; // Calculation of the MD5 sum. Now done before upload, to prepare the // file head. // This makes the file being parsed two times: once before upload, and // once for the actual upload InputStream md5InputStream = getInputStream(); try { digest = MessageDigest.getInstance("MD5"); while ((nbBytes = md5InputStream.read(md5Buffer, 0, BUFLEN)) > 0) { digest.update(md5Buffer, 0, nbBytes); } } catch (IOException e) { throw new JUploadIOException(e); } catch (NoSuchAlgorithmException e) { throw new JUploadException(e); } finally { try { md5InputStream.close(); } catch (IOException e) { throw new JUploadIOException(e); } } // Now properly format the md5 sum. byte md5sum[] = new byte[32]; if (digest != null) md5sum = digest.digest(); for (int i = 0; i < md5sum.length; i++) { ret.append(Integer.toHexString((md5sum[i] >> 4) & 0x0f)); ret.append(Integer.toHexString(md5sum[i] & 0x0f)); } this.md5sum = ret.toString(); } /** {@inheritDoc} */ public String getMimeType() { return this.mimeType; } /** {@inheritDoc} */ public boolean canRead() { // The commented line below doesn't seems to work. // return this.file.canRead(); // The canRead status is read once. This is done in this method, so that // it's available for all subclasses. If it were in the constructor, we // would have to initialize {@link #canRead} in all subclasses. // Let's store the status 'readible' only once. It's if (this.canRead == null) { try { InputStream is = new FileInputStream(this.file); is.close(); this.canRead = Boolean.valueOf(true); } catch (IOException e) { // Can't read the file! this.canRead = Boolean.valueOf(false); } } return this.canRead.booleanValue(); } /** {@inheritDoc} */ public File getFile() { return this.file; } /** {@inheritDoc} */ public String getRelativeDir() { if (null != this.fileRoot && (!this.fileRoot.equals("")) && (this.fileDir.startsWith(this.fileRoot))) { int skip = this.fileRoot.length(); if (!this.fileRoot.endsWith(File.separator)) skip++; if ((skip >= 0) && (skip < this.fileDir.length())) return this.fileDir.substring(skip); } return ""; } // //////////////////////////////////////////////////////// // UTILITIES // //////////////////////////////////////////////////////// /** * Returns the extension of the given file. To be clear: jpg is the * extension for the file named picture.jpg. * * @param file * the file whose the extension is wanted! * @return The extension, without the point, for the given file. */ public static String getExtension(File file) { String name = file.getName(); return name.substring(name.lastIndexOf('.') + 1); } /** * Return the 'biggest' common ancestror of the given file array. For * instance, the root for the files /usr/bin/toto and /usr/titi is /usr. * * @param fileArray * @return The common root for the given files. */ public static File getRoot(File[] fileArray) { // Let's find the common root for the dropped files. // If one file has been dropped (the minimum), the path of its parent // should be the root. File root = fileArray[0]; if (root.isDirectory()) { root = root.getParentFile(); } // Let's find the higher root level. while (root != null && !root.isDirectory()) { // We have a file. Let's take it's folder. root = root.getParentFile(); } if (root != null) { // root is the root for the first file. We add all directories, // and higher until the root. This will allow to find the // 'bigger' directory, which is the common root for all dropped // files. // If several files are being added, we take the common root for // them. String pathRoot = root.getAbsolutePath() + File.separator; String pathCurrentFileParentPath; File pathCurrentFileParent; // We start with the second item in the list, as we already // extracted the first. for (int i = 1; i < fileArray.length && root != null; i += 1) { // Let's check that all files in l are parents of the current // file. pathCurrentFileParent = fileArray[i]; pathCurrentFileParentPath = pathCurrentFileParent .getAbsolutePath() + File.separator; // We loop through the parent of the file, until we find a // common root. while (pathCurrentFileParent != null && !pathRoot.startsWith(pathCurrentFileParentPath)) { pathCurrentFileParent = pathCurrentFileParent .getParentFile(); pathCurrentFileParentPath = pathCurrentFileParent .getAbsolutePath() + File.separator; } // Let's store the new found root (which may actually be the // same as the last one) root = pathCurrentFileParent; pathRoot = pathCurrentFileParentPath; }// for // pathRoot contains the path for the found root. root = new File(pathRoot); } return root; } /** {@inheritDoc} */ public boolean isPreparedForUpload() { return this.preparedForUpload; } }