You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2018/11/18 09:33:02 UTC
[4/5] mina-sshd git commit: Added SftpCommandMain upload/download
marker progress indication (default=true)
Added SftpCommandMain upload/download marker progress indication (default=true)
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/bd40cd0c
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/bd40cd0c
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/bd40cd0c
Branch: refs/heads/master
Commit: bd40cd0c70866ec4543b61cbf87c57296296019b
Parents: 1a8924d
Author: Lyor Goldstein <lg...@apache.org>
Authored: Sun Nov 18 11:12:28 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Sun Nov 18 11:32:53 2018 +0200
----------------------------------------------------------------------
CHANGES.md | 14 ++
.../apache/sshd/cli/client/SftpCommandMain.java | 201 ++++++++++++++-----
.../SftpFileTransferProgressOutputStream.java | 129 ++++++++++++
3 files changed, 297 insertions(+), 47 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/bd40cd0c/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index e79d215..60f941b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -88,3 +88,17 @@ in order to provide key file(s) location information
* [SSHD-866](https://issues.apache.org/jira/browse/SSHD-866) - Counting empty challenges separately when enforcing
max. attempts during `keyboard-interactive` authentication
+
+* `SftpCommandMain` shows by default `get/put` command progress using the hash sign (`#`) marker. The marker
+can be enabled/disabled via the `progress` command:
+
+```
+ > progress
+
+ ... reponse is whether it is 'on' or 'off'
+
+ > progress on/off
+
+ ... set the progress marker indicator ...
+
+```
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/bd40cd0c/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index 99427ff..694d937 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -41,6 +41,7 @@ import java.util.ServiceLoader;
import java.util.TreeMap;
import java.util.logging.Level;
+import org.apache.sshd.cli.client.helper.SftpFileTransferProgressOutputStream;
import org.apache.sshd.client.ClientFactoryManager;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.client.subsystem.sftp.SftpClient;
@@ -89,6 +90,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
private final Map<String, SftpCommandExecutor> commandsMap;
private String cwdRemote;
private String cwdLocal;
+ private boolean showProgress = true;
public SftpCommandMain(SftpClient client) {
this.client = Objects.requireNonNull(client, "No client");
@@ -113,6 +115,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
new StatVfsCommandExecutor(),
new GetCommandExecutor(),
new PutCommandExecutor(),
+ new ProgressCommandExecutor(),
new HelpCommandExecutor()
)) {
String name = e.getName();
@@ -205,9 +208,11 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
}
- protected <A extends Appendable> A appendFileAttributes(A stdout, SftpClient sftp, String path, Attributes attrs) throws IOException {
- stdout.append('\t').append(Long.toString(attrs.getSize()))
- .append('\t').append(SftpFileSystemProvider.getRWXPermissions(attrs.getPermissions()));
+ protected <A extends Appendable> A appendFileAttributes(
+ A stdout, SftpClient sftp, String path, Attributes attrs)
+ throws IOException {
+ stdout.append(" ").append(Long.toString(attrs.getSize()))
+ .append(" ").append(SftpFileSystemProvider.getRWXPermissions(attrs.getPermissions()));
if (attrs.isSymbolicLink()) {
String linkValue = sftp.readLink(path);
stdout.append(" => ")
@@ -234,6 +239,14 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
cwdLocal = path;
}
+ public boolean isShowProgress() {
+ return showProgress;
+ }
+
+ public void setShowProgress(boolean showProgress) {
+ this.showProgress = showProgress;
+ }
+
@Override
public boolean isOpen() {
return client.isOpen();
@@ -249,7 +262,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
//////////////////////////////////////////////////////////////////////////
public static <A extends Appendable> A appendInfoValue(A sb, CharSequence name, Object value) throws IOException {
- sb.append('\t').append(name).append(": ").append(Objects.toString(value));
+ sb.append(" ").append(name).append(": ").append(Objects.toString(value));
return sb;
}
@@ -309,7 +322,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
setupLogging(level, stdout, stderr, logStream);
}
- ClientSession session = (logStream == null) ? null : setupClientSession(SFTP_PORT_OPTION, stdin, stdout, stderr, args);
+ ClientSession session = (logStream == null)
+ ? null
+ : setupClientSession(SFTP_PORT_OPTION, stdin, stdout, stderr, args);
if (session == null) {
System.err.println("usage: sftp [-v[v][v]] [-E logoutput] [-i identity] [-io nio2|mina|netty]"
+ " [-l login] [" + SFTP_PORT_OPTION + " port] [-o option=value]"
@@ -347,7 +362,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
stdout.println("Exiting");
return true;
@@ -365,10 +382,12 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
- stdout.append('\t').append("Remote: ").println(getCurrentRemoteDirectory());
- stdout.append('\t').append("Local: ").println(getCurrentLocalDirectory());
+ stdout.append(" ").append("Remote: ").println(getCurrentRemoteDirectory());
+ stdout.append(" ").append("Local: ").println(getCurrentLocalDirectory());
return false;
}
}
@@ -384,7 +403,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
SftpClient sftp = getClient();
ClientSession session = sftp.getSession();
@@ -414,7 +435,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
SftpClient sftp = getClient();
ClientSession session = sftp.getSession();
@@ -442,11 +465,13 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
SftpClient sftp = getClient();
Session session = sftp.getSession();
- stdout.append('\t').println(session.getServerVersion());
+ stdout.append(" ").println(session.getServerVersion());
Map<String, byte[]> extensions = sftp.getServerExtensions();
Map<String, ?> parsed = ParserUtils.parse(extensions);
@@ -457,7 +482,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
extensions.forEach((name, value) -> {
Object info = parsed.get(name);
- stdout.append('\t').append(name).append(": ");
+ stdout.append(" ").append(name).append(": ");
if (info == null) {
stdout.println(BufferUtils.toHex(value));
} else {
@@ -480,10 +505,12 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
SftpClient sftp = getClient();
- stdout.append('\t').println(sftp.getVersion());
+ stdout.append(" ").println(sftp.getVersion());
return false;
}
}
@@ -499,7 +526,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String newPath = resolveRemotePath(args);
@@ -520,7 +549,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
if (GenericUtils.isEmpty(args)) {
setCurrentLocalDirectory(System.getProperty("user.home"));
} else {
@@ -545,7 +576,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String path = resolveRemotePath(args);
@@ -566,7 +599,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
String[] comps = GenericUtils.split(args, ' ');
int numComps = GenericUtils.length(comps);
String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
@@ -584,7 +619,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
for (SftpClient.DirEntry entry : sftp.readDir(path)) {
String fileName = entry.getFilename();
SftpClient.Attributes attrs = entry.getAttributes();
- appendFileAttributes(stdout.append('\t').append(fileName), sftp, path + "/" + fileName, attrs).println();
+ appendFileAttributes(stdout.append(" ").append(fileName), sftp, path + "/" + fileName, attrs).println();
if (showLongName) {
stdout.append("\t\tlong-name: ").println(entry.getLongFilename());
}
@@ -605,7 +640,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
String[] comps = GenericUtils.split(args, ' ');
int numArgs = GenericUtils.length(comps);
ValidateUtils.checkTrue(numArgs >= 1, "No arguments");
@@ -643,14 +680,16 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
} else {
sftp.remove(path);
if (verbose) {
- stdout.append('\t').append("Removed ").println(path);
+ stdout.append(" ").append("Removed ").println(path);
}
}
return false;
}
- private void removeRecursive(SftpClient sftp, String path, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
+ private void removeRecursive(
+ SftpClient sftp, String path, Attributes attrs, PrintStream stdout, boolean verbose)
+ throws IOException {
if (attrs.isDirectory()) {
for (DirEntry entry : sftp.readDir(path)) {
String name = entry.getFilename();
@@ -666,13 +705,13 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
sftp.remove(path);
} else {
if (verbose) {
- stdout.append('\t').append("Skip special file ").println(path);
+ stdout.append(" ").append("Skip special file ").println(path);
return;
}
}
if (verbose) {
- stdout.append('\t').append("Removed ").println(path);
+ stdout.append(" ").append("Removed ").println(path);
}
}
}
@@ -688,7 +727,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkNotNullAndNotEmpty(args, "No remote directory specified");
String path = resolveRemotePath(args);
@@ -709,7 +750,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
String[] comps = GenericUtils.split(args, ' ');
ValidateUtils.checkTrue(GenericUtils.length(comps) == 2, "Invalid number of arguments: %s", args);
@@ -732,7 +775,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
String[] comps = GenericUtils.split(args, ' ');
int numArgs = GenericUtils.length(comps);
ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
@@ -741,7 +786,8 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
OpenSSHStatPathExtension ext = sftp.getExtension(OpenSSHStatPathExtension.class);
ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName());
- String remPath = resolveRemotePath((numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
+ String remPath = resolveRemotePath(
+ (numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
OpenSSHStatExtensionInfo info = ext.stat(remPath);
Field[] fields = info.getClass().getFields();
for (Field f : fields) {
@@ -752,7 +798,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
Object value = f.get(info);
- stdout.append('\t').append(name).append(": ").println(value);
+ stdout.append(" ").append(name).append(": ").println(value);
}
return false;
@@ -770,7 +816,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
String[] comps = GenericUtils.split(args, ' ');
ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
@@ -800,7 +848,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
SftpClient client = getClient();
String linkData = client.readLink(path);
- stdout.append('\t').println(linkData);
+ stdout.append(" ").println(linkData);
return false;
}
}
@@ -817,10 +865,12 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
@Override
@SuppressWarnings("synthetic-access")
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
ValidateUtils.checkTrue(GenericUtils.isEmpty(args), "Unexpected arguments: %s", args);
for (String cmd : commandsMap.keySet()) {
- stdout.append('\t').println(cmd);
+ stdout.append(" ").println(cmd);
}
return false;
}
@@ -846,7 +896,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
createDirectories(sftp, remotePath.substring(0, pos));
}
- protected void transferFile(SftpClient sftp, Path localPath, String remotePath, boolean upload, PrintStream stdout, boolean verbose) throws IOException {
+ protected void transferFile(
+ SftpClient sftp, Path localPath, String remotePath, boolean upload, PrintStream stdout, boolean verbose)
+ throws IOException {
// Create the file's hierarchy
if (upload) {
int pos = remotePath.lastIndexOf('/');
@@ -856,19 +908,33 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
Files.createDirectories(localPath.getParent());
}
+ boolean withProgress = isShowProgress();
+ long copySize;
try (InputStream input = upload ? Files.newInputStream(localPath) : sftp.read(remotePath);
- OutputStream output = upload ? sftp.write(remotePath) : Files.newOutputStream(localPath)) {
- IoUtils.copy(input, output, SftpClient.IO_BUFFER_SIZE);
+ OutputStream target = upload ? sftp.write(remotePath) : Files.newOutputStream(localPath);
+ OutputStream output = withProgress ? new SftpFileTransferProgressOutputStream(target, stdout) : target) {
+ if (withProgress) {
+ stdout.println();
+ }
+
+ copySize = IoUtils.copy(input, output, SftpClient.IO_BUFFER_SIZE);
+
+ if (withProgress) {
+ stdout.println();
+ }
}
if (verbose) {
- stdout.append('\t')
- .append("Copied ").append(upload ? localPath.toString() : remotePath)
- .append(" to ").println(upload ? remotePath : localPath.toString());
+ stdout.append(" ")
+ .append("Copied ").append(Long.toString(copySize)).append(" bytes")
+ .append(" from ").append(upload ? localPath.toString() : remotePath)
+ .append(" to ").println(upload ? remotePath : localPath.toString());
}
}
- protected void transferRemoteDir(SftpClient sftp, Path localPath, String remotePath, Attributes attrs, PrintStream stdout, boolean verbose) throws IOException {
+ protected void transferRemoteDir(
+ SftpClient sftp, Path localPath, String remotePath, Attributes attrs, PrintStream stdout, boolean verbose)
+ throws IOException {
if (attrs.isDirectory()) {
for (DirEntry entry : sftp.readDir(remotePath)) {
String name = entry.getFilename();
@@ -882,12 +948,14 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
transferFile(sftp, localPath, remotePath, false, stdout, verbose);
} else {
if (verbose) {
- stdout.append('\t').append("Skip remote special file ").println(remotePath);
+ stdout.append(" ").append("Skip remote special file ").println(remotePath);
}
}
}
- protected void transferLocalDir(SftpClient sftp, Path localPath, String remotePath, PrintStream stdout, boolean verbose) throws IOException {
+ protected void transferLocalDir(
+ SftpClient sftp, Path localPath, String remotePath, PrintStream stdout, boolean verbose)
+ throws IOException {
if (Files.isDirectory(localPath)) {
try (DirectoryStream<Path> ds = Files.newDirectoryStream(localPath)) {
for (Path entry : ds) {
@@ -899,7 +967,7 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
transferFile(sftp, localPath, remotePath, true, stdout, verbose);
} else {
if (verbose) {
- stdout.append('\t').append("Skip local special file ").println(localPath);
+ stdout.append(" ").append("Skip local special file ").println(localPath);
}
}
}
@@ -980,7 +1048,9 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
executeCommand(args, false, stdout);
return false;
}
@@ -997,9 +1067,46 @@ public class SftpCommandMain extends SshClientCliSupport implements Channel {
}
@Override
- public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr) throws Exception {
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
executeCommand(args, true, stdout);
return false;
}
}
+
+ private class ProgressCommandExecutor implements SftpCommandExecutor {
+ ProgressCommandExecutor() {
+ super();
+ }
+
+ @Override
+ public String getName() {
+ return "progress";
+ }
+
+ @Override
+ public boolean executeCommand(
+ String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
+ throws Exception {
+ String[] comps = GenericUtils.split(args, ' ');
+ int numArgs = GenericUtils.length(comps);
+ if (numArgs <= 0) {
+ stdout.append(" ").append(getName()).append(' ').println(isShowProgress() ? "on" : "off");
+ } else {
+ ValidateUtils.checkTrue(numArgs == 1, "Invalid arguments count: %d", numArgs);
+
+ String argVal = comps[0];
+ if ("on".equalsIgnoreCase(argVal)) {
+ setShowProgress(true);
+ } else if ("off".equalsIgnoreCase(argVal)) {
+ setShowProgress(false);
+ } else {
+ throw new IllegalArgumentException("Unknown value: " + argVal);
+ }
+ }
+
+ return false;
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/bd40cd0c/sshd-cli/src/main/java/org/apache/sshd/cli/client/helper/SftpFileTransferProgressOutputStream.java
----------------------------------------------------------------------
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/helper/SftpFileTransferProgressOutputStream.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/helper/SftpFileTransferProgressOutputStream.java
new file mode 100644
index 0000000..26330bc
--- /dev/null
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/helper/SftpFileTransferProgressOutputStream.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.sshd.cli.client.helper;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpFileTransferProgressOutputStream extends FilterOutputStream {
+ public static final char DEFAULT_PROGRESS_CHAR = '#';
+ public static final int DEFAULT_MARKS_PER_LINE = 72;
+ public static final int DEFAULT_MARKER_SIZE = IoUtils.DEFAULT_COPY_SIZE;
+
+ private final int markerSize;
+ private final char markerChar;
+ private final int markersPerLine;
+ private final Appendable stdout;
+ private final byte[] workBuf = {0};
+ private long byteCount;
+ private long lastMarkOffset;
+ private int curMarkersInLine;
+
+ public SftpFileTransferProgressOutputStream(OutputStream out, Appendable stdout) {
+ this(out, DEFAULT_MARKER_SIZE, DEFAULT_PROGRESS_CHAR, DEFAULT_MARKS_PER_LINE, stdout);
+ }
+
+ public SftpFileTransferProgressOutputStream(
+ OutputStream out, int markerSize, char markerChar, int markersPerLine, Appendable stdout) {
+ super(Objects.requireNonNull(out, "No target stream"));
+
+ ValidateUtils.checkTrue(markerSize > 0, "Invalid marker size: %d", markerSize);
+ this.markerSize = markerSize;
+
+ if ((markerChar <= ' ') || (markerChar > 0x7E)) {
+ throw new IllegalArgumentException("Non-printable marker character: 0x" + Integer.toHexString(markerChar));
+ }
+ this.markerChar = markerChar;
+
+ ValidateUtils.checkTrue(markersPerLine > 0, "Invalid markers per line: %d", markersPerLine);
+
+ this.markersPerLine = markersPerLine;
+ this.stdout = Objects.requireNonNull(stdout, "No progress report target");
+ }
+
+ public int getMarkerSize() {
+ return markerSize;
+ }
+
+ public char getMarkerChar() {
+ return markerChar;
+ }
+
+ public int getMarkersPerLine() {
+ return markersPerLine;
+ }
+
+ public Appendable getStdout() {
+ return stdout;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ workBuf[0] = (byte) (b & 0xFF);
+ write(workBuf, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if ((len < 0) || (off < 0)) {
+ throw new StreamCorruptedException("Invalid offset (" + off + ")/length(" + len + ")");
+ }
+ this.out.write(b, off, len);
+
+ byteCount += len;
+
+ long reportDiff = byteCount - lastMarkOffset;
+ int reportSize = getMarkerSize();
+ long markersCount = reportDiff / reportSize;
+ appendMarkers((int) markersCount);
+ lastMarkOffset += markersCount * reportSize;
+ }
+
+ protected void appendMarkers(int markersCount) throws IOException {
+ if (markersCount <= 0) {
+ return;
+ }
+
+ Appendable target = getStdout();
+ char marker = getMarkerChar();
+ for (int index = 1, limit = getMarkersPerLine(); index <= markersCount; index++) {
+ target.append(marker);
+ curMarkersInLine++;
+ if (curMarkersInLine >= limit) {
+ target.append(System.lineSeparator());
+ curMarkersInLine = 0;
+ }
+ }
+ }
+}