BITS(Background Intelligent Transfer Service) ile sessiz sedasız download ;)

// 27 Mayıs 2009 // Delphi, İşletim Sistemi, Programlama

Açılımı Background Intelligent Transfer Service olan BITS, arka planda dosya indirmeye yarayan bir windows servisidir. Windows 2000′den sonra ve daha ziyade Windows XP’ler ile tanınmış ve daha sıklıkla kullanılmıştır. Windows’un arka planda updatelerini yaptığı servisin adıdır aynı zamanda. Bu servisin en önemli özelliği network’ün bandwith’ini en uygun şekilde kullanmasıdır. Bundan sonra yazacaklarımızı deneyebilmeniz için öncelikle bu servisin makinanızda çalışıyor olduğundan emin olmalısınız.

bits_background_intelligent_transfer_service

BITS servisinin en önemli özelliğinin sizin internet trafiğinize pek zarar vermemesi olduğunu söylemiştik. C ve C++ kullanıcıları için geliştirilmiş olan BITS, kendisine verilen indirme emirlerini yerine getirmek için network’ün en az kullanıldığı zamanları takip eder(Idle). Bu zamanı tespit edebilmek için de ethernet kartını sürekli izler. Network’ün yeterince kullanılmadığını anladığında arka planda indirim işlemine başlar. Ancak network kullanımının artması durumunda hemen transfer hızını düşürür ve mevcut network sistemine zarar vermez. Tüm bu özelliklerinden dolayı Windows, kendisi için gereken update’leri bu sistem vasıtası ile karşı bilgisayardan sizin bilgisayarınıza indirebilmektedir.

Biz de bu makalemizde, mevcut ağımızı yormayan basit bir indirme yöneticisi yazmaya çalışacağız. Ancak kendi indirme yöneticimizi yazmaya başlamadan evvel BITS’i biraz daha tanımakta fayda var. BITS, mevcut ağ performansına zarar vermeden, iletişimi sekteye uğratmadan dosya indirmeleri için son derece uygundur. Yazdığımız programların internet üzerinden bir yerlerden güncellenmiş sürümlerini yada bazı dosyaları indirmesi gerektiğinde kullanılabilecek mükemmel bir yardımcıdır.

BITS servisinin her bir indirme görevine JOB(iş) adı verilir. Her bir Job birden fazla dosyayı barındırabilir. Ancak bir job en fazla 10 dosyadan müteşekkil olabilir. Bir job’un create edilmesi ile BITS yaşam döngüsüne başlamış olur. Yaşam döngüsü diyorum çünkü gerçekten de uzun bir süreci var. Bu süreçlere ileride detaylıca değineceğiz ama indirilmesi gereken herhangi bir dosyanın indirme işleminin 90 gün boyunca indirme listesinde durabileceğini belirterek yaşam döngüsüne işaret etmiş olayım. BITS, kısaca internet üzerindeki bir kaynaktan belirtilen belirli dosyaları diskiniz üzerinde belirttiğiniz yere belirttiğiniz dosya adları ile sistemi yormadan kopyalayan bir indirme yöneticisidir. İndirme işlemini gerçekleştirebilmesi için indirilecek dosyaların HTTP/HTTPS protokollerinde olması gereklidir. Bu da FTP üzerinden indirme yapamayacağı anlamına gelir. Uzaktaki dosyaların bilgisayarınıza indirilme sıraları sizin o dosyaları job’un içine hangi sırada soktuğunuz ile bire bir ilişkilidir. Yani FIFO mantığına göre çalışır. İlk eklenen dosya ilk olarak indirilecektir. BITS’in en faydalı özelliği persist yani kalıcı olmasıdır. Bazı dosyaların indirilmesi amacı ile oluşturduğunuz bir job , siz makinanızı kapatsanız da, elektrik kesilse de yada internet bağlantınız kopsa da zarara uğramaz. Daha sonra kaldığı yerden devam edebilir. BITS için bir job oluşturulduğunda oluşturulan job indirme işlemine hemen başlamaz. Önce uyku modundadır(suspend, tıpkı threadlerde olduğu gibi). İndirme işleminin başlayabilmesi için programcının kod ile uyku modundan çıkartması gerekir(Resume).

