login about faq

Hi!

I'm trying to implement streaming function of SFTP file download on Delphi XE3 using licensed 32-bit ActiveX SSH/SFTP component obtained from Chilkat (latest version 9.4.0).

Test condition: file size is 4096 bytes.

See the code below. When file size is exactly 4096 bytes (same as chunk size) the exception is thrown from ReadFileBytes method of SFtp.

uses CHILKATSSHLib_TLB;

function SFTP_FileDownload(const Host: string; Port: Integer; const Login, Password,
           RemoteURIPath, FileName, LocalPath: string; TimeoutSec: Integer): Boolean;
var
  SFTP: TChilkatSFtp;
  FileHandle: string;
  BEOF: Integer;
  TargetStream: TFileStream;
  NumRead: Integer;
  Buffer: array[0..4095] of Byte;
  DynBuffer: array of Byte;
begin
  Result := False;
  SFTP := TChilkatSFtp.Create(nil);
  try
    if SFTP.UnlockComponent('Test') <> 1 then Exit;
    SFTP.ConnectTimeoutMs := TimeoutSec * 1000;
    SFTP.IdleTimeoutMs := TimeoutSec * 1000;
    if SFTP.Connect(Host, Port) <> 1 then Exit;
    if SFTP.AuthenticatePw(Login, Password) <> 1 then Exit;
    if SFTP.InitializeSftp <> 1 then Exit;
    FileHandle := SFTP.OpenFile(RemoteURIPath + FileName, 'readOnly', 'openExisting');
    if Length(FileHandle) = 0 then Exit;
    try
      if not ForceDirectories(LocalPath) then Exit;
      TargetStream := TFileStream.Create(LocalPath + FileName, fmCreate);
      try
        BEOF := 0;
        while BEOF = 0 do
          begin
            //ReadFileBytes buggy call
            DynBuffer := SFTP.ReadFileBytes(FileHandle, 4096);
            //When file size is 4096 bytes the exception
            //is raised here (on the 2-nd iteration):
            //Exception class EVariantBadIndexError with
            //message 'Variant or safe array index out of bounds'.
            //
            if (SFTP.LastReadFailed(FileHandle) = 1) then Exit;
            NumRead := Length(DynBuffer);
            Move(Pointer(DynBuffer)^, Buffer, NumRead);
            TargetStream.Write(Buffer, NumRead);
            //
            //Also buggy
            BEOF := SFTP.Eof(FileHandle);
            //I also think Eof method has a bug. Because already on the
            //1-st iteration it is assumed to return 1. But it returns 0.
          end;
        Result := True;
      finally
        TargetStream.Free;
      end;
    finally
      SFTP.CloseHandle(FileHandle);
    end;
  finally
    SFTP.Disconnect;
    SFTP.Free;
  end;
end;

It would be quite nice to see comments from Chilkat.

asked Apr 18 '13 at 05:25

AlexanderAA's gravatar image

AlexanderAA
1112

edited Apr 18 '13 at 07:11


In this case, the SFTP.ReadFileBytes would be returning a Variant containing a 0-length array. Specifically, it's a VARIANT containing a SAFEARRAY containing unsigned bytes (VT_UI1) where the length of the SAFEARRAY is 0.

I suspect the exception is from Delphi. It must not be able to handle a Variant containing a 0-length SAFEARRAY. A solution might be to declare DynBuffer as a Variant. Check it first for 0-length, and then if it contains bytes, assign it to a variable declared as "array of Byte".

See http://stackoverflow.com/questions/3619753/how-to-use-variant-arrays-in-delphi

link

answered Apr 18 '13 at 10:12

chilkat's gravatar image

chilkat ♦♦
11.8k316358420

OK, I'll try it.

But what about Eof method?

In case of file size of 4096 bytes and after successful reading of 4096 bytes in the first iteration the call of Eof is assumed to return 1, but it returns 0. I think that's not OKay.

Because of such Eof's behaviour I have this issue with ReadFileBytes in the 2-nd iteration. I thought that the 2-nd iteration should never occur normally.

link

answered Apr 18 '13 at 11:14

AlexanderAA's gravatar image

AlexanderAA
1112

edited Apr 18 '13 at 11:26

The SFTP client would only know if it reached the EOF if the SFTP server responds to a read request with an internal status code of SSH_FX_EOF -- meaning the EOF has already been reached and there are no more bytes available. This is simply the way the protocol operates. If you have a 4096 byte file, and the SFTP client reads exactly 4096 bytes, the SFTP server will return those bytes. It is the next read that will hit the EOF.

(Apr 18 '13 at 11:28) chilkat ♦♦

Finally, below is the function that works well. Maybe it would be useful for one writing streaming SFTP downloads in Delphi.

function SFTP_FileDownload(const Host: string; Port: Integer; const Login, Password, RemoteURIPath, FileName, LocalPath: string; TimeoutSec: Integer): Boolean;
var
  SFTP: TChilkatSFtp;
  FileHandle: string;
  TargetStream: TFileStream;
  NumRead: Integer;
  Buffer: array[0..4095] of Byte;
  DynBuffer: array of Byte;
  ReadResult: OleVariant;
begin
  Result := False;
  SFTP := TChilkatSFtp.Create(nil);
  try
    if SFTP.UnlockComponent('Test') <> 1 then Exit;
    SFTP.ConnectTimeoutMs := TimeoutSec * 1000;
    SFTP.IdleTimeoutMs := TimeoutSec * 1000;
    if SFTP.Connect(Host, Port) <> 1 then Exit;
    if SFTP.AuthenticatePw(Login, Password) <> 1 then Exit;
    if SFTP.InitializeSftp <> 1 then Exit;
    FileHandle := SFTP.OpenFile(RemoteURIPath + FileName, 'readOnly', 'openExisting');
    if Length(FileHandle) = 0 then Exit;
    try
      if not ForceDirectories(LocalPath) then Exit;
      TargetStream := TFileStream.Create(LocalPath + FileName, fmCreate);
      try
        repeat
          ReadResult := SFTP.ReadFileBytes(FileHandle, SizeOf(Buffer));
          if VarArrayHighBound(ReadResult, 1) < 0 then Break;
          if (SFTP.LastReadFailed(FileHandle) = 1) then Exit;
          DynBuffer := ReadResult;
          NumRead := Length(DynBuffer);
          Move(Pointer(DynBuffer)^, Buffer, NumRead);
          TargetStream.Write(Buffer, NumRead);
        until SFTP.Eof(FileHandle) = 1;
        Result := True;
      finally
        TargetStream.Free;
      end;
    finally
      SFTP.CloseHandle(FileHandle);
    end;
  finally
    SFTP.Disconnect;
    SFTP.Free;
  end;
end;

link

answered Apr 18 '13 at 12:05

AlexanderAA's gravatar image

AlexanderAA
1112

Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or __italic__
  • **bold** or __bold__
  • link:[text](http://url.com/ "title")
  • image?![alt text](/path/img.jpg "title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Tags:

×8

Asked: Apr 18 '13 at 05:25

Seen: 1,348 times

Last updated: Apr 18 '13 at 12:05

powered by OSQA