View Javadoc
1   //
2   // $Id: DefaultFileData.java 267 2007-06-08 13:42:02 +0000 (ven., 08 juin 2007)
3   // felfert $
4   //
5   // jupload - A file upload applet.
6   // Copyright 2007 The JUpload Team
7   //
8   // Created: 2006-04-21
9   // Creator: etienne_sf
10  // Last modified: $Date: 2015-03-29 19:48:44 +0200 (dim., 29 mars 2015) $
11  //
12  // This program is free software; you can redistribute it and/or modify it under
13  // the terms of the GNU General Public License as published by the Free Software
14  // Foundation; either version 2 of the License, or (at your option) any later
15  // version. This program is distributed in the hope that it will be useful, but
16  // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18  // details. You should have received a copy of the GNU General Public License
19  // along with this program; if not, write to the Free Software Foundation, Inc.,
20  // 675 Mass Ave, Cambridge, MA 02139, USA.
21  
22  package wjhk.jupload2.filedata;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.security.MessageDigest;
30  import java.security.NoSuchAlgorithmException;
31  import java.text.SimpleDateFormat;
32  import java.util.Date;
33  import java.util.List;
34  
35  import javax.swing.tree.TreePath;
36  
37  import wjhk.jupload2.exception.JUploadException;
38  import wjhk.jupload2.exception.JUploadExceptionTooBigFile;
39  import wjhk.jupload2.exception.JUploadIOException;
40  import wjhk.jupload2.gui.filepanel.treeview.TreeFileDataNode;
41  import wjhk.jupload2.policies.DefaultUploadPolicy;
42  import wjhk.jupload2.policies.UploadPolicy;
43  import wjhk.jupload2.upload.helper.ByteArrayEncoder;
44  
45  /**
46   * This class contains all data and methods for a file to upload. The current *
47   * {@link wjhk.jupload2.policies.UploadPolicy} contains the necessary parameters * to personalize the way files must be
48   * handled. <BR>
49   * <BR>
50   * This class is the default FileData implementation. It gives the default behavior, and is used by
51   * {@link DefaultUploadPolicy}. It provides standard control on the files chosen for upload.
52   * 
53   * @see FileData
54   * @author etienne_sf
55   */
56  public class DefaultFileData implements FileData {
57  
58      /** The current upload policy. */
59      UploadPolicy uploadPolicy;
60  
61      /**
62       * Indicates whether the file is prepared for upload or not.
63       * 
64       * @see FileData#isPreparedForUpload()
65       */
66      boolean preparedForUpload = false;
67  
68      private final static int BUFLEN = 4096;
69  
70      // ///////////////////////////////////////////////////////////////////////////////////////////////////////
71      // /////////////////////// Protected attributes
72      // /////////////////////////////////////////////////////
73      // ///////////////////////////////////////////////////////////////////////////////////////////////////////
74  
75      /**
76       * Mime type of the file. It will be written in the upload HTTP request.
77       */
78      protected String mimeType = "application/octet-stream";
79  
80      // ///////////////////////////////////////////////////////////////////////////////////////////////////////
81      // /////////////////////// Private attributes
82      // ////////////////////////////////////////////////////////
83      // ///////////////////////////////////////////////////////////////////////////////////////////////////////
84  
85      /** Association with the hierarchical node associated with this file data */
86      TreeFileDataNode treeFileDataNode = null;
87  
88      /**
89       * file is the file about which this FileData contains data.
90       */
91      protected File file;
92  
93      /**
94       * Cached file size
95       */
96      protected long fileSize;
97  
98      /**
99       * Cached file directory
100      */
101     protected String fileDir;
102 
103     /**
104      * cached root of this file. This root is set during the prepareUpload call to set the root for the uploaded files,
105      * when it is known.
106      */
107     protected String fileRoot = null;
108 
109     /**
110      * Cached file modification time.
111      */
112     protected Date fileModified;
113 
114     /**
115      * Indicated whether this file should be uploaded or not. Default value is true.
116      */
117     protected boolean uploadFlag = true;
118 
119     /**
120      * The md5sum for the prepared file. Calculated in the {@link #beforeUpload()}, and cleared in the
121      * {@link #afterUpload()}.
122      */
123     protected String md5sum = null;
124 
125     /**
126      * Indicates whether the applet can read this file or not.
127      */
128     protected Boolean canRead = null;
129 
130     /**
131      * Standard constructor
132      * 
133      * @param file The file whose data this instance will give.
134      * @param absoluteRoot The directory root, to be able to calculate the result of {@link #getRelativeDir()}
135      * @param uploadPolicyParam The current upload policy.
136      */
137     public DefaultFileData(File file, UploadPolicy uploadPolicyParam) {
138         if (file.isDirectory()) {
139             throw new IllegalArgumentException(
140                     "Internal Error: DefaultFileData can't be created from a Directory. It needs a file");
141         }
142         uploadPolicyParam.displayDebug("Creation of the DefaultFileData for " + file.getAbsolutePath(), 10);
143         this.file = file;
144         uploadPolicy = uploadPolicyParam;
145         this.fileSize = this.file.length();
146         this.fileDir = this.file.getAbsoluteFile().getParent();
147         this.fileModified = new Date(this.file.lastModified());
148         this.mimeType = uploadPolicy.getContext().getMimeType(getFileExtension());
149     }
150 
151     /** {@inheritDoc} */
152     public void appendFileProperties(ByteArrayEncoder bae, int index) throws JUploadIOException {
153         bae.appendTextProperty("mimetype", getMimeType(), index);
154         bae.appendTextProperty("pathinfo", getDirectory(), index);
155         bae.appendTextProperty("relpathinfo", getRelativeDir(), index);
156         // To add the file date/time, we first have to format this date.
157         SimpleDateFormat dateformat = new SimpleDateFormat(uploadPolicy.getDateFormat());
158         String uploadFileModificationDate = dateformat.format(getLastModified());
159         bae.appendTextProperty("filemodificationdate", uploadFileModificationDate, index);
160     }
161 
162     /** {@inheritDoc} */
163     public synchronized void beforeUpload(String uploadFileRoot) throws JUploadException {
164         if (this.preparedForUpload) {
165             // Maybe an upload was stopped. Let's log a resume, and resume the
166             // job.
167             uploadPolicy.displayWarn("The file " + getFileName() + " is already prepared for upload");
168         } else {
169             // The file is now prepared for upload.
170             this.preparedForUpload = true;
171 
172             this.fileRoot = uploadFileRoot;
173 
174             // Should we calculate the MD5Sum for this file ?
175             if (uploadPolicy.getSendMD5Sum()) {
176                 calculateMD5Sum();
177             }
178 
179             // Default : we check that the file is smaller than the maximum
180             // upload size.
181             if (getUploadLength() > uploadPolicy.getMaxFileSize()) {
182                 throw new JUploadExceptionTooBigFile(getFileName(), getUploadLength(), uploadPolicy);
183             }
184         }
185     }
186 
187     /** {@inheritDoc} */
188     public long getUploadLength() {
189         if (!this.preparedForUpload) {
190             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
191         }
192         return this.fileSize;
193     }
194 
195     /** {@inheritDoc} */
196     public synchronized void afterUpload() {
197         if (!this.preparedForUpload) {
198             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
199         }
200         // Let's free resources or temporary calculation in DefaultFileData
201         this.md5sum = null;
202 
203         // Then, we change the preparation status.
204         this.preparedForUpload = false;
205     }
206 
207     /** {@inheritDoc} */
208     public synchronized InputStream getInputStream() throws JUploadException {
209         if (!this.preparedForUpload) {
210             throw new IllegalStateException("The file " + getFileName() + " is not prepared for upload");
211         }
212         // Standard FileData : we read the file.
213         try {
214             return new FileInputStream(this.file);
215         } catch (FileNotFoundException e) {
216             throw new JUploadIOException(e);
217         }
218     }
219 
220     /** {@inheritDoc} */
221     public String getFileName() {
222         return this.file.getName();
223     }
224 
225     /** {@inheritDoc} */
226     public String getFileExtension() {
227         return getExtension(getFileName());
228     }
229 
230     /** {@inheritDoc} */
231     public long getFileLength() {
232         return this.fileSize;
233     }
234 
235     /** {@inheritDoc} */
236     public Date getLastModified() {
237         return this.fileModified;
238     }
239 
240     /** {@inheritDoc} */
241     public String getDirectory() {
242         return this.fileDir;
243     }
244 
245     /** {@inheritDoc} */
246     public boolean getUploadFlag() {
247         return this.uploadFlag;
248     }
249 
250     /** {@inheritDoc} */
251     public void setUploadFlag(boolean uploadFlag) {
252         this.uploadFlag = uploadFlag;
253     }
254 
255     /** {@inheritDoc} */
256     public String getMD5() throws JUploadException {
257         if (this.md5sum == null) {
258             throw new JUploadException("The MD5Sum has not been calculated!");
259         }
260         return this.md5sum;
261     }
262 
263     /**
264      * Calculate the MD5Sum for the transformed file, or the original if no transformation should be done on the file,
265      * before upload.
266      * 
267      * @throws JUploadException
268      */
269     public void calculateMD5Sum() throws JUploadException {
270         StringBuffer ret = new StringBuffer();
271         MessageDigest digest = null;
272         byte md5Buffer[] = new byte[BUFLEN];
273         int nbBytes;
274 
275         // Calculation of the MD5 sum. Now done before upload, to prepare the
276         // file head.
277         // This makes the file being parsed two times: once before upload, and
278         // once for the actual upload
279         InputStream md5InputStream = getInputStream();
280         try {
281             digest = MessageDigest.getInstance("MD5");
282             while ((nbBytes = md5InputStream.read(md5Buffer, 0, BUFLEN)) > 0) {
283                 digest.update(md5Buffer, 0, nbBytes);
284             }
285         } catch (IOException e) {
286             throw new JUploadIOException(e);
287         } catch (NoSuchAlgorithmException e) {
288             throw new JUploadException(e);
289         } finally {
290             try {
291                 md5InputStream.close();
292             } catch (IOException e) {
293                 throw new JUploadIOException(e);
294             }
295         }
296 
297         // Now properly format the md5 sum.
298         byte md5sum[] = new byte[32];
299         if (digest != null)
300             md5sum = digest.digest();
301         for (int i = 0; i < md5sum.length; i++) {
302             ret.append(Integer.toHexString((md5sum[i] >> 4) & 0x0f));
303             ret.append(Integer.toHexString(md5sum[i] & 0x0f));
304         }
305 
306         this.md5sum = ret.toString();
307     }
308 
309     /** {@inheritDoc} */
310     public String getMimeType() {
311         return this.mimeType;
312     }
313 
314     /** {@inheritDoc} */
315     public boolean canRead() {
316         // The commented line below doesn't seems to work.
317         // return this.file.canRead();
318 
319         // The canRead status is read once. This is done in this method, so that
320         // it's available for all subclasses. If it were in the constructor, we
321         // would have to initialize {@link #canRead} in all subclasses.
322 
323         // Let's store the status 'readible' only once. It's
324         if (this.canRead == null) {
325             try {
326                 InputStream is = new FileInputStream(this.file);
327                 is.close();
328                 this.canRead = Boolean.valueOf(true);
329             } catch (IOException e) {
330                 // Can't read the file!
331                 this.canRead = Boolean.valueOf(false);
332             }
333         }
334 
335         return this.canRead.booleanValue();
336     }
337 
338     /**
339      * Standard getter, for the file described by the FileData instance.<BR/>
340      * Since JUpload 6.0, this method is no more in the {@link FileData} interface. Direct link between the DefaultFile
341      * and the File should be avoided. This is especially true to access the file path. You should the other method of
342      * this interface, instead of this one. Typically, using this method is incompatible with the
343      * {@link TreeViewUploadPolicy}<BR/>
344      * To check if a file is already in the current upload list, you should now use the {@link #getAbsolutePath()}
345      * method.
346      * 
347      * @return the File instance associated with this row.
348      */
349     protected File getFile() {
350         return this.file;
351     }
352 
353     /** {@inheritDoc} */
354     public String getRelativeDir() {
355         String ret = "";
356         switch (this.uploadPolicy.getFileListViewMode()) {
357             case FLAT:
358             case TREE_VIEW:
359                 if (null == this.fileRoot || this.fileRoot.equals("")) {
360                     // No common root. The relative dir is the whole path for the current DefaultFileData
361                     ret = this.fileDir;
362                 } else if (this.fileDir.startsWith(this.fileRoot)) {
363                     int skip = this.fileRoot.length();
364                     if (this.fileRoot.endsWith(File.separator))
365                         skip++;
366                     if ((skip >= 0) && (skip < this.fileDir.length()))
367                         ret = this.fileDir.substring(skip);
368                 } else {
369                     // Too bad, this should not happen
370                     throw new IllegalStateException("Root (" + this.fileRoot + ") is not part of current path ("
371                             + this.fileDir + ")");
372                 }
373                 break;
374             case INDEPENDENT_TREE_VIEW:
375                 TreePath treePath = treeFileDataNode.getTreePath();
376                 // Get through all parents of this node (but skip this node)
377                 for (int i = 0; i < treePath.getPathCount() - 1; i += 1) {
378                     String nodeName = ((TreeFileDataNode) treePath.getPathComponent(i)).toString();
379                     if (!nodeName.equals("")) {
380                         ret += (ret.equals("") ? "" : "/") + nodeName;
381                     }
382                 }// for
383         }
384 
385         return ret;
386     }
387 
388     /** {@inheritDoc} */
389     public String getAbsolutePath() {
390         return file.getAbsolutePath();
391     }
392 
393     // ////////////////////////////////////////////////////////
394     // UTILITIES
395     // ////////////////////////////////////////////////////////
396     /**
397      * Returns the extension of the given file. To be clear: <I>jpg</I> is the extension for the file named
398      * <I>picture.jpg</I>.
399      * 
400      * @param file the file whose the extension is wanted!
401      * @return The extension, without the point, for the given file.
402      */
403     public static String getExtension(String filename) {
404         return filename.substring(filename.lastIndexOf('.') + 1);
405     }
406 
407     /**
408      * Return the 'biggest' common ancestror of the given file array. For instance, the root for the files /usr/bin/toto
409      * and /usr/titi is /usr.
410      * 
411      * @param rows
412      * @return The common root for the given files.
413      */
414     public static File getRoot(List<? extends FileData> rows) {
415         // Let's find the common root for the dropped files.
416         // If one file has been dropped (the minimum), the path of its parent should be the root.
417         File root = ((DefaultFileData) rows.get(0)).file;
418         if (root.isDirectory()) {
419             root = root.getParentFile();
420         }
421         // Let's find the higher root level.
422         while (root != null && !root.isDirectory()) {
423             // We have a file. Let's take it's folder.
424             root = root.getParentFile();
425         }
426 
427         if (root != null) {
428             // root is the root for the first file. We add all directories, and higher until the root. This will allow
429             // to find the 'bigger' directory, which is the common root for all dropped files. If several files are
430             // being added, we take the common root for them.
431             String pathRoot = root.getAbsolutePath() + File.separator;
432             File pathCurrentFileParent;
433 
434             // We start with the second item in the list, as we already
435             // extracted the first.
436             for (int i = 1; i < rows.size() && root != null; i += 1) {
437                 // We ignore the unflagged files.
438                 if (rows.get(i).getUploadFlag()) {
439                     // Let's check that all files in l are parents of the current file.
440                     pathCurrentFileParent = ((DefaultFileData) rows.get(i)).file;
441                     String pathCurrentFileParentPath = pathCurrentFileParent.getAbsolutePath() + File.separator;
442 
443                     // We loop through the parent of the file, until we find a
444                     // common root.
445                     do {
446                         pathCurrentFileParent = pathCurrentFileParent.getParentFile();
447                         pathCurrentFileParentPath = (pathCurrentFileParent == null) ? "" : pathCurrentFileParent
448                                 .getAbsolutePath() + File.separator;
449                     } while (pathCurrentFileParent != null && !pathRoot.startsWith(pathCurrentFileParentPath));
450 
451                     // Let's store the new found root (which may actually be the
452                     // same as the last one)
453                     root = pathCurrentFileParent;
454                     pathRoot = pathCurrentFileParentPath;
455                 }// if
456             }// for
457 
458             // pathRoot contains the path for the found root.
459             if (pathRoot.equals("")) {
460                 // No common root (e.g.: on windows, adding C:\temp and D:\temp
461                 root = null;
462             } else {
463                 root = new File(pathRoot);
464             }
465         }
466 
467         return root;
468     }
469 
470     /** {@inheritDoc} */
471     public boolean isPreparedForUpload() {
472         return this.preparedForUpload;
473     }
474 
475     /**
476      * @return the treeFileDataNode
477      */
478     public TreeFileDataNode getTreeFileDataNode() {
479         return treeFileDataNode;
480     }
481 
482     /** {@inheritDoc} */
483     public void setTreeFileDataNode(TreeFileDataNode treeFileDataNode) {
484         this.treeFileDataNode = treeFileDataNode;
485     }
486 
487 }