diff --git a/src/main/java/net/schmizz/sshj/xfer/DefaultModeGetter.java b/src/main/java/net/schmizz/sshj/xfer/DefaultModeGetter.java index 28e1d843..fbe29c7c 100644 --- a/src/main/java/net/schmizz/sshj/xfer/DefaultModeGetter.java +++ b/src/main/java/net/schmizz/sshj/xfer/DefaultModeGetter.java @@ -46,6 +46,27 @@ public class DefaultModeGetter 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() { diff --git a/src/main/java/net/schmizz/sshj/xfer/FileSystemFile.java b/src/main/java/net/schmizz/sshj/xfer/FileSystemFile.java new file mode 100644 index 00000000..83049869 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/xfer/FileSystemFile.java @@ -0,0 +1,73 @@ +package net.schmizz.sshj.xfer; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + + +public class FileSystemFile implements LocalFile { + + private File file; + private FileFilter fileFilter; + + public FileSystemFile(String path) { + this.file = new File(path); + } + + public void setFileFilter(FileFilter fileFilter) { + this.fileFilter = fileFilter; + } + + @Override + public String getName() { + return file.getName(); + } + + @Override + public boolean isDirectory() { + return file.isDirectory(); + } + + @Override + public boolean isFile() { + return file.isFile(); + } + + @Override + public long length() { + return file.length(); + } + + @Override + public long lastModified() { + return file.lastModified(); + } + + @Override + public InputStream stream() throws IOException { + return new FileInputStream(file); + } + + @Override + public Iterable getChildren() throws IOException { + return getChildren(file); + } + + private Iterable getChildren(File f) throws IOException { + Collection files = new ArrayList(); + 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; + } +} diff --git a/src/main/java/net/schmizz/sshj/xfer/InMemoryFile.java b/src/main/java/net/schmizz/sshj/xfer/InMemoryFile.java new file mode 100644 index 00000000..49ff53e8 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/xfer/InMemoryFile.java @@ -0,0 +1,75 @@ +package net.schmizz.sshj.xfer; + +import java.io.ByteArrayInputStream; +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 getChildren() { + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/schmizz/sshj/xfer/LocalFile.java b/src/main/java/net/schmizz/sshj/xfer/LocalFile.java new file mode 100644 index 00000000..0c8ad7cd --- /dev/null +++ b/src/main/java/net/schmizz/sshj/xfer/LocalFile.java @@ -0,0 +1,19 @@ +package net.schmizz.sshj.xfer; + +import java.io.IOException; +import java.io.InputStream; + +public interface LocalFile { + String getName(); + + boolean isDirectory(); + boolean isFile(); + + long length(); + + long lastModified(); + + InputStream stream() throws IOException; + + Iterable getChildren() throws IOException; +} diff --git a/src/main/java/net/schmizz/sshj/xfer/ModeGetter.java b/src/main/java/net/schmizz/sshj/xfer/ModeGetter.java index 535b6254..101ee35d 100644 --- a/src/main/java/net/schmizz/sshj/xfer/ModeGetter.java +++ b/src/main/java/net/schmizz/sshj/xfer/ModeGetter.java @@ -45,6 +45,7 @@ public interface ModeGetter { long getLastModifiedTime(File f) throws IOException; + /** * Returns the permissions for {@code f}. * @@ -56,7 +57,44 @@ public interface ModeGetter { */ 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(); diff --git a/src/main/java/net/schmizz/sshj/xfer/scp/SCPFileTransfer.java b/src/main/java/net/schmizz/sshj/xfer/scp/SCPFileTransfer.java index cc0a671f..b7d95cb9 100644 --- a/src/main/java/net/schmizz/sshj/xfer/scp/SCPFileTransfer.java +++ b/src/main/java/net/schmizz/sshj/xfer/scp/SCPFileTransfer.java @@ -15,11 +15,12 @@ */ package net.schmizz.sshj.xfer.scp; +import java.io.IOException; + import net.schmizz.sshj.connection.channel.direct.SessionFactory; import net.schmizz.sshj.xfer.AbstractFileTransfer; import net.schmizz.sshj.xfer.FileTransfer; - -import java.io.IOException; +import net.schmizz.sshj.xfer.LocalFile; public class SCPFileTransfer extends AbstractFileTransfer @@ -51,4 +52,8 @@ public class SCPFileTransfer newSCPUploadClient().copy(localPath, remotePath); } + public void upload(LocalFile localFile, String remotePath) + throws IOException { + newSCPUploadClient().copy(localFile, remotePath); + } } diff --git a/src/main/java/net/schmizz/sshj/xfer/scp/SCPUploadClient.java b/src/main/java/net/schmizz/sshj/xfer/scp/SCPUploadClient.java index 6a60d883..cd5cdce9 100644 --- a/src/main/java/net/schmizz/sshj/xfer/scp/SCPUploadClient.java +++ b/src/main/java/net/schmizz/sshj/xfer/scp/SCPUploadClient.java @@ -15,12 +15,6 @@ */ package net.schmizz.sshj.xfer.scp; -import net.schmizz.sshj.common.IOUtils; -import net.schmizz.sshj.common.SSHException; -import net.schmizz.sshj.connection.channel.direct.SessionFactory; -import net.schmizz.sshj.xfer.ModeGetter; -import net.schmizz.sshj.xfer.TransferListener; - import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; @@ -29,6 +23,13 @@ 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.connection.channel.direct.SessionFactory; +import net.schmizz.sshj.xfer.LocalFile; +import net.schmizz.sshj.xfer.ModeGetter; +import net.schmizz.sshj.xfer.TransferListener; + /** Support for uploading files over a connected link using SCP. */ public final class SCPUploadClient extends SCPEngine { @@ -49,6 +50,18 @@ public final class SCPUploadClient return super.copy(sourcePath, targetPath); } + /** Upload a local file from {@code localFile} to {@code targetPath} on the remote host. */ + public synchronized int copy(LocalFile sourceFile, String remotePath) + throws IOException { + cleanSlate(); + try { + startCopy(sourceFile, remotePath); + } finally { + exit(); + } + return exitStatus; + } + public void setFileFilter(FileFilter fileFilter) { this.fileFilter = fileFilter; } @@ -61,6 +74,13 @@ public final class SCPUploadClient process(new File(sourcePath)); } + protected synchronized void startCopy(LocalFile sourceFile, String targetPath) + throws IOException { + init(targetPath); + check("Start status OK"); + process(sourceFile); + } + private File[] getChildren(File f) throws IOException { File[] files = fileFilter == null ? f.listFiles() : f.listFiles(fileFilter); @@ -92,6 +112,20 @@ public final class SCPUploadClient } else throw new IOException(f + " is not a regular file or directory"); } + + private void process(LocalFile f) + throws IOException { + if (f.isDirectory()) { + listener.startedDir(f.getName()); + sendDirectory(f); + listener.finishedDir(); + } else if (f.isFile()) { + listener.startedFile(f.getName(), f.length()); + sendFile(f); + listener.finishedFile(); + } else + throw new IOException(f + " is not a regular file or directory"); + } private void sendDirectory(File f) throws IOException { @@ -102,6 +136,15 @@ public final class SCPUploadClient sendMessage("E"); } + private void sendDirectory(LocalFile f) + throws IOException { + preserveTimeIfPossible(f); + sendMessage("D0" + getPermString(f) + " 0 " + f.getName()); + for (LocalFile child : f.getChildren()) + process(child); + sendMessage("E"); + } + private void sendFile(File f) throws IOException { preserveTimeIfPossible(f); @@ -115,16 +158,40 @@ public final class SCPUploadClient IOUtils.closeQuietly(src); } } + + private void sendFile(LocalFile f) + throws IOException { + preserveTimeIfPossible(f); + final InputStream src = f.stream(); + try { + sendMessage("C0" + getPermString(f) + " " + f.length() + " " + f.getName()); + transfer(src, scp.getOutputStream(), scp.getRemoteMaxPacketSize(), f.length()); + signal("Transfer done"); + check("Remote agrees transfer done"); + } finally { + IOUtils.closeQuietly(src); + } + } private void preserveTimeIfPossible(File f) throws IOException { if (modeGetter.preservesTimes()) sendMessage("T" + modeGetter.getLastModifiedTime(f) + " 0 " + modeGetter.getLastAccessTime(f) + " 0"); } + + private void preserveTimeIfPossible(LocalFile f) + throws IOException { + if (modeGetter.preservesTimes()) + sendMessage("T" + modeGetter.getLastModifiedTime(f) + " 0 " + modeGetter.getLastAccessTime(f) + " 0"); + } private String getPermString(File f) throws IOException { return Integer.toOctalString(modeGetter.getPermissions(f) & 07777); } - + + private String getPermString(LocalFile f) + throws IOException { + return Integer.toOctalString(modeGetter.getPermissions(f) & 07777); + } }