Bu indirme yöneticisinin belirli bir öncelikler seviyesi vardır(Priority). Bu seviyelere göre indirme işlemlerini yürütür. Arka planda yaptığı indirmeler 3 ayrı öncelik seviyesindedir. High, Normal ve Low. Varsayılan öncelik seviyesi Normal’dir. Aynı anda BITS içinde birden fazla indirme job’u olabilir. Ancak herzaman önceliği en yüksek olan job en önce indirilecek ve diğer job’lar onu bekleyeceklerdir. Aynı öncelik seviyesine sahip job’ların indirilmesi ise diğer daha küçük dosyalara sahip joblarda tıkanmanın önüne geçmek adına paraleldir. Yani High öncelik seviyesine sahip 3 ayrı job var ise bu bu jobların indirilmesi sırasında transfer zamanı tüm joblar için ortak bir şekilde paylaştırılır. Düşük öncelikli jobların indirme işlemine başlamasının 2 yolu vardır. Birincisi kendisinden yüksek önceliğe sahip tüm job’ların bitmiş olması yada kendisinden önceki job’ların hata durumuna düşmesi.

Dosyaların BITS tarafından makinanıza indirilmesi sırasında server tarafındaki dosyaların güncellenmesi durumunda, BITS indirmeye başlamış olduğu dosyayı yeniden indirmeye başlar. Ancak tamamen indirdiklerini bir daha indirmez. Örneğin indirme listesinde 2 dosya varsa bunlardan birini indirmiş ikincisini ise henüz indiriyor ise her 2
dosyanın da server tarafında değiştirilmesi durumunda sadece en son indirmeye çalıştığı dosyayı yeni baştan indirmeye başlar.

BITS’in karşı bilgisayardan sizin bilgisayarınıza dosyaları indirmesi sırasında kullandığı bir iletişim protokolü vardır. BITS, öncelikle karşı bilgisayara bağlanmaya çalışır eğer bağlantı kurabilir ise transferi başlatma işine girişir, aksi durumda düzeltilebilir hata konumuna geçer. Düzeltilebilir hata konumundan kastımız BITS’in 2 çeşit hata durumuna destek vermesidir. Birincisi BITS’in kendi başına düzeltemeyeceği, kullanıcı yardımına ihtiyacı olan fatal error durumu, ikincisi ise kendi başa çıkabileceği hata durumudur. Bu durumları ilerleyen zamanlarda job’un durum(state) bilgisini alırken detaylandıracağız.

BITS indirme yöneticisi indirme işlemine başladığında indirmesini söylediğiniz klasör içinde tmp uzantılı dosyalar oluşmaya başlar. İndirme işlemi bittiğinde , indirme işleminin bittiğini bizler onaylayana kadar o dosyalar tmp uzantısı ile kalırlar. İndirme işlemi sırasında BITS’in çözemeyeceği herhangi bir hata oluşması durumunda job’umuz suspend duruma geçecektir. Suspend duruma düşen bir job Cancel metodu ile iptal edilmedikçe yada Complete metodu ile onaylanmadıkça 90 gün boyunca indirme listesinde kalacaktır. 90 günün sonunda BITS tarafından otomatikman silinecektir. Bu sebeple indirme işleminin kontrolünü iyi yapmamız gerekir. Bazı bilgisayarlarda sistem yöneticileri, kullanıcı ve grup bazlı çeşitli poliçeler ile bir kullanıcının açabileceği azami job sayısını yada ilgili makinada açılabilecek azami job sayısını belirtmiş olabilirler. İndirme listesinde suspend durumda bulunan bu joblarımız bizim yeni joblar açmamıza bu kurallar nedeni ile engel olabilir.

BITS’in ciddi bir hataya düşmediği hatalarda devam edebildiğini yazmıştık. Bu devam işlemi için belirli bir zaman bekler. Bu zaman varsayılan olarak 600
saniyedir. Bekleme zamanı IBackgroundCopyJob interface’inin SetMinimumRetryDelay metodu ile değiştirilebilir. Bu fonksiyon saniye cinsinden parametre alır.
Verilebilecek en küçük saniye değeri 60′dır, 60′ın altındaki atamalarda BITS bu değeri otomatikman tekrar 60 yapacaktır. BITS’in indirme işlemine bir an evvel başlayabilmesi adına işletim sisteminin saatini ileri almak bir işe yaramaz. BITS yine ne kadar beklemesi gerekiyor ise o kadar bekleyecektir.

Bu kadar genelkültür bilgisinden sonra Delphi ile BITS yapısını kullanarak bir indirme yöneticisinin nasıl yapılacağı hususlarına biraz değinelim. Öncelikle bize BITS COM arabirimine ulaşmak için bir kütüphane gerekli. Ancak yaptığım araştırmalarda sistemimde gereken dosyayı bir türlü bulamadım. Eğer makinanızda Windows XP Service Pack 2 Platform SDK var ise siz BITS.IDL dosyasını bulabilirsiniz. Bu dosyayı bulmak başlı başına yeterli değildir. MIDL türü bir compiler ile bu dosyayı TBL haline getirmeli ve daha sonra da Delphi’de bu TBL dosyasını import etmeliyiz.

