View Javadoc
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 }