1 package wjhk.jupload2.upload;
2
3 import java.util.concurrent.ArrayBlockingQueue;
4 import java.util.concurrent.BlockingQueue;
5
6 import wjhk.jupload2.exception.JUploadEOFException;
7 import wjhk.jupload2.exception.JUploadException;
8 import wjhk.jupload2.filedata.FileData;
9 import wjhk.jupload2.gui.JUploadPanel;
10 import wjhk.jupload2.gui.filepanel.FilePanel;
11 import wjhk.jupload2.policies.UploadPolicy;
12 import wjhk.jupload2.upload.helper.ProgressBarManager;
13
14 /**
15 * This class is responsible for managing the upload. At the end of the upload, the
16 * {@link JUploadPanel#updateButtonState()} is called, to refresh the button state. Its job is to: <DIR> <LI>Prepare
17 * upload for the file (calls to {@link FileData#beforeUpload()} for each file in the file list. <LI>Create the thread
18 * to send a packet of files. <LI>Prepare the packets, that will be red by the upload thread. <LI>Manage the end of
19 * upload: trigger the call to {@link JUploadPanel#updateButtonState()} and the call to
20 * {@link UploadPolicy#afterUpload(Exception, String)}. <LI>Manage the 'stop' button reaction. </DIR> This class is
21 * created by {@link JUploadPanel}, when the user clicks on the upload button.
22 *
23 * @author etienne_sf
24 */
25 public class FileUploadManagerThreadImpl extends Thread implements FileUploadManagerThread {
26
27 /**
28 * Maximum number of files that can be stored in the filePreparationQueue. It's useless to have a too big value
29 * here, as, if too many files are there, it means that the file preparation process is much quicker than the file
30 * upload one.
31 */
32 final static int FILE_PREPARATION_QUEUE_SIZE = 50;
33
34 // /////////////////////////////////////////////////////////////////////////////////////////
35 // //////////////////// Possible Status for file upload
36 // /////////////////////////////////////////////////////////////////////////////////////////
37
38 /** The current file list. */
39 FilePanel filePanel = null;
40
41 /**
42 * The file preparatoin thread prepares each file for upload, and manage possible errors that can occurs at
43 * preparation time.
44 */
45 FilePreparationThread filePreparationThread = null;
46
47 /**
48 * This thread receives each prepared files in a queue, constructs the packets of files to be sent together, and
49 * posts them in another queue.
50 */
51 PacketConstructionThread packetConstructionThread = null;
52
53 /**
54 * The upload thread, that will wait for the next file packet to be ready, then send it.
55 */
56 FileUploadThread fileUploadThread = null;
57
58 /**
59 * This help controls the display of the progress bar and the status bar. It updates these bar, based on a timer.
60 */
61 ProgressBarManager progressBarManager;
62
63 /**
64 * Number of files that have been successfully uploaded. already been sent. The control on the upload success may be
65 * done or not. It's used to properly display the progress bar.
66 */
67 int nbSuccessfullyUploadedFiles = 0;
68
69 /**
70 * Indicates whether the upload is finished or not. Passed to true as soon as one of these conditions becomes true:
71 * <DIR> <LI>All files are uploaded (in the {@link #currentRequestIsFinished(UploadFilePacket)} method) <LI>An
72 * exception occurs (in the {@link #setUploadException(JUploadException)} method) <LI>The user stops the upload (in
73 * the {@link #stopUpload()} method) </DIR>
74 */
75 boolean uploadFinished = false;
76
77 /**
78 * If set to 'true', the thread will stop the current upload.
79 *
80 * @see UploadFileData#uploadFile(java.io.OutputStream, long)
81 */
82 boolean stop = false;
83
84 /** Thread Exception, if any occurred during upload. */
85 JUploadException uploadException = null;
86
87 /** A shortcut to the upload panel */
88 JUploadPanel uploadPanel = null;
89
90 /** The current upload policy. */
91 UploadPolicy uploadPolicy = null;
92
93 // ////////////////////////////////////////////////////////////////////////////
94 // To follow the upload speed.
95 // ////////////////////////////////////////////////////////////////////////////
96
97 /**
98 * Standard constructor of the class.
99 *
100 * @param uploadPolicy
101 * @throws JUploadException
102 */
103 public FileUploadManagerThreadImpl(UploadPolicy uploadPolicy) throws JUploadException {
104 super("FileUploadManagerThreadImpl thread");
105 constructor(uploadPolicy, null);
106 }
107
108 /**
109 * Internal constructor. It is used by the JUnit test, to create a FileUploadManagerThreadImpl instance, based on a
110 * non-active {@link FileUploadThread}.
111 *
112 * @param uploadPolicy The current uploadPolicy
113 * @param fileUploadThreadParam The instance of {@link FileUploadThread} that should be used. Allows execution of
114 * unit tests, based on a specific FileUploadThread, that does ... nothing.
115 * @throws JUploadException
116 */
117 FileUploadManagerThreadImpl(UploadPolicy uploadPolicy, FileUploadThread fileUploadThreadParam)
118 throws JUploadException {
119 super("FileUploadManagerThreadImpl test thread");
120 constructor(uploadPolicy, fileUploadThreadParam);
121 }
122
123 /**
124 * Called by the class constructors, to initialize the current instance.
125 *
126 * @param uploadPolicy
127 * @param fileUploadThreadParam
128 * @throws JUploadException
129 */
130 private synchronized void constructor(UploadPolicy uploadPolicy, FileUploadThread fileUploadThreadParam)
131 throws JUploadException {
132
133 // General shortcuts on the current applet.
134 this.uploadPolicy = uploadPolicy;
135 this.uploadPanel = uploadPolicy.getContext().getUploadPanel();
136 this.filePanel = this.uploadPanel.getFilePanel();
137
138 BlockingQueue<UploadFileData> preparedFileQueue = new ArrayBlockingQueue<UploadFileData>(
139 this.filePanel.getFilesLength());
140
141 // If the FileUploadThread was already created, we must take the same
142 // packetQueue.
143 BlockingQueue<UploadFilePacket> packetQueue;
144 if (fileUploadThreadParam == null) {
145 packetQueue = new ArrayBlockingQueue<UploadFilePacket>(this.filePanel.getFilesLength());
146 } else {
147 packetQueue = fileUploadThreadParam.getPacketQueue();
148 }
149 // Let's create (but not start) start the file preparation thread.
150 this.filePreparationThread = new FilePreparationThread(preparedFileQueue, this, this.uploadPolicy);
151 // The packet tread groups files together, depending on the current
152 // upload policy.
153 this.packetConstructionThread = new PacketConstructionThread(preparedFileQueue, packetQueue, this,
154 this.uploadPolicy);
155 // Let's start the upload thread. It will wait until the first
156 // packet is ready.
157 createUploadThread(packetQueue, fileUploadThreadParam);
158 // We're now ready to start the bar update job.
159 this.progressBarManager = new ProgressBarManager(this.uploadPolicy, this.filePreparationThread);
160 }
161
162 /**
163 * @see wjhk.jupload2.upload.FileUploadManagerThread#run()
164 */
165 @Override
166 final public void run() {
167 try {
168 this.uploadPolicy.displayDebug("Start of the FileUploadManagerThreadImpl", 5);
169
170 // Let's prepare the progress bar, to display the current upload
171 // stage.
172 progressBarManager.uploadIsStarted();
173
174 // Let's start the working threads.
175 this.filePreparationThread.start();
176 this.packetConstructionThread.start();
177 this.fileUploadThread.start();
178
179 // Let's let the current upload policy have any preparation work
180 this.uploadPolicy.beforeUpload();
181
182 // The upload is started. Let's change the button state.
183 this.uploadPanel.updateButtonState();
184
185 // The thread upload may need some information about the current
186 // one, like ... knowing that upload is actually finished (no more
187 // file to send).
188 // So we wait for it to finish.
189 while (this.fileUploadThread.isAlive() && !isUploadFinished()) {
190 try {
191 this.uploadPolicy.displayDebug("Waiting for fileUploadThread to die", 10);
192 this.fileUploadThread.join();
193 } catch (InterruptedException e) {
194 // This should not occur, and should not be a problem. Let's
195 // trace a warning info.
196 this.uploadPolicy
197 .displayWarn("An InterruptedException occured in FileUploadManagerThreadImpl.run()");
198 }
199 }// while
200
201 // If any error occurs, the prepared state of the file data may be true. We must free resources. So, to be
202 // sure, we do it in all cases.
203 for (FileData fd : this.uploadPanel.getFilePanel().getFiles()) {
204 if (fd.getUploadFlag() && fd.isPreparedForUpload()) {
205 fd.afterUpload();
206 }
207 }// for
208
209 // Let's restore the display.
210 this.uploadPanel.updateButtonState();
211 this.uploadPanel.getFilePanel().reload();
212 this.uploadPolicy.getContext().showStatus("");
213 this.uploadPolicy.getContext().getUploadPanel().getStatusLabel().setText("");
214
215 // If no error occurs, we tell to the upload policy that a successful upload has been done.
216
217 if (getUploadException() != null) {
218 this.uploadPolicy.sendDebugInformation("Error in Upload", getUploadException());
219 } else if (isUploadStopped()) {
220 this.uploadPolicy.displayInfo("Upload stopped by the user. "
221 + this.nbSuccessfullyUploadedFiles
222 + " file(s) uploaded in "
223 + (int) ((System.currentTimeMillis() - this.progressBarManager.getGlobalStartTime()) / 1000)
224 + " seconds. Average upload speed: "
225 + ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager
226 .getNbUploadedBytes() / this.progressBarManager.getUploadDuration())) : 0)
227 + " (kbytes/s)");
228 } else {
229 this.uploadPolicy.displayInfo("Upload finished normally. "
230 + this.nbSuccessfullyUploadedFiles
231 + " file(s) uploaded in "
232 + (int) ((System.currentTimeMillis() - this.progressBarManager.getGlobalStartTime()) / 1000)
233 + " seconds. Average upload speed: "
234 + ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager
235 .getNbUploadedBytes() / this.progressBarManager.getUploadDuration())) : 0)
236 + " (kbytes/s)");
237 // FIXME uploadDuration displayed is 0!
238 try {
239 this.uploadPolicy.afterUpload(this.getUploadException(), this.fileUploadThread.getResponseMsg());
240 } catch (JUploadException e1) {
241 this.uploadPolicy.displayErr("error in uploadPolicy.afterUpload (JUploadPanel)", e1);
242 }
243 }
244
245 // The job is finished. Let's stop the timer, and have a last
246 // refresh of the bars.
247 this.progressBarManager.uploadIsFinished();
248
249 // We wait for 5 seconds, before clearing the progress bar.
250 try {
251 sleep(5000);
252 } catch (InterruptedException e) {
253 // Nothing to do
254 }
255 // The job is finished for long enough, let's clear the progression
256 // bars (and any associated ressource, like the time).
257 // We'll clear the progress bar, only if this thread is in control
258 // of the upload, that is: if this instance is the currently
259 // FileUploadManagerThread referenced in the JUpload panel.
260 if (this == this.uploadPanel.getFileUploadManagerThread()) {
261 this.progressBarManager.clearBarContent();
262 this.uploadPolicy.getContext().getUploadPanel().getStatusLabel().setText("");
263 }
264
265 this.uploadPolicy.displayDebug("End of the FileUploadManagerThreadImpl", 5);
266 } catch (Exception e) {
267 // We need a JUploadException.
268 JUploadException jue = (e instanceof JUploadException) ? (JUploadException) e : new JUploadException(e);
269 setUploadException(jue);
270
271 // And go back into a 'normal' way.
272 stopUpload();
273 } finally {
274 // We restore the button state, just to be sure.
275 this.uploadPanel.updateButtonState();
276 }
277
278 // And we die of our beautiful death ... until next upload.
279 }// run
280
281 /**
282 * @see wjhk.jupload2.upload.FileUploadManagerThread#setUploadException(wjhk.jupload2.exception.JUploadException)
283 */
284 public synchronized void setUploadException(JUploadException uploadExceptionParam) {
285 // If the user stops the upload, the socket on which the applet reads
286 // the server response got closed. So we ignore this error.
287 if (isUploadStopped() && uploadExceptionParam instanceof JUploadEOFException) {
288 // Just ignore this error: the input stream from the server was
289 // closed, but it probably occurs because the applet itself closed
290 // the communicaiton.
291 } else {
292 // We don't override an existing exception
293 if (this.uploadException != null) {
294 this.uploadPolicy
295 .displayWarn("An exception has already been set in FileUploadManagerThreadImpl. The next one is just logged.");
296 } else {
297 this.uploadException = uploadExceptionParam;
298 }
299
300 String exceptionMsg = (uploadExceptionParam.getCause() == null) ? uploadExceptionParam.getMessage()
301 : uploadExceptionParam.getCause().getMessage();
302 String errMsg = this.uploadPolicy.getLocalizedString("errDuringUpload") + "\n\n" + exceptionMsg;
303 this.uploadPolicy.displayErr(errMsg, uploadException);
304 }
305 }
306
307 /**
308 * @see wjhk.jupload2.upload.FileUploadManagerThread#getUploadException()
309 */
310 public JUploadException getUploadException() {
311 return this.uploadException;
312 }
313
314 /**
315 * @see wjhk.jupload2.upload.FileUploadManagerThread#isUploadFinished()
316 */
317 public boolean isUploadFinished() {
318 // Indicate whether or not the upload is finished. Several conditions:
319 // all files are uploaded, there was an error and the user stops the
320 // upload here...
321 return this.uploadFinished || this.stop || this.uploadException != null;
322 }
323
324 /**
325 * @see FileUploadManagerThread#isUploadStopped()
326 */
327 public boolean isUploadStopped() {
328 return this.stop;
329 }
330
331 /**
332 * @see wjhk.jupload2.upload.FileUploadManagerThread#nbBytesUploaded(long, UploadFileData)
333 */
334 public synchronized void nbBytesUploaded(long nbBytes, UploadFileData uploadFileData) throws JUploadException {
335 this.progressBarManager.nbBytesUploaded(nbBytes, uploadFileData);
336 }
337
338 /**
339 * @see wjhk.jupload2.upload.FileUploadManagerThread#setUploadStatus(wjhk.jupload2.upload.UploadFilePacket,
340 * wjhk.jupload2.upload.UploadFileData, int)
341 */
342 public synchronized void setUploadStatus(UploadFilePacket uploadFilePacket, UploadFileData uploadFileData,
343 int uploadStatus) throws JUploadException {
344 this.progressBarManager.setUploadStatus(uploadFilePacket, uploadFileData, uploadStatus);
345 }
346
347 /**
348 * @see wjhk.jupload2.upload.FileUploadManagerThread#stopUpload()
349 */
350 public synchronized void stopUpload() {
351 this.stop = true;
352
353 // The upload is now finished ...
354 this.uploadFinished = true;
355
356 // We notify the various threads.
357 if (this.filePreparationThread != null && this.filePreparationThread.isAlive()) {
358 this.filePreparationThread.interrupt();
359 }
360 if (this.packetConstructionThread != null && this.packetConstructionThread.isAlive()) {
361 this.packetConstructionThread.interrupt();
362 }
363 if (this.fileUploadThread != null && this.fileUploadThread.isAlive()) {
364 this.fileUploadThread.interrupt();
365 }
366
367 // All 'sub-thread' is now interrupted. The upload thread can be stuck
368 // while waiting for the server response. We also interrupt the current
369 // thread.
370 this.interrupt();
371 }
372
373 // //////////////////////////////////////////////////////////////////////////////////////////////
374 // /////////////////// SYNCHRONIZATION METHODS
375 // //////////////////////////////////////////////////////////////////////////////////////////////
376
377 /**
378 * @see wjhk.jupload2.upload.FileUploadManagerThread#anotherFileHasBeenSent(wjhk.jupload2.upload.UploadFilePacket,
379 * wjhk.jupload2.upload.UploadFileData)
380 */
381 public synchronized void anotherFileHasBeenSent(UploadFilePacket uploadFilePacket,
382 UploadFileData newlyUploadedFileData) throws JUploadException {
383 this.progressBarManager.anotherFileHasBeenSent(uploadFilePacket, newlyUploadedFileData);
384 }
385
386 /**
387 * @see wjhk.jupload2.upload.FileUploadManagerThread#currentRequestIsFinished(wjhk.jupload2.upload.UploadFilePacket)
388 */
389 public synchronized void currentRequestIsFinished(UploadFilePacket uploadFilePacket) throws JUploadException {
390 // We are finished with this packet. Let's display it.
391 this.progressBarManager.setUploadStatus(uploadFilePacket, uploadFilePacket.get(uploadFilePacket.size() - 1),
392 FileUploadManagerThread.UPLOAD_STATUS_UPLOADED);
393
394 // We now remove this file from the list of files to upload, to show the user that there is less and less work
395 // to do.
396 for (FileData fileData : uploadFilePacket) {
397 this.filePanel.remove(fileData);
398 this.nbSuccessfullyUploadedFiles += 1;
399 }
400
401 this.filePanel.cleanHierarchy();
402
403 // If all files have been sent, the upload is finished.
404 if (!this.uploadFinished) {
405 this.uploadFinished = (this.nbSuccessfullyUploadedFiles == this.filePreparationThread.getNbFilesToSend());
406 }
407 }
408
409 // //////////////////////////////////////////////////////////////////////////////////////////////
410 // /////////////////// PRIVATE METHODS
411 // //////////////////////////////////////////////////////////////////////////////////////////////
412
413 /**
414 * Creates the upload thread, but does not start it. IThis thread will wait until the first packet is ready.
415 *
416 * @throws JUploadException
417 */
418 private synchronized void createUploadThread(BlockingQueue<UploadFilePacket> packetQueue,
419 FileUploadThread fileUploadThreadParam) throws JUploadException {
420 if (fileUploadThreadParam != null) {
421 // The FileUploadThread has already been created.
422 // We set the FileUploadThreadManager.
423 this.fileUploadThread = fileUploadThreadParam;
424 fileUploadThreadParam.setFileUploadThreadManager(this);
425 } else {
426 try {
427 if (this.uploadPolicy.getPostURL().substring(0, 4).equals("ftp:")) {
428 this.fileUploadThread = new FileUploadThreadFTP(this.uploadPolicy, packetQueue, this);
429 } else {
430 this.fileUploadThread = new FileUploadThreadHTTP(this.uploadPolicy, packetQueue, this);
431 }
432 } catch (JUploadException e1) {
433 // Too bad !
434 this.uploadPolicy.displayErr(e1);
435 }
436 }
437 }
438 }