...

понедельник, 15 июня 2015 г.

[Из песочницы] Программируем 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://ift.tt/1FWDwCA

Итак, приступим к написанию простого клиента на основе библиотеки 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: http://ift.tt/1fcADa8

Мы создали торрент-клиент, которым пользоваться возможно только в ОС Windows. Потому следует ожидать продолжение, в котором я расскажу о создании клиента для ОС Android и IOS, т.к все предпосылки для этого имеются.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий