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 }