Ancak ben sizleri bu kadar uğraştırmayacak ve TBL dosyasının kaynak kodunu sizlerin indirmesi için buraya koyacağım. Sizlerin tek yapacağı indirdiğiniz tbl dosyasını uses satırına ilave etmekten ibaret olacak. Daha detaylı bir malümat için her zaman MSDN‘e uğrayabilirsiniz. Şimdi biz BITS’i Delphi’de nasıl kullanacağız gelin ona bakalım:

type
  TDownloadState = (dsQueue, dsConnecting, dsTransferring, dsSuspended, dsError, dsTransferred, dsAcknowlegged, dsCancelled, dsUnknown);

  TDownloadError = record
    Context,
    Protocol,
    Description : String;
  end;

  TDownloadNotify = record
    JobID : String;
    DownloadState  : TDownloadState;
    BytesTotal	  : Int64;
    BytesTransferred  : Int64;
    FilesTotal      	     : Int64;
    FilesTransferred    : Int64;
    Error : TDownloadError;
  end;

  TOnStateChanged = procedure(const State : TDownloadNotify) of object;

  TDownloadJob = class
  private
    fLocalFileNames ,
    fRemoteFileNames : TStrings;

    fJobID,
    fDisplayName,
    fDescription : String;
  public
    constructor Create(const AJobID, ADisplayName, ADescription : String;
                              const ALocalFileNames, ARemoteFileNames : array of String
                             );

    function Suspend : Boolean;
    function Resume : Boolean;
    function Cancel : Boolean;
    function Complete : Boolean;

    property LocalFileNames : TStrings read fLocalFileNames;
    property RemoteFileNames: TStrings read fRemoteFileNames;

    property JobID : String read fJobID;
    property DisplayName : String read fDisplayName;
    property Description : String read fDescription;
  end;

  TDownloadJobs = class(TList)
  private
    function GetDownloadJob(AIndex : Integer) : TDownloadJob;
  protected
    function Add(Item: TDownloadJob): Integer;
    procedure Delete(Index: Integer);
    function IndexOf(Item: TDownloadJob): Integer;
    function Remove(Item: TDownloadJob): Integer;
    procedure DeleteAll;
  public
    destructor Destroy; override;

    function FindJob(const ID : String) : TDownloadJob;

    property Jobs[AIndex : Integer] : TDownloadJob read GetDownloadJob; default;
  end;

  TDownloadManager = class;
  TDownloadThread = class(TThread)
  private
    fOwner : TDownloadManager;

    fDisplayName,
    fDescription : String;

    fLocalFileNames,
    fRemoteFileNames : array of String;
  protected
    procedure Execute; override;
  public
    constructor Create(const AOwner : TDownloadManager; const DisplayName, Description : String; const LocalFileNames, RemoteFileNames : array of String);
    destructor Destroy; override;
  end;

  TDownloadManager = class
  private
    fOnStateChanged : TOnStateChanged;
    fCritSect : TCriticalSection;
    fDownloadJobs : TDownloadJobs;
    Manager : IBackgroundCopyManager;

    procedure EnumJobs;
    procedure Notify(const DownloadNotify : TDownloadNotify);
  public
    constructor Create;
    destructor Destroy; override;

    procedure Download(const DisplayName, Description : String; const LocalFileNames, RemoteFileNames : array of String);

    property DownloadJobs : TDownloadJobs read fDownloadJobs;
    property OnStateChanged : TOnStateChanged read fOnStateChanged write fOnStateChanged;
  end;

Yukarıda tanımlı TDownloadNotify record’u TDownloadThread isimli sınıf tarafından ana uygulamayı bilgilendirmek amacı ile kullanılmaktadır. BITS içindeki jobların o anki durumları ve hata bilgilerini içerir. Bütün işi yapan kısım TDownloadThread sınıfının Execute metodudur. TDownloadManager sınıfı her bir job için TDownloadThread create eden bir Download metoduna ev sahipliği yapar. Kodumuzun geri kalan kısmı aşağıdaki gibidir:

implementation

const
  BG_JOB_ENUM_ALL_USERS = $0001;

{ TDownloadManager }

constructor TDownloadManager.Create;
begin
  inherited Create;

  fCritSect := TCriticalSection.Create;
  fDownloadJobs := TDownloadJobs.Create;

  EnumJobs;
end;

