View Javadoc
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 }