Программируем 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, т.к все предпосылки для этого имеются.

ссылка на оригинал статьи http://habrahabr.ru/post/260339/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *