//
// $Id: UploadFileData.java 1360 2010-06-28 11:35:55Z etienne_sf $
//
// jupload - A file upload applet.
// Copyright 2007 The JUpload Team
//
// Created: 2006-11-20
// Creator: etienne_sf
// Last modified: $Date: 2010-06-28 08:35:55 -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.upload;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import wjhk.jupload2.exception.JUploadException;
import wjhk.jupload2.exception.JUploadIOException;
import wjhk.jupload2.exception.JUploadInterrupted;
import wjhk.jupload2.filedata.FileData;
import wjhk.jupload2.policies.UploadPolicy;
import wjhk.jupload2.upload.helper.ByteArrayEncoder;
/**
* This class implements the FileData interface, and is responsible to do the
* actual upload of the files.
*
* @author etienne_sf
*/
public class UploadFileData implements FileData {
/**
* The {@link FileData} instance that contains all information about the
* file to upload.
*/
private FileData fileData = null;
/**
* The value of the fileData InputStream. It's main use is to allow chunk
* upload, to reuse the previous InputStream, that is: each chunk will start
* reading the stream where the previous one stopped.
*/
InputStream uploadInputStream = null;
/**
* Indicates the position of the file in the current upload (from 0 to
* max-1). It is mainly used by the
* ProgressBarManager.updateUploadProgressBarText() method, to display the
* upload status to the user.
*/
int numOfFileInCurrentUpload = -1;
// FIXME numOfFileInCurrentUpload should be from 1 to max
/**
* Instance of the fileUploadManagerThread. This allow this class to send
* feedback to the thread.
*
* @see FileUploadManagerThread#nbBytesUploaded(long)
*/
private FileUploadManagerThread fileUploadManagerThread = 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 final static 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];
// /////////////////////////////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////// CONSTRUCTOR
// ///////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Standard constructor for the UploadFileData class.
*
* @param fileDataParam
* The file data the this instance must transmist.
* @param numOfFileInCurrentUpload
* @param fileUploadManagerThreadParam
* The current instance of {@link FileUploadThread}
* @param uploadPolicyParam
* The current upload policy, instance of {@link UploadPolicy}
*/
public UploadFileData(FileData fileDataParam, int numOfFileInCurrentUpload,
FileUploadManagerThread fileUploadManagerThreadParam,
UploadPolicy uploadPolicyParam) {
if (fileDataParam == null && !(this instanceof UploadFileDataPoisonned)) {
throw new NullPointerException(
"fileData is null in UploadFileData(FileData, FileUploadManagerThread, UploadPolicy) constructor");
}
this.fileData = fileDataParam;
this.numOfFileInCurrentUpload = numOfFileInCurrentUpload;
this.fileUploadManagerThread = fileUploadManagerThreadParam;
this.uploadPolicy = uploadPolicyParam;
}
/**
* This particular constructor is posted by the
* {@link FilePreparationThread} in the preparedFileQueue to indicate that
* the last file has been prepared.
*
* @param poisonned
* This parameter is here to avoid this constructor to be the
* default constructor. Its value must be 'true'.
*/
public UploadFileData(boolean poisonned) {
if (!poisonned) {
throw new IllegalArgumentException(
"poisonned must be true in UploadFileData(boolean) constructor");
}
}
/**
* 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()}.
* Note: 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;
}
/**
* 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.
* @throws JUploadInterrupted
* Thrown when an interruption of the thread is detected.
*/
void uploadFile(OutputStream outputStream, long amount)
throws JUploadException, JUploadInterrupted {
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.
InputStream inputStream = getInputStream();
while (amount > 0 && !this.fileUploadManagerThread.isUploadFinished()) {
// Are we interrupted ?
if (Thread.interrupted()) {
throw new JUploadInterrupted(getClass().getName()
+ ".uploadFile [" + this.getFileName() + "]",
this.uploadPolicy);
}
int toread = (amount > BUFLEN) ? BUFLEN : (int) amount;
int towrite = 0;
try {
towrite = inputStream.read(this.readBuffer, 0, toread);
} catch (IOException e) {
throw new JUploadIOException(e);
}
if (towrite > 0) {
try {
outputStream.write(this.readBuffer, 0, towrite);
this.fileUploadManagerThread.nbBytesUploaded(towrite, this);
amount -= towrite;
this.uploadRemainingLength -= towrite;
// For debug reason, I may need to simulate upload, that are
// on a real network. We then slow down the upload. This can
// occurs only when given a 'high' debugLevel (higher than
// what can be set with the applet GUI.
if (this.uploadPolicy.getDebugLevel() > 100) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// Nothing to do. We'll just take a look at the loop
// condition.
}
}
} catch (IOException ioe) {
throw new JUploadIOException(this.getClass().getName()
+ ".uploadFile()", ioe);
} catch (Exception e) {
// When the user may not override an existing file, I got a
// NullPointerException. Let's trap all errors here.
throw new JUploadException(
this.getClass().getName()
+ ".uploadFile() (check the user permission on the server)",
e);
}
}
}// while
}
/**
* Just transmit to {@link FileData#afterUpload()}.
*
* @see FileData#afterUpload()
*/
public void afterUpload() {
// Transmission to the 'real' FileData
this.fileData.afterUpload();
}
/** {@inheritDoc} */
public void appendFileProperties(ByteArrayEncoder bae, int index)
throws JUploadIOException {
this.fileData.appendFileProperties(bae, index);
}
/** {@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 (this.uploadInputStream == null) {
this.uploadInputStream = this.fileData.getInputStream();
}
return this.uploadInputStream;
}
/** {@inheritDoc} */
public Date getLastModified() {
return this.fileData.getLastModified();
}
/** {@inheritDoc} */
public String getMD5() throws JUploadException {
return this.fileData.getMD5();
}
/** {@inheritDoc} */
public String getMimeType() {
return this.fileData.getMimeType();
}
/** {@inheritDoc} */
public String getRelativeDir() {
return this.fileData.getRelativeDir();
}
/**
* 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.
* @throws JUploadException
* @see UploadPolicy#getUploadName(FileData, int)
*/
public String getUploadName(int index) throws JUploadException {
return this.uploadPolicy.getUploadName(this.fileData, index);
}
/**
* This methods stores locally the upload length. So, on the contrary of the
* {@link FileData} interface, this method may be called after
* {@link #afterUpload()}, at one condition: that it has been called once
* before {@link #afterUpload()} is called.
*
* @see FileData#getUploadLength()
*/
public long getUploadLength() {
return this.fileData.getUploadLength();
}
/** {@inheritDoc} */
public boolean isPreparedForUpload() {
return this.fileData.isPreparedForUpload();
}
/**
* @return the poisonned status. Returns always false, as this instance is a
* true one. false indicates the 'End Of Queue' marker in the
* preparedFileQueue, which is not the case here
* @see UploadFileDataPoisonned
*/
public boolean isPoisonned() {
return false;
}
/**
* @return the numOfFileInCurrentUpload
*/
public int getNumOfFileInCurrentUpload() {
return numOfFileInCurrentUpload;
}
}