1 //
2 // $Id: UploadFileData.java 1721 2015-03-29 17:48:44Z etienne_sf $
3 //
4 // jupload - A file upload applet.
5 // Copyright 2007 The JUpload Team
6 //
7 // Created: 2006-11-20
8 // Creator: etienne_sf
9 // Last modified: $Date: 2015-03-29 19:48:44 +0200 (dim., 29 mars 2015) $
10 //
11 // This program is free software; you can redistribute it and/or modify it under
12 // the terms of the GNU General Public License as published by the Free Software
13 // Foundation; either version 2 of the License, or (at your option) any later
14 // version. This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 // details. You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation, Inc.,
19 // 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 package wjhk.jupload2.upload;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.util.Date;
28
29 import wjhk.jupload2.exception.JUploadException;
30 import wjhk.jupload2.exception.JUploadIOException;
31 import wjhk.jupload2.exception.JUploadInterrupted;
32 import wjhk.jupload2.filedata.FileData;
33 import wjhk.jupload2.gui.filepanel.treeview.TreeFileDataNode;
34 import wjhk.jupload2.policies.UploadPolicy;
35 import wjhk.jupload2.upload.helper.ByteArrayEncoder;
36
37 /**
38 * This class implements the FileData interface, and is responsible to do the actual upload of the files.
39 *
40 * @author etienne_sf
41 */
42 public class UploadFileData implements FileData {
43
44 /**
45 * The {@link FileData} instance that contains all information about the file to upload.
46 */
47 private FileData fileData = null;
48
49 /**
50 * The value of the fileData InputStream. It's main use is to allow chunk upload, to reuse the previous InputStream,
51 * that is: each chunk will start reading the stream where the previous one stopped.
52 */
53 InputStream uploadInputStream = null;
54
55 /**
56 * Indicates the position of the file in the current upload (from 0 to max-1). It is mainly used by the
57 * ProgressBarManager.updateUploadProgressBarText() method, to display the upload status to the user.
58 */
59 int numOfFileInCurrentUpload = -1;
60
61 // FIXME numOfFileInCurrentUpload should be from 1 to max
62
63 /**
64 * Instance of the fileUploadManagerThread. This allow this class to send feedback to the thread.
65 *
66 * @see FileUploadManagerThread#nbBytesUploaded(long)
67 */
68 private FileUploadManagerThread fileUploadManagerThread = null;
69
70 /**
71 * The number of bytes to upload, for this file (without the head and tail defined for the HTTP multipart body).
72 */
73 private long uploadRemainingLength = -1;
74
75 /**
76 * The current {@link UploadPolicy}
77 */
78 private UploadPolicy uploadPolicy = null;
79
80 private final static int BUFLEN = 4096;
81
82 /**
83 * This field is no more static, as we could decide to upload two files simultaneously.
84 */
85 private final byte readBuffer[] = new byte[BUFLEN];
86
87 // /////////////////////////////////////////////////////////////////////////////////////////////////////
88 // //////////////////////////////////// CONSTRUCTOR
89 // ///////////////////////////////////////////
90 // /////////////////////////////////////////////////////////////////////////////////////////////////////
91
92 /**
93 * Standard constructor for the UploadFileData class.
94 *
95 * @param fileDataParam The file data the this instance must transmist.
96 * @param numOfFileInCurrentUpload
97 * @param fileUploadManagerThreadParam The current instance of {@link FileUploadThread}
98 * @param uploadPolicyParam The current upload policy, instance of {@link UploadPolicy}
99 */
100 public UploadFileData(FileData fileDataParam, int numOfFileInCurrentUpload,
101 FileUploadManagerThread fileUploadManagerThreadParam, UploadPolicy uploadPolicyParam) {
102 if (fileDataParam == null && !(this instanceof UploadFileDataPoisonned)) {
103 throw new NullPointerException(
104 "fileData is null in UploadFileData(FileData, FileUploadManagerThread, UploadPolicy) constructor");
105 }
106 this.fileData = fileDataParam;
107 this.numOfFileInCurrentUpload = numOfFileInCurrentUpload;
108 this.fileUploadManagerThread = fileUploadManagerThreadParam;
109 this.uploadPolicy = uploadPolicyParam;
110 }
111
112 /**
113 * This particular constructor is posted by the {@link FilePreparationThread} in the preparedFileQueue to indicate
114 * that the last file has been prepared.
115 *
116 * @param poisonned This parameter is here to avoid this constructor to be the default constructor. Its value must
117 * be 'true'.
118 */
119 public UploadFileData(boolean poisonned) {
120 if (!poisonned) {
121 throw new IllegalArgumentException("poisonned must be true in UploadFileData(boolean) constructor");
122 }
123 }
124
125 /**
126 * Get the number of files that are still to upload. It is initialized at the creation of the file, by a call to the
127 * {@link FileData#getUploadLength()}. <BR>
128 * <B>Note:</B> When the upload for this file is finish and you want to send it again (for instance the upload
129 * failed, and you want to do a retry), you should not reuse this instance, but, instead, create a new
130 * UploadFileData instance.
131 *
132 * @return Number of bytes still to upload.
133 * @see #getInputStream()
134 */
135 long getRemainingLength() {
136 return this.uploadRemainingLength;
137 }
138
139 /**
140 * This methods writes the file data (see {@link FileData#getInputStream()} to the given outputStream (the output
141 * toward the HTTP server).
142 *
143 * @param outputStream The stream on which the data is to be written.
144 * @param amount The number of bytes to write.
145 * @throws JUploadException if an I/O error occurs.
146 * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
147 */
148 void uploadFile(OutputStream outputStream, long amount) throws JUploadException, JUploadInterrupted {
149 if (this.uploadPolicy.getDebugLevel() >= 30) {
150 this.uploadPolicy.displayDebug("in UploadFileData.uploadFile (amount:" + amount + ", getUploadLength(): "
151 + getUploadLength() + ")", 30);
152 }
153
154 // getInputStream will put a new fileInput in the inputStream attribute,
155 // or leave it unchanged if it is not null.
156 InputStream inputStream = getInputStream();
157
158 while (amount > 0 && !this.fileUploadManagerThread.isUploadFinished()) {
159 // Are we interrupted ?
160 if (Thread.interrupted()) {
161 throw new JUploadInterrupted(getClass().getName() + ".uploadFile [" + this.getFileName() + "]",
162 this.uploadPolicy);
163 }
164
165 int toread = (amount > BUFLEN) ? BUFLEN : (int) amount;
166 int towrite = 0;
167
168 try {
169 towrite = inputStream.read(this.readBuffer, 0, toread);
170 } catch (IOException e) {
171 throw new JUploadIOException(e);
172 }
173 if (towrite > 0) {
174 try {
175 outputStream.write(this.readBuffer, 0, towrite);
176 this.fileUploadManagerThread.nbBytesUploaded(towrite, this);
177 amount -= towrite;
178 this.uploadRemainingLength -= towrite;
179
180 // For debug reason, I may need to simulate upload, that are
181 // on a real network. We then slow down the upload. This can
182 // occurs only when given a 'high' debugLevel (higher than
183 // what can be set with the applet GUI.
184 if (this.uploadPolicy.getDebugLevel() > 100) {
185 try {
186 Thread.sleep(20);
187 } catch (InterruptedException e) {
188 // Nothing to do. We'll just take a look at the loop
189 // condition.
190 }
191 }
192 } catch (IOException ioe) {
193 throw new JUploadIOException(this.getClass().getName() + ".uploadFile()", ioe);
194 } catch (Exception e) {
195 // When the user may not override an existing file, I got a
196 // NullPointerException. Let's trap all errors here.
197 throw new JUploadException(this.getClass().getName()
198 + ".uploadFile() (check the user permission on the server)", e);
199 }
200 }
201 }// while
202 }
203
204 /**
205 * Clean any local resources, then transmit to the encapsulated {@link FileData#afterUpload()}.
206 *
207 * @see FileData#afterUpload()
208 */
209 public void afterUpload() {
210 if (this.uploadInputStream != null) {
211 try {
212 this.uploadInputStream.close();
213 } catch (IOException ioe) {
214 // Let's ignore it.
215 }
216 this.uploadInputStream = null;
217 }
218 // Transmission to the 'real' FileData
219 this.fileData.afterUpload();
220 }
221
222 /**
223 * Clean any local resources, to allow retrying to upload this file. The file remains prepared..
224 */
225 public void beforeRetry() {
226 // Reset of our upload counter.
227 this.uploadRemainingLength = this.fileData.getUploadLength();
228
229 if (this.uploadInputStream != null) {
230 try {
231 this.uploadInputStream.close();
232 } catch (IOException ioe) {
233 // Let's ignore it.
234 this.uploadPolicy.displayDebug("[Warning] Ignoring " + ioe.getClass().getName()
235 + " in UploadFileData.beforeRetry() [" + ioe.getMessage() + "]", 30);
236 }
237 this.uploadInputStream = null;
238 }
239 }
240
241 /** {@inheritDoc} */
242 public void appendFileProperties(ByteArrayEncoder bae, int index) throws JUploadIOException {
243 this.fileData.appendFileProperties(bae, index);
244 }
245
246 /** {@inheritDoc} */
247 public void beforeUpload(String uploadFileRoot) throws JUploadException {
248 this.fileData.beforeUpload(uploadFileRoot);
249
250 // Calculation of some internal variables.
251 this.uploadRemainingLength = this.fileData.getUploadLength();
252 }
253
254 /** {@inheritDoc} */
255 public boolean canRead() {
256 return this.fileData.canRead();
257 }
258
259 /** {@inheritDoc} */
260 public String getDirectory() {
261 return this.fileData.getDirectory();
262 }
263
264 /** {@inheritDoc} */
265 public File getFile() {
266 throw new IllegalAccessError("Internal error: getFile is deprecated and should not be called from "
267 + this.getClass().getName());
268 }
269
270 /** {@inheritDoc} */
271 public String getFileExtension() {
272 return this.fileData.getFileExtension();
273 }
274
275 /** {@inheritDoc} */
276 public long getFileLength() {
277 return this.fileData.getFileLength();
278 }
279
280 /** {@inheritDoc} */
281 public String getFileName() {
282 return this.fileData.getFileName();
283 }
284
285 /** {@inheritDoc} */
286 public InputStream getInputStream() throws JUploadException {
287 if (this.uploadInputStream == null) {
288 this.uploadInputStream = this.fileData.getInputStream();
289 }
290 return this.uploadInputStream;
291 }
292
293 /** {@inheritDoc} */
294 public Date getLastModified() {
295 return this.fileData.getLastModified();
296 }
297
298 /** {@inheritDoc} */
299 public String getMD5() throws JUploadException {
300 return this.fileData.getMD5();
301 }
302
303 /** {@inheritDoc} */
304 public String getMimeType() {
305 return this.fileData.getMimeType();
306 }
307
308 /** {@inheritDoc} */
309 public String getRelativeDir() {
310 return this.fileData.getRelativeDir();
311 }
312
313 /** {@inheritDoc} */
314 public String getAbsolutePath() {
315 return this.fileData.getAbsolutePath();
316 }
317
318 /**
319 * Retrieves the file name, that should be used in the server application. Default is to send the original filename.
320 *
321 * @param index The index of this file in the current request to the server.
322 * @return The real file name. Not used in FTP upload.
323 * @throws JUploadException Thrown when an error occurs.
324 * @see UploadPolicy#getUploadFilename(FileData, int)
325 */
326 public String getUploadFilename(int index) throws JUploadException {
327 return this.uploadPolicy.getUploadFilename(this.fileData, index);
328 }
329
330 /**
331 * Retrieves the upload file name, that should be sent to the server. It's the technical name used to retrieve the
332 * file content. Default is File0, File1... This method just calls the
333 * {@link UploadPolicy#getUploadFilename(FileData, int)} method.
334 *
335 * @param index The index of this file in the current request to the server.
336 * @return The technical upload file name. Not used in FTP upload.
337 * @throws JUploadException
338 * @see UploadPolicy#getUploadName(FileData, int)
339 */
340 public String getUploadName(int index) throws JUploadException {
341 return this.uploadPolicy.getUploadName(this.fileData, index);
342 }
343
344 /**
345 * This methods stores locally the upload length. So, on the contrary of the {@link FileData} interface, this method
346 * may be called after {@link #afterUpload()}, at one condition: that it has been called once before
347 * {@link #afterUpload()} is called.
348 *
349 * @see FileData#getUploadLength()
350 */
351 public long getUploadLength() {
352 return this.fileData.getUploadLength();
353 }
354
355 /** {@inheritDoc} */
356 public boolean isPreparedForUpload() {
357 return this.fileData.isPreparedForUpload();
358 }
359
360 /**
361 * @return the poisonned status. Returns always false, as this instance is a true one. false indicates the 'End Of
362 * Queue' marker in the preparedFileQueue, which is not the case here
363 * @see UploadFileDataPoisonned
364 */
365 public boolean isPoisonned() {
366 return false;
367 }
368
369 /**
370 * @return the numOfFileInCurrentUpload
371 */
372 public int getNumOfFileInCurrentUpload() {
373 return numOfFileInCurrentUpload;
374 }
375
376 /** The upload floag is not managed here. This method alread returns true */
377 public boolean getUploadFlag() {
378 // TODO Auto-generated method stub
379 return false;
380 }
381
382 /**
383 * The upload floag is not managed here. This method should not be called.
384 *
385 * @throws IllegalArgumentException when uploadFlag is false.
386 */
387 public void setUploadFlag(boolean uploadFlag) {
388 if (!uploadFlag) {
389 throw new IllegalArgumentException(
390 "This method should not be called. At least, it may not be called with uploadeFlag=false");
391 }
392 }
393
394 public TreeFileDataNode getTreeFileDataNode() {
395 return fileData.getTreeFileDataNode();
396 }
397
398 public void setTreeFileDataNode(TreeFileDataNode node) {
399 throw new IllegalStateException("setTreeFileDataNode may not be called againts a " + this.getClass().getName());
400 }
401 }