destructor TDownloadManager.Destroy;
begin
  if Assigned(fDownloadJobs) then fDownloadJobs.Free;
  if Assigned(fCritSect) then fCritSect.Free;

  inherited;
end;

procedure TDownloadManager.Download(const DisplayName, Description: String;
  const LocalFileNames, RemoteFileNames: array of String);
begin
  TDownloadThread.Create(Self, DisplayName, Description, LocalFileNames, RemoteFileNames);
end;

procedure TDownloadManager.EnumJobs;
var
  Jobs 	: IEnumBackgroundCopyJobs;
  Job	 	: IBackgroundCopyJob;
  BackFiles  : IEnumBackgroundCopyFiles;
  BackFile   : IBackgroundCopyFile;

  retVal: HRESULT;
  dummy,
  FileCount : Cardinal;
  tmpJobId : GUID;

  iFileIndex  : Integer;
  LocalFiles,
  RemoteFiles	: array of String;

  pTemp : PWideChar;

  JobID,
  DisplayName,
  Description : String;

  DownloadJob : TDownloadJob;
begin
  CoInitialize(nil);

  Manager := CoBackgroundCopyManager_.Create;

  try
    retVal := Manager.EnumJobs(BG_JOB_ENUM_ALL_USERS, Jobs); // Tüm userlara ait joblara erişmeye çalış.!

    if not Succeeded(retVal) then
    begin
      retVal := Manager.EnumJobs(0, Jobs); // O andaki aktif windows kullanıcısının joblarına erişmeye çalış.!

      if not Succeeded(retVal) then
      begin
      	Manager := nil;
        CoUninitialize;

      	raise Exception.Create('Joblara erişimde sorunla karşılaşıldı.!');
      end;
    end;

    while Succeeded(Jobs.Next(1, Job, dummy)) and (dummy = 1) do
    begin
      JobID := '';
      DisplayName := '';
      Description := '';
      LocalFiles  := nil;
      RemoteFiles := nil;

      if Succeeded(Job.GetDisplayName(pTemp)) then 
      begin
        DisplayName := pTemp;
        CoTaskMemFree(pTemp); // PWideChar türündeki değişkenimize hafızayı BITS ayırır, o yüzden burada serbest bırakmak gerekir.
      end;

      if Succeeded(Job.GetDescription(pTemp)) then
      begin
      	Description := pTemp;
        CoTaskMemFree(pTemp);
      end;

      if Succeeded(Job.GetId(tmpJobId)) then
        JobID := GuidToString(TGuid(tmpJobID));

      FileCount := 0;
      retVal := Job.EnumFiles(BackFiles);
      if not Succeeded(retVal) then Continue;

      BackFiles.GetCount(FileCount);
      if FileCount <= 0 then Continue;

      SetLength(LocalFiles , FileCount);
      SetLength(RemoteFiles, FileCount);

      iFileIndex := 0;

      while Succeeded(BackFiles.Next(1, BackFile, dummy)) and (dummy = 1) do
      begin
      	if Succeeded(BackFile.GetLocalName(pTemp)) then
        begin
          LocalFiles[iFileIndex] := pTemp;
          CoTaskMemFree(pTemp);
        end;

        if Succeeded(BackFile.GetRemoteName(pTemp)) then
        begin
          RemoteFiles[iFileIndex] := pTemp;
          CoTaskMemFree(pTemp);
        end;

        Inc(iFileIndex);
      end;

      DownLoadJob := TDownloadJob.Create(JobId, DisplayName,Description,LocalFiles,RemoteFiles);
      DownloadJobs.Add( DownloadJob );

      LocalFiles := nil;
      RemoteFiles:= nil;
    end;

  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

procedure TDownloadManager.Notify(const DownloadNotify: TDownloadNotify);
begin
  fCritSect.Enter;
    if Assigned(fOnStateChanged) then fOnStateChanged(DownloadNotify);
  fCritSect.Leave;
end;

{ TDownloadJob }

function TDownloadJob.Cancel : Boolean;
var
  Manager 		: IBackgroundCopyManager;
  foundJobID	: GUID;
  foundJob		: IBackgroundCopyJob;
begin
  Result := false;

  CoInitialize(nil);
  try
    Manager := CoBackgroundCopyManager_.Create;
    foundJobID := GUID(StringToGuid(JobID));
    Result := Succeeded(Manager.GetJob(foundJobID, foundJob));
    if Result then
    	Result := Result and Succeeded(foundJob.Cancel);
  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

function TDownloadJob.Complete : Boolean;
var
  Manager 		: IBackgroundCopyManager;
  foundJobID	: GUID;
  foundJob		: IBackgroundCopyJob;
