[Из песочницы] Программируем BitTorrent-клиент. Чистый Delphi
Прошло 8 лет после написания статьи Игорем Антоновым (Spider_NET) про создание торрент-клиента на C#, но в сети так и не появилось самого простого примера, как это можно сделать на Delphi.Чтобы развеять сомнения по поводу неэффективности языка Delphi в таком «непростом» деле, как написание полноценного битторрент-клиента, я и решил написать эту статью.
Сразу скажу, что наш торрент-клиент на Delphi будет с открытым исходным кодом и будет поддерживать практически все современные битторрент-технологии, в том числе DHT, magnet-ссылки, последовательная закачка и т.д. Поиск в интернете уже готовых исходников клиента на Delphi привел к результатам, но эти результаты оказались далеко неидеальными. Первым результатом оказался давно заброшенный Torrent Torque (2007 г), причём альфа-версия. TorrentTorque мне не удалось нормально скомпилировать и испытать.Следующим результатом поиска, оказался малоизвестный в рунете Ares Galaxy, который оказался вполне работоспособным и даже популярным в некоторых странах торрент-клиентом. Помучавшись с компиляцией, мне всё же удалось испытать желанный код, но у него оказались недостатки, которые как выяснилось, разработчиками не исправляются уже давно. Кроме того, Ares Galaxy написан на Delphi 7, а это значит, что для компиляции в более новых версиях RAD Studio необходимо переписывать огромное количество кода. Но меня это не остановило и я нашёл другой выход для решения данной задачи.
Начав разбираться в исходниках Ares Galaxy, выяснил, что многие операции выполняются в одном потоке, что в результате кратковременно останавливает процесс закачки всех торрентов в списке. Потому я решил исправить недостатки и вынес процедуры, которые замедляют выполнение кода, в отдельные потоки.
Получив положительный результат, решил разместить исходники на sourceforge.net. Код выполнил в виде dll-библиотеки BTService, с применением системы плагинов, о которой в своё время подробно рассказал Александр Алексеев в своей серии статей «Разработка системы плагинов». Так что с применением такой системы плагинов возможно создание битторрент-клиента в любом компиляторе RAD Studio и не только на Delphi, но и на других языках программирования. Библиотека BTService и её исходники доступны по ссылке: http://btservice.sourceforge.net/
Итак, приступим к написанию простого клиента на основе библиотеки BTService.
Интерфейс клиента выполним в классическом стиле, с минимальным набором компонентов, но чтобы была видна основная функциональность библиотеки.

Пять кнопок на панели инструментов: добавление magnet-ссылки, добавление торрента, создание торрента, запуск торрента и остановка торрента.
Список торрентов будем отображать в стандартном TListView. Списки файлов, подключенных пиров и трекеров также разместим на TListView, которые соответственно будут отображаться при открытии вкладок TPageControl. Ну, а внизу главной формы StatusBar, на котором будет отображаться magnet-ссылка выделенного в списке торрента, четыре состояния торрента и общие скорости закачки и отдачи для всех торрентов в списке.
Теперь по порядку разберёмся с событиями создания, запуска и остановки торрентов.
Все подробности связанные с созданием торрента и спецификацией битторрент-протокола описывать не будем. Весь код, выполняющий создание торрента, доступен в библиотеке BTService. Кому интересна его реализация, смотрите исходники библиотеки. Ну, а я лишь укажу код, взаимодействующий с библиотекой.
Для начала создадим форму «Создать новый торрент». На которой разместим три кнопки: «Добавить файл», «Добавить папку» и «Создать». Добавим TPageControl. На первой вкладке разместим основные параметры. Параметр «Размер части» выполним в TCombobox, «Начать закачку» и «Частный торрент» выполним в TCheckBox. На других вкладках TPageControl разместим поля TMemo, добавляющие в торрент-файл адреса трекеров, веб-сидов и комментарии. Процесс создания торрента будем отображать на двух TProgressBar. На первом будет отображаться выполнение хэширования отдельного файла, а на другом общий процесс выполнения хэширования всех файлов торрента.

