|
Showing Download Progress in Delphi SOAP
Author: Deepak Shenoy (shenoy@agnisoft.com)
Download the code for this white paper (dlprogress.zip, 12 KB)
Abstract
This paper talks about how you can show a download "progress" bar (like many download utilities
do) when using SOAP to transfer data. I've used Delphi 7 in this article, and made
some code changes to the source code, so this might not work with any other version of Delphi.
Introduction
If you've read the paper in http://www.agnisoft.com/white_papers/SOAP2.asp#binaryfile
which tells you how you can transfer binary data using SOAP. In Delphi 7, you can also use
the TSOAPAttachment class. But what if you need to use a progress bar to display
data being transferred?
There's an event in the THTTPRio.HTTPWebNode, called OnReceivingData. This event gives you a Size and a Total parameter,
but they're always the same...so you can't use it to determine the actual download status. Or can you?
Digging into Delphi
Looking at the Delphi SOAP Source code, I realized that I could modify the way this event was being called, and sending real data -
where Size would mean the amount that's currently been downloaded, and Total would give you the expected total size of the
download.
Looking into SOAPHTTPTrans.pas (located in the Source\Soap folder under your Delphi 7 install), I found that the procedure Receive
was calling the OnReceivingData event. I had to make some modifications to the code as follows: (In the Receive procedure)
Note: Please backup this file before makign any changes.
1. Declare variables, TotalSize and TotalRead as DWORD:
var
TotalSize, TotalRead : DWORD;
|
2. In the code for the Receive procedure, after the line:
HttpQueryInfo(Pointer(Context), HTTP_QUERY_CONTENT_TYPE,
@FContentType[1], Size, Index);
SetLength(FContentType, Size);
|
Add:
// ADDED Deepak 2.2.2004
Index := 0;
HttpQueryInfo(Pointer(Context), HTTP_QUERY_CONTENT_Length or
HTTP_QUERY_FLAG_NUMBER,
@TotalSize, Len, Index<);
TotalRead := 0;
|
Essentially, what we're doing here is to find out the actual length of the response, given
in the Content-Length response header.
3. Further down, within the repeat loop, you'll see the following code:
if Assigned(FOnReceivingData) then
FOnReceivingData(Size, Downloaded)
|
Replace it with:
if Assigned(FOnReceivingData) then
begin // ADDED Deepak 2.2.2004
Inc(TotalRead, Downloaded);
FOnReceivingData(TotalRead, TotalSize)
end;
|
Here, we're just ensuring that we keep a sum of the downloaded chunks handy (in the TotalRead variable)
and we call the event with the total bytes read and the total bytes available.
4. Remember to include $(DELPHI)\Source\SOAP in your client project search path, so that these
changes will be compiled into your application.
Demo
I've created a simple WAD based server which allows you to download files in the same directory as the
WAD application with an IDownloader interface. There's a function in the interface, DownloadFile
implemented as follows:
function TIDownloader.DownloadFile(FileName: String): TByteDynArray;
var
CurDir : string;
begin
CurDir := GetCurrentDirectory;
if FileExists( CurDir + FileName) then
Result := FileToByteArray(CurDir + FileName)
else
Result := nil;
end;
|
The FileToByteArray comes from the CompressHelper utility unit I'd created for my earlier paper,
Transferring binary data using SOAP. I've also included
it in the source code with this article.
What that piece of code does is to look in the folder (where the server application resides) for the existence of a file with the
name given in the FileName parameter. If a file is found, its contents are returned in a dynamic array of bytes.
If you're wondering what GetCurrentDirectory does:
function TIDownloader.GetCurrentDirectory: string;
var
FileName : array[0..MAX_PATH] of char;
begin
FillChar(FileName, SizeOf(FileName), #0);
GetModuleFileName(HInstance, FileName, SizeOf(FileName));
Result:= ExtractFilePath(FileName);
end;
|
The reason I've not used the more commonly known Application.ExeName is that you can
use this code even in ISAPI dlls (where Application.ExeName isn't valid)
The Client Application
I'm skipping the part where you create a new application, import this WSDL etc. I've created a form with an edit box for the file name,
a Download button, and a progress bar indicator and panel. Here's the code for the download button:
procedure TForm2.Button1Click(Sender: TObject);
var FileArray : TByteDynArray;
begin
// start downloading
FileArray := (rioDownload as IIDownloader).DownloadFile(edtFileName.Text);
if FileArray <> nil then
begin
ByteArrayToFile( FileArray, ExtractFilePath(Application.ExeName)
+ edtFileName.Text);
end;
end;
|
A file is requested from the server, and if it does arrive, we save it to a file in the same folder as the client application.
(Here Application.ExeName is ok, because this is a client application that will always be an EXE)
I've used another CompressHelper function to save the response byte array to disk.
But the code to actually populate the progress bar is in an event - THTTPRio.HTTPWebNode.OnReceivingData:
procedure TForm2.rioDownloadHTTPWebNode1ReceivingData(Read,
Total: Integer);
begin
pbDownload.Position := Trunc( Read/Total *100 );
lblProgress.Caption := Format('Downloaded %d of %d bytes',[Read, Total]);
pnlProgress.Caption := Format('%d%%',[pbDownload.Position]);
Application.ProcessMessages;
end;
|
The reason I have Application.ProcessMessages; in there is so that you can actually see the user
interface change - otherwise the progress bar does not get updated. (But you probably knew that already)
What have we got?
This.
Caveats?
- This works only for Delphi SOAP that uses WinInet. If you use INDY,
there's probably a different approach to making it work.
- You can't use this with a client built using the SOAP package. That's because your source code
changes to SOAPHTTPTrans.pas don't change the package.
- I have also included a Download as Attachment to demonstrate that the changes you made work
even with TSOAPAttachments. The source code reveals all.
Who's Deepak Shenoy?
He's part of the big pointy haired team at Agni.
Download the code for this white paper (dlprogress.zip, 12 KB)
|