begin
  Result := false;

  CoInitialize(nil);
  try
    Manager := CoBackgroundCopyManager_.Create;
    foundJobID := GUID(StringToGuid(JobID));
    Result := Succeeded(Manager.GetJob(foundJobID, foundJob));

    if Result then
    	Result := Result and Succeeded(foundJob.Complete);
  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

constructor TDownloadJob.Create(const AJobID, ADisplayName, ADescription: String;
  const ALocalFileNames, ARemoteFileNames: array of String);
var
  iCounter : Integer;
begin
  inherited Create;

  if Length(ALocalFileNames) <> Length(ARemoteFileNames) then
  	raise Exception.Create('İndirilecek dosyalar ile kaydedilecek dosyaların sayısında uyumsuzluk var.!');

  for iCounter := Low(ALocalFileNames) to High(ALocalFileNames) do
    if not DirectoryExists(ExtractFileDir(ALocalFileNames[iCounter])) then
      raise Exception.Create(ExtractFileDir(ALocalFileNames[iCounter]) + ' dizini bulunamadı.!');

  fLocalFileNames := TStringList.Create;
  fRemoteFileNames:= TStringList.Create;

  fJobID := AJobID;
  fDisplayName := ADisplayName;
  fDescription := ADescription;

  for iCounter := Low(ALocalFileNames) to High(ALocalFileNames) do
  begin
    fLocalFileNames.Add(ALocalFileNames[iCounter]);
    fRemoteFileNames.Add(ARemoteFileNames[iCounter]);
  end;
end;

function TDownloadJob.Resume : Boolean;
var
  Manager 		: IBackgroundCopyManager;
  foundJobID	: GUID;
  foundJob		: IBackgroundCopyJob;
begin
  Result := false;

  CoInitialize(nil);
  try
    Manager := CoBackgroundCopyManager_.Create;
    foundJobID := GUID(StringToGuid(JobID));
    Result := Succeeded(Manager.GetJob(foundJobID, foundJob));

    if Result then
    	Result := Result and Succeeded(foundJob.Resume);
  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

function TDownloadJob.Suspend : Boolean;
var
  Manager 		: IBackgroundCopyManager;
  foundJobID	: GUID;
  foundJob		: IBackgroundCopyJob;
begin
  Result := false;

  CoInitialize(nil);
  try
    Manager := CoBackgroundCopyManager_.Create;
    foundJobID := GUID(StringToGuid(JobID));
    Result := Succeeded(Manager.GetJob(foundJobID, foundJob));

    if Result then
    	Result := Result and Succeeded(foundJob.Suspend);
  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

{ TDownloadJobs }

function TDownloadJobs.Add(Item: TDownloadJob): Integer;
begin
  Result := inherited Add(Item);
end;

procedure TDownloadJobs.Delete(Index: Integer);
begin
  try
    Jobs[Index].Free;
  except
  end;

  inherited Delete(Index);
end;

procedure TDownloadJobs.DeleteAll;
begin
  while Count > 0 do Delete(0);
end;

destructor TDownloadJobs.Destroy;
begin
  DeleteAll;

  inherited;
end;

function TDownloadJobs.FindJob(const ID: String): TDownloadJob;
var
  iCounter : Integer;
begin
  Result := nil;

  for iCounter := 0 to Count - 1 do
    if Jobs[iCounter].JobID = ID then
    begin
      Result := Jobs[iCounter];
      Break;
    end;
end;

function TDownloadJobs.GetDownloadJob(AIndex: Integer): TDownloadJob;
begin
  Result := TDownloadJob(inherited Items[AIndex]);
end;

function TDownloadJobs.IndexOf(Item: TDownloadJob): Integer;
begin
  Result := inherited IndexOf(Item);
end;

function TDownloadJobs.Remove(Item: TDownloadJob): Integer;
begin
  try
    Item.Free;
  except
  end;

  Result := inherited Remove(Item);
end;

{ TDownloadThread }

constructor TDownloadThread.Create(const AOwner : TDownloadManager; const DisplayName, Description: String;
  const LocalFileNames, RemoteFileNames: array of String);
var
  iCounter : Integer;
begin
  inherited Create(true);

  fOwner := AOwner;
  fDisplayName := DisplayName;
  fDescription := Description;

  SetLength(fLocalFileNames , Length(LocalFileNames));
  SetLength(fRemoteFileNames, Length(RemoteFileNames));

  for iCounter := Low(LocalFileNames) to High(LocalFileNames) do
  begin
    fLocalFileNames[iCounter] := LocalFileNames[iCounter];
    fRemoteFileNames[iCounter] := RemoteFileNames[iCounter];
  end;

  Resume;
