|
@@ -15,62 +15,107 @@
|
|
|
* See the License for the specific language governing permissions and
|
|
|
* limitations under the License.
|
|
|
*/
|
|
|
+
|
|
|
package org.apache.hadoop.fs.sftp;
|
|
|
|
|
|
+import java.io.EOFException;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
|
|
|
+import com.jcraft.jsch.ChannelSftp;
|
|
|
+import com.jcraft.jsch.SftpATTRS;
|
|
|
+import com.jcraft.jsch.SftpException;
|
|
|
+
|
|
|
+import org.apache.hadoop.fs.FSExceptionMessages;
|
|
|
import org.apache.hadoop.fs.FSInputStream;
|
|
|
import org.apache.hadoop.fs.FileSystem;
|
|
|
+import org.apache.hadoop.fs.Path;
|
|
|
|
|
|
/** SFTP FileSystem input stream. */
|
|
|
class SFTPInputStream extends FSInputStream {
|
|
|
|
|
|
- public static final String E_SEEK_NOTSUPPORTED = "Seek not supported";
|
|
|
- public static final String E_NULL_INPUTSTREAM = "Null InputStream";
|
|
|
- public static final String E_STREAM_CLOSED = "Stream closed";
|
|
|
-
|
|
|
+ private final ChannelSftp channel;
|
|
|
+ private final Path path;
|
|
|
private InputStream wrappedStream;
|
|
|
private FileSystem.Statistics stats;
|
|
|
private boolean closed;
|
|
|
private long pos;
|
|
|
+ private long nextPos;
|
|
|
+ private long contentLength;
|
|
|
|
|
|
- SFTPInputStream(InputStream stream, FileSystem.Statistics stats) {
|
|
|
-
|
|
|
- if (stream == null) {
|
|
|
- throw new IllegalArgumentException(E_NULL_INPUTSTREAM);
|
|
|
+ SFTPInputStream(ChannelSftp channel, Path path, FileSystem.Statistics stats)
|
|
|
+ throws IOException {
|
|
|
+ try {
|
|
|
+ this.channel = channel;
|
|
|
+ this.path = path;
|
|
|
+ this.stats = stats;
|
|
|
+ this.wrappedStream = channel.get(path.toUri().getPath());
|
|
|
+ SftpATTRS stat = channel.lstat(path.toString());
|
|
|
+ this.contentLength = stat.getSize();
|
|
|
+ } catch (SftpException e) {
|
|
|
+ throw new IOException(e);
|
|
|
}
|
|
|
- this.wrappedStream = stream;
|
|
|
- this.stats = stats;
|
|
|
+ }
|
|
|
|
|
|
- this.pos = 0;
|
|
|
- this.closed = false;
|
|
|
+ @Override
|
|
|
+ public synchronized void seek(long position) throws IOException {
|
|
|
+ checkNotClosed();
|
|
|
+ if (position < 0) {
|
|
|
+ throw new EOFException(FSExceptionMessages.NEGATIVE_SEEK);
|
|
|
+ }
|
|
|
+ nextPos = position;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public void seek(long position) throws IOException {
|
|
|
- throw new IOException(E_SEEK_NOTSUPPORTED);
|
|
|
+ public synchronized int available() throws IOException {
|
|
|
+ checkNotClosed();
|
|
|
+ long remaining = contentLength - nextPos;
|
|
|
+ if (remaining > Integer.MAX_VALUE) {
|
|
|
+ return Integer.MAX_VALUE;
|
|
|
+ }
|
|
|
+ return (int) remaining;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void seekInternal() throws IOException {
|
|
|
+ if (pos == nextPos) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (nextPos > pos) {
|
|
|
+ long skipped = wrappedStream.skip(nextPos - pos);
|
|
|
+ pos = pos + skipped;
|
|
|
+ }
|
|
|
+ if (nextPos < pos) {
|
|
|
+ wrappedStream.close();
|
|
|
+ try {
|
|
|
+ wrappedStream = channel.get(path.toUri().getPath());
|
|
|
+ pos = wrappedStream.skip(nextPos);
|
|
|
+ } catch (SftpException e) {
|
|
|
+ throw new IOException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean seekToNewSource(long targetPos) throws IOException {
|
|
|
- throw new IOException(E_SEEK_NOTSUPPORTED);
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public long getPos() throws IOException {
|
|
|
- return pos;
|
|
|
+ public synchronized long getPos() throws IOException {
|
|
|
+ return nextPos;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public synchronized int read() throws IOException {
|
|
|
- if (closed) {
|
|
|
- throw new IOException(E_STREAM_CLOSED);
|
|
|
+ checkNotClosed();
|
|
|
+ if (this.contentLength == 0 || (nextPos >= contentLength)) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
-
|
|
|
+ seekInternal();
|
|
|
int byteRead = wrappedStream.read();
|
|
|
if (byteRead >= 0) {
|
|
|
pos++;
|
|
|
+ nextPos++;
|
|
|
}
|
|
|
if (stats != null & byteRead >= 0) {
|
|
|
stats.incrementBytesRead(1);
|
|
@@ -78,23 +123,6 @@ class SFTPInputStream extends FSInputStream {
|
|
|
return byteRead;
|
|
|
}
|
|
|
|
|
|
- public synchronized int read(byte[] buf, int off, int len)
|
|
|
- throws IOException {
|
|
|
- if (closed) {
|
|
|
- throw new IOException(E_STREAM_CLOSED);
|
|
|
- }
|
|
|
-
|
|
|
- int result = wrappedStream.read(buf, off, len);
|
|
|
- if (result > 0) {
|
|
|
- pos += result;
|
|
|
- }
|
|
|
- if (stats != null & result > 0) {
|
|
|
- stats.incrementBytesRead(result);
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
public synchronized void close() throws IOException {
|
|
|
if (closed) {
|
|
|
return;
|
|
@@ -103,4 +131,12 @@ class SFTPInputStream extends FSInputStream {
|
|
|
wrappedStream.close();
|
|
|
closed = true;
|
|
|
}
|
|
|
+
|
|
|
+ private void checkNotClosed() throws IOException {
|
|
|
+ if (closed) {
|
|
|
+ throw new IOException(
|
|
|
+ path.toUri() + ": " + FSExceptionMessages.STREAM_IS_CLOSED
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|