Для кнопки «Добавить файл» код будет следующий:
procedure TfCreateTorrent.btnAddFileClick (Sender: TObject); begin FormStyle:= fsNormal; if OpenDialog1.Execute then begin ComboBox1.Text:= PChar (OpenDialog1.Filename); // выбор файла btnCreate.Enabled:= true; end; FormStyle:= fsStayOnTop; end; Для кнопки «Добавить папку» код будет следующий: procedure TfCreateTorrent.btnAddFolderClick (Sender: TObject); var chosenDirectory: string; begin FormStyle:= fsNormal; if SelectDirectory ('Выберите каталог: ', '', chosenDirectory) then begin ComboBox1.Text:= chosenDirectory; // выбор папки btnCreate.Enabled:= true; end; FormStyle:= fsStayOnTop; end; То есть в поле «Выбор источника»(ComboBox1.Text) добавляется путь файла или папки, в зависимости от того, что мы хотим добавить в торрент, один файл или несколько файлов в папке.Далее на кнопку «Создать» пишем код:
Код создания торрент-файла procedure TfCreateTorrent.btnCreateClick (Sender: TObject); var BTCreateTorrent: IBTCreateTorrent; X: Integer; FindBTPlugin: Boolean; Id: string; begin if ButtonSave then begin if ComboBox1.Text[length (ComboBox1.Text)] = '\' then ComboBox1.Text:= copy (ComboBox1.Text, 1, length (ComboBox1.Text) — 1);
if FileExists (ComboBox1.Text) then begin SaveDialog1.Filter:= 'Torrent Files (*.torrent)|*.torrent'; SaveDialog1.Filename:= ComboBox1.Text + '.torrent'; SaveDialog1.DefaultExt:= 'Torrent files (*.torrent)'; FormStyle:= fsNormal; if SaveDialog1.Execute then begin FormStyle:= fsStayOnTop; TorrentFileName:= SaveDialog1.Filename; // выбор пути для сохраниния торрент-файла ButtonSave:= False; btnCreate.Caption:= 'Остановить';
EnterCriticalSection (TorrentSection); try for X:= 0 to Plugins.Count — 1 do begin if (Supports (Plugins[X], IBTCreateTorrent, BTCreateTorrent)) then // поиск интерфейса IBTCreateTorrent, отвечающего за создание торрент-файла begin FindBTPlugin:= true; break; end; end; finally LeaveCriticalSection (TorrentSection); end;
if FindBTPlugin then begin try Id:= IntToStr (CreateTorrentID) except end;
EnterCriticalSection (TorrentSection); try try BTCreateTorrent.SingleFileTorrent ((Id), (ComboBox1.Text), (SaveDialog1.Filename), (mmoComment.Lines.Text), (GetAnnounceURL), (mmWebSeeds.Lines.Text), CheckBox2.checked, ComboBox2.ItemIndex, False, False, '', '', '', ''); //запуск процедуры создания торрент-файла из плагина BTService для одиночного файла except end; finally LeaveCriticalSection (TorrentSection); end;
repeat application.ProcessMessages;
EnterCriticalSection (TorrentSection); try try GetInGeted (BTCreateTorrent.GetInfoTorrentCreating (Id)); // получение информации о создании торрент файла except end; finally LeaveCriticalSection (TorrentSection); end;
if (Stop) and (not (GetedStatus = 'stoped')) then begin EnterCriticalSection (TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread (Id); // остановка создания торрент-файла except end; finally LeaveCriticalSection (TorrentSection); end; end;
WaitingCreation;
if (Stop) and (not (GetedStatus = 'stoped')) then begin EnterCriticalSection (TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread (Id); // остановка создания торрент-файла except end; finally LeaveCriticalSection (TorrentSection); end; end;
sleep (10); until (GetedStatus = 'completed') or (GetedStatus = 'stoped'); end;
ReleaseCreateTorrentThread (BTCreateTorrent, Id); // уничтожение потока создания торрент-файла
if (GetedStatus = 'completed') then begin sGauge2.Position:= sGauge2.Max; sGauge1.Position:= sGauge1.Max; StatusBar1.Panels[0].Text:= 'Создание торрент-файла успешно завершено!';
if CheckBox1.checked then StartTorrent; end; if (GetedStatus = 'stoped') then begin StatusBar1.Panels[0].Text:= 'Остановлено.'; end;
Stop:= False; btnCreate.Enabled:= true; ButtonSave:= true; btnCreate.Caption:= 'Создать'; end; end else if DirectoryExists (ComboBox1.Text) then begin SaveDialog1.Filter:= 'Torrent Files (*.torrent)|*.torrent'; SaveDialog1.Filename:= ComboBox1.Text + '.torrent'; SaveDialog1.DefaultExt:= 'Torrent files (*.torrent)'; FormStyle:= fsNormal; if SaveDialog1.Execute then begin FormStyle:= fsStayOnTop; TorrentFileName:= SaveDialog1.Filename; // выбор пути для сохраниния торрент-файла ButtonSave:= False; btnCreate.Caption:= 'Остановить';
EnterCriticalSection (TorrentSection); try for X:= 0 to Plugins.Count — 1 do begin if (Supports (Plugins[X], IBTCreateTorrent, BTCreateTorrent)) then // поиск интерфейса IBTCreateTorrent, отвечающего за создание торрент-файла begin FindBTPlugin:= true; break; end; end; finally LeaveCriticalSection (TorrentSection); end;
if FindBTPlugin then begin try Id:= IntToStr (CreateTorrentID) except end;
EnterCriticalSection (TorrentSection); try try BTCreateTorrent.CreateFolderTorrent ((Id), (ComboBox1.Text), (SaveDialog1.Filename), (mmoComment.Lines.Text), (GetAnnounceURL), (mmWebSeeds.Lines.Text), CheckBox2.checked, ComboBox2.ItemIndex, False, False, '', '', '', ''); //запуск процедуры создания торрент-файла из плагина BTService для каталога с файлами except end; finally LeaveCriticalSection (TorrentSection); end;
repeat application.ProcessMessages;
EnterCriticalSection (TorrentSection); try try GetInGeted (BTCreateTorrent.GetInfoTorrentCreating (Id)); // получение информации о создании торрент файла except end; finally LeaveCriticalSection (TorrentSection); end;
if (Stop) and (not (GetedStatus = 'stoped')) then begin EnterCriticalSection (TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread (Id); // остановка создания торрент-файла except end; finally LeaveCriticalSection (TorrentSection); end; end;
WaitingCreation;
if (Stop) and (not (GetedStatus = 'stoped')) then begin EnterCriticalSection (TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread (Id); // остановка создания торрент-файла except end; finally LeaveCriticalSection (TorrentSection); end; end;
sleep (10); until (GetedStatus = 'completed') or (GetedStatus = 'stoped');
try ReleaseCreateTorrentThread (BTCreateTorrent, Id); // уничтожение потока создания торрент-файла except end;
if (GetedStatus = 'completed') then begin sGauge2.Position:= sGauge2.Max; sGauge1.Position:= sGauge1.Max; StatusBar1.Panels[0].Text:= 'Создание торрент-файла успешно завершено!'; if CheckBox1.checked then StartTorrent; end; if (GetedStatus = 'stoped') then begin StatusBar1.Panels[0].Text:= 'Остановлено.'; end;
Stop:= False; btnCreate.Enabled:= true; ButtonSave:= true; btnCreate.Caption:= 'Создать'; end; end; end else begin Stop:= False; btnCreate.Enabled:= true; ButtonSave:= true; btnCreate.Caption:= 'Создать'; end; end else begin Stop:= true; btnCreate.Enabled:= False; ButtonSave:= true; StatusBar1.Panels[0].Text:= 'Приостановка процесса…'; btnCreate.Caption:= 'Останавливается…'; end; end; Как видно из кода, первым делом происходит выбор каталога для сохранения торрента. А далее происходит поиск интерфейса IBTCreateTorrent, отвечающего за вызов процедуры SingleFileTorrent из плагина BTService. Данная процедура запускает процесс создания торрент-файла с содержанием одного файла, а для папки с файлами запускается процедура CreateFolderTorrent. После этого запускатся цикл repeat, в котором происходит периодическое обращение к функции GetInfoTorrentCreating, которая возвращает результат действий из плагина в процессе создания торрента и информацию о проценте выполненного хеширования. Если результат возвращается GetedStatus = 'completed', то процесс создания торрента завершился удачно и можно выходить из цикла.Для добавления торрента в список создадим форму «Добавить торрент». Разместим на неё две кнопки: «Закачать» и «Добавить в список». Первая будет добавлять торрент в список и сразу начинать процесс скачивания, а вторая будет просто добавлять торрент в список для ожидания последующих действий над ним. Для отображения информации о торренте добавим на форму TEdit («Файл торрента:»), TComboBox («Сохранить в:»), TLabel («Имя торрента:», «Описание:», «Дата:») и список TListView, который будет показывать содержимое файлов и папок торрента.

Код добавления и закачки торрента procedure TfAddTorrent.btnDownloadClick (Sender: TObject); begin if AddTask (true, false) then close; end;
function TfAddTorrent.AddTask (Now: Boolean; ShowPrev: Boolean): Boolean; var find: Boolean; TorrentDataSL: TStringList; X: Integer; DataTask: TTask; BTPluginAddTrackers: IBTServicePluginAddTrackers; begin Result:= false; if Trim (HashValue) = '' then begin MessageBox (Handle, PChar ('Нет доступа к торрент файлу или ошибка чтения торрент-файла'), PChar (Options.Name), MB_OK or MB_ICONWARNING or MB_TOPMOST); Exit; end;
find:= false; with TasksList.LockList do try for X:= 0 to Count — 1 do begin DataTask:= Items[X]; if DataTask.Status <> tsDeleted then if DataTask.HashValue = HashValue then begin find:= true; break; end; end; finally TasksList.UnLockList; end; if find then begin if MessageBox (Application.Handle, PChar ('Вы пытаетесь добавить торрент, который уже есть в списке. Хотите загрузить из него список трекеров?'), PChar (Options.Name), MB_OKCANCEL or MB_ICONWARNING) = ID_OK then begin for X:= 0 to Plugins.Count — 1 do begin if (Supports (Plugins[X], IBTServicePluginAddTrackers, BTPluginAddTrackers)) then begin try BTPluginAddTrackers.AddTrackers (HashValue, trackers); // Добавление трекеров из торрент-файла except end; break; end; end; end; Exit; end;
TorrentDataSL:= TStringList.Create; try TorrentDataSL.Insert (0, BoolToStr (true));
if Now then TorrentDataSL.Insert (1, BoolToStr (true)) else TorrentDataSL.Insert (1, BoolToStr (false));
TorrentDataSL.Insert (2, Edit1.Text); TorrentDataSL.Insert (3, ExcludeTrailingBackSlash (cbDirectory.Text)); TorrentDataSL.Insert (4, IntToStr (0)); TorrentDataSL.Insert (5, Edit2.Text);
AddTorrent (TorrentDataSL.Text, HashValue, Now, ShowPrev); finally TorrentDataSL.Free; end;
try ForceDirectories (ExcludeTrailingBackSlash (cbDirectory.Text)); except end;
SaveTasksList; Result:= true; end;
function TfAddTorrent.AddTorrent (TorrData: string; HashValue: string; Now: Boolean; ShowPrev: Boolean): Boolean; var AddDataTask: TTask; AddedData: TStringList; CreaName, CreatedName: string; Plugin2: IAddDownload; IndexPlugin2: Integer; Silent: Boolean; Down: Boolean; begin Result:= false; AddedData:= TStringList.Create;
try AddedData.Text:= TorrData; try Silent:= StrToBool (AddedData[0]); Down:= StrToBool (AddedData[1]); except Down:= true; Silent:= true; end; if Silent then begin AddDataTask:= TTask.Create; AddDataTask.TorrentFileName:= AddedData[2]; // путь к добавляемому торрент-файлу AddDataTask.HashValue:= HashValue; // info hash AddDataTask.LinkToFile:= 'magnet:? xt=urn: btih:' + AnsiLowerCase (AddDataTask.HashValue); // magnet-ссылка AddDataTask.Directory:= ExcludeTrailingBackSlash (AddedData[3]); // директория для сохранения содержимого закачиваемого торрента AddDataTask.ID:= Options.LastID + 1; // идентификатор в списке торрентов Options.LastID:= AddDataTask.ID; CreaName:= AddedData[5]; CreaName:= trimleft (CreaName); CreaName:= trimright (CreaName); CreatedName:= CreaName; AddDataTask.FileName:= CreatedName; // имя файла или каталога закачиваемого торрента AddDataTask.Description:= ''; if CheckBox1.Checked then AddDataTask.ProgressiveDownload:= true // функция последовательной закачки включена else AddDataTask.ProgressiveDownload:= false; // функция последовательной закачки отключена
if Down then AddDataTask.Status:= tsQueue // добавляем закачку в очередь else AddDataTask.Status:= tsReady; // торрент готов к закачке
AddDataTask.TotalSize:= SizeTorrent; // размер содержимого файлов закачиваемого торрента AddDataTask.LoadSize:= 0; AddDataTask.TimeBegin:= 0; AddDataTask.TimeEnd:= 0; AddDataTask.TimeTotal:= 0; AddDataTask.MPBar:= TAMultiProgressBar.Create (nil); // создание прогрессбара
Plugin2:= nil; DeterminePlugin2('bittorrent', IServicePlugin, Plugin2, IndexPlugin2); if Plugin2 <> nil then if Plugins[IndexPlugin2] <> nil then if (Plugins[IndexPlugin2].TaskIndexIcon > 0) then AddDataTask.TaskServPlugIndexIcon:= Plugins[IndexPlugin2] .TaskIndexIcon else begin if pos ('magnet:?', AnsiLowerCase (AddDataTask.LinkToFile)) = 1 then AddDataTask.TaskServPlugIndexIcon:= 34; end;
TasksList.Add (AddDataTask); Result:= true;
PostMessage (Options.MainFormHandle, WM_MYMSG, 0, 12345);
if Now then LoadTorrentThreads.Add (TLoadTorrent.Create (false, AddDataTask, true)); // создание потока выполняющего запуск торрента end;
finally AddedData.Free; end; end; В процедуре добавления торрента происходит проверка на наличие info hash в списке торрентов. Если info hash найден, то вместо добавления торрента в список, будет предложено добавить адреса трекеров из торрента BTPluginAddTrackers.AddTrackers (HashValue, trackers), иначе добавление торрента в список будет продолжено. После добавления торрента в список TasksList.Add (AddDataTask), будет создан поток TLoadTorrent (модуль uTorrentThreads), который выполнит запуск торрента BTPlugin.StartTorrent (DataTorrent) и в котором также запустится цикл repeat, проверяющий состояние и получающий информацию о торренте каждую секунду GetedData:= BTPlugin.GetInfoTorrent (DataTask.HashValue).За отображение полученной информации отвечает событие TListView OnData:
Код отображения полученной информации procedure TfMainForm.lvTasksData (Sender: TObject; Item: TListItem); var i: Integer; Task: TTask; Procent, Procent1, Procent2: string; EndPoint: Integer; begin with TasksList.LockList do try for i:= 0 to Count — 1 do begin if i = Item.Index then begin Task:= Items[Item.Index]; if Task.Status = tsReady then // Ожидание закачки Item.ImageIndex:= 0; if Task.Status = tsQueue then // В очереди Item.ImageIndex:= 1; if Task.Status = tsError then // Ошибка завершена Item.ImageIndex:= 2; if Task.Status = tsErroring then // Ошибка не завершена Item.ImageIndex:= 2; if Task.Status = tsLoading then // Закачка Item.ImageIndex:= 3; if Task.Status = tsStoping then // Остановка Item.ImageIndex:= 4; if Task.Status = tsStoped then // Пауза Item.ImageIndex:= 5; if Task.Status = tsLoad then // Закачено Item.ImageIndex:= 6; if Task.Status = tsGetUrl then // Получение ссылки Item.ImageIndex:= 7; if Task.Status = tsProcessing then // Поиск Item.ImageIndex:= 8; if Task.Status = tsSeeding then // Раздача Item.ImageIndex:= 9; if Task.Status = tsBittorrentMagnetDiscovery then // Обработка магнет Item.ImageIndex:= 10; if (Task.Status = tsDelete) or (Task.Status = tsDeleted) then // Удален Item.ImageIndex:= 11;
Item.SubItems.Add (Task.FileName); // Имя Файла Item.SubItems.Add (Task.LinkToFile); // Ссылка Item.SubItemImages[1] := 12;
// Состояние
if Task.Status = tsReady then
Item.SubItems.Add ('Ожидание');
if (Task.TotalSize > 0) and (Task.Status = tsStoped) then
Item.SubItems.Add
(FloatToStrF ((Task.LoadSize / Task.TotalSize) * 100, ffFixed,
6, 1) + '% ' + 'Пауза');
if (Task.TotalSize <= 0) and (Task.Status = tsStoped) then
Item.SubItems.Add('0% ' + 'Пауза');
if Task.Status = tsQueue then
Item.SubItems.Add('В очереди');
if Task.Status = tsStoping then
Item.SubItems.Add('Останавливается');
if Task.Status = tsLoad then
Item.SubItems.Add('Завершено');
if Task.Status = tsError then
Item.SubItems.Add('Ошибка');
if (Task.Status = tsDelete) or (Task.Status = tsDeleted) then
Item.SubItems.Add('Удалено');
if Task.Status = tsBittorrentMagnetDiscovery then
Item.SubItems.Add('Magnet-поиск');
if Task.Status = tsSeeding then
Item.SubItems.Add('Раздача');
if Task.Status = tsFileError then
Item.SubItems.Add('Ошибка торрента');
if Task.Status = tsAllocating then
Item.SubItems.Add('Распределение');
if Task.Status = tsFinishedAllocating then
Item.SubItems.Add('Распределение завершено');
if Task.Status = tsRebuilding then
Item.SubItems.Add('Восстановление');
if Task.Status = tsProcessing then
Item.SubItems.Add('Поиск');
if Task.Status = tsJustCompleted then
Item.SubItems.Add('Завершается');
if Task.Status = tsCancelled then
Item.SubItems.Add('Отменено');
if Task.Status = tsQueuedSource then
Item.SubItems.Add('Очередь');
if Task.Status = tsUploading then
Item.SubItems.Add('Раздача');
if Task.Status = tsStartProcess then
Item.SubItems.Add('Запуск');
if Task.Status = tsLoading then
begin
if Task.TotalSize > 0 then
begin
Procent:= FloatToStr ((Task.LoadSize / Task.TotalSize) * 100);
begin
EndPoint:= pos (',', Procent);
if EndPoint <> 0 then
begin
Procent1:= copy (Procent, 1, EndPoint — 1);
Procent2:= copy (Procent, 1, EndPoint + 1);
Item.SubItems.Add (Procent2 + '% ' + 'Закачка');
end
else
begin
try
Procent2:= FloatToStrF (StrToInt (Procent), ffFixed, 6, 1);
except
end;
Item.SubItems.Add (Procent2 + '% ' + 'Закачка');
end;
end;
end
else
Item.SubItems.Add ('');
end;
if Task.Speed > 0 then
begin
Item.SubItems.Add
(GetTimeStr ((Task.TotalSize — Task.LoadSize) div Task.Speed)); // Осталось
end
else
Item.SubItems.Add ('');
if Task.TotalSize > 0 then
Item.SubItems.Add (BytesToText (Task.TotalSize)) // Размер
else
Item.SubItems.Add (' ');
Item.SubItems.Add (BytesToText (Task.LoadSize)); // Закачано
if Task.Speed > 0 then
Item.SubItems.Add (BytesToText (Task.Speed) + '/s') // Скорость
else
Item.SubItems.Add ('');
if Task.NumConnectedSeeders > 0 then
Item.SubItems.Add (IntToStr (Task.NumConnectedSeeders)) // Сиды
else
Item.SubItems.Add ('');
if Task.NumConnectedLeechers > 0 then
Item.SubItems.Add (IntToStr (Task.NumConnectedLeechers)) // Пиры
else
Item.SubItems.Add ('');
if Task.UploadSpeed > 0 then
Item.SubItems.Add (BytesToText (Task.UploadSpeed) + '/s') // Скорость отдачи
else
Item.SubItems.Add ('');
if Task.UploadSize > 0 then
Item.SubItems.Add (BytesToText (Task.UploadSize)) // Отдано
else
Item.SubItems.Add ('');
break;
end;
end;
finally
TasksList.UnLockList;
end;
end;
Вот мы и подошли к моменту тестирования. Хотя код клиента и является завершённым, я решил всё же дополнить его прогрессбаром, который разместил в статусбаре нашего клиента, вместо отображения magnet-ссылки, которая и так отображается в списке торрентов. Это нам нужно для того, чтобы видеть как происходит закачка, последовательно или нет.После компиляции запускаем наш клиент, добавляем торрент в список нажав на кнопку «Добавить торрент». Один торрент добавим без установки метки «Последовательная закачка», а на другом установим эту метку и дождёмся начала закачки. В результате во время закачки мы должны увидеть следующую картину: 

Т.е при выделении в списке торрента, в котором мы установили метку «Последовательная закачка», закачка происходит последовательно, а у другого торрента закачка частей торрента происходит выборочно, без последовательности.
В итоге мы получили действующий торрент-клиент, полностью выполненный на языке Delphi и неуступающий функциональности современных клиентов. Исходники битторрент-библиотеки BTService и исходники клиента DelphiTorrent (каталог examples) доступны по SVN: svn.code.sf.net/p/btservice/svn
Мы создали торрент-клиент, которым пользоваться возможно только в ОС Windows. Потому следует ожидать продолжение, в котором я расскажу о создании клиента для ОС Android и IOS, т.к все предпосылки для этого имеются.