end;

destructor TDownloadThread.Destroy;
begin
  fLocalFileNames := nil;
  fRemoteFileNames:= nil;

  inherited;
end;

procedure TDownloadThread.Execute;
var
  Manager : IBackgroundCopyManager;
  Job : IBackgroundCopyJob;
  Error : IBackgroundCopyError;
  pTemp : PWideChar;
  JobID : GUID;
  State : BG_JOB_STATE;
  Progress : _BG_JOB_PROGRESS;
  bRun : Boolean;

  iCounter : Integer;
  DownloadNotify : TDownloadNotify;
begin
  inherited;

  CoInitialize(nil);
  Manager := CoBackgroundCopyManager_.Create;

  try
    if not Succeeded(Manager.CreateJob(PWideChar(fDisplayName), BG_JOB_TYPE_DOWNLOAD, JobID, Job)) then
    begin
      Manager := nil;
      CoUninitialize;
      Exit;
    end;

    if not Succeeded(Job.SetDescription(PWideChar(fDescription))) then
    begin
      Manager := nil;
      CoUninitialize;
      Exit;
    end;

    for iCounter := Low(fLocalFileNames) to High(fLocalFileNames) do
      if not Succeeded(Job.AddFile( StringToOleStr(fRemoteFileNames[iCounter]), StringToOleStr(fLocalFileNames[iCounter]) )) then
      begin
        Manager := nil;
        CoUninitialize;
      	Exit;
      end;

    if not Succeeded(Job.SetPriority(BG_JOB_PRIORITY_HIGH)) then
    begin
      Manager := nil;
      CoUninitialize;
      Exit;
    end;

    fOwner.DownloadJobs.Add( TDownloadJob.Create( GUIDToString(TGuid(JobID)), fDisplayName, fDescription, fLocalFileNames, fRemoteFileNames ) );

    bRun := false;
    repeat
      if not Succeeded(Job.GetState(State)) then Break;

      job.GetProgress(Progress);

      DownloadNotify.JobID 			:= GUIDToString(TGuid(JobID));
      DownloadNotify.BytesTotal 		:= Progress.BytesTotal;
      DownloadNotify.BytesTransferred 	:= Progress.BytesTransferred;
      DownloadNotify.FilesTotal 		:= Progress.FilesTotal;
      DownloadNotify.FilesTransferred 	:= Progress.FilesTransferred;
      DownloadNotify.Error.Context 		:= '';
      DownloadNotify.Error.Protocol 		:= '';
      DownloadNotify.Error.Description 	:= '';

