Чтобы развеять сомнения по поводу неэффективности языка 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-ссылка выделенного в списке торрента, четыре состояния торрента и общие скорости закачки и отдачи для всех торрентов в списке.
Теперь по порядку разберёмся с событиями создания, запуска и остановки торрентов.
Все подробности связанные с созданием торрента и спецификацией битторрент-протокола описывать не будем, т.к. до нас это сделал Игорь в своей статье «Кодим BitTorrent-клиент. Часть первая». Весь код, выполняющий создание торрента, доступен в библиотеке 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
Id := IntToStr(CreateTorrentID)
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;
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
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
Id := IntToStr(CreateTorrentID)
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
GetInGeted(BTCreateTorrent.GetInfoTorrentCreating(Id)); // получение информации о создании торрент файла
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');
EnterCriticalSection(TorrentSection);
try
try
ReleaseCreateTorrentThread(BTCreateTorrent, Id); // уничтожение потока создания торрент-файла
except
end;
finally
LeaveCriticalSection(TorrentSection);
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, 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];
// Иконка состояния
case Task.Status of
tsReady: Item.ImageIndex := 0; // Ожидание закачки
tsQueue: Item.ImageIndex := 1; // В очереди
tsError: Item.ImageIndex := 2; // Ошибка завершена
tsErroring: Item.ImageIndex := 2; // Ошибка не завершена
tsLoading: Item.ImageIndex := 3; // Закачка
tsStoping: Item.ImageIndex := 4; // Остановка
tsStoped: Item.ImageIndex := 5; // Пауза
tsLoad: Item.ImageIndex := 6; // Закачано
tsProcessing: Item.ImageIndex := 8; // Поиск
tsSeeding: Item.ImageIndex := 9; // Раздача
tsBittorrentMagnetDiscovery: Item.ImageIndex := 10; // Magnet-поиск
tsDelete: Item.ImageIndex := 11; // Удаляется
tsDeleted: Item.ImageIndex := 11; // Удален
end;
Item.SubItems.Add(Task.FileName); // Имя Файла
Item.SubItems.Add(Task.LinkToFile); // Ссылка
Item.SubItemImages[1] := 12;
// Состояние
case Task.Status of
tsReady: Item.SubItems.Add('Ожидание');
tsQueue: Item.SubItems.Add('В очереди');
tsError: Item.SubItems.Add('Ошибка');
tsErroring: Item.SubItems.Add('Ошибка');
tsLoading:
begin
if Task.TotalSize > 0 then
begin
Procent := FloatToStr((Task.LoadSize / Task.TotalSize) * 100);
begin
EndPoint := pos(',', Procent);
if EndPoint <> 0 then
begin
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;
tsStoping: Item.SubItems.Add('Останавливается');
tsStoped: if (Task.TotalSize > 0) then
Item.SubItems.Add(FloatToStrF((Task.LoadSize / Task.TotalSize) *
100, ffFixed, 6, 1) + '% ' + 'Пауза')
else
Item.SubItems.Add('0% ' + 'Пауза');
tsLoad: Item.SubItems.Add('Завершено');
tsProcessing: Item.SubItems.Add('Поиск');
tsSeeding: Item.SubItems.Add('Раздача');
tsBittorrentMagnetDiscovery: Item.SubItems.Add('Magnet-поиск');
tsDelete: Item.SubItems.Add('Удалено');
tsDeleted: Item.SubItems.Add('Удалено');
tsFileError: Item.SubItems.Add('Ошибка торрента');
tsAllocating: Item.SubItems.Add('Распределение');
tsFinishedAllocating: Item.SubItems.Add('Распределение завершено');
tsRebuilding: Item.SubItems.Add('Восстановление');
tsJustCompleted: Item.SubItems.Add('Завершается');
tsCancelled: Item.SubItems.Add('Отменено');
tsUploading: Item.SubItems.Add('Раздача');
tsStartProcess: 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, т.к все предпосылки для этого имеются.
Комментарии (56)
Infanty
15.06.2015 17:21+3Спасибо за вашу библиотеку.
valexey
15.06.2015 19:08+2Только нужно учесть, что эта либа распространяется под GPL v2 или выше так как оригинальный код Ares Galaxy был под GPL 2. Следовательно и, например, клиент описанный в данной статье, также будет под GPL, равно как и любая другая программа которая будет использовать эту либу.
PS. Кстати, автору неплохо бы указать лицензию на sf.net явным образом.avtorfile Автор
15.06.2015 19:24+1PS. Кстати, автору неплохо бы указать лицензию на sf.net явным образом.
Спасибо, что заметили. Указал.
Ivan_83
15.06.2015 17:53+5Хорошая проба пера.
Но:
1. Что дельфи умеет сеть было известно ещё году в 2003, когда спамеры заюзали инди либу для своих спам ботов. Кажется кип на дельфях был писан.
2. сорсфорс — помойка, уже столько писали про это в последнее время. А отдельные личности ещё лет 5 назад сказали что это гандюшник.
3. Отталкиватся стоило не от «а вот здесь у нас узкое место, добавим ещё поток» а «сейчас запилим правильный скелет а потом обвесим мясом из этого донора».
Для примера, что будет с вашим клиентом если закинуть в него 5000 торрент файлов?
Из реально не решённых проблем современности в торрентостроении можно отметить одну весьма актуальную:
есть папка с .torrent файлами и папка с файлами (и подпапками) скачанными, и нужно чтобы торрент клиент сам мог сохрать торрент файлы а потом найти, прочекать и подхватить все файлы из помойки на диске.
Проблема клиента на дельфи и как он работает с комбобоксом не интересуют современное человечество :)avtorfile Автор
15.06.2015 18:02-2Из реально не решённых проблем современности в торрентостроении можно отметить одну весьма актуальную:
есть папка с .torrent файлами и папка с файлами (и подпапками) скачанными, и нужно чтобы торрент клиент сам мог сохрать торрент файлы а потом найти, прочекать и подхватить все файлы из помойки на диске.
Проблема клиента на дельфи и как он работает с комбобоксом не интересуют современное человечество :)
Можно конечно сказать о существовании торрент-клиентов, которые имеют более отлаженный проверенный годами код и зачем изобретать велосипед? Но история показывает, что и проверенные клиенты невсегда чисты на руку и добаляют в свой код различные майнеры, которые могут замедлять работу процессора без особой на то надобности. Потому, на сколько же хорошо иметь приложения, которые можно самостоятельно компилировать, определять и исправлять недостатки, чем приложения с закрытым кодом, который в какой-то момент может оказаться зловредным. Потому такая библиотека не будет лишней…
valexey
15.06.2015 18:02+1Что дельфи умеет сеть было известно ещё году в 2003, когда спамеры заюзали инди либу для своих спам ботов. Кажется кип на дельфях был писан.
Это вообще было известно сразу после появления делфи — там же есть полный (или почти полный) биндинг к WinAPI. В том числе и к сети. А если вдруг биндинга нужного нет, то всегда можно сделать — биндинги к сишному добру там делаются элементарно (как и в куче других ЯП).
В каком месте тут можно было усомниться в возможности на делфи написать торрент-качалку, не ясно.
gene4000
15.06.2015 18:23+4Вот лучше бы библиотеку BTService описали: Как протокол PEX работает, как DHT запустить.
js605451
15.06.2015 18:54+10Ну почему в каждой статье про Delphi я вижу что-то типа «ComboBox1»? (вопрос риторический)
Imp5
15.06.2015 21:18+6try try BTCreateTorrent.StopCreateTorrentThread(Id); // остановка создания торрент-файла except end; finally LeaveCriticalSection(TorrentSection); end;
Это что за конструкция?
Автор понимает, как работает try / finally / except?
vlx
15.06.2015 21:37+9Автор не понимает даже банальных конструкций языка, отмахиваясь благой целью показать «возможности делфи». Вас десятиэтажные ифы, в которых даже else нет никак не заставили задуматься, а только обработка исключений? :) Хрен с ним, с case'ом, но где else хотя бы?
xSomeonEx
16.06.2015 17:00А тут все просто. Подразумевается, что если AV вывалится в BTCreateTorrent.StopCreateTorrentThread(Id), то дальнейший участок кода(в данном случае секция finally..end и все, что после finally..end) пройдет без проблем. Если ж выдать просто:
try BTCreateTorrent.StopCreateTorrentThread(Id); // остановка создания торрент-файла finally LeaveCriticalSection(TorrentSection); end;
то при AV в BTCreateTorrent.StopCreateTorrentThread(Id), оно выполнит секцию finally..end и вывалится из метода, ЕМНИПxSomeonEx
16.06.2015 17:15Хотя с другой стороны, можно было нашлепать просто:
try BTCreateTorrent.StopCreateTorrentThread(Id); // остановка создания торрент-файла except end; LeaveCriticalSection(TorrentSection);
Но exception как-то обрабатывать-то нужно по-человечески.
AveryanovSergey
16.06.2015 00:27+10Код невыразимо прекрасен: и полное отсутствие ООП с развешиванием кучи кода на обработчиках событий, и семантика имен со всякими ComboBox1, и чуть ли не десяток уровней вложенности if. С 2003 года, когда я писал на Delphi последний раз, ничего не поменялось: большинство кода на нем — это формошлепство вчерашних школьников.
Статья хорошо подходит, чтобы отбить любое желание писать на, в общем-то, неплохом инструменте.vlx
16.06.2015 01:57Это «корован» последовательных ифов, а не вложенных
svd71
16.06.2015 15:50Вы ошиблись по Фрейду или по грамматике?
vlx
16.06.2015 15:52Я не ошибался.
svd71
16.06.2015 23:49-1Последовательность одинаковых вещей обычно назавается «кАрАван».
А вот кОрОван наверное как то связан с коровами. Но я не знаю как.vlx
17.06.2015 00:05lurkmore.to/%D0%9A%D0%BE%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D1%8B
svd71
17.06.2015 09:25+1спасибо за ссылку. Обычно на такие сайты не хватает времени, что бы быть всегда в тренде.
withkittens
17.06.2015 18:42+1Так ведь слово даже в кавычки взяли.
Это должно было навести на мысль, что «корован»-то не обычный ;)
dyadyaSerezha
16.06.2015 14:38Замечательно, просто замечательно. Но! Еще остались отдельные несознательные граждане, которые не верят, что на Дельфи можно написать: браузер с поддержкой последних стандартов, поисковик, АвтоКАД, Фотошоп, Скайп и т.д. и т.п. Надеемся, что вы не остановитесь на достигнутом, продолжите свое доказательство, и поэтому с нетерпением ждем следующих статей и реализаций. ;)
Tujh
16.06.2015 16:49+1Вроде как в Скайпе UI как раз написан на Делфи.
Akvel
17.06.2015 11:47Слабо верится, что Микрософт использует делфи
avtorfile Автор
17.06.2015 11:58Skype для платформы Windows написан на Delphi, в википедии это указано: ru.wikipedia.org/wiki/Skype
Tujh
17.06.2015 12:10Сколько времени развивается проект Skype, и сколько времени он принадлежит Microsoft-у? Думаете они сразу возьмут и всё перепишут, ну скажем, на C#? :)
MetaDone
я не знаком с делфи, но там конструкции switch разве нет?
valexey
Тем более что там явно же enum нужно отобразить в число, а еще точнее, в ID картинки. Поскольку мы полностю контроллируем и именование/нумерацию наших картинок, и enum, думаю легко можно было бы весь этот код превратить в одну строчку преобразования из одного в другое.
avtorfile Автор
Вместо switch имеется Case. Суть статьи не в написании идеального кода на Delphi, а показать возможности Delphi в создании программ на подобии торрент-клиента.
Seekeer
Простите, вы действительно считаете, что оператор управления используется только в идеальном коде?
avtorfile Автор
Может всё таки по теме обсуждение вести?
valexey
Ну, если уж даже на хаскеле это возможно ( github.com/jlouis/combinatorrent ), то это возможно и на делфи и на js :-)
avtorfile Автор
Ранее были сомнения на этот счёт. Ещё одно подтверждение возможности не будет лишним )
valexey
У кого?
avtorfile Автор
Эта статья является продолжением серии статей, написанной Игорем Антоновым ещё в 2007-2008 г. Он сам попросил написать новую статью. Если почитаете эту серию, то поймёте, что после прочтения может сложиться впечатление, что на delphi писать клиент сложнее чем на других яп. Многие дельфисты, да и не только дельфисты до сих пор наталкиваются на старые статьи и как вы думаете, у них не будет возникать сомнений по поводу Delphi?
kahi4
У вашей логики две проблемы: первая — сложнее != невозможно. Даже «сложнее» вызывает сомнения (не уж то делфи настолько не приспособлен к работе с сетью и файловой системой?).
Второе — вы ничего данным примером не показали. Кто знает, как внутри эта библиотека устроена кроме вас? Может там часть написана на другом языке, а на делфи только обертка?
upd. Что-то сначала не увидел исходников, только dll. Вторая проблема снимается.
Какое-то ненаучное доказательство несуществующей проблемы.
avtorfile Автор
Благодарю за критику. Согласен, что не всё гладко, но не согласен, что нет доказательства. Исходный код, как и бинарники приложены.
valexey
Ок. Я нашел эти статьи (статьи выходили в журнале Хакер — уже дурной признак):
iantonov.me/page/programmiruem-torrent-klient-na-delphi
iantonov.me/page/programmiruem-torrent-klient-na-sdelphis-c
Процитирую отрывок из первой
И второй:
Из этого сделать вывод, что «на делфи невозможно написать торрент-клиент» может сделать только «программист», руководствующийся принципом «это невозможно сделать, если для этого не написано готового компонента/библиотеки».
Не думал что делфисты, в массе своей, имеют настолько низкую квалификацию. Что они настолько не знают инструмент которым пользуются, и его возможности, что могут из подобных вот статеек сделать такие вот выводы.
PS. И да, обзор архитектуры и особенностей библиотеки для торрента, писанной на Delphi был бы намного интересней, чем статья про лепления гуйни к готовой уже библиотеки. Уж что лепка гуйни на делфи возможна, думаю, знают даже делфисты.
avtorfile Автор
Начинающие программисты (не только дельфисты) вполне могут.
В следующей статье вопрос архитектуры постараюсь раскрыть.
valexey
Начинающие программисты будут скорее читать хабр, или rsdn сегодняшний, нежели статьи в Хакере которые выходили 7 лет назад.
avtorfile Автор
Статьи не только в хакере, а также на VR и на сайте Игоря.
valexey
Кстати, если статья рассчитана на начинающих программистов, то код в статье должен быть вылизан идеально.
Таким же кодом как в статье (ComboBox1, вот этот вот унылый if и прчее) вы прививаете начинающему делфипрограммеру дурные манеры. Вы ему как бы говорите — смотри, парень, так прокатывает даже в статье! Так что к тебе то спросу и вовсе не будет! Так писать — норма!
В итоге люди которые не знакомы с делфи (либо уже предвзято к делфи относятся) смотрят на статью, и делают вывод — что даже в статье по делфи быдлокод, значит средний делфикодер накодит вообще страх и ужас => делфикодеры недопрограммисты.
А люди которые только начинают на делфи программировать переймут эту же манеру писать и будут уже на рабочих местах таки доказывать что да, делфикодеры это недопрограммисты.
Таким образом, статья в нынешнем виде является антирекламой делфи.
avtorfile Автор
Кроме того, у библиотеки BTService имеются возможности написания торрент-клиента и на других ЯП. Если кому интересно, то читайте про систему плагинов Александра Алексеева, (ссылка на блок Александра имеется в статье).
kahi4
Эм. Нет, суть статьи «смотрите, я нашел библиотеку для делфи, с помощью которой можно скачивать торренты, зацените».
Я думаю, тут все догадываются, что качалку торрента можно написать даже на бреинфаке, если сильно постараться (хотя я удивлен, что еще никто не постарался или я, по крайней мере, не нашел).
Вообще по стилю кода — все ужасно. Просто ужасно. Последний раз я описывал бизнес-логику в хандлере кнопки на первом курсе, наверное.
avtorfile Автор
Библиотека писалась мною. Это не реклама, бизнесменам — проходите мимо… или самостоятельно пилите…
kahi4
Мне кажется, что гораздо интереснее было бы здесь привести участки кода библиотеки, как соединяетесь с пирами и прочее. Как подключить библиотеку, думаю, понять не очень сложно.
Ну и немного конструктивной критики — конечно безумно хотелось бы исходники на гитхабе, как и небольшую документацию там, раз это не реклама. Впрочем, может у вас проприетарное решение.
Есть такое ощущение, что это свойство dll, не зависить от языка, на котором его подключают.
Вы уж определитесь, писалась она вами или же только правилась? Я ни в коем случае не сомневаюсь в ваших способностях, может в делфи заведено не пользоваться switch-case, но как-то сложно поверить в высокое качество библиотеки после увиденного в ваших примерах, уж простите.
avtorfile Автор
Учту при написании следующей статьи.
Не так. Если использовать не тот тип данных при обмене м/у ядром и библиотекой, то получится моноязычная библиотека.
Мною писался плагин на основе исходников Ares Galaxy, который пришлось корректировать в силу его недостатков. А точнее зависание основного потока, который останавливает работу всех торрентов списка.
vlx
Целые игры Age of wonders были написаны на Делфи, с чего вдруг торрент клиент стал непосильной задачей?
AtomKrieg
А вам нравится Оберон?
avtorfile Автор
Считаю критику обоснованной. Код подправил