代码
这里我们同样使用 IdHTTP的Get过程,函数的aURL是网址,aFile是保存的文件名,bResume确定是否续传,需要注意的就是续传方式时的代码:
procedure
TForm1.HttpDownLoad(aURL, aFile:
string
; bResume: Boolean);
var
tStream: TFileStream;
begin // Http 方式下载
if FileExists(aFile) then // 如果文件已经存在
tStream : = TFileStream.Create(aFile, fmOpenWrite) else
tStream : = TFileStream.Create(aFile, fmCreate);
if bResume then // 续传方式
begin
IdHTTP1.Request.ContentRangeStart : = tStream.Size - 1 ;
tStream.Position : = tStream.Size - 1 ; // 移动到最后继续下载
IdHTTP1.Head(aURL);
IdHTTP1.Request.ContentRangeEnd : = IdHTTP1.Response.ContentLength;
end else // 覆盖或新建方式
begin
IdHTTP1.Request.ContentRangeStart : = 0 ;
end ;
try
IdHTTP1.Get(aURL, tStream); // 开始下载
finally
tStream.Free;
end ;
end ;
var
tStream: TFileStream;
begin // Http 方式下载
if FileExists(aFile) then // 如果文件已经存在
tStream : = TFileStream.Create(aFile, fmOpenWrite) else
tStream : = TFileStream.Create(aFile, fmCreate);
if bResume then // 续传方式
begin
IdHTTP1.Request.ContentRangeStart : = tStream.Size - 1 ;
tStream.Position : = tStream.Size - 1 ; // 移动到最后继续下载
IdHTTP1.Head(aURL);
IdHTTP1.Request.ContentRangeEnd : = IdHTTP1.Response.ContentLength;
end else // 覆盖或新建方式
begin
IdHTTP1.Request.ContentRangeStart : = 0 ;
end ;
try
IdHTTP1.Get(aURL, tStream); // 开始下载
finally
tStream.Free;
end ;
end ;
IdHTTP1.Request.ContentRangeStart := tStream.Size - 1;
tStream.Position := tStream.Size - 1; // 移动到最后继续下载
IdHTTP1.Head(aURL);
IdHTTP1.Request.ContentRangeEnd := IdHTTP1.Response.ContentLength;
第 一行我们将下载开始位置设置为读入文件流的末尾,也就是设置为已经下载了的那部分文件的大小,第二行我们将文件流本身也指向自己的末尾,第三行我们通过 Head过程得到网址头信息,在第四行将头信息的文件总大小赋值给下载的结束的位置,至于这里为什么第一行和第二行代码最后都要-1,我当时没有加-1的 时候在续下载一个完整的已经下载的文件的时候总是提示错误,最后跟踪IdHTTP的代码发现他在处理下载范围的时候如果开始的位置和结束位置一样时会引发 将浮点数转为整数的错误,因而这里加上-1防止这种错误发生,另外一种处理方法就是比较如果开始位置等于结束位置就退出也是可以的。
再来看FTP 协议的下载过程:
procedure TForm1.FtpDownLoad(aURL, aFile: string; bResume: Boolean);
var
tStream: TFileStream;
sName, sPass, sHost, sPort, sDir: string;
begin //ftp 方式下载
if FileExists(aFile) then //建立文件流
tStream := TFileStream.Create(aFile, fmOpenWrite) else
tStream := TFileStream.Create(aFile, fmCreate);
GetFTPParams(aURL, sName, sPass, sHost, sPort, sDir);
with IdFTP1 do
try
if Connected then Disconnect; // 重新连接
Username := sName;
Password := sPass;
Host := sHost;
Port := StrToInt(sPort);
Connect;
except
exit;
end;
IdFTP1.ChangeDir(sDir); // 改变目录
BytesToTransfer := IdFTP1.Size(aFile);
try
if bResume then // 续传
begin
tStream.Position := tStream.Size;
IdFTP1.Get(aFile, tStream, True);
end else
begin
IdFTP1.Get(aFile, tStream, False);
end;
finally
tStream.Free;
end;
end;
这 个过程中我们就用到了GetFTPParams()函数将网址的用户名、密码、主机地址、端口、路径等信息分离出来,IdFTP利用这些信息登陆服务器并 到相应目录,最后利用Get()过程就很容易实现下载了,它的续传就比HTTP协议要简单很多,因为IdFTP的Get()本身就支持续传。
这里 我简单穿插一点的内容,一个服务器是否支持断点续传,我们可以通过发送"REST 1"FTP指令来检测,如果返回350则表示支持。
最后我们根 据网址来确定使用什么协议来下载:
function TForm1.GetProt(aURL: string): Byte;
begin // 检测下载的地址是http还是ftp
Result := 0;
if Pos('http', LowerCase(aURL)) = 1 then
Result := 1; //http 协议
if Pos('ftp', LowerCase(aURL)) = 1 then
Result := 2; //ftp 协议
end;
这个函数根据网址返回整数供我们使用。
procedure TForm1.MyDownLoad(aURL, aFile: string; bResume: Boolean);
begin
case GetProt(aURL) of
0: ShowMessage(' 不可识别的地址!');
1: HttpDownLoad(aURL, aFile, bResume);
2: FtpDownLoad(aURL, aFile, bResume);
end;
end;
这 个过程就利用GetProt()函数返回的整数执行相应的协议下载过程。
(2) 接下来看看每个按钮的代码,有了上面的函数,按钮的代码就简单多 了:
下载按钮:
procedure TForm1.Button1Click(Sender: TObject);
var
aURL, aFile: string;
begin
aURL := ComboBox1.Text; // 下载地址
aFile := GetURLFileName(aURL); //得到文件名,例如"demo.exe"
if FileExists(aFile) then
begin
case MessageDlg(' 文件已经存在,是否续传?', mtConfirmation, mbYesNoCancel, 0) of
mrYes: MyDownLoad(aURL, aFile, True); // 续传
mrNo: MyDownLoad(aURL, aFile, False); //覆盖
mrCancel: Exit; // 取消
end;
end else MyDownLoad(aURL, aFile, False); //建立新文件下载
end;
MessageDlg() 函数弹出一个对话框让用户选择续传、覆盖还是取消下载。
中断按钮:
procedure TForm1.Button2Click(Sender: TObject);
begin
AbortTransfer := True;
end;
前 面忘了介绍,所以这里大家看不明白,AbortTransfer是我们定义的一个私有变量,在开始下载的时候将它设为False,下载的过程中随时监测这 个变量,一旦变为True就利用IdHTTP的Disconnect和IdFTP1的Abort方法中断下载,如果没有下载完就中断,那程序的目录中就会 有一个下载不完整的程序或者其他东西,下次再下载的时候我们就可以选择续传来完成剩下的下载过程。
procedure TForm1.IdHTTP1WorkBegin(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCountMax: Integer);
begin
AbortTransfer := False;
……
end;
在 IdHTTP1和IdFTP的OnWorkBegin事件我们就将AbortTransfer设置为False了,在他们的Work事件中,我们检测 AbortTransfer变量来完成是否中断的操作。
procedure TForm1.IdHTTP1Work(Sender: TObject; AWorkMode: TWorkMode;
const AWorkCount: Integer);
begin
if AbortTransfer then
begin // 中断下载
IdHTTP1.Disconnect;
IdFTP1.Abort;
end;
ProgressBar1.Position := AWorkCount;
Application.ProcessMessages;
end;
(3) 最 后是连接状态等信息的代码:
在IdHTTP和IdFTP的OnStatus事件写入:
procedure TForm1.IdHTTP1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
ListBox1.ItemIndex := ListBox1.Items.Add(AStatusText);
end;
因 为IdHTTP和IdFTP在OnWork、OnStatus等事件上执行的代码都是一样的,所以我们只用写其中一个的代码,然后另外一个选择相同的事件 就OK了。
图8.3.4
3.全部代码写完收工,F9运行一下看看效果,是不是能断点续传。
【程序小结】
本程序 主要的功能由IdHTTP和IdFTP组件完成,主要掌握他们的Get过程实现断点续传的方法以及字符串的分析分解方法,这里我们同样使用了流格式,不过 这次不是内存流而是文件流。通过本例,读者应该初步掌握调试程序时断点的使用,事件代码的共用等。
【作者后话】
在写完这篇文章不久,作者 偶然间察看了Indy系列组件的帮助,发现一个封装了分析URL结构的类TIdURI,在IdURI单元,这个类可以很轻松的将我们上面的 GetFTPParams()函数的功能实现,例如:
var
URI: TIdURI;
begin
URI := TIdURI.Create(aURL); // 建立
try
sProtocol := URI.Protocol; //协议
sHost := URI.Host; // 主机
//……等等都可以通过URI的属性得到
finally
URI.Free;
end;
end;