(*
	BG_JOB_STATE_QUEUED 		: Job kuyrukta ve çalıştırılmayı bekliyor. Eğer job transfer edilirken logoff olunur ise job kuyruğa atılır.
	BG_JOB_STATE_CONNECTING 	: BITS server ile bağlantı kurmaya çalışıyor. Eğer bağlantı sağlanır ise BG_JOB_STATE_TRANSFERRING durumuna geçilir, aksi durumda BG_JOB_STATE_TRANSIENT_ERROR durumuna geçilir.

	BG_JOB_STATE_TRANSFERRING	: Veriler transfer edilmeye başlandı.
	BG_JOB_STATE_SUSPENDED 		: Job'un durdurulduğunu gösterir. Resume, Complete yada Cancel metodları çağırılmadan bu durumdan çıkmaz.
	BG_JOB_STATE_ERROR			: Dosyanın transfer edilemediğini belirtir. Eğer server'a ulaşılamamasından yada yetki sorunlarından kaynaklanan bir hata nedeni ile bu state'de isek hata giderildikten sonra Resume metodunun çalıştırılması ile job kaldığı yerden devam edebilir.
Eğer hata düzeltilemiyor ise Cancel metodu çağırılabilir, bu durumda o zamana kadar yapılan indirmeler de iptal edilmiş olur, yada Complete metodu çağırılabilir,
o zamana kadar yapılan download onaylanır.
BG_JOB_STATE_TRANSIENT_ERROR         : Ciddi bir hata olmadığı durumlarda bu duruma alınır. BITS indirme çabalarına devam edecektir. Ancak bir job için belirtilen zamanın aşılması durumunda job durumunu BG_JOB_STATE_ERROR konumuna alacaktır.BITS network bağlantısının kesilmesi yada disk üzerinde lock işlemi uygulayan bir process'in varlığı durumunda(chkdsk gibi) indirmeyi denemekten vazgeçecektir.
BG_JOB_STATE_TRANSFERRED		: Dosya indirme işlemi tamamlandı. Complete metodunu çağırmalıyız.
BG_JOB_STATE_ACKNOWLEDGED		: Job'un Complete metodu çağırılması ile düzgün bir şekilde indirildiğinin onaylandığını belirtir.
BG_JOB_STATE_CANCELLED			: Cancel metodunun çağırılması vasıtası ile indirme işleminin iptal edildiği bilgisidir.
*)

      case State of
        BG_JOB_STATE_QUEUED : 
        begin
          DownloadNotify.DownloadState := dsQueue;
          fOwner.Notify(DownloadNotify);
          bRun := true;
        end;
        BG_JOB_STATE_CONNECTING	: 
        begin
          DownloadNotify.DownloadState := dsConnecting;
          fOwner.Notify(DownloadNotify);
          bRun := true;
        end;
        BG_JOB_STATE_TRANSFERRING: 
        begin
          DownloadNotify.DownloadState := dsTransferring;
          fOwner.Notify(DownloadNotify);
          bRun := true;
        end;
        BG_JOB_STATE_SUSPENDED : 
        begin
          DownloadNotify.DownloadState := dsSuspended;
          fOwner.Notify(DownloadNotify);

          if not Succeeded(Job.Resume)
          then bRun := false
          else bRun := true;
        end;
        BG_JOB_STATE_ERROR 	,
        BG_JOB_STATE_TRANSIENT_ERROR: 
        begin
          DownloadNotify.DownloadState := dsError;

          if Succeeded(job.GetError(Error)) then
          begin
            if Succeeded(Error.GetErrorDescription(LANG_NEUTRAL, pTemp)) then
            begin
              DownloadNotify.Error.Description := String(pTemp);
              CoTaskMemFree(pTemp);
            end;

            if Succeeded(Error.GetErrorContextDescription(LANG_NEUTRAL, pTemp)) then
            begin
              DownloadNotify.Error.Context := String(pTemp);
              CoTaskMemFree(pTemp);
            end;

            if Succeeded(Error.GetProtocol(pTemp)) then
            begin
              DownloadNotify.Error.Protocol := String(pTemp);
              CoTaskMemFree(pTemp);
            end;
          end;

          fOwner.Notify(DownloadNotify);
          bRun := false;
        end;
        BG_JOB_STATE_TRANSFERRED : 
        begin
          Job.Complete;
          DownloadNotify.DownloadState := dsTransferred;
          fOwner.Notify(DownloadNotify);
          bRun := true;
        end;
        BG_JOB_STATE_ACKNOWLEDGED : 
        begin
          DownloadNotify.DownloadState := dsAcknowlegged;
          fOwner.Notify(DownloadNotify);
          bRun := false;
        end;
        BG_JOB_STATE_CANCELLED : 
        begin
          DownloadNotify.DownloadState := dsCancelled;
          fOwner.Notify(DownloadNotify);
          bRun := false;
        end;
        else
        begin
          DownloadNotify.DownloadState := dsUnknown;
          fOwner.Notify(DownloadNotify);
          bRun := false;
        end;
      end; // case State of
    until not bRun; // repeat..until
  finally
    Manager := nil;
    CoUninitialize;
  end;
end;

Son olarak kullanımından kısa bir örnek vererek makalemizi tamamlayalım:

procedure TForm1.Button2Click(Sender: TObject);
var
  iCounter : Integer;
begin
  Downloader := TDownloadManager.Create;
  Downloader.OnStateChanged := StateChanged;
  for iCounter := 0 to Downloader.DownloadJobs.Count - 1 do
  	Memo1.Lines.Add(Downloader.DownloadJobs[iCounter].DisplayName + Downloader.DownloadJobs[iCounter].Description);

  Downloader.Download('Bits Demo', 'Bir adet açıklama', ['c:\BitsDownload\1.zip'], ['http://xyz.com/abc.zip']);
  Downloader.Download('Bits Demo', 'Bir adet açıklama', ['c:\BitsDownload\2.zip'], ['http://xyz.com/def.zip']);
  Downloader.Download('Bits Demo', 'Bir adet açıklama', ['c:\BitsDownload\3.zip', 'c:\BitsDownload\4.zip'], ['http://xyz.com/abc.zip', 'http://xyz.com/def.zip']);
end;

BackgroundCopyManager_TLB ve untSilentDownloader isimli dosyaları da buradan indirebilirsiniz.

Sevgiler, saygılar..

