package wjhk.jupload2.upload;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import wjhk.jupload2.exception.JUploadEOFException;
import wjhk.jupload2.exception.JUploadException;
import wjhk.jupload2.filedata.FileData;
import wjhk.jupload2.gui.JUploadPanel;
import wjhk.jupload2.gui.filepanel.FilePanel;
import wjhk.jupload2.policies.UploadPolicy;
import wjhk.jupload2.upload.helper.ProgressBarManager;
/**
* This class is responsible for managing the upload. At the end of the upload,
* the {@link JUploadPanel#updateButtonState()} is called, to refresh the button
* state. Its job is to:
Prepare upload for the file (calls to
* {@link FileData#beforeUpload()} for each file in the file list. Create
* the thread to send a packet of files. Prepare the packets, that will be
* red by the upload thread. Manage the end of upload: trigger the call to
* {@link JUploadPanel#updateButtonState()} and the call to
* {@link UploadPolicy#afterUpload(Exception, String)}. Manage the 'stop'
* button reaction. This class is created by {@link JUploadPanel}, when
* the user clicks on the upload button.
*
* @author etienne_sf
*/
public class FileUploadManagerThreadImpl extends Thread implements
FileUploadManagerThread {
/**
* Maximum number of files that can be stored in the filePreparationQueue.
* It's useless to have a too big value here, as, if too many files are
* there, it means that the file preparation process is much quicker than
* the file upload one.
*/
final static int FILE_PREPARATION_QUEUE_SIZE = 50;
// /////////////////////////////////////////////////////////////////////////////////////////
// //////////////////// Possible Status for file upload
// /////////////////////////////////////////////////////////////////////////////////////////
/** The current file list. */
FilePanel filePanel = null;
/**
* The file preparatoin thread prepares each file for upload, and manage
* possible errors that can occurs at preparation time.
*/
FilePreparationThread filePreparationThread = null;
/**
* This thread receives each prepared files in a queue, constructs the
* packets of files to be sent together, and posts them in another queue.
*/
PacketConstructionThread packetConstructionThread = null;
/**
* The upload thread, that will wait for the next file packet to be ready,
* then send it.
*/
FileUploadThread fileUploadThread = null;
/**
* This help controls the display of the progress bar and the status bar. It
* updates these bar, based on a timer.
*/
ProgressBarManager progressBarManager;
/**
* Number of files that have been successfully uploaded. already been sent.
* The control on the upload success may be done or not. It's used to
* properly display the progress bar.
*/
int nbSuccessfullyUploadedFiles = 0;
/**
* Indicates whether the upload is finished or not. Passed to true as soon
* as one of these conditions becomes true: All files are uploaded
* (in the {@link #currentRequestIsFinished(UploadFilePacket)} method)
* An exception occurs (in the {@link #setUploadException(JUploadException)}
* method) The user stops the upload (in the {@link #stopUpload()}
* method)
*/
boolean uploadFinished = false;
/**
* If set to 'true', the thread will stop the current upload.
*
* @see UploadFileData#uploadFile(java.io.OutputStream, long)
*/
boolean stop = false;
/** Thread Exception, if any occurred during upload. */
JUploadException uploadException = null;
/** A shortcut to the upload panel */
JUploadPanel uploadPanel = null;
/** The current upload policy. */
UploadPolicy uploadPolicy = null;
// ////////////////////////////////////////////////////////////////////////////
// To follow the upload speed.
// ////////////////////////////////////////////////////////////////////////////
/**
* Standard constructor of the class.
*
* @param uploadPolicy
* @throws JUploadException
*/
public FileUploadManagerThreadImpl(UploadPolicy uploadPolicy)
throws JUploadException {
super("FileUploadManagerThreadImpl thread");
constructor(uploadPolicy, null);
}
/**
* Internal constructor. It is used by the JUnit test, to create a
* FileUploadManagerThreadImpl instance, based on a non-active
* {@link FileUploadThread}.
*
* @param uploadPolicy The current uploadPolicy
* @param fileUploadThreadParam The instance of {@link FileUploadThread}
* that should be used. Allows execution of unit tests, based on
* a specific FileUploadThread, that does ... nothing.
* @throws JUploadException
*/
FileUploadManagerThreadImpl(UploadPolicy uploadPolicy,
FileUploadThread fileUploadThreadParam) throws JUploadException {
super("FileUploadManagerThreadImpl test thread");
constructor(uploadPolicy, fileUploadThreadParam);
}
/**
* Called by the class constructors, to initialize the current instance.
*
* @param uploadPolicy
* @param fileUploadThreadParam
* @throws JUploadException
*/
private synchronized void constructor(UploadPolicy uploadPolicy,
FileUploadThread fileUploadThreadParam) throws JUploadException {
// General shortcuts on the current applet.
this.uploadPolicy = uploadPolicy;
this.uploadPanel = uploadPolicy.getContext().getUploadPanel();
this.filePanel = this.uploadPanel.getFilePanel();
BlockingQueue preparedFileQueue = new ArrayBlockingQueue(
this.filePanel.getFilesLength());
// If the FileUploadThread was already created, we must take the same
// packetQueue.
BlockingQueue packetQueue;
if (fileUploadThreadParam == null) {
packetQueue = new ArrayBlockingQueue(
this.filePanel.getFilesLength());
} else {
packetQueue = fileUploadThreadParam.getPacketQueue();
}
// Let's create (but not start) start the file preparation thread.
this.filePreparationThread = new FilePreparationThread(
preparedFileQueue, this, this.uploadPolicy);
// The packet tread groups files together, depending on the current
// upload policy.
this.packetConstructionThread = new PacketConstructionThread(
preparedFileQueue, packetQueue, this, this.uploadPolicy);
// Let's start the upload thread. It will wait until the first
// packet is ready.
createUploadThread(packetQueue, fileUploadThreadParam);
// We're now ready to start the bar update job.
this.progressBarManager = new ProgressBarManager(this.uploadPolicy,
this.filePreparationThread);
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#run()
*/
@Override
final public void run() {
try {
this.uploadPolicy.displayDebug(
"Start of the FileUploadManagerThreadImpl", 5);
// Let's start the working threads.
this.filePreparationThread.start();
this.packetConstructionThread.start();
this.fileUploadThread.start();
// Let's let the current upload policy have any preparation work
this.uploadPolicy.beforeUpload();
// The upload is started. Let's change the button state.
this.uploadPanel.updateButtonState();
// Let's prepare the progress bar, to display the current upload
// stage.
progressBarManager.uploadIsStarted();
// The thread upload may need some information about the current
// one, like ... knowing that upload is actually finished (no more
// file to send).
// So we wait for it to finish.
while (this.fileUploadThread.isAlive() && !isUploadFinished()) {
try {
this.uploadPolicy.displayDebug(
"Waiting for fileUploadThread to die", 10);
this.fileUploadThread.join();
} catch (InterruptedException e) {
// This should not occur, and should not be a problem. Let's
// trace a warning info.
this.uploadPolicy
.displayWarn("An InterruptedException occured in FileUploadManagerThreadImpl.run()");
}
}// while
// If any error occurs, the prepared state of the file data may be
// true. We must free resources. So, to be sure, we do it in all
// cases.
FileData[] fileDataArray = this.uploadPanel.getFilePanel()
.getFiles();
for (int i = 0; i < fileDataArray.length; i += 1) {
if (fileDataArray[i].isPreparedForUpload()) {
fileDataArray[i].afterUpload();
}
}
// Let's restore the button state.
this.uploadPanel.updateButtonState();
this.uploadPolicy.getContext().showStatus("");
this.uploadPolicy.getContext().getUploadPanel().getStatusLabel()
.setText("");
// If no error occurs, we tell to the upload policy that a
// successful
// upload has been done.
if (getUploadException() != null) {
this.uploadPolicy.sendDebugInformation("Error in Upload",
getUploadException());
} else if (isUploadStopped()) {
this.uploadPolicy
.displayInfo("Upload stopped by the user. "
+ this.nbSuccessfullyUploadedFiles
+ " file(s) uploaded in "
+ (int) ((System.currentTimeMillis() - this.progressBarManager
.getGlobalStartTime()) / 1000)
+ " seconds. Average upload speed: "
+ ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager
.getNbUploadedBytes() / this.progressBarManager
.getUploadDuration()))
: 0) + " (kbytes/s)");
} else {
this.uploadPolicy
.displayInfo("Upload finished normally. "
+ this.nbSuccessfullyUploadedFiles
+ " file(s) uploaded in "
+ (int) ((System.currentTimeMillis() - this.progressBarManager
.getGlobalStartTime()) / 1000)
+ " seconds. Average upload speed: "
+ ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager
.getNbUploadedBytes() / this.progressBarManager
.getUploadDuration()))
: 0) + " (kbytes/s)");
// FIXME uploadDuration displayed is 0!
try {
this.uploadPolicy.afterUpload(this.getUploadException(),
this.fileUploadThread.getResponseMsg());
} catch (JUploadException e1) {
this.uploadPolicy.displayErr(
"error in uploadPolicy.afterUpload (JUploadPanel)",
e1);
}
}
// The job is finished. Let's stop the timer, and have a last
// refresh of the bars.
this.progressBarManager.uploadIsFinished();
// We wait for 5 seconds, before clearing the progress bar.
try {
sleep(5000);
} catch (InterruptedException e) {
// Nothing to do
}
// The job is finished for long enough, let's clear the progression
// bars (and any associated ressource, like the time).
// We'll clear the progress bar, only if this thread is in control
// of the upload, that is: if this instance is the currently
// FileUploadManagerThread referenced in the JUpload panel.
if (this == this.uploadPanel.getFileUploadManagerThread()) {
this.progressBarManager.clearBarContent();
this.uploadPolicy.getContext().getUploadPanel()
.getStatusLabel().setText("");
}
this.uploadPolicy.displayDebug(
"End of the FileUploadManagerThreadImpl", 5);
} catch (Exception e) {
// We need a JUploadException.
JUploadException jue = (e instanceof JUploadException) ? (JUploadException) e
: new JUploadException(e);
setUploadException(jue);
// And go back into a 'normal' way.
stopUpload();
} finally {
// We restore the button state, just to be sure.
this.uploadPanel.updateButtonState();
}
// And we die of our beautiful death ... until next upload.
}// run
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#setUploadException(wjhk.jupload2.exception.JUploadException)
*/
public synchronized void setUploadException(
JUploadException uploadExceptionParam) {
// If the user stops the upload, the socket on which the applet reads
// the server response got closed. So we ignore this error.
if (isUploadStopped()
&& uploadExceptionParam instanceof JUploadEOFException) {
// Just ignore this error: the input stream from the server was
// closed, but it probably occurs because the applet itself closed
// the communicaiton.
} else {
// We don't override an existing exception
if (this.uploadException != null) {
this.uploadPolicy
.displayWarn("An exception has already been set in FileUploadManagerThreadImpl. The next one is just logged.");
} else {
this.uploadException = uploadExceptionParam;
}
String exceptionMsg = (uploadExceptionParam.getCause() == null) ? uploadExceptionParam
.getMessage()
: uploadExceptionParam.getCause().getMessage();
String errMsg = this.uploadPolicy
.getLocalizedString("errDuringUpload")
+ "\n\n" + exceptionMsg;
this.uploadPolicy.displayErr(errMsg, uploadException);
}
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#getUploadException()
*/
public JUploadException getUploadException() {
return this.uploadException;
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#isUploadFinished()
*/
public boolean isUploadFinished() {
// Indicate whether or not the upload is finished. Several conditions:
// all files are uploaded, there was an error and the user stops the
// upload here...
return this.uploadFinished || this.stop || this.uploadException != null;
}
/**
* @see FileUploadManagerThread#isUploadStopped()
*/
public boolean isUploadStopped() {
return this.stop;
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#nbBytesUploaded(long,
* UploadFileData)
*/
public synchronized void nbBytesUploaded(long nbBytes,
UploadFileData uploadFileData) throws JUploadException {
this.progressBarManager.nbBytesUploaded(nbBytes, uploadFileData);
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#setUploadStatus(wjhk.jupload2.upload.UploadFilePacket,
* wjhk.jupload2.upload.UploadFileData, int)
*/
public synchronized void setUploadStatus(UploadFilePacket uploadFilePacket,
UploadFileData uploadFileData, int uploadStatus)
throws JUploadException {
this.progressBarManager.setUploadStatus(uploadFilePacket,
uploadFileData, uploadStatus);
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#stopUpload()
*/
public synchronized void stopUpload() {
this.stop = true;
// The upload is now finished ...
this.uploadFinished = true;
// We notify the various threads.
if (this.filePreparationThread != null
&& this.filePreparationThread.isAlive()) {
this.filePreparationThread.interrupt();
}
if (this.packetConstructionThread != null
&& this.packetConstructionThread.isAlive()) {
this.packetConstructionThread.interrupt();
}
if (this.fileUploadThread != null && this.fileUploadThread.isAlive()) {
this.fileUploadThread.interrupt();
}
// All 'sub-thread' is now interrupted. The upload thread can be stuck
// while waiting for the server response. We also interrupt the current
// thread.
this.interrupt();
}
// //////////////////////////////////////////////////////////////////////////////////////////////
// /////////////////// SYNCHRONIZATION METHODS
// //////////////////////////////////////////////////////////////////////////////////////////////
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#anotherFileHasBeenSent(wjhk.jupload2.upload.UploadFilePacket,
* wjhk.jupload2.upload.UploadFileData)
*/
public synchronized void anotherFileHasBeenSent(
UploadFilePacket uploadFilePacket,
UploadFileData newlyUploadedFileData) throws JUploadException {
this.progressBarManager.anotherFileHasBeenSent(uploadFilePacket,
newlyUploadedFileData);
}
/**
* @see wjhk.jupload2.upload.FileUploadManagerThread#currentRequestIsFinished(wjhk.jupload2.upload.UploadFilePacket)
*/
public synchronized void currentRequestIsFinished(
UploadFilePacket uploadFilePacket) throws JUploadException {
// We are finished with this packet. Let's display it.
this.progressBarManager.setUploadStatus(uploadFilePacket,
uploadFilePacket.get(uploadFilePacket.size() - 1),
FileUploadManagerThread.UPLOAD_STATUS_UPLOADED);
// We should now remove this file from the list of files to upload,
// to show the user that there is less and less work to do.
for (FileData fileData : uploadFilePacket) {
this.filePanel.remove(fileData);
this.nbSuccessfullyUploadedFiles += 1;
}
// If all files have been sent, the upload is finished.
if (!this.uploadFinished) {
this.uploadFinished = (this.nbSuccessfullyUploadedFiles == this.filePreparationThread
.getNbFilesToSent());
}
}
// //////////////////////////////////////////////////////////////////////////////////////////////
// /////////////////// PRIVATE METHODS
// //////////////////////////////////////////////////////////////////////////////////////////////
/**
* Creates the upload thread, but does not start it. IThis thread will wait
* until the first packet is ready.
*
* @throws JUploadException
*/
private synchronized void createUploadThread(
BlockingQueue packetQueue,
FileUploadThread fileUploadThreadParam) throws JUploadException {
if (fileUploadThreadParam != null) {
// The FileUploadThread has already been created.
// We set the FileUploadThreadManager.
this.fileUploadThread = fileUploadThreadParam;
fileUploadThreadParam.setFileUploadThreadManager(this);
} else {
try {
if (this.uploadPolicy.getPostURL().substring(0, 4).equals(
"ftp:")) {
this.fileUploadThread = new FileUploadThreadFTP(
this.uploadPolicy, packetQueue, this);
} else {
this.fileUploadThread = new FileUploadThreadHTTP(
this.uploadPolicy, packetQueue, this);
}
} catch (JUploadException e1) {
// Too bad !
this.uploadPolicy.displayErr(e1);
}
}
}
}