Carrying on with the file transfer interface refactoring

* Got rid of ModeGetter/ModeSetter, moved that to LocalFile
* Instead of InMemoryFile now InMemorySourceFile (wraps istream) and InMemoryDestFile (wraps ostream)
* Uploading with a LocalFile instance rather than String path
This commit is contained in:
Shikhar Bhushan
2011-04-05 21:51:03 +01:00
parent 2e70b56ba3
commit a2c82de260
23 changed files with 758 additions and 737 deletions

View File

@@ -16,6 +16,7 @@
package net.schmizz.sshj.sftp;
import net.schmizz.sshj.xfer.FilePermission;
import net.schmizz.sshj.xfer.LocalFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -238,6 +239,16 @@ public class SFTPClient
xfer.upload(source, dest);
}
public void get(String source, LocalFile dest)
throws IOException {
xfer.download(source, dest);
}
public void put(LocalFile source, String dest)
throws IOException {
xfer.upload(source, dest);
}
@Override
public void close()
throws IOException {

View File

@@ -15,22 +15,20 @@
*/
package net.schmizz.sshj.sftp;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import net.schmizz.sshj.common.StreamCopier;
import net.schmizz.sshj.sftp.Response.StatusCode;
import net.schmizz.sshj.xfer.AbstractFileTransfer;
import net.schmizz.sshj.xfer.FileSystemFile;
import net.schmizz.sshj.xfer.FileTransfer;
import net.schmizz.sshj.xfer.FileTransferUtil;
import net.schmizz.sshj.xfer.LocalFile;
import net.schmizz.sshj.xfer.LocalFileFilter;
import net.schmizz.sshj.xfer.TransferListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.EnumSet;
public class SFTPFileTransfer
extends AbstractFileTransfer
implements FileTransfer {
@@ -38,22 +36,8 @@ public class SFTPFileTransfer
private final SFTPEngine engine;
private final PathHelper pathHelper;
private volatile FileFilter uploadFilter = defaultLocalFilter;
private volatile RemoteResourceFilter downloadFilter = defaultRemoteFilter;
private static final FileFilter defaultLocalFilter = new FileFilter() {
@Override
public boolean accept(File pathName) {
return true;
}
};
private static final RemoteResourceFilter defaultRemoteFilter = new RemoteResourceFilter() {
@Override
public boolean accept(RemoteResourceInfo resource) {
return true;
}
};
private volatile LocalFileFilter uploadFilter;
private volatile RemoteResourceFilter downloadFilter;
public SFTPFileTransfer(SFTPEngine engine) {
this.engine = engine;
@@ -65,30 +49,36 @@ public class SFTPFileTransfer
throws IOException {
new Uploader().upload(new FileSystemFile(source), dest);
}
@Override
public void upload(LocalFile localFile, String remotePath)
throws IOException {
new Uploader().upload(localFile, remotePath);
}
@Override
public void download(String source, String dest)
throws IOException {
final PathComponents pathComponents = pathHelper.getComponents(source);
final FileAttributes attributes = engine.stat(source);
new Downloader().download(new RemoteResourceInfo(pathComponents, attributes), new File(dest));
download(source, new FileSystemFile(dest));
}
public void setUploadFilter(FileFilter uploadFilter) {
this.uploadFilter = (this.uploadFilter == null) ? defaultLocalFilter : uploadFilter;
@Override
public void upload(LocalFile localFile, String remotePath)
throws IOException {
new Uploader().upload(localFile, remotePath);
}
@Override
public void download(String source, LocalFile dest)
throws IOException {
final PathComponents pathComponents = pathHelper.getComponents(source);
final FileAttributes attributes = engine.stat(source);
new Downloader().download(new RemoteResourceInfo(pathComponents, attributes), dest);
}
public void setUploadFilter(LocalFileFilter uploadFilter) {
this.uploadFilter = uploadFilter;
}
public void setDownloadFilter(RemoteResourceFilter downloadFilter) {
this.downloadFilter = (this.downloadFilter == null) ? defaultRemoteFilter : downloadFilter;
this.downloadFilter = downloadFilter;
}
public FileFilter getUploadFilter() {
public LocalFileFilter getUploadFilter() {
return uploadFilter;
}
@@ -100,9 +90,9 @@ public class SFTPFileTransfer
private final TransferListener listener = getTransferListener();
private void download(final RemoteResourceInfo remote, final File local)
private void download(final RemoteResourceInfo remote, final LocalFile local)
throws IOException {
final File adjustedFile;
final LocalFile adjustedFile;
switch (remote.getAttributes().getType()) {
case DIRECTORY:
listener.startedDir(remote.getName());
@@ -111,7 +101,7 @@ public class SFTPFileTransfer
break;
case UNKNOWN:
log.warn("Server did not supply information about the type of file at `{}` " +
"-- assuming it is a regular file!", remote.getPath());
"-- assuming it is a regular file!", remote.getPath());
case REGULAR:
listener.startedFile(remote.getName(), remote.getAttributes().getSize());
adjustedFile = downloadFile(remote, local);
@@ -124,30 +114,30 @@ public class SFTPFileTransfer
}
private File downloadDir(final RemoteResourceInfo remote, final File local)
private LocalFile downloadDir(final RemoteResourceInfo remote, final LocalFile local)
throws IOException {
final File adjusted = FileTransferUtil.getTargetDirectory(local, remote.getName());
final LocalFile adjusted = local.getTargetDirectory(remote.getName());
final RemoteDirectory rd = engine.openDir(remote.getPath());
try {
for (RemoteResourceInfo rri : rd.scan(getDownloadFilter()))
download(rri, new File(adjusted.getPath(), rri.getName()));
download(rri, adjusted.getChild(rri.getName()));
} finally {
rd.close();
}
return adjusted;
}
private File downloadFile(final RemoteResourceInfo remote, final File local)
private LocalFile downloadFile(final RemoteResourceInfo remote, final LocalFile local)
throws IOException {
final File adjusted = FileTransferUtil.getTargetFile(local, remote.getName());
final LocalFile adjusted = local.getTargetFile(remote.getName());
final RemoteFile rf = engine.open(remote.getPath());
try {
final FileOutputStream fos = new FileOutputStream(adjusted);
final OutputStream os = adjusted.getOutputStream();
try {
StreamCopier.copy(rf.getInputStream(), fos, engine.getSubsystem()
.getLocalMaxPacketSize(), false, listener);
StreamCopier.copy(rf.getInputStream(), os,
engine.getSubsystem().getLocalMaxPacketSize(), false, listener);
} finally {
fos.close();
os.close();
}
} finally {
rf.close();
@@ -155,13 +145,13 @@ public class SFTPFileTransfer
return adjusted;
}
private void copyAttributes(final RemoteResourceInfo remote, final File local)
private void copyAttributes(final RemoteResourceInfo remote, final LocalFile local)
throws IOException {
final FileAttributes attrs = remote.getAttributes();
getModeSetter().setPermissions(local, attrs.getMode().getPermissionsMask());
if (getModeSetter().preservesTimes() && attrs.has(FileAttributes.Flag.ACMODTIME)) {
getModeSetter().setLastAccessedTime(local, attrs.getAtime());
getModeSetter().setLastModifiedTime(local, attrs.getMtime());
local.setPermissions(attrs.getMode().getPermissionsMask());
if (local.preservesTimes() && attrs.has(FileAttributes.Flag.ACMODTIME)) {
local.setLastAccessedTime(attrs.getAtime());
local.setLastModifiedTime(attrs.getMtime());
}
}
@@ -202,7 +192,7 @@ public class SFTPFileTransfer
OpenMode.CREAT,
OpenMode.TRUNC));
try {
final InputStream fis = local.stream();
final InputStream fis = local.getInputStream();
try {
final int bufSize = engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead();
StreamCopier.copy(fis, rf.getOutputStream(), bufSize, false, listener);
@@ -265,10 +255,9 @@ public class SFTPFileTransfer
private FileAttributes getAttributes(LocalFile local)
throws IOException {
final FileAttributes.Builder builder = new FileAttributes.Builder()
.withPermissions(getModeGetter().getPermissions(local));
if (getModeGetter().preservesTimes())
builder.withAtimeMtime(getModeGetter().getLastAccessTime(local), getModeGetter().getLastModifiedTime(local));
final FileAttributes.Builder builder = new FileAttributes.Builder().withPermissions(local.getPermissions());
if (local.preservesTimes())
builder.withAtimeMtime(local.getLastAccessTime(), local.getLastModifiedTime());
return builder.build();
}

View File

@@ -22,30 +22,10 @@ public abstract class AbstractFileTransfer {
protected final Logger log = LoggerFactory.getLogger(getClass());
public static final ModeGetter DEFAULT_MODE_SETTER = new DefaultModeGetter();
public static final ModeSetter DEFAULT_MODE_GETTER = new DefaultModeSetter();
public static final LoggingTransferListener LOGGING_TRANSFER_LISTENER = new LoggingTransferListener();
private volatile ModeGetter modeGetter = DEFAULT_MODE_SETTER;
private volatile ModeSetter modeSetter = DEFAULT_MODE_GETTER;
private volatile TransferListener transferListener = LOGGING_TRANSFER_LISTENER;
public void setModeGetter(ModeGetter modeGetter) {
this.modeGetter = (modeGetter == null) ? DEFAULT_MODE_SETTER : modeGetter;
}
public ModeGetter getModeGetter() {
return this.modeGetter;
}
public void setModeSetter(ModeSetter modeSetter) {
this.modeSetter = (modeSetter == null) ? DEFAULT_MODE_GETTER : modeSetter;
}
public ModeSetter getModeSetter() {
return this.modeSetter;
}
public TransferListener getTransferListener() {
return transferListener;
}
@@ -54,4 +34,4 @@ public abstract class AbstractFileTransfer {
this.transferListener = (transferListener == null) ? LOGGING_TRANSFER_LISTENER : transferListener;
}
}
}

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import java.io.File;
import java.io.IOException;
/**
* Default implementation of {@link ModeGetter} that supplies file permissions as {@code "0644"}, directory permissions
* as {@code "0755"}, and preserves timestamps. Note that there is no way of getting the last access time with Java file
* API's so it is returned as the current system time.
*/
public class DefaultModeGetter
implements ModeGetter {
@Override
public long getLastAccessTime(File f) {
return System.currentTimeMillis() / 1000;
}
@Override
public long getLastModifiedTime(File f) {
return f.lastModified() / 1000;
}
@Override
public int getPermissions(File f)
throws IOException {
if (f.isDirectory())
return 0755;
else if (f.isFile())
return 0644;
else
throw new IOException("Unsupported file type: " + f);
}
@Override
public long getLastAccessTime(LocalFile f) {
return System.currentTimeMillis() / 1000;
}
@Override
public long getLastModifiedTime(LocalFile f) {
return f.lastModified() / 1000;
}
@Override
public int getPermissions(LocalFile f)
throws IOException {
if (f.isDirectory())
return 0755;
else if (f.isFile())
return 0644;
else
throw new IOException("Unsupported file type: " + f);
}
@Override
public boolean preservesTimes() {
return true;
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
/**
* Default implementation of {@link ModeSetter} attempts to preserve timestamps and permissions to the extent allowed by
* Java File API.
*/
public class DefaultModeSetter
implements ModeSetter {
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
public void setLastAccessedTime(File f, long t)
throws IOException {
// Can't do anything
}
@Override
public void setLastModifiedTime(File f, long t)
throws IOException {
if (!f.setLastModified(t * 1000))
log.warn("Could not set last modified time for {} to {}", f, t);
}
@Override
public void setPermissions(File f, int perms)
throws IOException {
final boolean r = f.setReadable(FilePermission.USR_R.isIn(perms),
!(FilePermission.OTH_R.isIn(perms) || FilePermission.GRP_R.isIn(perms)));
final boolean w = f.setWritable(FilePermission.USR_W.isIn(perms),
!(FilePermission.OTH_W.isIn(perms) || FilePermission.GRP_W.isIn(perms)));
final boolean x = f.setExecutable(FilePermission.USR_X.isIn(perms),
!(FilePermission.OTH_X.isIn(perms) || FilePermission.GRP_X.isIn(perms)));
if (!(r && w && x))
log.warn("Could not set permissions for {} to {}", f, Integer.toString(perms, 16));
}
@Override
public boolean preservesTimes() {
return true;
}
}

View File

@@ -15,80 +15,210 @@
*/
package net.schmizz.sshj.xfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FileSystemFile
implements LocalFile {
public class FileSystemFile implements LocalFile {
protected final Logger log = LoggerFactory.getLogger(getClass());
private File file;
private FileFilter fileFilter;
private final File file;
public FileSystemFile(String path) {
this.file = new File(path);
}
public void setFileFilter(FileFilter fileFilter) {
this.fileFilter = fileFilter;
}
public FileSystemFile(String path) {
this(new File(path));
}
@Override
public String getName() {
return file.getName();
}
public FileSystemFile(File file) {
this.file = file;
}
@Override
public boolean isDirectory() {
return file.isDirectory();
}
public File getFile() {
return file;
}
@Override
public boolean isFile() {
return file.isFile();
}
@Override
public String getName() {
return file.getName();
}
@Override
public long length() {
return file.length();
}
@Override
public boolean isFile() {
return file.isFile();
}
@Override
public long lastModified() {
return file.lastModified();
}
@Override
public boolean isDirectory() {
return file.isDirectory();
}
@Override
public InputStream stream() throws IOException {
return new FileInputStream(file);
}
@Override
public long length() {
return file.length();
}
@Override
public Iterable<LocalFile> getChildren() throws IOException {
return getChildren(file);
}
@Override
public InputStream getInputStream()
throws IOException {
return new FileInputStream(file);
}
@Override
public OutputStream getOutputStream()
throws IOException {
return new FileOutputStream(file);
}
@Override
public Iterable<FileSystemFile> getChildren()
throws IOException {
return getChildren(null);
}
@Override
public Iterable<FileSystemFile> getChildren(final LocalFileFilter filter)
throws IOException {
final Map<File, FileSystemFile> cache = new HashMap<File, FileSystemFile>();
File[] childFiles = filter == null ? file.listFiles() : file.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
final FileSystemFile fsf = new FileSystemFile(file);
cache.put(file, fsf);
return filter.accept(fsf);
}
});
if (childFiles == null)
throw new IOException("Error listing files in directory: " + this);
final List<FileSystemFile> children = new ArrayList<FileSystemFile>();
for (File f : childFiles) {
children.add(cache.get(f));
}
return children;
}
@Override
public long getLastAccessTime()
throws IOException {
return System.currentTimeMillis() / 1000;
}
@Override
public long getLastModifiedTime()
throws IOException {
return file.lastModified() / 1000;
}
@Override
public int getPermissions()
throws IOException {
if (isDirectory())
return 0755;
else if (isFile())
return 0644;
else
throw new IOException("Unsupported file type");
}
@Override
public void setLastAccessedTime(long t)
throws IOException {
// ...
}
@Override
public void setLastModifiedTime(long t)
throws IOException {
if (!file.setLastModified(t * 1000))
log.warn("Could not set last modified time for {} to {}", file, t);
}
@Override
public void setPermissions(int perms)
throws IOException {
final boolean r = file.setReadable(FilePermission.USR_R.isIn(perms),
!(FilePermission.OTH_R.isIn(perms) || FilePermission.GRP_R.isIn(perms)));
final boolean w = file.setWritable(FilePermission.USR_W.isIn(perms),
!(FilePermission.OTH_W.isIn(perms) || FilePermission.GRP_W.isIn(perms)));
final boolean x = file.setExecutable(FilePermission.USR_X.isIn(perms),
!(FilePermission.OTH_X.isIn(perms) || FilePermission.GRP_X.isIn(perms)));
if (!(r && w && x))
log.warn("Could not set permissions for {} to {}", file, Integer.toString(perms, 16));
}
@Override
public boolean preservesTimes() {
return true;
}
@Override
public FileSystemFile getChild(String name) {
return new FileSystemFile(new File(file, name));
}
@Override
public FileSystemFile getTargetFile(String filename)
throws IOException {
FileSystemFile f = this;
if (f.isDirectory())
f = f.getChild(filename);
if (!f.getFile().exists()) {
if (!f.getFile().createNewFile())
throw new IOException("Could not create: " + file);
} else if (f.isDirectory())
throw new IOException("A directory by the same name already exists: " + f);
return f;
}
@Override
public FileSystemFile getTargetDirectory(String dirname)
throws IOException {
FileSystemFile f = this;
if (f.getFile().exists())
if (f.isDirectory()) {
if (!f.getName().equals(dirname))
f = f.getChild(dirname);
} else
throw new IOException(f + " - already exists as a file; directory required");
if (!f.getFile().exists() && !f.getFile().mkdir())
throw new IOException("Failed to create directory: " + f);
return f;
}
@Override
public boolean equals(Object other) {
return (other instanceof FileSystemFile)
&& file.equals(((FileSystemFile) other).file);
}
@Override
public int hashCode() {
return file != null ? file.hashCode() : 0;
}
@Override
public String toString() {
return file.toString();
}
@Override
public Iterable<LocalFile> getChildren(FileFilter filter) throws IOException {
setFileFilter(filter);
return getChildren(file);
}
private Iterable<LocalFile> getChildren(File f) throws IOException {
Collection<LocalFile> files = new ArrayList<LocalFile>();
File[] childFiles = fileFilter == null ? f.listFiles() : f.listFiles(fileFilter);
if (childFiles == null)
throw new IOException("Error listing files in directory: " + f);
for (File childFile : childFiles) {
FileSystemFile localChild = new FileSystemFile(childFile.getName());
localChild.setFileFilter(fileFilter);
files.add(localChild);
}
return files;
}
}

View File

@@ -22,19 +22,14 @@ public interface FileTransfer {
void upload(String localPath, String remotePath)
throws IOException;
void upload(LocalFile localFile, String remotePath)
throws IOException;
void download(String remotePath, String localPath)
throws IOException;
ModeGetter getModeGetter();
void upload(LocalFile localFile, String remotePath)
throws IOException;
void setModeGetter(ModeGetter modeGetter);
ModeSetter getModeSetter();
void setModeSetter(ModeSetter modeSetter);
void download(String remotePath, LocalFile localFile)
throws IOException;
TransferListener getTransferListener();

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import java.io.File;
import java.io.IOException;
public class FileTransferUtil {
public static File getTargetDirectory(File f, String dirname)
throws IOException {
if (f.exists())
if (f.isDirectory()) {
if (!f.getName().equals(dirname))
f = new File(f, dirname);
} else
throw new IOException(f + " - already exists as a file; directory required");
if (!f.exists() && !f.mkdir())
throw new IOException("Failed to create directory: " + f);
return f;
}
public static File getTargetFile(File f, String filename)
throws IOException {
if (f.isDirectory())
f = new File(f, filename);
if (!f.exists()) {
if (!f.createNewFile())
throw new IOException("Could not create: " + f);
} else if (f.isDirectory())
throw new IOException("A directory by the same name already exists: " + f);
return f;
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class InMemoryDestFile
implements LocalFile {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final String filename;
protected final OutputStream outStream;
public InMemoryDestFile(String filename, OutputStream outStream) {
this.filename = filename;
this.outStream = outStream;
}
@Override
public String getName() {
return filename;
}
@Override
public LocalFile getTargetFile(String filename)
throws IOException {
if (filename.equals(this.filename))
return this;
else
throw new IOException("Filename mismatch");
}
@Override
public boolean isFile() {
return true;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public OutputStream getOutputStream()
throws IOException {
return outStream;
}
// Everything else is unimplemented
@Override
public long length() {
return 0;
}
@Override
public InputStream getInputStream()
throws IOException {
return null;
}
@Override
public Iterable<LocalFile> getChildren()
throws IOException {
return null;
}
@Override
public Iterable<LocalFile> getChildren(LocalFileFilter filter)
throws IOException {
return null;
}
@Override
public long getLastAccessTime()
throws IOException {
return 0;
}
@Override
public long getLastModifiedTime()
throws IOException {
return 0;
}
@Override
public int getPermissions()
throws IOException {
return 0;
}
@Override
public void setLastAccessedTime(long t)
throws IOException {
}
@Override
public void setLastModifiedTime(long t)
throws IOException {
}
@Override
public void setPermissions(int perms)
throws IOException {
}
@Override
public boolean preservesTimes() {
return false;
}
@Override
public LocalFile getChild(String name) {
return null;
}
@Override
public LocalFile getTargetDirectory(String dirname) {
return null;
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import java.io.ByteArrayInputStream;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
public class InMemoryFile implements LocalFile {
private static final int EOF = -1;
private static final int DEFAULT_BUFFER_SIZE = 4096;
private String name;
private Long cachedLength;
private InputStream stream;
public InMemoryFile(String filename, ByteArrayInputStream stream) {
this.name = filename;
this.stream = stream;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public boolean isFile() {
return true;
}
@Override
public long length() {
if (cachedLength == null) {
cachedLength = computeLength();
}
return cachedLength;
}
private long computeLength() {
try {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long length = 0;
int readBytes = 0;
while (EOF != (readBytes = stream.read(buffer))) {
length += readBytes;
}
stream.reset();
return length;
} catch (IOException e) {
throw new RuntimeException("Impossible to read in memory file", e);
}
}
@Override
public long lastModified() {
return System.currentTimeMillis() / 1000;
}
@Override
public InputStream stream() {
return stream;
}
@Override
public Iterable<LocalFile> getChildren() {
return Collections.emptyList();
}
@Override
public Iterable<LocalFile> getChildren(FileFilter filter)
throws IOException {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class InMemorySourceFile
implements LocalFile {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final String filename;
protected final long length;
protected final InputStream inStream;
public InMemorySourceFile(String filename, long length, InputStream inStream) {
this.filename = filename;
this.length = length;
this.inStream = inStream;
}
@Override
public String getName() {
return filename;
}
@Override
public LocalFile getTargetFile(String filename)
throws IOException {
if (filename.equals(this.filename))
return this;
else
throw new IOException("Filename mismatch");
}
@Override
public boolean isFile() {
return true;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public long length() {
return length;
}
@Override
public InputStream getInputStream()
throws IOException {
return inStream;
}
@Override
public int getPermissions()
throws IOException {
return 0644;
}
// Everything else is unimplemented
@Override
public OutputStream getOutputStream()
throws IOException {
return null;
}
@Override
public Iterable<LocalFile> getChildren()
throws IOException {
return null;
}
@Override
public Iterable<LocalFile> getChildren(LocalFileFilter filter)
throws IOException {
return null;
}
@Override
public long getLastAccessTime()
throws IOException {
return 0;
}
@Override
public long getLastModifiedTime()
throws IOException {
return 0;
}
@Override
public void setLastAccessedTime(long t)
throws IOException {
}
@Override
public void setLastModifiedTime(long t)
throws IOException {
}
@Override
public void setPermissions(int perms)
throws IOException {
}
@Override
public boolean preservesTimes() {
return false;
}
@Override
public LocalFile getChild(String name) {
return null;
}
@Override
public LocalFile getTargetDirectory(String dirname) {
return null;
}
}

View File

@@ -15,24 +15,100 @@
*/
package net.schmizz.sshj.xfer;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
// TODO Document all methods properly
public interface LocalFile {
String getName();
String getName();
boolean isDirectory();
boolean isFile();
boolean isDirectory();
long length();
long lastModified();
InputStream stream() throws IOException;
InputStream getInputStream() throws IOException;
Iterable<LocalFile> getChildren() throws IOException;
Iterable<LocalFile> getChildren(FileFilter filter) throws IOException;
OutputStream getOutputStream() throws IOException;
}
Iterable<? extends LocalFile> getChildren() throws IOException;
Iterable<? extends LocalFile> getChildren(LocalFileFilter filter) throws IOException;
/**
* Returns last access time for the underlying file.
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastAccessTime()
throws IOException;
/**
* Returns last access time for the underlying file.
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastModifiedTime()
throws IOException;
/**
* Returns the permissions for the underlying file
*
* @return permissions in octal format, e.g. 0644
*
* @throws IOException
*/
int getPermissions()
throws IOException;
/**
* Set the last access time for the underlying file.
*
* @param t time in seconds since Unix epoch
*
* @throws IOException
*/
void setLastAccessedTime(long t)
throws IOException;
/**
* Set the last modified time for the underlying file.
*
* @param f the file
* @param t time in seconds since Unix epoch
*
* @throws IOException
*/
void setLastModifiedTime(long t)
throws IOException;
/**
* Set the permissions for the underlying file.
*
* @param f the file
* @param perms permissions in octal format, e.g. 0644
*
* @throws IOException
*/
void setPermissions(int perms)
throws IOException;
/** @return whether this implementation is interested in preserving mtime and atime. */
boolean preservesTimes();
/** @return A child file of this directory having {@code name} as filename */
LocalFile getChild(String name);
LocalFile getTargetFile(String filename) throws IOException;
LocalFile getTargetDirectory(String dirname) throws IOException;
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
public interface LocalFileFilter {
boolean accept(LocalFile file);
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import java.io.File;
import java.io.IOException;
/** An interface for retrieving information about file permissions and times. */
public interface ModeGetter {
/**
* Returns last access time for {@code f}.
*
* @param f the file
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastAccessTime(File f)
throws IOException;
/**
* Returns last modified time for {@code f}.
*
* @param f the file
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastModifiedTime(File f)
throws IOException;
/**
* Returns the permissions for {@code f}.
*
* @param f the file
*
* @return permissions in octal format, e.g. 0644
*
* @throws IOException
*/
int getPermissions(File f)
throws IOException;
/**
* Returns last access time for {@code f}.
*
* @param f the file
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastAccessTime(LocalFile f)
throws IOException;
/**
* Returns last modified time for {@code f}.
*
* @param f the file
*
* @return time in seconds since Unix epoch
*
* @throws IOException
*/
long getLastModifiedTime(LocalFile f)
throws IOException;
/**
* Returns the permissions for {@code f}.
*
* @param f the file
*
* @return permissions in octal format, e.g. 0644
*
* @throws IOException
*/
int getPermissions(LocalFile f)
throws IOException;
/** @return whether this implementation can provide mtime and atime information. */
boolean preservesTimes();
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2010, 2011 sshj contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.schmizz.sshj.xfer;
import java.io.File;
import java.io.IOException;
/** An interface for setting file permissions and times. */
public interface ModeSetter {
/**
* Set the last access time for {@code f}.
*
* @param f the file
* @param t time in seconds since Unix epoch
*
* @throws IOException
*/
void setLastAccessedTime(File f, long t)
throws IOException;
/**
* Set the last modified time for {@code f}.
*
* @param f the file
* @param t time in seconds since Unix epoch
*
* @throws IOException
*/
void setLastModifiedTime(File f, long t)
throws IOException;
/**
* Set the permissions for {@code f}.
*
* @param f the file
* @param perms permissions in octal format, e.g. "644"
*
* @throws IOException
*/
void setPermissions(File f, int perms)
throws IOException;
/** @return whether this implementation is interested in preserving mtime and atime. */
boolean preservesTimes();
}

View File

@@ -15,38 +15,32 @@
*/
package net.schmizz.sshj.xfer.scp;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.xfer.FileTransferUtil;
import net.schmizz.sshj.xfer.ModeSetter;
import net.schmizz.sshj.xfer.LocalFile;
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
/** Support for uploading files over a connected link using SCP. */
public final class SCPDownloadClient {
private final ModeSetter modeSetter;
private boolean recursive = true;
private SCPEngine engine;
SCPDownloadClient(SCPEngine engine, ModeSetter modeSetter) {
SCPDownloadClient(SCPEngine engine) {
this.engine = engine;
this.modeSetter = modeSetter;
}
/** Download a file from {@code sourcePath} on the connected host to {@code targetPath} locally. */
public synchronized int copy(String sourcePath, String targetPath)
public synchronized int copy(String sourcePath, LocalFile targetFile)
throws IOException {
engine.cleanSlate();
try {
startCopy(sourcePath, targetPath);
startCopy(sourcePath, targetFile);
} finally {
engine.exit();
}
@@ -61,28 +55,23 @@ public final class SCPDownloadClient {
this.recursive = recursive;
}
void startCopy(String sourcePath, String targetPath)
void startCopy(String sourcePath, LocalFile targetFile)
throws IOException {
init(sourcePath);
engine.signal("Start status OK");
String msg = engine.readMessage(true);
do
process(null, msg, new File(targetPath));
while ((msg = engine.readMessage(false)) != null);
}
private void init(String source)
throws SSHException {
List<Arg> args = new LinkedList<Arg>();
args.add(Arg.SOURCE);
args.add(Arg.QUIET);
if (recursive)
args.add(Arg.RECURSIVE);
if (modeSetter.preservesTimes())
if (targetFile.preservesTimes())
args.add(Arg.PRESERVE_TIMES);
engine.execSCPWith(args, source);
engine.execSCPWith(args, sourcePath);
engine.signal("Start status OK");
String msg = engine.readMessage(true);
do
process(null, msg, targetFile);
while ((msg = engine.readMessage(false)) != null);
}
private long parseLong(String longString, String valType)
@@ -103,7 +92,7 @@ public final class SCPDownloadClient {
return Integer.parseInt(cmd.substring(1), 8);
}
private boolean process(String bufferedTMsg, String msg, File f)
private boolean process(String bufferedTMsg, String msg, LocalFile f)
throws IOException {
if (msg.length() < 1)
throw new SCPException("Could not parse message `" + msg + "`");
@@ -139,7 +128,7 @@ public final class SCPDownloadClient {
return false;
}
private void processDirectory(String dMsg, String tMsg, File f)
private void processDirectory(String dMsg, String tMsg, LocalFile f)
throws IOException {
final String[] dMsgParts = tokenize(dMsg, 3); // D<perms> 0 <dirname>
final long length = parseLong(dMsgParts[1], "dir length");
@@ -148,7 +137,7 @@ public final class SCPDownloadClient {
throw new IOException("Remote SCP command sent strange directory length: " + length);
engine.startedDir(dirname);
{
f = FileTransferUtil.getTargetDirectory(f, dirname);
f = f.getTargetDirectory(dirname);
engine.signal("ACK: D");
do {
} while (!process(null, engine.readMessage(), f));
@@ -158,20 +147,20 @@ public final class SCPDownloadClient {
engine.finishedDir();
}
private void processFile(String cMsg, String tMsg, File f)
private void processFile(String cMsg, String tMsg, LocalFile f)
throws IOException {
final String[] cMsgParts = tokenize(cMsg, 3); // C<perms> <size> <filename>
final long length = parseLong(cMsgParts[1], "length");
final String filename = cMsgParts[2];
engine.startedFile(length, filename);
{
f = FileTransferUtil.getTargetFile(f, filename);
f = f.getTargetFile(filename);
engine.signal("Remote can start transfer");
final FileOutputStream fos = new FileOutputStream(f);
final OutputStream os = f.getOutputStream();
try {
engine.transfertFromRemote(length, fos);
engine.transferFromRemote(length, os);
} finally {
IOUtils.closeQuietly(fos);
IOUtils.closeQuietly(os);
}
engine.check("Remote agrees transfer done");
setAttributes(f, parsePermissions(cMsgParts[0]), tMsg);
@@ -180,13 +169,13 @@ public final class SCPDownloadClient {
engine.finishedFile();
}
private void setAttributes(File f, int perms, String tMsg)
private void setAttributes(LocalFile f, int perms, String tMsg)
throws IOException {
modeSetter.setPermissions(f, perms);
if (tMsg != null && modeSetter.preservesTimes()) {
f.setPermissions(perms);
if (tMsg != null && f.preservesTimes()) {
String[] tMsgParts = tokenize(tMsg, 4); // e.g. T<mtime> 0 <atime> 0
modeSetter.setLastModifiedTime(f, parseLong(tMsgParts[0].substring(1), "last modified time"));
modeSetter.setLastAccessedTime(f, parseLong(tMsgParts[2], "last access time"));
f.setLastModifiedTime(parseLong(tMsgParts[0].substring(1), "last modified time"));
f.setLastAccessedTime(parseLong(tMsgParts[2], "last access time"));
}
}

View File

@@ -15,7 +15,6 @@
*/
package net.schmizz.sshj.xfer.scp;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -162,14 +161,14 @@ class SCPEngine {
scp.getOutputStream().flush();
}
void transfertToRemote(LocalFile f, final InputStream src)
void transferToRemote(LocalFile f, final InputStream src)
throws IOException {
transfer(src, scp.getOutputStream(), scp.getRemoteMaxPacketSize(), f.length());
}
void transfertFromRemote(final long length, final FileOutputStream fos)
void transferFromRemote(final long length, final OutputStream os)
throws IOException {
transfer(scp.getInputStream(), fos, scp.getLocalMaxPacketSize(), length);
transfer(scp.getInputStream(), os, scp.getLocalMaxPacketSize(), length);
}
private void transfer(InputStream in, OutputStream out, int bufSize, long len)
@@ -218,4 +217,5 @@ class SCPEngine {
void finishedFile() {
listener.finishedFile();
}
}

View File

@@ -34,32 +34,39 @@ public class SCPFileTransfer
}
public SCPDownloadClient newSCPDownloadClient() {
return new SCPDownloadClient(getSCPEngine(), getModeSetter());
return new SCPDownloadClient(newSCPEngine());
}
public SCPUploadClient newSCPUploadClient() {
return new SCPUploadClient(getSCPEngine(), getModeGetter());
return new SCPUploadClient(newSCPEngine());
}
private SCPEngine getSCPEngine() {
private SCPEngine newSCPEngine() {
return new SCPEngine(sessionFactory, getTransferListener());
}
@Override
public void download(String remotePath, String localPath)
throws IOException {
newSCPDownloadClient().copy(remotePath, localPath);
}
@Override
public void upload(String localPath, String remotePath)
throws IOException {
newSCPUploadClient().copy(new FileSystemFile(localPath), remotePath);
}
@Override
public void download(String remotePath, String localPath)
throws IOException {
download(remotePath, new FileSystemFile(localPath));
}
@Override
public void download(String remotePath, LocalFile localFile)
throws IOException {
newSCPDownloadClient().copy(remotePath, localFile);
}
@Override
public void upload(LocalFile localFile, String remotePath)
throws IOException {
newSCPUploadClient().copy(localFile, remotePath);
}
}

View File

@@ -15,103 +15,94 @@
*/
package net.schmizz.sshj.xfer.scp;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.xfer.LocalFile;
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.common.SSHException;
import net.schmizz.sshj.xfer.LocalFile;
import net.schmizz.sshj.xfer.ModeGetter;
import net.schmizz.sshj.xfer.scp.SCPEngine.Arg;
/** Support for uploading files over a connected link using SCP. */
public final class SCPUploadClient {
private final ModeGetter modeGetter;
private SCPEngine engine;
SCPUploadClient(SCPEngine engine, ModeGetter modeGetter) {
SCPUploadClient(SCPEngine engine) {
this.engine = engine;
this.modeGetter = modeGetter;
}
/** Upload a local file from {@code localFile} to {@code targetPath} on the remote host. */
public synchronized int copy(LocalFile sourceFile, String remotePath)
throws IOException {
engine.cleanSlate();
try {
startCopy(sourceFile, remotePath);
} finally {
engine.exit();
}
return engine.getExitStatus();
public synchronized int copy(LocalFile sourceFile, String remotePath)
throws IOException {
engine.cleanSlate();
try {
startCopy(sourceFile, remotePath);
} finally {
engine.exit();
}
return engine.getExitStatus();
}
private synchronized void startCopy(LocalFile sourceFile, String targetPath)
throws IOException {
init(targetPath);
engine.check("Start status OK");
process(sourceFile);
}
private void init(String target)
throws SSHException {
throws IOException {
List<Arg> args = new LinkedList<Arg>();
args.add(Arg.SINK);
args.add(Arg.RECURSIVE);
if (modeGetter.preservesTimes())
if (sourceFile.preservesTimes())
args.add(Arg.PRESERVE_TIMES);
engine.execSCPWith(args, target);
engine.execSCPWith(args, targetPath);
engine.check("Start status OK");
process(sourceFile);
}
private void process(LocalFile f)
throws IOException {
if (f.isDirectory()) {
engine.startedDir(f);
sendDirectory(f);
engine.finishedDir();
} else if (f.isFile()) {
engine.startedFile(f);
sendFile(f);
engine.finishedFile();
} else
throw new IOException(f + " is not a regular file or directory");
}
throws IOException {
if (f.isDirectory()) {
engine.startedDir(f);
sendDirectory(f);
engine.finishedDir();
} else if (f.isFile()) {
engine.startedFile(f);
sendFile(f);
engine.finishedFile();
} else
throw new IOException(f + " is not a regular file or directory");
}
private void sendDirectory(LocalFile f)
throws IOException {
preserveTimeIfPossible(f);
engine.sendMessage("D0" + getPermString(f) + " 0 " + f.getName());
for (LocalFile child : f.getChildren())
process(child);
engine.sendMessage("E");
}
private void sendDirectory(LocalFile f)
throws IOException {
preserveTimeIfPossible(f);
engine.sendMessage("D0" + getPermString(f) + " 0 " + f.getName());
for (LocalFile child : f.getChildren())
process(child);
engine.sendMessage("E");
}
private void sendFile(LocalFile f)
throws IOException {
preserveTimeIfPossible(f);
final InputStream src = f.stream();
try {
engine.sendMessage("C0" + getPermString(f) + " " + f.length() + " " + f.getName());
engine.transfertToRemote(f, src);
engine.signal("Transfer done");
engine.check("Remote agrees transfer done");
} finally {
IOUtils.closeQuietly(src);
}
}
throws IOException {
preserveTimeIfPossible(f);
final InputStream src = f.getInputStream();
try {
engine.sendMessage("C0" + getPermString(f) + " " + f.length() + " " + f.getName());
engine.transferToRemote(f, src);
engine.signal("Transfer done");
engine.check("Remote agrees transfer done");
} finally {
IOUtils.closeQuietly(src);
}
}
private void preserveTimeIfPossible(LocalFile f)
throws IOException {
if (modeGetter.preservesTimes())
engine.sendMessage("T" + modeGetter.getLastModifiedTime(f) + " 0 " + modeGetter.getLastAccessTime(f) + " 0");
}
throws IOException {
if (f.preservesTimes())
engine.sendMessage("T" + f.getLastModifiedTime() + " 0 " + f.getLastAccessTime() + " 0");
}
private String getPermString(LocalFile f)
throws IOException {
return Integer.toOctalString(modeGetter.getPermissions(f) & 07777);
throws IOException {
return Integer.toOctalString(f.getPermissions() & 07777);
}
}