/*******************************************************************************
* Copyright (c) 2012, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.internal.server.sftpfile;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.*;
import java.io.*;
import java.net.URI;
import java.util.*;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.provider.FileInfo;
import org.eclipse.core.filesystem.provider.FileStore;
import org.eclipse.core.runtime.*;
import org.eclipse.orion.internal.server.servlets.Activator;
import org.eclipse.osgi.util.NLS;
/**
* A handle representing a single file or directory in a remote SFTP server.
* This implementation defaults to using the user home directory when no
* initial path is specified.
*/
public class SftpFileStore extends FileStore {
/**
* Path representing user home directory.
*/
private static final Path HOME = new Path("~"); //$NON-NLS-1$
private final URI host;
private final IPath path;
//cache fetched info just for purpose of determining if we are a directory
private IFileInfo cachedInfo;
/**
* Converts a jsch attributes object to an EFS file info.
*/
private static FileInfo attrsToInfo(String fileName, SftpATTRS stat) {
FileInfo info = new FileInfo(fileName);
info.setExists(true);
info.setDirectory(stat.isDir());
info.setLength(stat.getSize());
info.setLastModified(((long) stat.getMTime()) * 1000);
return info;
}
public SftpFileStore(URI host, IPath path) {
this.host = host;
//if no path specified, default to user home directory
this.path = path.segmentCount() == 0 ? HOME : path;
}
@Override
public IFileInfo[] childInfos(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
Vector<LsEntry> children = channel.ls(getPathString(channel));
List<IFileInfo> childInfos = new ArrayList<IFileInfo>(children.size());
for (LsEntry child : children) {
if (!shouldSkip(child.getFilename()))
childInfos.add(attrsToInfo(child.getFilename(), child.getAttrs()));
}
return childInfos.toArray(new IFileInfo[childInfos.size()]);
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
private String getPathString(SynchronizedChannel channel) throws SftpException {
if (path.segmentCount() > 0 && path.segment(0).equals(HOME.segment(0))) {
IPath result = new Path(channel.getHome());
result = result.append(path.removeFirstSegments(1));
return result.toString();
}
return path.toString();
}
@Override
public String[] childNames(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
Vector<LsEntry> children = channel.ls(getPathString(channel));
List<String> childNames = new ArrayList<String>(children.size());
for (LsEntry child : children) {
if (!shouldSkip(child.getFilename()))
childNames.add(child.getFilename());
}
return childNames.toArray(new String[childNames.size()]);
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
@Override
public IFileInfo fetchInfo(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
SftpATTRS stat = channel.stat(getPathString(channel));
cachedInfo = attrsToInfo(getName(), stat);
return cachedInfo;
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
/**
* Returns the channel for communicating with this file store.
*/
private SynchronizedChannel getChannel() throws CoreException {
return ChannelCache.getChannel(host);
}
@Override
public IFileStore getChild(String name) {
return new SftpFileStore(host, path.append(name));
}
@Override
public String getName() {
return path.lastSegment();
}
@Override
public IFileStore getParent() {
if (path.equals(HOME)) {
return null;
}
return new SftpFileStore(host, path.removeLastSegments(1));
}
@Override
public IFileStore mkdir(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
try {
channel.mkdir(getPathString(channel));
} catch (SftpException sftpException) {
//jsch mkdir fails if dir already exists, but EFS API says we should not fail
SftpATTRS stat = channel.stat(getPathString(channel));
if (stat.isDir())
return this;
//rethrow and fail
throw sftpException;
}
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
return this;
}
@Override
public InputStream openInputStream(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
return new BufferedInputStream(channel.get(getPathString(channel)));
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
@Override
public OutputStream openOutputStream(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
return new BufferedOutputStream(channel.put(getPathString(channel)));
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
@Override
public void putInfo(IFileInfo info, int options, IProgressMonitor monitor) throws CoreException {
//not supported, but don't fail
}
@Override
public void delete(int options, IProgressMonitor monitor) throws CoreException {
SynchronizedChannel channel = getChannel();
try {
//we need to know if we are a directory or file, but used the last fetched info if available
IFileInfo info = cachedInfo;
//use local field in case of concurrent change to cached info
if (info == null)
info = fetchInfo();
if (info.isDirectory())
channel.rmdir(getPathString(channel));
else
channel.rm(getPathString(channel));
} catch (Exception e) {
ChannelCache.flush(host);
throw wrap(e);
}
}
/**
* Returns whether the given file name should be ignored by sftp file system
*/
protected boolean shouldSkip(String fileName) {
//skip parent and self references
if (".".equals(fileName) || "..".equals(fileName))//$NON-NLS-1$ //$NON-NLS-2$
return true;
return false;
}
@Override
public URI toURI() {
return URIUtil.append(host, path.toString());
}
/**
* Wraps a jsch exception in a form suitable to return to caller of EFS API.
*/
private CoreException wrap(Exception e) {
String msg = NLS.bind("Failure connecting to {0}", host, e.getMessage());
return new CoreException(new Status(IStatus.ERROR, Activator.PI_SERVER_SERVLETS, msg, e));
}
}