Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
FileUploadManagerThreadImpl |
|
| 3.357142857142857;3,357 |
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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | boolean stop = false; |
83 | ||
84 | /** Thread Exception, if any occurred during upload. */ | |
85 | 0 | JUploadException uploadException = null; |
86 | ||
87 | /** A shortcut to the upload panel */ | |
88 | 0 | JUploadPanel uploadPanel = null; |
89 | ||
90 | /** The current upload policy. */ | |
91 | 0 | 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 | 0 | super("FileUploadManagerThreadImpl thread"); |
105 | 0 | constructor(uploadPolicy, null); |
106 | 0 | } |
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 | 0 | super("FileUploadManagerThreadImpl test thread"); |
120 | 0 | constructor(uploadPolicy, fileUploadThreadParam); |
121 | 0 | } |
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 | 0 | this.uploadPolicy = uploadPolicy; |
135 | 0 | this.uploadPanel = uploadPolicy.getContext().getUploadPanel(); |
136 | 0 | this.filePanel = this.uploadPanel.getFilePanel(); |
137 | ||
138 | 0 | BlockingQueue<UploadFileData> preparedFileQueue = new ArrayBlockingQueue<UploadFileData>( |
139 | 0 | this.filePanel.getFilesLength()); |
140 | ||
141 | // If the FileUploadThread was already created, we must take the same | |
142 | // packetQueue. | |
143 | BlockingQueue<UploadFilePacket> packetQueue; | |
144 | 0 | if (fileUploadThreadParam == null) { |
145 | 0 | packetQueue = new ArrayBlockingQueue<UploadFilePacket>(this.filePanel.getFilesLength()); |
146 | } else { | |
147 | 0 | packetQueue = fileUploadThreadParam.getPacketQueue(); |
148 | } | |
149 | // Let's create (but not start) start the file preparation thread. | |
150 | 0 | this.filePreparationThread = new FilePreparationThread(preparedFileQueue, this, this.uploadPolicy); |
151 | // The packet tread groups files together, depending on the current | |
152 | // upload policy. | |
153 | 0 | 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 | 0 | createUploadThread(packetQueue, fileUploadThreadParam); |
158 | // We're now ready to start the bar update job. | |
159 | 0 | this.progressBarManager = new ProgressBarManager(this.uploadPolicy, this.filePreparationThread); |
160 | 0 | } |
161 | ||
162 | /** | |
163 | * @see wjhk.jupload2.upload.FileUploadManagerThread#run() | |
164 | */ | |
165 | @Override | |
166 | final public void run() { | |
167 | try { | |
168 | 0 | this.uploadPolicy.displayDebug("Start of the FileUploadManagerThreadImpl", 5); |
169 | ||
170 | // Let's prepare the progress bar, to display the current upload | |
171 | // stage. | |
172 | 0 | progressBarManager.uploadIsStarted(); |
173 | ||
174 | // Let's start the working threads. | |
175 | 0 | this.filePreparationThread.start(); |
176 | 0 | this.packetConstructionThread.start(); |
177 | 0 | this.fileUploadThread.start(); |
178 | ||
179 | // Let's let the current upload policy have any preparation work | |
180 | 0 | this.uploadPolicy.beforeUpload(); |
181 | ||
182 | // The upload is started. Let's change the button state. | |
183 | 0 | 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 | 0 | while (this.fileUploadThread.isAlive() && !isUploadFinished()) { |
190 | try { | |
191 | 0 | this.uploadPolicy.displayDebug("Waiting for fileUploadThread to die", 10); |
192 | 0 | this.fileUploadThread.join(); |
193 | 0 | } catch (InterruptedException e) { |
194 | // This should not occur, and should not be a problem. Let's | |
195 | // trace a warning info. | |
196 | 0 | this.uploadPolicy |
197 | 0 | .displayWarn("An InterruptedException occured in FileUploadManagerThreadImpl.run()"); |
198 | 0 | } |
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 | 0 | for (FileData fd : this.uploadPanel.getFilePanel().getFiles()) { |
204 | 0 | if (fd.getUploadFlag() && fd.isPreparedForUpload()) { |
205 | 0 | fd.afterUpload(); |
206 | } | |
207 | 0 | }// for |
208 | ||
209 | // Let's restore the display. | |
210 | 0 | this.uploadPanel.updateButtonState(); |
211 | 0 | this.uploadPanel.getFilePanel().reload(); |
212 | 0 | this.uploadPolicy.getContext().showStatus(""); |
213 | 0 | 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 | 0 | if (getUploadException() != null) { |
218 | 0 | this.uploadPolicy.sendDebugInformation("Error in Upload", getUploadException()); |
219 | 0 | } else if (isUploadStopped()) { |
220 | 0 | this.uploadPolicy.displayInfo("Upload stopped by the user. " |
221 | + this.nbSuccessfullyUploadedFiles | |
222 | + " file(s) uploaded in " | |
223 | 0 | + (int) ((System.currentTimeMillis() - this.progressBarManager.getGlobalStartTime()) / 1000) |
224 | + " seconds. Average upload speed: " | |
225 | 0 | + ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager |
226 | 0 | .getNbUploadedBytes() / this.progressBarManager.getUploadDuration())) : 0) |
227 | + " (kbytes/s)"); | |
228 | } else { | |
229 | 0 | this.uploadPolicy.displayInfo("Upload finished normally. " |
230 | + this.nbSuccessfullyUploadedFiles | |
231 | + " file(s) uploaded in " | |
232 | 0 | + (int) ((System.currentTimeMillis() - this.progressBarManager.getGlobalStartTime()) / 1000) |
233 | + " seconds. Average upload speed: " | |
234 | 0 | + ((this.progressBarManager.getUploadDuration() > 0) ? ((int) (this.progressBarManager |
235 | 0 | .getNbUploadedBytes() / this.progressBarManager.getUploadDuration())) : 0) |
236 | + " (kbytes/s)"); | |
237 | // FIXME uploadDuration displayed is 0! | |
238 | try { | |
239 | 0 | this.uploadPolicy.afterUpload(this.getUploadException(), this.fileUploadThread.getResponseMsg()); |
240 | 0 | } catch (JUploadException e1) { |
241 | 0 | this.uploadPolicy.displayErr("error in uploadPolicy.afterUpload (JUploadPanel)", e1); |
242 | 0 | } |
243 | } | |
244 | ||
245 | // The job is finished. Let's stop the timer, and have a last | |
246 | // refresh of the bars. | |
247 | 0 | this.progressBarManager.uploadIsFinished(); |
248 | ||
249 | // We wait for 5 seconds, before clearing the progress bar. | |
250 | try { | |
251 | 0 | sleep(5000); |
252 | 0 | } catch (InterruptedException e) { |
253 | // Nothing to do | |
254 | 0 | } |
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 | 0 | if (this == this.uploadPanel.getFileUploadManagerThread()) { |
261 | 0 | this.progressBarManager.clearBarContent(); |
262 | 0 | this.uploadPolicy.getContext().getUploadPanel().getStatusLabel().setText(""); |
263 | } | |
264 | ||
265 | 0 | this.uploadPolicy.displayDebug("End of the FileUploadManagerThreadImpl", 5); |
266 | 0 | } catch (Exception e) { |
267 | // We need a JUploadException. | |
268 | 0 | JUploadException jue = (e instanceof JUploadException) ? (JUploadException) e : new JUploadException(e); |
269 | 0 | setUploadException(jue); |
270 | ||
271 | // And go back into a 'normal' way. | |
272 | 0 | stopUpload(); |
273 | 0 | } finally { |
274 | // We restore the button state, just to be sure. | |
275 | 0 | this.uploadPanel.updateButtonState(); |
276 | 0 | } |
277 | ||
278 | // And we die of our beautiful death ... until next upload. | |
279 | 0 | }// 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 | 0 | 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 | 0 | if (this.uploadException != null) { |
294 | 0 | this.uploadPolicy |
295 | 0 | .displayWarn("An exception has already been set in FileUploadManagerThreadImpl. The next one is just logged."); |
296 | } else { | |
297 | 0 | this.uploadException = uploadExceptionParam; |
298 | } | |
299 | ||
300 | 0 | String exceptionMsg = (uploadExceptionParam.getCause() == null) ? uploadExceptionParam.getMessage() |
301 | 0 | : uploadExceptionParam.getCause().getMessage(); |
302 | 0 | String errMsg = this.uploadPolicy.getLocalizedString("errDuringUpload") + "\n\n" + exceptionMsg; |
303 | 0 | this.uploadPolicy.displayErr(errMsg, uploadException); |
304 | } | |
305 | 0 | } |
306 | ||
307 | /** | |
308 | * @see wjhk.jupload2.upload.FileUploadManagerThread#getUploadException() | |
309 | */ | |
310 | public JUploadException getUploadException() { | |
311 | 0 | 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 | 0 | return this.uploadFinished || this.stop || this.uploadException != null; |
322 | } | |
323 | ||
324 | /** | |
325 | * @see FileUploadManagerThread#isUploadStopped() | |
326 | */ | |
327 | public boolean isUploadStopped() { | |
328 | 0 | 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 | 0 | this.progressBarManager.nbBytesUploaded(nbBytes, uploadFileData); |
336 | 0 | } |
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 | 0 | this.progressBarManager.setUploadStatus(uploadFilePacket, uploadFileData, uploadStatus); |
345 | 0 | } |
346 | ||
347 | /** | |
348 | * @see wjhk.jupload2.upload.FileUploadManagerThread#stopUpload() | |
349 | */ | |
350 | public synchronized void stopUpload() { | |
351 | 0 | this.stop = true; |
352 | ||
353 | // The upload is now finished ... | |
354 | 0 | this.uploadFinished = true; |
355 | ||
356 | // We notify the various threads. | |
357 | 0 | if (this.filePreparationThread != null && this.filePreparationThread.isAlive()) { |
358 | 0 | this.filePreparationThread.interrupt(); |
359 | } | |
360 | 0 | if (this.packetConstructionThread != null && this.packetConstructionThread.isAlive()) { |
361 | 0 | this.packetConstructionThread.interrupt(); |
362 | } | |
363 | 0 | if (this.fileUploadThread != null && this.fileUploadThread.isAlive()) { |
364 | 0 | 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 | 0 | this.interrupt(); |
371 | 0 | } |
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 | 0 | this.progressBarManager.anotherFileHasBeenSent(uploadFilePacket, newlyUploadedFileData); |
384 | 0 | } |
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 | 0 | 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 | 0 | for (FileData fileData : uploadFilePacket) { |
397 | 0 | this.filePanel.remove(fileData); |
398 | 0 | this.nbSuccessfullyUploadedFiles += 1; |
399 | 0 | } |
400 | ||
401 | 0 | this.filePanel.cleanHierarchy(); |
402 | ||
403 | // If all files have been sent, the upload is finished. | |
404 | 0 | if (!this.uploadFinished) { |
405 | 0 | this.uploadFinished = (this.nbSuccessfullyUploadedFiles == this.filePreparationThread.getNbFilesToSend()); |
406 | } | |
407 | 0 | } |
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 | 0 | if (fileUploadThreadParam != null) { |
421 | // The FileUploadThread has already been created. | |
422 | // We set the FileUploadThreadManager. | |
423 | 0 | this.fileUploadThread = fileUploadThreadParam; |
424 | 0 | fileUploadThreadParam.setFileUploadThreadManager(this); |
425 | } else { | |
426 | try { | |
427 | 0 | if (this.uploadPolicy.getPostURL().substring(0, 4).equals("ftp:")) { |
428 | 0 | this.fileUploadThread = new FileUploadThreadFTP(this.uploadPolicy, packetQueue, this); |
429 | } else { | |
430 | 0 | this.fileUploadThread = new FileUploadThreadHTTP(this.uploadPolicy, packetQueue, this); |
431 | } | |
432 | 0 | } catch (JUploadException e1) { |
433 | // Too bad ! | |
434 | 0 | this.uploadPolicy.displayErr(e1); |
435 | 0 | } |
436 | } | |
437 | 0 | } |
438 | } |