ВСЕ ЗАПИСИ

Статьи,заметки

Загрузка файлов с докачкой, с использованием компонента Indy

Indy (Internet Direct) - компоненты для удобной работы с популярными интернет-протоколами. Эти компоненты часто используют программисты на Дельфи в своих разработках. Причина такой популярности Indy понятна, - Indy по умолчанию встроен в Delphi 7, многие начинали свой путь в программирование именно через Паскаль и Дельфи, под Indy в сети можно найти множество хороших примеров и готовых исходников.

Но вот, как мне пришлось убедиться, если вы хотите сделать на Indy свой загрузчик файлов, поддерживающий докачку после обрыва соединения, то готовых решений в сети вы не найдёте. Обсуждение этого вопроса можно найти на многих форумах по программированию, и во всех случаях, которые мне известны, ветка обсуждения обрывается на том, что автор топика отписывается, что мол, попробовал, все варианты, ничего не получается, ну его нафиг, этот глючный Indy, буду использовать другие компоненты; или пишет, сделал, всё заработало, но что интересно, никто так и не показывает рабочий код. А потом, вопрос, как использовать Indy для загрузки файлов, появляется на форумах снова и снова.

Поэтому я решил выложить здесь свой вариант, который я использовал в одном из своих проектов, думаю, что многим он будет полезен. Код 100% рабочий, легко адаптируемый, используется версия Indy 9, та, что установлена в Delphi 7 изначально. Следует иметь в виду что , из-за ограничений 9-й версии, этот код не может быть использован для загрузки файлов размером более 2Гб, но сейчас вряд ли где можно найти прямые ссылки на такие большие файлы, такой контент давно уже можно встретить только в торрентах, а вот для скачивания например, видео с youtube, этот код можно использовать, самые большие файлы, которые мне попадались на youtube весили 1,5 - 1,8 Гб.

Чтобы загрузить файл, нужно прежде всего создать/открыть файл, либо MemoryStream, куда будут сбрасываться данные, инициализировать компонент IdHTTP, и воспользоваться методом Get.

...................

var

http: TIdHTTP;

fs: TFileStream;

s,url: string;

begin

s:='file.ext';

url:='http://site.domain/file.ext';

fs:=TFileStream.Create(s,fmCreate or fmShareDenyNone);

http:=TIdHTTP.Create(nil);

http.Get(url,fs);

FreeAndNil(http);
FreeAndNil(fs);

end;

..........................

Этого кода достаточно, чтобы открыть адрес http://site.domain/file.ext и сохранить по этой ссылке файл file.ext на жёсткий диск.

Но.. Файл-то скачается, но если он достаточно большого размера, то пока закачка не завершится, приложение повиснет и перестанет отвечать на другие запросы (чтобы этого не происходило, процесс закачки обычно выносят в другой поток, но для этого придется усложнять код). Если закачку прервать, то на диске останется файл того же размера, который занимал бы полностью закачанный файл. А если запустить закачку снова, то файл придётся загружать с самого начала.

Чтобы было возможно докачивать незавершённые файлы, нужно знать их размер. Размер файла можно узнать с помощью свойства TIdHTTP.Response.ContentLength - возвращаемое значение - размер файла в байтах. Определяем размер незавершённого файла, который лежит на диске, вычисляем, сколько байт ещё осталось докачать, и с помощью свойств TIdHTTP.Request.ContentRangeStart и TIdHTTP.Request.ContentRangeEnd задаем размер части, которую нужно догрузить.

В моём коде компонент TIdHTTP создаётся динамически (его не нужно бросать на форму при создании проекта), закачка производится в цикле, в один период цикла программа получает сегмент данных размером 200 Кб (как это сделано в известном менеджере закачек - FlashGet) и сразу записывает их в файл, при этом программа не успевает зависать, что позволяет осуществлять закачку в основном потоке программы, без написания отдельного потока для загрузчика. Размер файла на диске, также увеличивается пропорционально полученным данным, и в случае преждевременной остановки загрузки, зная полный размер файла, и прочитав размер файла на диске можно легко определить с какого места нужно докачивать.

