View Javadoc
1   //
2   // $Id$
3   //
4   // jupload - A file upload applet.
5   //
6   // Copyright 2010 The JUpload Team
7   //
8   // Created: 10 fevr. 2010
9   // Creator: etienne_sf
10  // Last modified: $Date$
11  //
12  // This program is free software; you can redistribute it and/or modify
13  // it under the terms of the GNU General Public License as published by
14  // the Free Software Foundation; either version 2 of the License, or
15  // (at your option) any later version.
16  //
17  // This program is distributed in the hope that it will be useful,
18  // but WITHOUT ANY WARRANTY; without even the implied warranty of
19  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  // GNU General Public License for more details.
21  //
22  // You should have received a copy of the GNU General Public License
23  // along with this program; if not, write to the Free Software
24  // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25  
26  package wjhk.jupload2.upload.helper;
27  
28  import java.awt.event.ActionEvent;
29  import java.awt.event.ActionListener;
30  
31  import javax.swing.JProgressBar;
32  import javax.swing.Timer;
33  
34  import wjhk.jupload2.exception.JUploadException;
35  import wjhk.jupload2.gui.JUploadPanel;
36  import wjhk.jupload2.gui.filepanel.SizeRenderer;
37  import wjhk.jupload2.policies.UploadPolicy;
38  import wjhk.jupload2.upload.FilePreparationThread;
39  import wjhk.jupload2.upload.FileUploadManagerThread;
40  import wjhk.jupload2.upload.UploadFileData;
41  import wjhk.jupload2.upload.UploadFilePacket;
42  
43  /**
44   * @author etienne_sf
45   */
46  public class ProgressBarManager implements ActionListener {
47      /**
48       * The delay between to updates of the progress bar, in ms.
49       */
50      public final static int DELAY_FOR_UPDATE_OF_PROGRESS_BAR = 100;
51  
52      /**
53       * Contains the date/time (as a long) of the start of the current upload. This allows to sum the time of the actual
54       * upload, and ignore the time the applet is waiting for the server's response. Once the request is finished, and
55       * the applet waits for the server's response, the duration of the sending to the server is added to
56       * currentRequestStartTime, and currentRequestStartTime is reseted to 0. It's then ready for the next upload
57       * request.
58       */
59      long currentRequestStartTime = 0;
60  
61      /**
62       * The file that is currently being uploaded. Allow to refresh the progress bar, with up to date information, based
63       * on a timer event.
64       */
65      UploadFileData currentUploadFileData = null;
66  
67      /**
68       * The files which is currently being sent to the server.
69       */
70      UploadFilePacket currentUploadFilePacket = null;
71  
72      /**
73       * The file preparatoin thread prepares each file for upload, and manage possible errors that can occurs at
74       * preparation time.
75       */
76      FilePreparationThread filePreparationThread = null;
77  
78      /**
79       * Contains the system time of the start of the global upload. This is used to calculate the ETA, and display it to
80       * the user, on the status bar.
81       */
82      long globalStartTime = 0;
83  
84      /**
85       * Indicated the number of bytes that have currently been sent for the current file. This allows the management of
86       * the progress bar.
87       */
88      long nbBytesUploadedForCurrentFile = 0;
89  
90      /**
91       * Number of files that have already been sent. The control on the upload success may be done or not. It's used to
92       * properly display the progress bar.
93       */
94      int nbSentFiles = 0;
95  
96      /** Current number of bytes that have been uploaded. */
97      long nbUploadedBytes = 0;
98  
99      /**
100      * The {@link JUploadPanel} progress bar, to follow the file preparation progress.
101      */
102     JProgressBar preparationProgressBar = null;
103 
104     /**
105      * The timer which schedules the update for the progress and status bar.
106      */
107     Timer timer;
108 
109     /**
110      * Contains the sum of the upload duration for all requests, in milliseconds. For instance, if sending in 10 chunks
111      * one big file, the uploadDuration contains the sum of the sending of these 10 request to the server. This allows
112      * to calculate the true upload speed, and ignore the time we'll wait for the server's response.
113      */
114     long totalUploadDuration = 0;
115 
116     /** The current upload policy */
117     UploadPolicy uploadPolicy;
118 
119     /**
120      * The {@link JUploadPanel} progress bar, to follow the upload of the prepared files to the server.
121      */
122     JProgressBar uploadProgressBar = null;
123 
124     /**
125      * Indicates what is the current file being uploaded, and its upload status.
126      */
127     int uploadStatus = FileUploadManagerThread.UPLOAD_STATUS_NOT_STARTED;
128 
129     /**
130      * @param uploadPolicy
131      * @param filePreparationThread
132      */
133     public ProgressBarManager(UploadPolicy uploadPolicy, FilePreparationThread filePreparationThread) {
134         this.uploadPolicy = uploadPolicy;
135         this.filePreparationThread = filePreparationThread;
136         // our timer is a daemon.
137         this.timer = new Timer(DELAY_FOR_UPDATE_OF_PROGRESS_BAR, this);
138 
139         JUploadPanel uploadPanel = uploadPolicy.getContext().getUploadPanel();
140 
141         this.uploadProgressBar = uploadPanel.getUploadProgressBar();
142         this.preparationProgressBar = uploadPanel.getPreparationProgressBar();
143 
144         updateUploadProgressBarText(null);
145     }
146 
147     /**
148      * The only event managed here is the timer event. We update the progress and status bar.
149      * 
150      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
151      */
152     public void actionPerformed(ActionEvent arg0) {
153         updateUploadProgressBarValue();
154         updateUploadStatusBar();
155 
156     }
157 
158     /**
159      * Called when a new file is uploaded. This method update the bars accordingly to this new status, by calling the
160      * updateUploadProgressBarText() method.
161      * 
162      * @param uploadFilePacket
163      * @param uploadFileData
164      * @throws JUploadException
165      * @see wjhk.jupload2.upload.FileUploadManagerThread#anotherFileHasBeenSent(wjhk.jupload2.upload.UploadFilePacket,
166      *      wjhk.jupload2.upload.UploadFileData)
167      */
168     public synchronized void anotherFileHasBeenSent(UploadFilePacket uploadFilePacket, UploadFileData uploadFileData)
169             throws JUploadException {
170         if (uploadFilePacket != this.currentUploadFilePacket) {
171             throw new java.lang.AssertionError("Wrong file packet in " + this.getClass().getName()
172                     + ".anotherFileHasBeenSent()");
173         }
174         if (uploadFileData != this.currentUploadFileData) {
175             throw new java.lang.AssertionError("Wrong file packet in " + this.getClass().getName()
176                     + ".anotherFileHasBeenSent()");
177         }
178         this.nbSentFiles += 1;
179         this.nbBytesUploadedForCurrentFile = 0;
180         this.uploadPolicy.displayDebug(this.getClass().getName()
181                 + ".anotherFileHasBeenSent(): before call to newlyUploadedFileData.getUploadLength()", 100);
182 
183         // We are finished with this one. Let's display it.
184         this.uploadStatus = FileUploadManagerThread.UPLOAD_STATUS_UPLOADED;
185         updateUploadProgressBarText(uploadFilePacket);
186     }
187 
188     /**
189      * Clean all bar content.
190      */
191     public void clearBarContent() {
192         // Let's stop the update process.
193         this.timer.stop();
194 
195         this.preparationProgressBar.setValue(0);
196         this.preparationProgressBar.setString("");
197         this.uploadProgressBar.setValue(0);
198         this.uploadProgressBar.setString("");
199 
200     }
201 
202     /**
203      * @return the globalStartTime
204      */
205     public long getGlobalStartTime() {
206         return this.globalStartTime;
207     }
208 
209     /**
210      * @return the nbUploadedBytes
211      */
212     public long getNbUploadedBytes() {
213         return this.nbUploadedBytes;
214     }
215 
216     /**
217      * @return the uploadDuration
218      */
219     public long getUploadDuration() {
220         long currentRequestDuration = 0;
221         if (this.currentRequestStartTime != 0) {
222             // A request is running on
223             currentRequestDuration = System.currentTimeMillis() - this.currentRequestStartTime;
224         }
225 
226         return this.totalUploadDuration + currentRequestDuration;
227     }
228 
229     /**
230      * Initialize the maximum value for the two progress bar: 100*the number of files to upload.
231      * 
232      * @throws JUploadException
233      * @see #updateUploadProgressBar(UploadFilePacket, UploadFileData)
234      */
235     private void initProgressBar() throws JUploadException {
236         // To follow the state of file preparation
237         this.preparationProgressBar.setMaximum(100 * this.filePreparationThread.getNbFilesToSend());
238         this.preparationProgressBar.setString("");
239 
240         // To follow the state of the actual upload.
241         this.uploadProgressBar.setMaximum(100 * filePreparationThread.getNbFilesToSend());
242         this.uploadProgressBar.setString("");
243     }
244 
245     /**
246      * The progressBar is updated each 50ms and each 10% of the target file.
247      * 
248      * @param nbBytes
249      * @param uploadFileData
250      * @throws JUploadException
251      * @see wjhk.jupload2.upload.FileUploadManagerThread#nbBytesUploaded(long, UploadFileData)
252      */
253     public synchronized void nbBytesUploaded(long nbBytes, UploadFileData uploadFileData) throws JUploadException {
254         this.nbUploadedBytes += nbBytes;
255         this.nbBytesUploadedForCurrentFile += nbBytes;
256     }
257 
258     /**
259      * Set an error text, that will be displayed on the progress bar
260      * 
261      * @param errorTexte
262      */
263     public void setErrorMessage(String errorTexte) {
264         this.preparationProgressBar.setString(errorTexte);
265     }
266 
267     /**
268      * @param uploadFilePacket
269      * @param uploadFileData
270      * @param uploadStatus
271      * @throws JUploadException
272      */
273     public synchronized void setUploadStatus(UploadFilePacket uploadFilePacket, UploadFileData uploadFileData,
274             int uploadStatus) throws JUploadException {
275         // Let's store the file we're working on.
276         this.currentUploadFileData = uploadFileData;
277         this.currentUploadFilePacket = uploadFilePacket;
278 
279         switch (uploadStatus) {
280             case FileUploadManagerThread.UPLOAD_STATUS_CHUNK_UPLOADED_WAITING_FOR_RESPONSE:
281             case FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE:
282                 // We're waiting for the server: let's add it to the sending
283                 // duration.
284                 this.totalUploadDuration += System.currentTimeMillis() - this.currentRequestStartTime;
285                 this.currentRequestStartTime = 0;
286                 break;
287             case FileUploadManagerThread.UPLOAD_STATUS_UPLOADING:
288                 // We mark the start of the request, if it was not already done.
289                 if (this.currentRequestStartTime == 0) {
290                     this.currentRequestStartTime = System.currentTimeMillis();
291                 }
292                 break;
293             case FileUploadManagerThread.UPLOAD_STATUS_UPLOADED:
294                 // Indicated that the current request is finished. Nothing to do
295                 break;
296             default:
297                 this.uploadPolicy.displayWarn("Unknown value for uploadStatus: " + uploadStatus);
298         }
299         this.uploadStatus = uploadStatus;
300 
301         this.updateUploadProgressBarText(uploadFilePacket);
302     }
303 
304     /**
305      * Update the progress bar, based on the following data: <DIR> <LI>nbSentFiles: number of files that have already
306      * been updated. <LI>nbBytesUploadedForCurrentFile: allows calculation of the upload progress for the current file,
307      * based on it total upload length. </DIR> <BR>
308      * Note 1: The progress bar update is ignored, if last update was less than 100ms before.<BR>
309      * Note 2: This method calls the {@link #updateUploadProgressBarValue(UploadFileData)} method, to also update its
310      * value.
311      * 
312      * @throws JUploadException
313      */
314     private void updateUploadProgressBarText(UploadFilePacket uploadFilePacket) {
315         /*
316          * final String msgInfoUploaded = this.uploadPolicy .getLocalizedString("infoUploaded"); final String
317          * msgInfoUploading = this.uploadPolicy .getLocalizedString("infoUploading"); final String msgNbUploadedFiles =
318          * this.uploadPolicy .getLocalizedString("nbUploadedFiles");
319          */
320         updateUploadProgressBarValue();
321 
322         String msg = null;
323         switch (this.uploadStatus) {
324             case FileUploadManagerThread.UPLOAD_STATUS_NOT_STARTED:
325                 msg = "";
326                 break;
327             case FileUploadManagerThread.UPLOAD_STATUS_UPLOADING:
328             case FileUploadManagerThread.UPLOAD_STATUS_CHUNK_UPLOADED_WAITING_FOR_RESPONSE:
329                 // Uploading files %1$s
330                 msg = this.uploadPolicy.getLocalizedString("infoUploading", (this.nbSentFiles + 1));
331                 break;
332             case FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE:
333                 // %1$s file(s) uploaded. Waiting for server response ...
334 
335                 // nbSentFiles it number of files whose data is already sent to the server. This include the
336                 // currentUploadFileData (which should be the last file in the packet)
337                 int firstFileInPacket = this.nbSentFiles - uploadFilePacket.size() + 1;
338                 int currentFile = this.nbSentFiles;
339 
340                 if (this.currentUploadFilePacket.size() == 1) {
341                     msg = currentFile + "/" + this.filePreparationThread.getNbFilesToSend();
342                 } else {
343                     msg = firstFileInPacket + "-" + currentFile + "/" + this.filePreparationThread.getNbFilesToSend();
344                 }
345                 msg = this.uploadPolicy.getLocalizedString("infoUploaded", msg);
346 
347                 break;
348             case FileUploadManagerThread.UPLOAD_STATUS_UPLOADED:
349                 // %1$d file(s) uploaded
350                 msg = this.uploadPolicy.getLocalizedString("nbUploadedFiles", (this.nbSentFiles));
351                 break;
352             default:
353                 // Hum, that's strange !
354                 this.uploadPolicy
355                         .displayWarn("Unknown upload status in FileUploadManagerThreadImpl.updateProgressBar(): "
356                                 + this.uploadStatus);
357         }
358 
359         // Let's show the modifications to the user
360         this.uploadProgressBar.setString(msg);
361         // To be sure that the new text is displayed, we force instantaneous
362         // refresh. This won't slow down the upload, as it's done in separate
363         // thread.
364         this.uploadProgressBar.repaint(0);
365     }
366 
367     /**
368      * Update the progress bar value, that is: the percent of upload of the current file. This is based on
369      * nbBytesUploadedForCurrentFile and the total upload length of the current file.<BR>
370      * Note: The progress bar update is ignored, if last update was less than 100ms before.
371      * 
372      * @throws JUploadException
373      */
374     private void updateUploadProgressBarValue() {
375         /*
376          * final String msgInfoUploaded = this.uploadPolicy .getLocalizedString("infoUploaded"); final String
377          * msgInfoUploading = this.uploadPolicy .getLocalizedString("infoUploading"); final String msgNbUploadedFiles =
378          * this.uploadPolicy .getLocalizedString("nbUploadedFiles");
379          */
380         int percent = 0;
381 
382         // First, we update the bar itself.
383         if (this.nbBytesUploadedForCurrentFile == 0
384                 || this.nbSentFiles == this.filePreparationThread.getNbFilesToSend()) {
385             percent = 0;
386         } else if (this.currentUploadFileData == null) {
387             percent = 0;
388         } else {
389             if (this.currentUploadFileData.isPreparedForUpload()) {
390                 percent = (int) (this.nbBytesUploadedForCurrentFile * 100 / this.currentUploadFileData
391                         .getUploadLength());
392             } else {
393                 // Hum, hum. The file is not prepared for upload yet. So we
394                 // actually didn't send any thing for it.
395                 percent = 0;
396             }
397             // Usually, a percentage if advancement for one file is no more than
398             // 100. Let's check that.
399             if (percent > 100) {
400                 this.uploadPolicy.displayWarn("percent is more than 100 (" + percent
401                         + ") in FileUploadManagerThreadImpl.update.UploadProgressBar");
402                 percent = 100;
403             }
404         }
405 
406         this.uploadProgressBar.setValue(100 * this.nbSentFiles + percent);
407     }
408 
409     /**
410      * Displays the current upload speed on the status bar.
411      */
412     private void updateUploadStatusBar() {
413         // We'll update the status bar, only if it exists and if the upload
414         // actually started.
415         if (null != this.uploadPolicy.getContext().getUploadPanel().getStatusLabel() && this.nbUploadedBytes > 0) {
416             double percent;
417             // uploadCPS: contains the upload speed.
418             double uploadSpeed;
419             // globalCPS: contains the average speed, including the time the
420             // applet is waiting for the server response.
421             double globalCPS;
422             long remaining;
423             String eta;
424 
425             try {
426                 percent = 100.0 * this.nbUploadedBytes / this.filePreparationThread.getTotalFileBytesToSend();
427 
428             } catch (ArithmeticException e1) {
429                 percent = 100;
430             }
431 
432             // Calculation of the 'pure' upload speed.
433             uploadSpeed = ((double) this.nbUploadedBytes) / ((double) getUploadDuration() / 1000);
434             if (uploadSpeed == Double.POSITIVE_INFINITY) {
435                 this.uploadPolicy.displayDebug("uploadSpeed is Infinity, for nbUploadedBytes=" + nbUploadedBytes
436                         + " and actualUploadDuration(ms)=" + getUploadDuration(), 80);
437             }
438 
439             // Calculation of the 'global' upload speed.
440             try {
441                 globalCPS = ((double) this.nbUploadedBytes) / (System.currentTimeMillis() - this.globalStartTime)
442                         * 1000;
443             } catch (ArithmeticException e1) {
444                 globalCPS = this.nbUploadedBytes;
445             }
446 
447             // Calculation of the ETA. It's based on the global upload speed.
448             try {
449                 remaining = (long) ((this.filePreparationThread.getTotalFileBytesToSend() - this.nbUploadedBytes) / globalCPS);
450                 if (remaining > 3600) {
451                     eta = this.uploadPolicy.getLocalizedString("timefmt_hms", Long.valueOf(remaining / 3600),
452                             Long.valueOf((remaining / 60) % 60), Long.valueOf(remaining % 60));
453                 } else if (remaining > 60) {
454                     eta = this.uploadPolicy.getLocalizedString("timefmt_ms", Long.valueOf(remaining / 60),
455                             Long.valueOf(remaining % 60));
456                 } else
457                     eta = this.uploadPolicy.getLocalizedString("timefmt_s", Long.valueOf(remaining));
458             } catch (ArithmeticException e1) {
459                 eta = this.uploadPolicy.getLocalizedString("timefmt_unknown");
460             }
461             String status = this.uploadPolicy.getLocalizedString("status_msg", Integer.valueOf((int) percent),
462                     SizeRenderer.formatFileUploadSpeed(uploadSpeed, this.uploadPolicy), eta);
463             this.uploadPolicy.getContext().getUploadPanel().getStatusLabel().setText(status);
464             // this.uploadPanel.getStatusLabel().repaint();
465             this.uploadPolicy.getContext().showStatus(status);
466             // this.uploadPolicy.displayDebug("[updateUploadStatusBar] " +
467             // status, 101);
468         }
469     }
470 
471     /**
472      * This just stops the timer. A 'last' update is done.
473      */
474     public void uploadIsFinished() {
475         // Let's stop the update process.
476         this.timer.stop();
477 
478         updateUploadProgressBarText(null);
479         updateUploadStatusBar();
480     }
481 
482     /**
483      * @throws JUploadException
484      */
485     public void uploadIsStarted() throws JUploadException {
486         // Ok, the upload just starts. We keep the date, to later calculate the
487         // ETA.
488         this.globalStartTime = System.currentTimeMillis();
489         initProgressBar();
490 
491         // Let's start the update process.
492         this.timer.start();
493     }
494 
495 }