// // $Id: UploadFileData.java 441 2008-04-16 07:58:02Z etienne_sf $ // // jupload - A file upload applet. // Copyright 2007 The JUpload Team // // Created: 2006-11-20 // Creator: etienne_sf // Last modified: $Date: 2008-04-16 00:58:02 -0700 (Wed, 16 Apr 2008) $ // // 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.upload; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import wjhk.jupload2.exception.JUploadException; import wjhk.jupload2.exception.JUploadExceptionTooBigFile; import wjhk.jupload2.exception.JUploadIOException; import wjhk.jupload2.filedata.FileData; import wjhk.jupload2.policies.UploadPolicy; import wjhk.jupload2.upload.helper.ByteArrayEncoder; class UploadFileData implements FileData { /** * The {@link FileData} instance that contains all information about the * file to upload. */ private FileData fileData = null; /** * Instance of the fileUploadThread. This allow this class to send feedback * to the thread. * * @see FileUploadThread#nbBytesUploaded(long) */ private FileUploadThread fileUploadThread = null; /** * inputStream contains the stream that read from the file to upload. This * may be a transformed version of the file (for instance, a compressed * one). * * @see FileData#getInputStream() */ private InputStream inputStream = null; /** * The number of bytes to upload, for this file (without the head and tail * defined for the HTTP multipart body). */ private long uploadRemainingLength = -1; /** * The current {@link UploadPolicy} */ private UploadPolicy uploadPolicy = null; private static final int BUFLEN = 4096; /** * This field is no more static, as we could decide to upload two files * simultaneously. */ private final byte readBuffer[] = new byte[BUFLEN]; private int status = 0; private long filebytes; // number of bytes written to the file // ///////////////////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////// CONSTRUCTOR // /////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////////////////////////////////////// /** * Standard constructor for the UploadFileData class. * * @param fileDataParam The file data the this instance must transmist. * @param fileUploadThreadParam The current instance of * {@link FileUploadThread} * @param uploadPolicyParam The current upload policy, instance of * {@link UploadPolicy} */ public UploadFileData(FileData fileDataParam, FileUploadThread fileUploadThreadParam, UploadPolicy uploadPolicyParam) { this.fileData = fileDataParam; this.fileUploadThread = fileUploadThreadParam; this.uploadPolicy = uploadPolicyParam; } /** * Get the number of files that are still to upload. It is initialized at * the creation of the file, by a call to the * {@link FileData#getUploadLength()}. <BR> * <B>Note:</B> When the upload for this file is finish and you want to * send it again (for instance the upload failed, and you want to do a * retry), you should not reuse this instance, but, instead, create a new * UploadFileData instance. * * @return Number of bytes still to upload. * @see #getInputStream() */ long getRemainingLength() { return this.uploadRemainingLength; } /** * Retrieves the MD5 sum of the file.<BR> * <U>Caution:</U> since 3.3.0, this method has been rewrited. The file is * now parsed once within this method. This allows proper calculation of the * file head and tail, before upload. * * @return The corresponding MD5 sum. */ String getMD5() 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 = this.fileData.getInputStream(); try { digest = MessageDigest.getInstance("MD5"); while ((nbBytes = md5InputStream.read(md5Buffer, 0, BUFLEN)) > 0) { digest.update(md5Buffer, 0, nbBytes); } md5InputStream.close(); } catch (IOException e) { throw new JUploadIOException(e); } catch (NoSuchAlgorithmException e) { throw new JUploadException(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)); } return ret.toString(); } /** * This methods writes the file data (see {@link FileData#getInputStream()} * to the given outputStream (the output toward the HTTP server). * * @param outputStream The stream on which the data is to be written. * @param amount The number of bytes to write. * @throws JUploadException if an I/O error occurs. */ void uploadFile(OutputStream outputStream, long amount) throws JUploadException { this.uploadPolicy.displayDebug("in UploadFileData.uploadFile (amount:" + amount + ", getUploadLength(): " + getUploadLength() + ")", 30); // getInputStream will put a new fileInput in the inputStream attribute, // or leave it unchanged if it is not null. getInputStream(); while (!this.fileUploadThread.isUploadStopped() && (0 < amount)) { int toread = (amount > BUFLEN) ? BUFLEN : (int) amount; int towrite = 0; try { towrite = this.inputStream.read(this.readBuffer, 0, toread); } catch (IOException e) { e.printStackTrace(); throw new JUploadIOException(e); } if (towrite > 0) { try { outputStream.write(this.readBuffer, 0, towrite); this.filebytes += towrite; this.fileUploadThread.nbFileBytesUploaded(this.filebytes); this.fileUploadThread.nbBytesUploaded(towrite); amount -= towrite; this.uploadRemainingLength -= towrite; } catch (IOException e) { e.printStackTrace(); throw new JUploadIOException(e); } } }// while } /* fire the callbacks to Javascript */ public void uploadStart() { this.fileData.uploadStart(); } public void uploadComplete() { this.fileData.uploadComplete(); } public void uploadProgress(Long bytes_complete, Long total_bytes) { this.fileData.uploadProgress(bytes_complete,total_bytes); } /* fire upload Success back to javascript */ public void uploadSuccess(String server_response) { this.fileData.uploadSuccess(server_response); } /* fire upload error back to javascript */ public void uploadError(Integer error_code, String message) { this.fileData.uploadError(error_code,message); } /** * This method closes the inputstream, and remove the file from the * filepanel. Then it calls {@link FileData#afterUpload()}. * * @see FileData#afterUpload() */ public void afterUpload() { // 1. Close the InputStream if (this.inputStream != null) { try { this.inputStream.close(); } catch (IOException e) { this.uploadPolicy.displayWarn(e.getClass().getName() + ": " + e.getMessage() + " (in UploadFileData.afterUpload()"); } this.inputStream = null; } // 2. Ask the FileData to release any other locked resource. this.fileData.afterUpload(); } /** {@inheritDoc} */ public void appendFileProperties(ByteArrayEncoder bae) throws JUploadIOException { this.fileData.appendFileProperties(bae); } /** {@inheritDoc} */ public void beforeUpload() throws JUploadException { this.fileData.beforeUpload(); // Calculation of some internal variables. this.uploadRemainingLength = this.fileData.getUploadLength(); } /** {@inheritDoc} */ public boolean canRead() { return this.fileData.canRead(); } /** {@inheritDoc} */ public String getDirectory() { return this.fileData.getDirectory(); } /** {@inheritDoc} */ public File getFile() { return this.fileData.getFile(); } /** {@inheritDoc} */ public String getFileExtension() { return this.fileData.getFileExtension(); } /** {@inheritDoc} */ public long getFileLength() { return this.fileData.getFileLength(); } /** {@inheritDoc} */ public String getFileName() { return this.fileData.getFileName(); } /** {@inheritDoc} */ public InputStream getInputStream() throws JUploadException { // If you didn't already open the input stream, the remaining length // should be non 0. if (this.inputStream == null) { if (this.uploadRemainingLength <= 0) { // Too bad: we already uploaded this file. Perhaps its Ok (a // second try?) // To avoid this warning, just create a new UploadFileData // instance, and not use an already existing one. this.uploadPolicy .displayWarn("[" + getFileName() + "] UploadFileData.getInputStream(): uploadRemainingLength is <= 0. Trying a new upload ?"); this.uploadRemainingLength = this.fileData.getUploadLength(); } // Ok, this is the start of upload for this file. Let's get its // InputStream. this.filebytes = 0; this.inputStream = this.fileData.getInputStream(); } return this.inputStream; } /** {@inheritDoc} */ public Date getLastModified() { return this.fileData.getLastModified(); } /** {@inheritDoc} */ public String getMimeType() { return this.fileData.getMimeType(); } /** {@inheritDoc} */ public String getRelativeDir() { return this.fileData.getRelativeDir(); } /** {@inheritDoc} */ public String external_id() { return this.fileData.external_id(); } /** {@inheritDoc} */ public int external_index() { return this.fileData.external_index(); } /** {@inheritDoc} */ public String getJSON() { return this.fileData.getJSON(); } /** * Retrieves the file name, that should be used in the server application. * Default is to send the original filename. * * @param index The index of this file in the current request to the server. * @return The real file name. Not used in FTP upload. * @throws JUploadException Thrown when an error occurs. * * @see UploadPolicy#getUploadFilename(FileData, int) */ public String getUploadFilename(int index) throws JUploadException { return this.uploadPolicy.getUploadFilename(this.fileData, index); } /** * Retrieves the upload file name, that should be sent to the server. It's * the technical name used to retrieve the file content. Default is File0, * File1... This method just calls the * {@link UploadPolicy#getUploadFilename(FileData, int)} method. * * @param index The index of this file in the current request to the server. * @return The technical upload file name. Not used in FTP upload. * * @see UploadPolicy#getUploadName(FileData, int) */ public String getUploadName(int index) { return this.uploadPolicy.getUploadName(this.fileData, index); } /** @see FileData#getUploadLength() */ public long getUploadLength() throws JUploadException { long uploadLength = this.fileData.getUploadLength(); // We check the filesize only now: the file to upload may be different // from the original file. For instance, // a selected picture on the local hard drive may be bigger than // maxFileSize, but, as the picture can be // resized before upload, the picture to upload may be still be smaller // than maxFileSize. if (uploadLength > this.uploadPolicy.getMaxFileSize()) { throw new JUploadExceptionTooBigFile(this.fileData.getFileName(), this.fileData.getUploadLength(), this.uploadPolicy); } return uploadLength; } /** * {@inheritDoc} */ public String getJSON( int index) { String json_obj; /* TODO creation_date, modification_date, filestatus */ json_obj = "{ id : '" + "File_"+this.hashCode() + "', " + " index : '" + index + "', " + " name : '" + this.getFileName() + "'," + " size : " + this.getFileLength() + "," + " type : '" + this.getFileExtension() + "'," + " relative_path : '" + this.getRelativeDir()+ "'," + " filestatus : "+ this.status + " } "; return json_obj; } public int status() { return this.status; } public int setStatus(int mstatus) { this.status = mstatus; return this.status; } }