В коде дополнительно используются две функции function online ,которая проверяет, есть ли подключение к интернету, в момент старта закачки, и function BytesToMbytes ,которая используется для вывода количества скачанных данных в реальном времени, - размер файла по ссылке и количество скачанного в мегабайтах с точностью до сотых, выводится на метки формы, я использовал их вместо прогрессбара, но при желании можно его добавить на форму.

Следует обратить внимание, что после завершения загрузки показания счётчика загруженных данных будут немного отличаться от размера файла по ссылке в большую сторону, это нормально, такое наблюдается практически во всех менеджерах загрузки, вот например, что нам покажет сам виндовский проводник

проводник

И напоследок расскажу ещё об одном глюке программы, который мне так и не удалось устранить, но по-видимому здесь может помочь только смена версии Indy на более свежую, но пока я не могу проверить этот код на 10-й версии Indy. На работоспособности кода этот глюк не отражается, и на том спасибо:)

Проявляется это так: в момент, когда уже загружен последний байт IdHTTP выдаёт ошибку, что '*/xxx is not a valid integer value' , где ххх - полный размер в байтах файла по ссылке, то есть параметр TIdHTTP.Response.ContentLength ,получается что в момент завершения Indy вместо числового значения, выдаёт его строкой, вместе с символами '*/' . Я пробовал даже не использовать Response.ContentLength , - файл всё равно нормально докачивается до конца,просто мы не увидим размер файла.но сообщение об ошибке всё-равно выдаётся. Пробовал получить все нужные мне значения в строковом виде, отфильтровать лишние символы самостоятельно и перевести необходимые параметры в числовые, методом TIdHTTP.Response.RawHeaders. GetText  , но всё напрасно - эта ошибка неубиваема! Пришлось поставить на этот участок кода обработчик try...except ,теперь конечно, даже если произойдёт сбой во время загрузки, например, обрыв сети, программа выдаст, что закачка успешно завершена, но по счётчику данных и размеру файла на винте всё-равно можно понять, что произошла ошибка. Просто запускаем докачку с места обрыва, файл будет корректно загружен в любом случае, был ли это непредвиденный сбой или остановка загрузки вручную, именно для этого код и был разработан :)

Исходный код программы:

