View Javadoc
1   package wjhk.jupload2.upload;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.List;
6   import java.util.concurrent.BlockingQueue;
7   
8   import javax.swing.JProgressBar;
9   
10  import wjhk.jupload2.exception.JUploadException;
11  import wjhk.jupload2.filedata.DefaultFileData;
12  import wjhk.jupload2.filedata.FileData;
13  import wjhk.jupload2.gui.JUploadPanel;
14  import wjhk.jupload2.gui.filepanel.FilePanel;
15  import wjhk.jupload2.policies.UploadPolicy;
16  
17  /**
18   * This thread is responsible for preparing all files for upload. It stores each prepared file in the preparedFileQueue,
19   * for further processing.
20   * 
21   * @author etienne_sf
22   */
23  public class FilePreparationThread extends Thread {
24  
25      /**
26       * The array of files to send, from the {@link FilePanel}
27       */
28      List<FileData> fileDataArray = null;
29  
30      /**
31       * Number of files that are prepared for upload. A file is prepared for upload, if the
32       * {@link FileData#beforeUpload()} has been called.
33       */
34      int nbPreparedFiles = 0;
35  
36      /**
37       * The total of files that must be sent. It is initialized by the total number of files in the list, and is
38       * decremented each time an error occurs during a file preparation, and the user wants to go on.
39       */
40      int nbFilesToPrepare = -1;
41  
42      /**
43       * We need to know from the starting point, the actual number of files to upload. This is necessary, so the end of
44       * the upload is always correctly detected.
45       */
46      int nbFilesToSend = 0;
47  
48      /**
49       * Sum of the length for all prepared files. This allow the calculation of the estimatedTotalLength.
50       * 
51       * @see #anotherFileIsPrepared(UploadFileData)
52       */
53      long nbTotalNumberOfPreparedBytes = 0;
54  
55      /**
56       * The {@link JUploadPanel} progress bar, to follow the file preparation progress.
57       */
58      JProgressBar preparationProgressBar = null;
59  
60      /** A shortcut to the upload panel */
61      JUploadPanel uploadPanel = null;
62  
63      /** The current upload policy. */
64      UploadPolicy uploadPolicy = null;
65  
66      /** The thread which globally manages the upload */
67      FileUploadManagerThread fileUploadManagerThread = null;
68  
69      /** The current file list. */
70      FilePanel filePanel = null;
71  
72      /**
73       * The queue where each prepared file will be stored, for further processing
74       */
75      BlockingQueue<UploadFileData> preparedFileQueue = null;
76  
77      /**
78       * @param preparedFileQueue
79       * @param fileUploadManagerThread
80       * @param uploadPolicy
81       */
82      public FilePreparationThread(BlockingQueue<UploadFileData> preparedFileQueue,
83              FileUploadManagerThread fileUploadManagerThread, UploadPolicy uploadPolicy) {
84          // A thread name is very useful, when debugging...
85          super("FilePreparationThread");
86  
87          this.preparedFileQueue = preparedFileQueue;
88          this.fileUploadManagerThread = fileUploadManagerThread;
89          this.uploadPolicy = uploadPolicy;
90          this.uploadPanel = uploadPolicy.getContext().getUploadPanel();
91          this.filePanel = this.uploadPanel.getFilePanel();
92          this.preparationProgressBar = this.uploadPanel.getPreparationProgressBar();
93  
94          // Clean the file list, from all files which are not to upload
95          this.filePanel.removeFileNotToUpload();
96  
97          // Prepare the list of files to upload, cleaned from the file marked as 'no to upload', see just above.
98          // To avoid concurrency issues, we clone the list, before starting the upload.
99          this.fileDataArray = new ArrayList<FileData>(this.filePanel.getFiles().size());
100         for (FileData fd : this.filePanel.getFiles()) {
101             if (fd.getUploadFlag()) {
102                 this.fileDataArray.add(fd);
103             }
104         }
105         nbFilesToSend = this.fileDataArray.size();
106 
107         this.uploadPolicy.displayDebug("Nb files to send: " + nbFilesToSend, 20);
108 
109         this.preparationProgressBar.setMaximum(100 * nbFilesToSend);
110     }
111 
112     /**
113      * The actual command to prepare files.
114      * 
115      * @see java.lang.Thread#run()
116      * @see FileData#beforeUpload()
117      */
118     @Override
119     final public void run() {
120         // We loop through all files, and check before each if we should
121         // stop (for instance if an error occurs)
122 
123         // Let's calculate the common root for all these files
124         File fileRoot = DefaultFileData.getRoot(fileDataArray);
125         String uploadFileRoot = (fileRoot == null) ? "" : fileRoot.getAbsolutePath();
126 
127         if (fileDataArray.size() != nbFilesToSend) {
128             this.uploadPolicy.displayWarn(fileDataArray.size() + " files to prepare, but there are " + nbFilesToSend
129                     + " files to send!");
130         }
131 
132         // numFileInCurrentUpload is the index of the file in the current index.
133         // It should be the array index. But, if a file preparation fails in error, numFileInCurrentUpload will be the
134         // array index minus 1, if 2 files, it will be the array index minus 2...
135         int numFileInCurrentUpload = 0;
136 
137         for (int i = 0; i < fileDataArray.size() && !this.fileUploadManagerThread.isUploadFinished(); i += 1) {
138             // We upload only the files which have the uploadFlag set to true.
139             // All other files have been removed in the constructor for this instance.
140             try {
141                 this.uploadPolicy.displayDebug("============== Start of file preparation ("
142                         + fileDataArray.get(i).getFileName() + ")", 30);
143                 UploadFileData uploadFileData = new UploadFileData(fileDataArray.get(i), numFileInCurrentUpload,
144                         this.fileUploadManagerThread, this.uploadPolicy);
145 
146                 // Let's indicate to the user what's running on.
147                 this.preparationProgressBar.setString(this.uploadPolicy.getLocalizedString("preparingFile",
148                         Integer.valueOf(i + 1), Integer.valueOf(fileDataArray.size())));
149                 // We want an immediate repaint, to be sure that the new text is displayed to the user.
150                 this.preparationProgressBar.repaint(0);
151 
152                 // Then, we work
153 
154                 // Let's check that everything is Ok
155                 // More debug output, to understand where the applet freezes.
156                 this.uploadPolicy.displayDebug(this.getClass().getName()
157                         + ".prepareFiles(): before call to beforeUpload()", 100);
158 
159                 try {
160                     // Let's try to prepare the upload.
161                     uploadFileData.beforeUpload(uploadFileRoot);
162 
163                     // If we arrive, here, it means that beforeUpload() did not throw an exception, that is: the
164                     // file is now prepared.
165                     // Next file will be ... next in the current upload.
166                     numFileInCurrentUpload += 1;
167 
168                     // TODO Whe error during file preparation ask the user.
169 
170                     this.uploadPolicy.displayDebug(
171                             "============== End of file preparation (" + uploadFileData.getFileName() + ")", 30);
172                     try {
173                         anotherFileIsPrepared(uploadFileData);
174                     } catch (InterruptedException e) {
175                         // There was a problem putting the item. Let's try again in the next loop.
176                         i -= 1;
177                     }
178                 } catch (JUploadException e) {
179                     // An error occurs during file preparation. We'll send one file less than expected.
180                     this.nbFilesToPrepare -= 1;
181                     this.uploadPolicy.displayErr(e.getMessage());
182                     throw e;
183                 } catch (Exception e) {
184                     // Ooups !
185                     this.nbFilesToPrepare -= 1;
186                     String msg = "An exception " + e.getClass().getName()
187                             + " occured during file preparation for file " + fileDataArray.get(i).getAbsolutePath()
188                             + ". This file will not be uploaded.";
189                     this.uploadPolicy.displayErr(msg);
190                     throw new JUploadException(msg, e);
191                 }
192 
193                 // The file preparation is finished. Let's update the
194                 // progress
195                 // bar.
196                 this.preparationProgressBar.setValue(this.nbPreparedFiles * 100);
197                 this.preparationProgressBar.repaint();
198             } catch (JUploadException e) {
199                 this.fileUploadManagerThread.setUploadException(e);
200             }
201         }// for
202 
203         // All prepared files are posted on the preparedQueue. Let's send the
204         // 'End of Queue' marker.
205         try {
206             this.preparedFileQueue.put(new UploadFileDataPoisonned());
207         } catch (InterruptedException e) {
208             // We should not be interrupted here. If it happens, it should be
209             // because the upload was stopped. But, then, we may have the
210             // PacketConstructionThread blocked, waiting for this packet. So,
211             // let's log a warning.
212             this.uploadPolicy
213                     .displayWarn("Got interrupted, while posting the poisoned UploadFileData on the preparedQueue!");
214         }
215 
216         // Let's clear the bar, which is no more accurate. We let the value to 100%
217         this.preparationProgressBar.setString("");
218         this.uploadPolicy.displayDebug(this.nbPreparedFiles + " files has been prepared (for " + nbFilesToSend
219                 + " files to send)", 10);
220     }
221 
222     /**
223      * This method is called each time a new file is ready to upload. It calculates if a new packet of files is ready to
224      * upload. It is private, as it may be called only from this class.
225      * 
226      * @throws JUploadException
227      */
228     private void anotherFileIsPrepared(UploadFileData newlyPreparedFileData) throws JUploadException,
229             InterruptedException {
230         this.nbPreparedFiles += 1;
231         this.nbTotalNumberOfPreparedBytes += newlyPreparedFileData.getUploadLength();
232         this.preparedFileQueue.put(newlyPreparedFileData);
233     }
234 
235     /**
236      * Returns the total number of bytes to upload. This takes into account only the prepared file content. It ignores
237      * the possible head and tails of the request (for instance: http headers...). This gives a good idea of the total
238      * amount to send, and allows is suffisiant to properly manage the upload progress bar.<BR>
239      * The total number of bytes can only be calculated when all files are prepared. When this method is called before
240      * this, an estimation is done for non prepared files, based on the average size of the already prepared files.
241      * 
242      * @return The real or estimated total number of bytes to send
243      */
244     public double getTotalFileBytesToSend() {
245         double totalFileBytesToSend;
246 
247         // Let's estimate the total, or calculate it, of all files are prepared
248         if (this.nbPreparedFiles == this.nbFilesToSend) {
249             // All files are prepared: it's no more an estimation !
250             totalFileBytesToSend = this.nbTotalNumberOfPreparedBytes;
251         } else if (this.nbPreparedFiles == 0) {
252             totalFileBytesToSend = 0;
253         } else {
254             // We sum the total number of prepared bytes, and we estimate
255             // the size of the files that are not prepared yet
256             totalFileBytesToSend = this.nbTotalNumberOfPreparedBytes
257                     +
258                     // And we sum it with the average amount per file
259                     // prepared for the others
260                     (this.fileDataArray.size() - this.nbPreparedFiles) * this.nbTotalNumberOfPreparedBytes
261                     / this.nbPreparedFiles;
262         }
263 
264         return totalFileBytesToSend;
265     }
266 
267     /**
268      * @return the nbPreparedFiles
269      */
270     public int getNbPreparedFiles() {
271         return nbPreparedFiles;
272     }
273 
274     /**
275      * @return the nbFilesToSent
276      */
277     public int getNbFilesToSend() {
278         return nbFilesToSend;
279     }
280 
281     /**
282      * @return the nbTotalNumberOfPreparedBytes
283      */
284     public long getNbTotalNumberOfPreparedBytes() {
285         return nbTotalNumberOfPreparedBytes;
286     }
287 }