1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.provider.ftp;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.apache.commons.net.ftp.FTPFile;
22 import org.apache.commons.vfs.FileName;
23 import org.apache.commons.vfs.FileObject;
24 import org.apache.commons.vfs.FileSystemException;
25 import org.apache.commons.vfs.FileType;
26 import org.apache.commons.vfs.RandomAccessContent;
27 import org.apache.commons.vfs.provider.AbstractFileObject;
28 import org.apache.commons.vfs.provider.UriParser;
29 import org.apache.commons.vfs.util.Messages;
30 import org.apache.commons.vfs.util.MonitorInputStream;
31 import org.apache.commons.vfs.util.MonitorOutputStream;
32 import org.apache.commons.vfs.util.RandomAccessMode;
33 import org.apache.commons.vfs.util.FileObjectUtils;
34
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.Calendar;
39 import java.util.Collections;
40 import java.util.Iterator;
41 import java.util.Map;
42 import java.util.TreeMap;
43
44 /***
45 * An FTP file.
46 *
47 * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
48 * @version $Revision: 484943 $ $Date: 2006-12-09 08:42:06 +0100 (Sa, 09 Dez 2006) $
49 */
50 public class FtpFileObject
51 extends AbstractFileObject
52 {
53 private Log log = LogFactory.getLog(FtpFileObject.class);
54
55 private static final Map EMPTY_FTP_FILE_MAP = Collections.unmodifiableMap(new TreeMap());
56
57 private final FtpFileSystem ftpFs;
58 private final String relPath;
59
60
61 private FTPFile fileInfo;
62 private Map children;
63 private FileObject linkDestination;
64
65 private boolean inRefresh=false;
66
67 protected FtpFileObject(final FileName name,
68 final FtpFileSystem fileSystem,
69 final FileName rootName)
70 throws FileSystemException
71 {
72 super(name, fileSystem);
73 ftpFs = fileSystem;
74 String relPath = UriParser.decode(rootName.getRelativeName(name));
75 if (".".equals(relPath))
76 {
77
78
79
80
81 this.relPath = null;
82 }
83 else
84 {
85 this.relPath = relPath;
86 }
87 }
88
89 /***
90 * Called by child file objects, to locate their ftp file info.
91 *
92 * @param name the filename in its native form ie. without uri stuff (%nn)
93 * @param flush recreate children cache
94 */
95 private FTPFile getChildFile(final String name, final boolean flush) throws IOException
96 {
97 if (flush)
98 {
99 children = null;
100 }
101
102
103 doGetChildren();
104
105
106 FTPFile ftpFile = (FTPFile) children.get(name);
107 return ftpFile;
108 }
109
110 /***
111 * Fetches the children of this file, if not already cached.
112 */
113 private void doGetChildren() throws IOException
114 {
115 if (children != null)
116 {
117 return;
118 }
119
120 final FtpClient client = ftpFs.getClient();
121 try
122 {
123 final FTPFile[] tmpChildren = client.listFiles(relPath);
124 if (tmpChildren == null || tmpChildren.length == 0)
125 {
126 children = EMPTY_FTP_FILE_MAP;
127 }
128 else
129 {
130 children = new TreeMap();
131
132
133 for (int i = 0; i < tmpChildren.length; i++)
134 {
135 final FTPFile child = tmpChildren[i];
136 if (child == null)
137 {
138 if (log.isDebugEnabled())
139 {
140 log.debug(Messages.getString("vfs.provider.ftp/invalid-directory-entry.debug",
141 new Object[]
142 {
143 new Integer(i), relPath
144 }));
145 }
146 continue;
147 }
148 if (!".".equals(child.getName())
149 && !"..".equals(child.getName()))
150 {
151 children.put(child.getName(), child);
152 }
153 }
154 }
155 }
156 finally
157 {
158 ftpFs.putClient(client);
159 }
160 }
161
162 /***
163 * Attaches this file object to its file resource.
164 */
165 protected void doAttach()
166 throws IOException
167 {
168
169 getInfo(false);
170 }
171
172 /***
173 * Fetches the info for this file.
174 */
175 private void getInfo(boolean flush) throws IOException
176 {
177 final FtpFileObject parent = (FtpFileObject) FileObjectUtils.getAbstractFileObject(getParent());
178 FTPFile newFileInfo;
179 if (parent != null)
180 {
181 newFileInfo = parent.getChildFile(UriParser.decode(getName().getBaseName()), flush);
182 }
183 else
184 {
185
186 newFileInfo = new FTPFile();
187 newFileInfo.setType(FTPFile.DIRECTORY_TYPE);
188 }
189
190 this.fileInfo = newFileInfo;
191 }
192
193 /***
194 *
195 * @throws FileSystemException
196 */
197 public void refresh() throws FileSystemException
198 {
199 if (!inRefresh)
200 {
201 try
202 {
203 inRefresh = true;
204 super.refresh();
205 try
206 {
207
208 getInfo(true);
209 }
210 catch (IOException e)
211 {
212 throw new FileSystemException(e);
213 }
214 }
215 finally
216 {
217 inRefresh = false;
218 }
219 }
220 }
221
222 /***
223 * Detaches this file object from its file resource.
224 */
225 protected void doDetach()
226 {
227 this.fileInfo = null;
228 children = null;
229 }
230
231 /***
232 * Called when the children of this file change.
233 */
234 protected void onChildrenChanged(FileName child, FileType newType)
235 {
236 if (children != null && newType.equals(FileType.IMAGINARY))
237 {
238 try
239 {
240 children.remove(UriParser.decode(child.getBaseName()));
241 }
242 catch (FileSystemException e)
243 {
244 throw new RuntimeException(e.getMessage());
245 }
246 }
247 else
248 {
249
250
251 children = null;
252 }
253 }
254
255 /***
256 * Called when the type or content of this file changes.
257 */
258 protected void onChange() throws IOException
259 {
260 children = null;
261
262 if (getType().equals(FileType.IMAGINARY))
263 {
264
265 this.fileInfo = null;
266 return;
267 }
268
269 getInfo(true);
270 }
271
272 /***
273 * Determines the type of the file, returns null if the file does not
274 * exist.
275 */
276 protected FileType doGetType()
277 throws Exception
278 {
279 if (this.fileInfo == null)
280 {
281 return FileType.IMAGINARY;
282 }
283 else if (this.fileInfo.isDirectory())
284 {
285 return FileType.FOLDER;
286 }
287 else if (this.fileInfo.isFile())
288 {
289 return FileType.FILE;
290 }
291 else if (this.fileInfo.isSymbolicLink())
292 {
293 return getLinkDestination().getType();
294 }
295
296 throw new FileSystemException("vfs.provider.ftp/get-type.error", getName());
297 }
298
299 private FileObject getLinkDestination() throws FileSystemException
300 {
301 if (linkDestination == null)
302 {
303 final String path = this.fileInfo.getLink();
304 FileName relativeTo = getName().getParent();
305 if (relativeTo == null)
306 {
307 relativeTo = getName();
308 }
309 FileName linkDestinationName = getFileSystem().getFileSystemManager().resolveName(relativeTo, path);
310 linkDestination = getFileSystem().resolveFile(linkDestinationName);
311 }
312
313 return linkDestination;
314 }
315
316 protected FileObject[] doListChildrenResolved() throws Exception
317 {
318 if (this.fileInfo.isSymbolicLink())
319 {
320 return getLinkDestination().getChildren();
321 }
322
323 return null;
324 }
325
326 /***
327 * Lists the children of the file.
328 */
329 protected String[] doListChildren()
330 throws Exception
331 {
332
333 doGetChildren();
334
335
336 final String[] childNames = new String[children.size()];
337 int childNum = -1;
338 Iterator iterChildren = children.values().iterator();
339 while (iterChildren.hasNext())
340 {
341 childNum++;
342 final FTPFile child = (FTPFile) iterChildren.next();
343 childNames[childNum] = child.getName();
344 }
345
346 return UriParser.encode(childNames);
347 }
348
349 /***
350 * Deletes the file.
351 */
352 protected void doDelete() throws Exception
353 {
354 final boolean ok;
355 final FtpClient ftpClient = ftpFs.getClient();
356 try
357 {
358 if (this.fileInfo.isDirectory())
359 {
360 ok = ftpClient.removeDirectory(relPath);
361 }
362 else
363 {
364 ok = ftpClient.deleteFile(relPath);
365 }
366 }
367 finally
368 {
369 ftpFs.putClient(ftpClient);
370 }
371
372 if (!ok)
373 {
374 throw new FileSystemException("vfs.provider.ftp/delete-file.error", getName());
375 }
376 this.fileInfo = null;
377 children = EMPTY_FTP_FILE_MAP;
378 }
379
380 /***
381 * Renames the file
382 */
383 protected void doRename(FileObject newfile) throws Exception
384 {
385 final boolean ok;
386 final FtpClient ftpClient = ftpFs.getClient();
387 try
388 {
389 String oldName = getName().getPath();
390 String newName = newfile.getName().getPath();
391 ok = ftpClient.rename(oldName, newName);
392 }
393 finally
394 {
395 ftpFs.putClient(ftpClient);
396 }
397
398 if (!ok)
399 {
400 throw new FileSystemException("vfs.provider.ftp/rename-file.error", new Object[]{getName().toString(), newfile});
401 }
402 this.fileInfo = null;
403 children = EMPTY_FTP_FILE_MAP;
404 }
405
406 /***
407 * Creates this file as a folder.
408 */
409 protected void doCreateFolder()
410 throws Exception
411 {
412 final boolean ok;
413 final FtpClient client = ftpFs.getClient();
414 try
415 {
416 ok = client.makeDirectory(relPath);
417 }
418 finally
419 {
420 ftpFs.putClient(client);
421 }
422
423 if (!ok)
424 {
425 throw new FileSystemException("vfs.provider.ftp/create-folder.error", getName());
426 }
427 }
428
429 /***
430 * Returns the size of the file content (in bytes).
431 */
432 protected long doGetContentSize() throws Exception
433 {
434 if (this.fileInfo.isSymbolicLink())
435 {
436 return getLinkDestination().getContent().getSize();
437 }
438 else
439 {
440 return this.fileInfo.getSize();
441 }
442 }
443
444 /***
445 * get the last modified time on an ftp file
446 *
447 * @see org.apache.commons.vfs.provider.AbstractFileObject#doGetLastModifiedTime()
448 */
449 protected long doGetLastModifiedTime() throws Exception
450 {
451 if (this.fileInfo.isSymbolicLink())
452 {
453 return getLinkDestination().getContent().getLastModifiedTime();
454 }
455 else
456 {
457 Calendar timestamp = this.fileInfo.getTimestamp();
458 if (timestamp == null)
459 {
460 return 0L;
461 }
462 else
463 {
464 return (timestamp.getTime().getTime());
465 }
466 }
467 }
468
469 /***
470 * Creates an input stream to read the file content from.
471 */
472 protected InputStream doGetInputStream() throws Exception
473 {
474 final FtpClient client = ftpFs.getClient();
475 final InputStream instr = client.retrieveFileStream(relPath);
476 return new FtpInputStream(client, instr);
477 }
478
479 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
480 {
481 return new FtpRandomAccessContent(this, mode);
482 }
483
484 /***
485 * Creates an output stream to write the file content to.
486 */
487 protected OutputStream doGetOutputStream(boolean bAppend)
488 throws Exception
489 {
490 final FtpClient client = ftpFs.getClient();
491 OutputStream out = null;
492 if (bAppend)
493 {
494 out = client.appendFileStream(relPath);
495 }
496 else
497 {
498 out = client.storeFileStream(relPath);
499 }
500
501 if (out == null)
502 {
503 throw new FileSystemException("vfs.provider.ftp/output-error.debug", new Object[]
504 {
505 this.getName(),
506 client.getReplyString()
507 });
508 }
509
510 return new FtpOutputStream(client, out);
511 }
512
513 String getRelPath()
514 {
515 return relPath;
516 }
517
518 FtpInputStream getInputStream(long filePointer) throws IOException
519 {
520 final FtpClient client = ftpFs.getClient();
521 final InputStream instr = client.retrieveFileStream(relPath, filePointer);
522 if (instr == null)
523 {
524 throw new FileSystemException("vfs.provider.ftp/input-error.debug", new Object[]
525 {
526 this.getName(),
527 client.getReplyString()
528 });
529 }
530 return new FtpInputStream(client, instr);
531 }
532
533 /***
534 * An InputStream that monitors for end-of-file.
535 */
536 class FtpInputStream
537 extends MonitorInputStream
538 {
539 private final FtpClient client;
540
541 public FtpInputStream(final FtpClient client, final InputStream in)
542 {
543 super(in);
544 this.client = client;
545 }
546
547 void abort() throws IOException
548 {
549 client.abort();
550 close();
551 }
552
553 /***
554 * Called after the stream has been closed.
555 */
556 protected void onClose() throws IOException
557 {
558 final boolean ok;
559 try
560 {
561 ok = client.completePendingCommand();
562 }
563 finally
564 {
565 ftpFs.putClient(client);
566 }
567
568 if (!ok)
569 {
570 throw new FileSystemException("vfs.provider.ftp/finish-get.error", getName());
571 }
572 }
573 }
574
575 /***
576 * An OutputStream that monitors for end-of-file.
577 */
578 private class FtpOutputStream
579 extends MonitorOutputStream
580 {
581 private final FtpClient client;
582
583 public FtpOutputStream(final FtpClient client, final OutputStream outstr)
584 {
585 super(outstr);
586 this.client = client;
587 }
588
589 /***
590 * Called after this stream is closed.
591 */
592 protected void onClose() throws IOException
593 {
594 final boolean ok;
595 try
596 {
597 ok = client.completePendingCommand();
598 }
599 finally
600 {
601 ftpFs.putClient(client);
602 }
603
604 if (!ok)
605 {
606 throw new FileSystemException("vfs.provider.ftp/finish-put.error", getName());
607 }
608 }
609 }
610 }