unit Main;
interface
uses
Windows,Messages,SysUtils,Variants, Classes,Graphics,Controls, Forms,Dialogs,StdCtrls,
IdHTTP,XPMan,Math;
type
TForm1 = class(TForm)
Edit1:TEdit;
Button1:TButton;
Label1:TLabel;
Edit2:TEdit;
Button2:TButton;
Label3:TLabel;
XPManifest1:TXPManifest;
Label4:TLabel;
Label5:TLabel;
Label7:TLabel;
Label6:TLabel;
procedure Button1Click(Sender:TObject);
procedure Button2Click(Sender:TObject);
private
{ Private declarations }
public
n:Integer;//эта переменная используется для того, чтобы прервать загрузку вручную
{ Public declarations }
end;
var
Form1:TForm1;
implementation
{$R *.dfm}
//проверка на доступность подключения к интернету, перед стартом процесса загрузки
function online(URL:string):boolean;
var H:Tidhttp;
begin
Result:=true;
H:=Tidhttp.Create(nil);
try
H.Get(URL);
except
Result:=false;
end;
H.Free;
end;
//перевод байтов в мегабайты - для вывода индикации процесса загрузки на Label'ы
function BytesToMbytes(size:Int64):string;
var
s:string;
d:Double;
begin
s:=IntToStr(size);
d:=StrToFloat(s);
d:=RoundTo(d /(1024*1024), -2);
Result:=FloatToStr(d);
end;
procedure TForm1.Button1Click(Sender:TObject);
var
sz,sz1,sz2,full:Int64;
s,url:string;
http:TIdHTTP;
f:TFileStream;
i:Integer;
dlsize,endsize:string;
label
a;
begin
begin
if online('http://www.ya.ru') = true then
//--------------
else
begin
ShowMessage('No network connection!');
exit;
end;
end;
Button1.Enabled:=false;
n:=0;
s:=Edit1.Text;
url:=Edit2.Text;
//проверяем, нет ли на диске недокачанного файла
if FileExists(s) then
begin
f:=TFileStream.Create (s,fmOpenReadWrite or fmShareDenyNone);
sz:=f.Size;
Label1.Caption:='Unfinished download found: '+s;
sz1:=sz-1;
goto a;
end
else
begin
sz1:=0;
end;
//создаём файл куда будем сбрасывать полученные данные
f:=TFileStream.Create(s,fmCreate or fmShareDenyNone);
a:
FreeAndNil(f);
http:=TIdHTTP.Create(nil);
http.Response.KeepAlive := true;
http.HandleRedirects:=true;
http.Head(url);
full:=http.Response.ContentLength;
endsize:=BytesToMbytes(full);
//определяем размер файла, который будет скачиваться
Label6.Caption:= endsize+' Mb';
begin
sz2:=http.Request.ContentRangeEnd;
begin
for i:= 0 to 1000000000 do
begin
try
sz2:=sz1+200000-1;//здесь задан размер сегмента закачки 200 000 байт, качаем порциями по 200 Кб -
//достаточно оптимальный размер, чтобы это не влияло на скорость скачивания и не подвисала форма
//можно попробовать увеличить на свой страх и риск
dlsize:=BytesToMbytes(sz2-200000);
//выводим на Label в режиме реального времени ко-во скачанных данных в мегабайтах
Label7.Caption:='Downloaded '+dlsize+' Mb of';
http.Request.ContentRangeStart:=sz1;
http.Request.ContentRangeEnd:=sz2;
f:=TFileStream.Create(s, fmOpenReadWrite or fmShareDenyNone);
f.Seek(0-1,soFromEnd);
Application.ProcessMessages;
http.Get(url,f);
full:=http.Response.ContentLength;
FreeAndNil(f);
sz1:=sz2;
//обработка нажатия кнопки СТОП
if n=1 then
begin
ShowMessage('Download Paused!');
exit;
end;
except
begin
{это блок обработки ошибок, в девятой версии Indy присутствует неисправимый глюк,
который ВСЕГДА вызывает ошибку в момент нормального завершения закачки, при этом закачка
завершается корректно. Поэтому и при успешной закачке, и в случае неудачи(обрыв связи,например)
будет всё равно выдано сообщение Download Complete!
Но так как данный код позволяет докачивать незавершённые файлы, просто запустите программу и
файл нормально докачается до конца, байт в байт}
//ShowMessage('ERROR!');
ShowMessage('Download Complete!');
FreeAndNil(http);
FreeAndNil(f);
Button1.Enabled:=true;
Label1.Caption:='';
Label6.Caption:='';
Label7.Caption:='';
exit;
end;
if (sz2 <> full) then
begin
end
else
begin
//этот блок кода фактически не нужен, так как в любом случае сработает блок обработки ошибок,
//который находится выше, но для понимания логики работы программы, я решил его оставить
ShowMessage('Download Complete!');
FreeAndNil(http);
FreeAndNil(f);
Button1.Enabled:=true;
Label1.Caption:='';
Label6.Caption:='';
Label7.Caption:='';
exit;
end;
end;
end;
end;
end;
end;
procedure TForm1.Button2Click(Sender:TObject);//кнопка СТОП/ПАУЗА
begin
n:=1;
Button1.Enabled:=true;
end;
end.


Скачать исходник

А вот ещё парочка исходников, которые можно применить для загрузки файлов.

Здесь используется IdHTTP, вынесенный в отдельный поток, присутствует прогрессбар для индикации, докачка не реализована. Исходник взял с http://delphi.int.ru , я адаптировал его под Indy v.9 скачать

А в этом исходнике использован компонент WinInet, закачка вынесена в отдельный поток, есть прогрессбар, докачка поддерживается. Разработан в какой-то из новых версий Delphi, но в Delphi 7 также компиллится без проблем. скачать

Добавлено: сентябрь 2013

©Veterock




комментарии (0)



Имя

Сообщение

введите защитный код


Обновить

Powered by ©Veterock Studio 2013