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 }