“BITS(Background Intelligent Transfer Service) ile sessiz sedasız download ;)” için 12 Yorum

  1. sadettinpolat diyor ki:

    tugrl hocam , tesekkurler. bu bloga ugradikca insan yeni yeni seyler ogreniyor.

  2. Tuğrul HELVACI diyor ki:

    Rica ederim üstad. Bir teşekkür bin makale eder..

  3. Olcay DAĞLI diyor ki:

    Hocam çok güzel bir makale olmuş yine, sayende işletim sisteminide öğrenmiş oluyoruz. Bu makalede özellikle TBL dosyasının oluşturulma çok hoş olmuş, özellikle hazıra alışmış olan bazı programcılara(benim gibi), araştırmanın güzelliğini anlatan harika bir makale, eline sağlık… ;)

  4. Tuğrul HELVACI diyor ki:

    İyi günlerde kullan Olcay’ım :) Senden de makale bekliyoruz dememe gerek yok sanırım ;)

  5. mustafa diyor ki:

    merhaba bu siteye İlk kez google amca sayesinde uğradım :)
    tuğrul bey gerçekten acemi bir app.developer olarak
    diyebilirm ki harika uygulamalar var.
    Bu uygulamaları araştırmak bile çok zor eminim.
    uzun söze ne hacet Elinize Sağlık.
    Çok güzel bilgiler bunlar.

  6. Knn diyor ki:

    Elinize, emeğinize sağlık.

  7. Nevzat ÇAVUŞ diyor ki:

    Tuğrul bey Merhaba,

    Öcelikle burada yapmış olduğunuz çalışmalardan dolayı sizi tebrik ederim. Bu konu ile alakalı olurmu bilmiyorum ama bir sorunum var yardımcı olursanız sevinirim. Windows Service uygulaması içerisinden Winexec veya Shellexecute ile uygulama çalıştırmak istediğimde uygulamam show olmuyor.Kısacası Windows Service içerisinden Exe çalıştırma ile yardımlarınızı bekliyorum. Saygılarımla…

    • Tuğrul HELVACI diyor ki:

      Merhaba Nevzat Bey, servis uygulamanız hangi işletim sistemi altında çalışıyor ? Servis uygulamaları ile desktop uygulamaları farklı güvenlik katmanlarında çalışırlar. Eğer bir servis uygulaması içinden çalıştıracağınız uygulama bir başka servis uygulaması ise o zaman o uygulamanın desktop interaction özelliğine bakmanızı önerebilirim. Aksi bir durumda, çalıştıracağınız uygulamanın desktop ortamında çalıştırılabilmesi için ilgili security özelliklerine sahip olması gerekir. Bir servisin içinden çalıştırılan uygulama otomatik olarak o ortamın güvenlik hakları ile başlatılacaktır. Ancak siz bu duruma; OpenProcessToken, CreateProcessAsUser, LookupAccountSid gibi güvenlik API’leri ile biraz haşır neşir olarak müdahale edebilirsiniz.

  8. Cemali OZAN diyor ki:

    Eline zihine sağlık usta.
    “BITS’in indirme işlemine bir an evvel başlayabilmesi adına işletim sisteminin saatini ileri almak bir işe yaramaz. BITS yine ne kadar beklemesi gerekiyor ise o kadar bekleyecektir.” Dumura uğrayan kodcuları düşününce burada koptum. :) ))

  9. deniz diyor ki:

    elinize sağlık tuğrul bey ben delphi 7 kullanıyorum ve pekte iyi sayılmam affınıza sığınarak yukarıda vermiş olduğunuz örnek koddaki Downloader kodunu tanımlatamadım yardımcı olabilirmisiniz ?

  10. Sedat YILDIRIM diyor ki:

    1)Private kısmında Procedure StateChanged(const State:TDownloadNotify); prosedurunu tanımla.

    2)Bu Prosedurun yapacağı iş aşağıdaki gibi olabilir:
    Procedure TForm1.StateChanged(const State:TDownloadNotify);
    begin
    Case State.DownloadState of
    dsQueue:Label3.Caption:=’Kuyrukta bekliyor…’;
    dsConnecting:Label3.Caption:=’Bağlanıyor…’;
    dsTransferring:Label3.Caption:=’Transfer ediliyor…’;
    dsTransferred:Label3.Caption:=’Bitti…’;
    dsCancelled:Label3.Caption:=’İptal edildi…’;
    End;

    3)Button2Click Prosedüründeki Downloader değişkenin tanımını yapmayı unutma…(Downloader:TDownloadManager;) Muhtemelen çalışacaktır.

Nevzat ÇAVUŞ için yorum yazın