Delphi XE2 DataSnap - Download File via TStream With Progress Bar
Asked Answered
S

2

9

I've written a DataSnap server method that returns a TStream object to transfer a file. The client application calls the method and reads the stream fine. My issue is that the method call takes a while to complete before the TStream object is available to read, but on the server side I can see that the method call only takes a second to create the object to return. I was hoping the stream object would be returned immediately so that I can read the stream and display a progress bar for the download progress. Is there another way I can do this?

The server method is very simple :

function TServerMethods.DespatchDocument(sCompanyID, sDocOurRef: string): TStream;
var
  sSourceFilePath: string;
  strFileStream: TFileStream;
begin
  sSourceFilePath := GetDocumentPDFFilePath(sCompanyID, sDocOurRef);

  strFileStream := TFileStream.Create(sSourceFilePath, fmOpenRead);
  Result := strFileStream;
end;
Saddle answered 17/1, 2012 at 9:38 Comment(1)
possible duplicate of ProgressBar for TResourceStream (Delphi)Voccola
C
3

This is how I did it a while back. I used XE and haven't had a chance to clean it up.

//Server side:

function TServerMethods1.DownloadFile(out Size: Int64): TStream;
begin
    Result := TFileStream.Create('upload.fil', fmOpenRead or fmShareDenyNone);
    Size := Result.Size;

    Result.Position := 0;
end;

//Client side:

procedure TfMain.DownloadFile(Sender: TObject);
var
    RetStream: TStream;
    Buffer: PByte;
    Mem: TMemoryStream;
    BytesRead: Integer;
    DocumentId: Int64;
    Size: Int64;
    filename: WideString;
    BufSize: Integer;
begin
    BufSize := 1024;

    try
      Mem := TMemoryStream.Create;
      GetMem( Buffer, BufSize );

      try
        RetStream := FDownloadDS.DownloadFile(Size);
        RetStream.Position := 0;

        if ( Size <> 0 ) then
        begin
          filename := 'download.fil';

          repeat
            BytesRead := RetStream.Read( Pointer( Buffer )^, BufSize );

            if ( BytesRead > 0 ) then
            begin
              Mem.WriteBuffer( Pointer( Buffer )^, BytesRead );
            end;

            lStatus.Caption := IntToStr( Mem.Size ) + '/' + IntToStr( Size );
            Application.ProcessMessages;

          until ( BytesRead < BufSize );

          if ( Size <> Mem.Size ) then
          begin
            raise Exception.Create( 'Error downloading file...' );
          end;
        end
        else
        begin
          lStatus.Caption := '';
        end;
      finally
        FreeMem( Buffer, BufSize );
        FreeAndNIl(Mem);
      end;
    except
      on E: Exception do
      begin
        lErrorMessage.Caption := PChar( E.ClassName + ': ' + E.Message );
      end;
    end;
end;

You can adjust BufSize however you like. I was having trouble getting the size of the stream until I did it this way. I experimented with XE2 and didn't seem to have the same problem but I was uploading. There is probably a better way to retrieve the size of the stream. If I get the answer soon I'll let you know....

On another note - I haven't figured out how to display a progress bar on the server side. I'm still trying to figure this out too.

I hope this helps! Let me know if you have any questions!

Cybil answered 18/1, 2012 at 11:18 Comment(6)
No, I have not had any delays. I've been downloading files of all sizes (up to 5 megs). The progress indicator works just fine. I know this sounds odd but at times I've had trouble with the way DataSnap behaves on certain ports. I changed to a different port and everything worked fine. Unfortunately, I never had a chance to figure out why or I would tell you. What is the size of your files? And what high and low ranges have you tried for BufSize?Cybil
The file I'm testing with is just under 5 megs. I've created the same DataSnap method as you have, and the method call is taking around 40 secs to complete before the client can read the stream with a progress bar. I'm currently just using port 8080 over HTTP, using the TDSRESTConnection to call the DataSnap method. How are you connecting to the DataSnap service?Saddle
I haven't had a chance to look at DataSnap's REST capabilities yet. I'm connecting over TCP using the default 211 port and client TSQLConnection. The app I was having trouble with I set to port 9999 (and another app to 9998) and the problems went away. I think it was my ISP's filter mechanism. Another thing - I am using XE2 Update 3. Have you got this? You doing anything with JSON? I've had to fix a few things with it too but I can't image it affecting your download. If you've got a small sample I'll build it and see if I have the same problem.Cybil
BTW - I might add my current project is doing it with security too. It seems to be working great but I don't have it in product yet. And another thought - My LifeCycle property for the TDSServerClass is set to Session.Cybil
Hi, OK this must be a REST thing. I've created a new Delphi DS client connecting with TSQLConnection (DBExpress) instead of TDSRESTConnection to the same DS server and guess what - the method call returns the stream within 1 second. The thing is the target client application I'm writing is for Android and the Java proxy class I have doesn't have an equivalent TSQLConnection, only a TDSRESTConnection. Need to understand if there's a way of making REST behave the same for streaming files or if it's just the way becasue of it's architecture.Saddle
I'm using XE2 Update 3 too, I hear that Update 4 is due out any day now. Yes I'm using JSON objects to pass/return data in the method calls, nothing fancy just have a function to convert the data returned in a TDataSet to a TJSONArray.Saddle
C
0

Glad you have some luck! This is the other fix I had to do. You can refer to this link https://forums.embarcadero.com/thread.jspa?threadID=66490&tstart=0

After diving in the code I found in "Data.DBXJSONReflect.pas"

procedure TJSONPopulationCustomizer.PrePopulate(Data: TObject; rttiContext: TRttiContext); ...

3473: rttiField.GetValue(Data).AsObject.Free;
3474: rttiField.SetValue(Data, TValue.Empty);

...

I think it should be this way:

3473: rttiField.SetValue(Data, TValue.Empty);
3474: rttiField.GetValue(Data).AsObject.Free;
Cybil answered 25/1, 2012 at 11:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.