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 }