[SSHD-401] Allow user control over ScpCommand send/receive buffer size Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/6af9457d Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/6af9457d Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/6af9457d
Branch: refs/heads/master Commit: 6af9457d3032080d49e6dbf41d2dad0d376c9b7f Parents: d6ab2b8 Author: Guillaume Nodet <[email protected]> Authored: Tue Feb 10 11:11:14 2015 +0100 Committer: Guillaume Nodet <[email protected]> Committed: Tue Feb 10 11:11:14 2015 +0100 ---------------------------------------------------------------------- .../sshd/client/scp/DefaultScpClient.java | 6 +- .../org/apache/sshd/common/scp/ScpHelper.java | 81 ++++++++--- .../apache/sshd/server/command/ScpCommand.java | 116 +++++++++++++-- .../sshd/server/command/ScpCommandFactory.java | 145 ++++++++++++++----- 4 files changed, 283 insertions(+), 65 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java index 911eb5c..7527102 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/scp/DefaultScpClient.java @@ -104,7 +104,8 @@ public class DefaultScpClient implements ScpClient { helper.receive(target, options.contains(Option.Recursive), options.contains(Option.TargetIsDirectory), - options.contains(Option.PreserveAttributes)); + options.contains(Option.PreserveAttributes), + ScpHelper.DEFAULT_RECEIVE_BUFFER_SIZE); channel.close(false); } @@ -155,7 +156,8 @@ public class DefaultScpClient implements ScpClient { helper.send(Arrays.asList(local), options.contains(Option.Recursive), - options.contains(Option.PreserveAttributes)); + options.contains(Option.PreserveAttributes), + ScpHelper.DEFAULT_SEND_BUFFER_SIZE); channel.close(false); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java index af4daa5..517cb36 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/scp/ScpHelper.java @@ -46,6 +46,20 @@ public class ScpHelper { public static final int WARNING = 1; public static final int ERROR = 2; + /** + * Default size (in bytes) of send / receive buffer size + */ + public static final int DEFAULT_COPY_BUFFER_SIZE = 8192; + public static final int DEFAULT_RECEIVE_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE; + public static final int DEFAULT_SEND_BUFFER_SIZE = DEFAULT_COPY_BUFFER_SIZE; + + /** + * The minimum size for sending / receiving files + */ + public static final int MIN_COPY_BUFFER_SIZE = Byte.MAX_VALUE; + public static final int MIN_RECEIVE_BUFFER_SIZE = MIN_COPY_BUFFER_SIZE; + public static final int MIN_SEND_BUFFER_SIZE = MIN_COPY_BUFFER_SIZE; + public static final int S_IRUSR = 0000400; public static final int S_IWUSR = 0000200; public static final int S_IXUSR = 0000100; @@ -66,7 +80,7 @@ public class ScpHelper { this.root = root; } - public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve) throws IOException { + public void receive(SshFile path, boolean recursive, boolean shouldBeDir, boolean preserve, int bufferSize) throws IOException { if (shouldBeDir) { if (!path.doesExist()) { throw new SshException("Target directory " + path.toString() + " does not exists"); @@ -110,19 +124,19 @@ public class ScpHelper { if (recursive && isDir) { - receiveDir(line, path, time, preserve); + receiveDir(line, path, time, preserve, bufferSize); time = null; } else { - receiveFile(line, path, time, preserve); + receiveFile(line, path, time, preserve, bufferSize); time = null; } } } - public void receiveDir(String header, SshFile path, long[] time, boolean preserve) throws IOException { + public void receiveDir(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException { if (log.isDebugEnabled()) { log.debug("Receiving directory {}", path); } @@ -166,10 +180,10 @@ public class ScpHelper { header = readLine(); log.debug("Received header: " + header); if (header.startsWith("C")) { - receiveFile(header, file, time, preserve); + receiveFile(header, file, time, preserve, bufferSize); time = null; } else if (header.startsWith("D")) { - receiveDir(header, file, time, preserve); + receiveDir(header, file, time, preserve, bufferSize); time = null; } else if (header.equals("E")) { ack(); @@ -184,7 +198,7 @@ public class ScpHelper { } - public void receiveFile(String header, SshFile path, long[] time, boolean preserve) throws IOException { + public void receiveFile(String header, SshFile path, long[] time, boolean preserve, int bufferSize) throws IOException { if (log.isDebugEnabled()) { log.debug("Receiving file {}", path); } @@ -192,9 +206,24 @@ public class ScpHelper { throw new IOException("Expected a C message but got '" + header + "'"); } + if (bufferSize < MIN_RECEIVE_BUFFER_SIZE) { + throw new IOException("receiveFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_RECEIVE_BUFFER_SIZE + ")"); + } + String perms = header.substring(1, 5); long length = Long.parseLong(header.substring(6, header.indexOf(' ', 6))); String name = header.substring(header.indexOf(' ', 6) + 1); + if (length < 0L) { // TODO consider throwing an exception... + log.warn("receiveFile(" + path + ") bad length in header: " + header); + } + + // if file size is less than buffer size allocate only expected file size + int bufSize = (int) Math.min(length, bufferSize); + if (bufSize < 0) { // TODO consider throwing an exception + log.warn("receiveFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_RECEIVE_BUFFER_SIZE + ")"); + bufSize = MIN_RECEIVE_BUFFER_SIZE; + } + SshFile file; if (path.doesExist() && path.isDirectory()) { @@ -218,7 +247,7 @@ public class ScpHelper { try { ack(); - byte[] buffer = new byte[8192]; + byte[] buffer = new byte[bufSize]; while (length > 0) { int len = (int) Math.min(length, buffer.length); len = in.read(buffer, 0, len); @@ -267,7 +296,7 @@ public class ScpHelper { } } - public void send(List<String> paths, boolean recursive, boolean preserve) throws IOException { + public void send(List<String> paths, boolean recursive, boolean preserve, int bufferSize) throws IOException { readAck(false); for (String pattern : paths) { int idx = pattern.indexOf('*'); @@ -282,13 +311,13 @@ public class ScpHelper { for (String path : included) { SshFile file = root.getFile(basedir + "/" + path); if (file.isFile()) { - sendFile(file, preserve); + sendFile(file, preserve, bufferSize); } else if (file.isDirectory()) { if (!recursive) { out.write(ScpHelper.WARNING); out.write((path + " not a regular file\n").getBytes()); } else { - sendDir(file, preserve); + sendDir(file, preserve, bufferSize); } } else { out.write(ScpHelper.WARNING); @@ -307,12 +336,12 @@ public class ScpHelper { throw new IOException(file + ": no such file or directory"); } if (file.isFile()) { - sendFile(file, preserve); + sendFile(file, preserve, bufferSize); } else if (file.isDirectory()) { if (!recursive) { throw new IOException(file + " not a regular file"); } else { - sendDir(file, preserve); + sendDir(file, preserve, bufferSize); } } else { throw new IOException(file + ": unknown file type"); @@ -321,11 +350,15 @@ public class ScpHelper { } } - public void sendFile(SshFile path, boolean preserve) throws IOException { + public void sendFile(SshFile path, boolean preserve, int bufferSize) throws IOException { if (log.isDebugEnabled()) { log.debug("Sending file {}", path); } + if (bufferSize < MIN_SEND_BUFFER_SIZE) { + throw new IOException("sendFile(" + path + ") buffer size (" + bufferSize + ") below minimum (" + MIN_SEND_BUFFER_SIZE + ")"); + } + Map<SshFile.Attribute,Object> attrs = path.getAttributes(true); if (preserve) { StringBuffer buf = new StringBuffer(); @@ -355,9 +388,21 @@ public class ScpHelper { out.flush(); readAck(false); + long fileSize = path.getSize(); + if (fileSize < 0L) { // TODO consider throwing an exception... + log.warn("sendFile(" + path + ") bad file size: " + fileSize); + } + + // if file size is less than buffer size allocate only expected file size + int bufSize = (int) Math.min(fileSize, bufferSize); + if (bufSize < 0) { // TODO consider throwing an exception + log.warn("sendFile(" + path + ") bad buffer size (" + bufSize + ") using default (" + MIN_SEND_BUFFER_SIZE + ")"); + bufSize = MIN_SEND_BUFFER_SIZE; + } + InputStream is = path.createInputStream(0); try { - byte[] buffer = new byte[8192]; + byte[] buffer = new byte[bufSize]; for (;;) { int len = is.read(buffer, 0, buffer.length); if (len == -1) { @@ -372,7 +417,7 @@ public class ScpHelper { readAck(false); } - public void sendDir(SshFile path, boolean preserve) throws IOException { + public void sendDir(SshFile path, boolean preserve, int bufferSize) throws IOException { if (log.isDebugEnabled()) { log.debug("Sending directory {}", path); } @@ -407,9 +452,9 @@ public class ScpHelper { for (SshFile child : path.listSshFiles()) { if (child.isFile()) { - sendFile(child, preserve); + sendFile(child, preserve, bufferSize); } else if (child.isDirectory()) { - sendDir(child, preserve); + sendDir(child, preserve, bufferSize); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java index 4355be4..68dc9ef 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommand.java @@ -63,26 +63,107 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { protected ExecutorService executors; protected boolean shutdownExecutor; protected Future<?> pendingFuture; + protected int sendBufferSize; + protected int receiveBufferSize; + /** + * Simple constructor - uses an ad-hoc {@link ExecutorService} to + * run the command as well as default send / receive buffer sizes + * + * @param command The command to be executed + * @see #ScpCommand(String, ExecutorService, boolean, int, int) + * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE + */ public ScpCommand(String command) { - this(command, null); + this(command, ScpHelper.DEFAULT_COPY_BUFFER_SIZE); } + /** + * Uses an ad-hoc {@link ExecutorService} to run the command + * + * @param command The command to be executed + * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U> + * sending and receiving files + * @see #ScpCommand(String, ExecutorService, int) + */ + public ScpCommand(String command, int bufferSize) { + this(command, null, bufferSize); + } + + /** + * @param command The command to be executed + * @param executorService An {@link ExecutorService} to be used when + * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc + * single-threaded service is created and used. <B>Note:</B> the + * executor service will <U>not</U> be shutdown when command terminates + * unless it is the ad-hoc service + * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U> + * sending and receiving files + * @see #ScpCommand(String, ExecutorService, boolean, int, int) + */ + public ScpCommand(String command, ExecutorService executorService, int bufferSize) { + this(command, executorService, false, bufferSize); + } + + /** + * @param command The command to be executed + * @param executorService An {@link ExecutorService} to be used when + * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc + * single-threaded service is created and used. <B>Note:</B> the + * executor service will <U>not</U> be shutdown when command terminates + * unless it is the ad-hoc service + * @see #ScpCommand(String, ExecutorService, boolean, int) + */ public ScpCommand(String command, ExecutorService executorService) { this(command, executorService, false); } /** - * @param command The command to be executed + * @param command The command to be executed * @param executorService An {@link ExecutorService} to be used when - * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc - * single-threaded service is created and used. - * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} - * will be called when command terminates - unless it is the ad-hoc - * service, which will be shutdown regardless - * @see ThreadUtils#newSingleThreadExecutor(String) + * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc + * single-threaded service is created and used. + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @see #ScpCommand(String, ExecutorService, boolean, int) + * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE */ public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit) { + this(command, executorService, false, ScpHelper.DEFAULT_COPY_BUFFER_SIZE); + } + + /** + * @param command The command to be executed + * @param executorService An {@link ExecutorService} to be used when + * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc + * single-threaded service is created and used. + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U> + * sending and receiving files + * @see #ScpCommand(String, ExecutorService, boolean, int, int) + */ + public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int bufferSize) { + this(command, executorService, shutdownOnExit, bufferSize, bufferSize); + } + + /** + * @param command The command to be executed + * @param executorService An {@link ExecutorService} to be used when + * {@link #start(Environment)}-ing execution. If {@code null} an ad-hoc + * single-threaded service is created and used. + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @param sendSize Size (in bytes) of buffer to use when sending files + * @param receiveSize Size (in bytes) of buffer to use when receiving files + * @see ThreadUtils#newSingleThreadExecutor(String) + * @see ScpHelper#MIN_SEND_BUFFER_SIZE + * @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE + */ + public ScpCommand(String command, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize) { name = command; if ((executors = executorService) == null) { @@ -93,6 +174,14 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { shutdownExecutor = shutdownOnExit; } + if ((sendBufferSize = sendSize) < ScpHelper.MIN_SEND_BUFFER_SIZE) { + throw new IllegalArgumentException("<ScpCommmand>(" + command + ") send buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")"); + } + + if ((receiveBufferSize = receiveSize) < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) { + throw new IllegalArgumentException("<ScpCommmand>(" + command + ") receive buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")"); + } + log.debug("Executing command {}", command); String[] args = command.split(" "); for (int i = 1; i < args.length; i++) { @@ -120,7 +209,7 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { } } } else { - path = command.substring(command.indexOf(args[i-1]) + args[i-1].length() + 1); + path = command.substring(command.indexOf(args[i - 1]) + args[i - 1].length() + 1); if (path.startsWith("\"") && path.endsWith("\"") || path.startsWith("'") && path.endsWith("'")) { path = path.substring(1, path.length() - 1); } @@ -193,9 +282,9 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { ScpHelper helper = new ScpHelper(in, out, root); try { if (optT) { - helper.receive(root.getFile(path), optR, optD, optP); + helper.receive(root.getFile(path), optR, optD, optP, receiveBufferSize); } else if (optF) { - helper.send(Collections.singletonList(path), optR, optP); + helper.send(Collections.singletonList(path), optR, optP, sendBufferSize); } else { throw new IOException("Unsupported mode"); } @@ -218,5 +307,8 @@ public class ScpCommand implements Command, Runnable, FileSystemAware { } } - + @Override + public String toString() { + return name; + } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/6af9457d/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java index 07ec477..87e980f 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/command/ScpCommandFactory.java @@ -20,6 +20,7 @@ package org.apache.sshd.server.command; import java.util.concurrent.ExecutorService; +import org.apache.sshd.common.scp.ScpHelper; import org.apache.sshd.server.Command; import org.apache.sshd.server.CommandFactory; @@ -41,15 +42,37 @@ public class ScpCommandFactory implements CommandFactory { private CommandFactory delegate; private ExecutorService executors; private boolean shutdownExecutor; + private int sendBufferSize; + private int receiveBufferSize; + /** + * Default constructor - uses an ad-hoc {@link ExecutorService} with + * no delegate {@link CommandFactory} and default send/receive buffer + * sizes + * + * @see ScpHelper#DEFAULT_COPY_BUFFER_SIZE + */ public ScpCommandFactory() { this(null, null); } /** + * Uses an ad-hoc {@link ExecutorService} with no delegate {@link CommandFactory} + * + * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U> + * sending and receiving files + * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int, int) + */ + public ScpCommandFactory(int bufferSize) { + this(null, null, true, bufferSize); + } + + /** * @param executorService An {@link ExecutorService} to be used when - * starting {@link ScpCommand} execution. If {@code null} an ad-hoc - * single-threaded service is created and used. + * starting {@link ScpCommand} execution. If {@code null} an ad-hoc + * single-threaded service is created and used. <B>Note:</B> the + * executor service will <U>not</U> be shutdown when command terminates + * unless it is the ad-hoc service */ public ScpCommandFactory(ExecutorService executorService) { this(null, executorService); @@ -57,9 +80,9 @@ public class ScpCommandFactory implements CommandFactory { /** * @param delegateFactory A {@link CommandFactory} to be used if the - * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} - * will be thrown when attempting to invoke {@link #createCommand(String)} - * with a non-SCP command + * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} + * will be thrown when attempting to invoke {@link #createCommand(String)} + * with a non-SCP command * @see #SCP_COMMAND_PREFIX */ public ScpCommandFactory(CommandFactory delegateFactory) { @@ -68,14 +91,14 @@ public class ScpCommandFactory implements CommandFactory { /** * @param delegateFactory A {@link CommandFactory} to be used if the - * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} - * will be thrown when attempting to invoke {@link #createCommand(String)} - * with a non-SCP command + * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} + * will be thrown when attempting to invoke {@link #createCommand(String)} + * with a non-SCP command * @param executorService An {@link ExecutorService} to be used when - * starting {@link ScpCommand} execution. If {@code null} then a single-threaded - * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown - * when the command is terminated - unless it is the ad-hoc service, which will be - * shutdown regardless + * starting {@link ScpCommand} execution. If {@code null} then a single-threaded + * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown + * when the command is terminated - unless it is the ad-hoc service, which will be + * shutdown regardless * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean) */ public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService) { @@ -84,22 +107,72 @@ public class ScpCommandFactory implements CommandFactory { /** * @param delegateFactory A {@link CommandFactory} to be used if the - * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} - * will be thrown when attempting to invoke {@link #createCommand(String)} - * with a non-SCP command + * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} + * will be thrown when attempting to invoke {@link #createCommand(String)} + * with a non-SCP command * @param executorService An {@link ExecutorService} to be used when - * starting {@link ScpCommand} execution. If {@code null} then a single-threaded - * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown - * when the command is terminated - unless it is the ad-hoc service, which will be - * shutdown regardless - * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} - * will be called when command terminates - unless it is the ad-hoc - * service, which will be shutdown regardless + * starting {@link ScpCommand} execution. If {@code null} then a single-threaded + * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown + * when the command is terminated - unless it is the ad-hoc service, which will be + * shutdown regardless + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int) */ public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit) { + this(delegateFactory, executorService, shutdownOnExit, ScpHelper.DEFAULT_COPY_BUFFER_SIZE); + } + + /** + * @param delegateFactory A {@link CommandFactory} to be used if the + * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} + * will be thrown when attempting to invoke {@link #createCommand(String)} + * with a non-SCP command + * @param executorService An {@link ExecutorService} to be used when + * starting {@link ScpCommand} execution. If {@code null} then a single-threaded + * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown + * when the command is terminated - unless it is the ad-hoc service, which will be + * shutdown regardless + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @param bufferSize Size (in bytes) of buffer to be used for <U>both</U> + * sending and receiving files + * @see #ScpCommandFactory(CommandFactory, ExecutorService, boolean, int, int) + */ + public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit, int bufferSize) { + this(delegateFactory, executorService, shutdownOnExit, bufferSize, bufferSize); + } + + /** + * @param delegateFactory A {@link CommandFactory} to be used if the + * command is not an SCP one. If {@code null} then an {@link IllegalArgumentException} + * will be thrown when attempting to invoke {@link #createCommand(String)} + * with a non-SCP command + * @param executorService An {@link ExecutorService} to be used when + * starting {@link ScpCommand} execution. If {@code null} then a single-threaded + * ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown + * when the command is terminated - unless it is the ad-hoc service, which will be + * shutdown regardless + * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()} + * will be called when command terminates - unless it is the ad-hoc + * service, which will be shutdown regardless + * @param sendSize Size (in bytes) of buffer to use when sending files + * @param receiveSize Size (in bytes) of buffer to use when receiving files + * @see ScpHelper#MIN_SEND_BUFFER_SIZE + * @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE + */ + public ScpCommandFactory(CommandFactory delegateFactory, ExecutorService executorService, boolean shutdownOnExit, int sendSize, int receiveSize) { delegate = delegateFactory; executors = executorService; shutdownExecutor = shutdownOnExit; + if ((sendBufferSize = sendSize) < ScpHelper.MIN_SEND_BUFFER_SIZE) { + throw new IllegalArgumentException("<ScpCommandFactory>() send buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")"); + } + if ((receiveBufferSize = receiveSize) < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) { + throw new IllegalArgumentException("<ScpCommandFactory>() receive buffer size (" + sendSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")"); + } } public CommandFactory getDelegateCommandFactory() { @@ -114,6 +187,14 @@ public class ScpCommandFactory implements CommandFactory { return shutdownExecutor; } + public int getSendBufferSize() { + return sendBufferSize; + } + + public int getReceiveBufferSize() { + return receiveBufferSize; + } + /** * Parses a command string and verifies that the basic syntax is * correct. If parsing fails the responsibility is delegated to @@ -126,17 +207,15 @@ public class ScpCommandFactory implements CommandFactory { * @see #SCP_COMMAND_PREFIX */ public Command createCommand(String command) { - try { - if (!command.startsWith(SCP_COMMAND_PREFIX)) { - throw new IllegalArgumentException("Unknown command, does not begin with '" + SCP_COMMAND_PREFIX + "': " + command); - } - - return new ScpCommand(command, getExecutorService(), isShutdownOnExit()); - } catch (IllegalArgumentException iae) { - if (delegate != null) { - return delegate.createCommand(command); - } - throw iae; + if (command.startsWith(SCP_COMMAND_PREFIX)) { + return new ScpCommand(command, getExecutorService(), isShutdownOnExit(), getSendBufferSize(), getReceiveBufferSize()); } + + CommandFactory factory = getDelegateCommandFactory(); + if (factory != null) { + return factory.createCommand(command); + } + + throw new IllegalArgumentException("Unknown command, does not begin with '" + SCP_COMMAND_PREFIX + "': " + command); } }
