001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.vfs.provider.sftp;
018:
019: import com.jcraft.jsch.ChannelSftp;
020: import com.jcraft.jsch.ChannelSftp.LsEntry;
021: import com.jcraft.jsch.SftpATTRS;
022: import com.jcraft.jsch.SftpException;
023: import org.apache.commons.vfs.FileName;
024: import org.apache.commons.vfs.FileObject;
025: import org.apache.commons.vfs.FileSystemException;
026: import org.apache.commons.vfs.FileType;
027: import org.apache.commons.vfs.NameScope;
028: import org.apache.commons.vfs.RandomAccessContent;
029: import org.apache.commons.vfs.VFS;
030: import org.apache.commons.vfs.provider.AbstractFileObject;
031: import org.apache.commons.vfs.provider.UriParser;
032: import org.apache.commons.vfs.util.FileObjectUtils;
033: import org.apache.commons.vfs.util.MonitorInputStream;
034: import org.apache.commons.vfs.util.MonitorOutputStream;
035: import org.apache.commons.vfs.util.RandomAccessMode;
036:
037: import java.io.ByteArrayInputStream;
038: import java.io.ByteArrayOutputStream;
039: import java.io.IOException;
040: import java.io.InputStream;
041: import java.io.OutputStream;
042: import java.util.ArrayList;
043: import java.util.Iterator;
044: import java.util.Vector;
045:
046: /**
047: * An SFTP file.
048: *
049: * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
050: * @version $Revision: 520079 $ $Date: 2005-10-14 19:59:47 +0200 (Fr, 14 Okt
051: * 2005) $
052: */
053: public class SftpFileObject extends AbstractFileObject implements
054: FileObject {
055: private final SftpFileSystem fileSystem;
056: private SftpATTRS attrs;
057: private final String relPath;
058:
059: protected SftpFileObject(final FileName name,
060: final SftpFileSystem fileSystem) throws FileSystemException {
061: super (name, fileSystem);
062: this .fileSystem = fileSystem;
063: relPath = UriParser.decode(fileSystem.getRootName()
064: .getRelativeName(name));
065: }
066:
067: /**
068: * Determines the type of this file, returns null if the file does not
069: * exist.
070: */
071: protected FileType doGetType() throws Exception {
072: if (attrs == null) {
073: statSelf();
074: }
075:
076: if (attrs == null) {
077: return FileType.IMAGINARY;
078: }
079:
080: if ((attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_PERMISSIONS) == 0) {
081: throw new FileSystemException(
082: "vfs.provider.sftp/unknown-permissions.error");
083: }
084: if (attrs.isDir()) {
085: return FileType.FOLDER;
086: } else {
087: return FileType.FILE;
088: }
089: }
090:
091: /**
092: * Called when the type or content of this file changes.
093: */
094: protected void onChange() throws Exception {
095: statSelf();
096: }
097:
098: /**
099: * Fetches file attrs from server.
100: */
101: private void statSelf() throws Exception {
102: ChannelSftp channel = fileSystem.getChannel();
103: try {
104: setStat(channel.stat(relPath));
105: } catch (final SftpException e) {
106: try {
107: // maybe the channel has some problems, so recreate the channel and retry
108: if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
109: channel.disconnect();
110: channel = fileSystem.getChannel();
111: setStat(channel.stat(relPath));
112: } else {
113: // Really does not exist
114: attrs = null;
115: }
116: } catch (final SftpException e2) {
117: // TODO - not strictly true, but jsch 0.1.2 does not give us
118: // enough info in the exception. Should be using:
119: // if ( e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE )
120: // However, sometimes the exception has the correct id, and
121: // sometimes
122: // it does not. Need to look into why.
123:
124: // Does not exist
125: attrs = null;
126: }
127: } finally {
128: fileSystem.putChannel(channel);
129: }
130: }
131:
132: /**
133: * Set attrs from listChildrenResolved
134: */
135: private void setStat(SftpATTRS attrs) {
136: this .attrs = attrs;
137: }
138:
139: /**
140: * Creates this file as a folder.
141: */
142: protected void doCreateFolder() throws Exception {
143: final ChannelSftp channel = fileSystem.getChannel();
144: try {
145: channel.mkdir(relPath);
146: } finally {
147: fileSystem.putChannel(channel);
148: }
149: }
150:
151: protected long doGetLastModifiedTime() throws Exception {
152: if (attrs == null
153: || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_ACMODTIME) == 0) {
154: throw new FileSystemException(
155: "vfs.provider.sftp/unknown-modtime.error");
156: }
157: return attrs.getMTime() * 1000L;
158: }
159:
160: /**
161: * Sets the last modified time of this file. Is only called if
162: * {@link #doGetType} does not return {@link FileType#IMAGINARY}. <p/>
163: *
164: * @param modtime
165: * is modification time in milliseconds. SFTP protocol can send
166: * times with nanosecond precision but at the moment jsch send
167: * them with second precision.
168: */
169: protected void doSetLastModifiedTime(final long modtime)
170: throws Exception {
171: final ChannelSftp channel = fileSystem.getChannel();
172: try {
173: int newMTime = (int) (modtime / 1000L);
174:
175: attrs.setACMODTIME(attrs.getATime(), newMTime);
176: channel.setStat(relPath, attrs);
177: } finally {
178: fileSystem.putChannel(channel);
179: }
180: }
181:
182: /**
183: * Deletes the file.
184: */
185: protected void doDelete() throws Exception {
186: final ChannelSftp channel = fileSystem.getChannel();
187: try {
188: if (getType() == FileType.FILE) {
189: channel.rm(relPath);
190: } else {
191: channel.rmdir(relPath);
192: }
193: } finally {
194: fileSystem.putChannel(channel);
195: }
196: }
197:
198: /**
199: * Rename the file.
200: */
201: protected void doRename(FileObject newfile) throws Exception {
202: final ChannelSftp channel = fileSystem.getChannel();
203: try {
204: channel.rename(relPath, ((SftpFileObject) newfile).relPath);
205: } finally {
206: fileSystem.putChannel(channel);
207: }
208: }
209:
210: /**
211: * Lists the children of this file.
212: */
213: protected FileObject[] doListChildrenResolved() throws Exception {
214: // List the contents of the folder
215: final Vector vector;
216: final ChannelSftp channel = fileSystem.getChannel();
217: try {
218: vector = channel.ls(relPath);
219: } finally {
220: fileSystem.putChannel(channel);
221: }
222: if (vector == null) {
223: throw new FileSystemException(
224: "vfs.provider.sftp/list-children.error");
225: }
226:
227: // Extract the child names
228: final ArrayList children = new ArrayList();
229: for (Iterator iterator = vector.iterator(); iterator.hasNext();) {
230: final LsEntry stat = (LsEntry) iterator.next();
231:
232: String name = stat.getFilename();
233: if (VFS.isUriStyle()) {
234: if (stat.getAttrs().isDir()
235: && name.charAt(name.length() - 1) != '/') {
236: name = name + "/";
237: }
238: }
239:
240: if (name.equals(".") || name.equals("..")
241: || name.equals("./") || name.equals("../")) {
242: continue;
243: }
244:
245: FileObject fo = getFileSystem().resolveFile(
246: getFileSystem().getFileSystemManager().resolveName(
247: getName(), UriParser.encode(name),
248: NameScope.CHILD));
249:
250: ((SftpFileObject) FileObjectUtils.getAbstractFileObject(fo))
251: .setStat(stat.getAttrs());
252:
253: children.add(fo);
254: }
255:
256: return (FileObject[]) children.toArray(new FileObject[children
257: .size()]);
258: }
259:
260: /**
261: * Lists the children of this file.
262: */
263: protected String[] doListChildren() throws Exception {
264: // use doListChildrenResolved for performance
265: return null;
266: }
267:
268: /**
269: * Returns the size of the file content (in bytes).
270: */
271: protected long doGetContentSize() throws Exception {
272: if (attrs == null
273: || (attrs.getFlags() & SftpATTRS.SSH_FILEXFER_ATTR_SIZE) == 0) {
274: throw new FileSystemException(
275: "vfs.provider.sftp/unknown-size.error");
276: }
277: return attrs.getSize();
278: }
279:
280: protected RandomAccessContent doGetRandomAccessContent(
281: final RandomAccessMode mode) throws Exception {
282: return new SftpRandomAccessContent(this , mode);
283: }
284:
285: /**
286: * Creates an input stream to read the file content from.
287: */
288: InputStream getInputStream(long filePointer) throws IOException {
289: final ChannelSftp channel = fileSystem.getChannel();
290: try {
291: // hmmm - using the in memory method is soooo much faster ...
292: // TODO - Don't read the entire file into memory. Use the
293: // stream-based methods on ChannelSftp once they work properly final
294: // .... no stream based method with resume???
295: ByteArrayOutputStream outstr = new ByteArrayOutputStream();
296: try {
297: channel.get(getName().getPathDecoded(), outstr, null,
298: ChannelSftp.RESUME, filePointer);
299: } catch (SftpException e) {
300: throw new FileSystemException(e);
301: }
302: outstr.close();
303: return new ByteArrayInputStream(outstr.toByteArray());
304: } finally {
305: fileSystem.putChannel(channel);
306: }
307: }
308:
309: /**
310: * Creates an input stream to read the file content from.
311: */
312: protected InputStream doGetInputStream() throws Exception {
313: // VFS-113: avoid npe
314: synchronized (fileSystem) {
315: final ChannelSftp channel = fileSystem.getChannel();
316: try {
317: // return channel.get(getName().getPath());
318: // hmmm - using the in memory method is soooo much faster ...
319:
320: // TODO - Don't read the entire file into memory. Use the
321: // stream-based methods on ChannelSftp once they work properly
322:
323: /*
324: final ByteArrayOutputStream outstr = new ByteArrayOutputStream();
325: channel.get(relPath, outstr);
326: outstr.close();
327: return new ByteArrayInputStream(outstr.toByteArray());
328: */
329: return new SftpInputStream(channel, channel
330: .get(relPath));
331:
332: } finally {
333: // fileSystem.putChannel(channel);
334: }
335: }
336: }
337:
338: /**
339: * Creates an output stream to write the file content to.
340: */
341: protected OutputStream doGetOutputStream(boolean bAppend)
342: throws Exception {
343: // TODO - Don't write the entire file into memory. Use the stream-based
344: // methods on ChannelSftp once the work properly
345: /*
346: final ChannelSftp channel = fileSystem.getChannel();
347: return new SftpOutputStream(channel);
348: */
349:
350: final ChannelSftp channel = fileSystem.getChannel();
351: return new SftpOutputStream(channel, channel.put(relPath));
352: }
353:
354: /**
355: * An InputStream that monitors for end-of-file.
356: */
357: private class SftpInputStream extends MonitorInputStream {
358: private final ChannelSftp channel;
359:
360: public SftpInputStream(final ChannelSftp channel,
361: final InputStream in) {
362: super (in);
363: this .channel = channel;
364: }
365:
366: /**
367: * Called after the stream has been closed.
368: */
369: protected void onClose() throws IOException {
370: fileSystem.putChannel(channel);
371: }
372: }
373:
374: /**
375: * An OutputStream that wraps an sftp OutputStream, and closes the channel
376: * when the stream is closed.
377: */
378: private class SftpOutputStream extends MonitorOutputStream {
379: private final ChannelSftp channel;
380:
381: public SftpOutputStream(final ChannelSftp channel,
382: OutputStream out) {
383: super (out);
384: this .channel = channel;
385: }
386:
387: /**
388: * Called after this stream is closed.
389: */
390: protected void onClose() throws IOException {
391: /*
392: try
393: {
394: final ByteArrayOutputStream outstr = (ByteArrayOutputStream) out;
395: channel.put(new ByteArrayInputStream(outstr.toByteArray()),
396: relPath);
397: }
398: catch (final SftpException e)
399: {
400: throw new FileSystemException(e);
401: }
402: finally
403: */
404: {
405: fileSystem.putChannel(channel);
406: }
407: }
408: }
409:
410: }
|