I hope you enjoyed the first part of the introduction to BITS.
In this second part, we want to improve our job list (a little bit) and start own dowloads.

The first task is, to replace the old TListbox with a TListview. I switched the ViewStyle to vsReport and added 3 columns: Name, State, GUID. Saving the GUID of the job as a string is not fine but goes with my laziness. :mrgreen: We also have to change the code of the Timer. My whole OnTimer-Event looks like this:

var
  Job : IBackgroundCopyJob;
  Jobs : IEnumBackgroundCopyJobs;
  Res : HRESULT;
  DisplayName : PWideChar;
  Fetched : ULong;
  State : BG_JOB_STATE;
  JobID : TGUID;
  Item : TListItem;
begin
  lv_Jobs.Clear;

  Res := FManager.EnumJobs(BG_JOB_ENUM_ALL_USERS, Jobs);

  if not Succeeded(Res) then
    Res := FManager.EnumJobs(0, Jobs);  

  if not Succeeded(Res) then
    raise Exception.Create(‘Can not enum BackgroundCopyJobs’);

  while Succeeded(Jobs.Next(1, Job, @Fetched)) and (Fetched = 1) do
  begin
    Item := lv_Jobs.Items.Add;

    Item.SubItems.Add(‘unknown’);  //state of the job
    Item.SubItems.Add(‘unknown’);  //GUID of the job

    if Succeeded(Job.GetDisplayName(DisplayName)) then
    begin
      Item.Caption := DisplayName;
      CoTaskMemFree(DisplayName);
    end;

    if Succeeded(Job.GetState(State)) then
    begin
      case State of
        BG_JOB_STATE_QUEUED:          Item.SubItems[0] := ‘queued’;
        BG_JOB_STATE_CONNECTING:      Item.SubItems[0] := ‘connecting’;
        BG_JOB_STATE_TRANSFERRING:    Item.SubItems[0] := ‘transfering’;
        BG_JOB_STATE_SUSPENDED:       Item.SubItems[0] := ‘suspended’;
        BG_JOB_STATE_ERROR:           Item.SubItems[0] := ‘error’;
        BG_JOB_STATE_TRANSIENT_ERROR: Item.SubItems[0] := ‘transient error’;
        BG_JOB_STATE_TRANSFERRED:     Item.SubItems[0] := ‘transferred’;
        BG_JOB_STATE_ACKNOWLEDGED:    Item.SubItems[0] := ‘acknowledged’;
        BG_JOB_STATE_CANCELLED:       Item.SubItems[0] := ‘cancelled’;
      end;
    end;

    if Succeeded(Job.GetId(JobID)) then
      Item.SubItems[1] := GUIDToString(JobID);

    Job := nil;

  end;
end;

Ok, this code will never win an art-award but is does what I want.

Now we have a fine list. But what is missing? Action … coders need interaction! :-) Thats why we add a TPopupMenu to the listview for these 4 actions: Resume, Suspend, Cancel, Complete. Each OnClick-Code may look like this:

var
  Job : IBackgroundCopyJob;
begin
  if lv_Jobs.ItemIndex > -1 then
  begin
    if Succeeded(FManager.GetJob(StringToGUID(lv_Jobs.Selected.SubItems[1]), Job)) then
      Job.Resume; //change this to Suspend, Cancel, Complete
  end;
end;

So whats our state of affairs? There is a list of the current BITS-Jobs of our system. And we can control the states of the jobs. And – you are right – it’s still boring.

Starting a download job is very easy. Just put this code somewhere (for example in the OnClick-Event of a TButton):

var
  Job : IBackgroundCopyJob;
  JobID : TGUID;
begin
  if Succeeded(FManager.CreateJob(‘My Download’,
                                   BG_JOB_TYPE_DOWNLOAD,
                                   JobID,
                                   Job)) then
  begin
    if Succeeded(Job.AddFile(‘http://blog.delphi-jedi.net/wp-content/uploads/2008/04/jwabits.zip’,
                             ‘d:\thebitsfile.zip’)) then
    begin
      Job.Resume;
    end;
  end;
end;

Thats it. :-) Easy, isn’t it? Just run the app, click the button and watch the listview. If all went ok, the state of the job will switch from suspended via connecting, transferring to transferred.

Now its time to wonder: Where is my file? 8-O The file is already there, although with another name. BITS creates temporary files, while downloading the content. These files are usally named like BIT*.tmp. In order to get the real file out of the temporary file, we have to call the Complete procedure of the corresponding job, when it has reached the state transferred. There are at least 2 ways to find out, if the job is ready to be completed: Poll the state of the job or implement the IBackgroundCopyCallback interface. The last way is the smart one and it minimizes the length of code. Only 4 steps are needed to implment this interface:

  1. add IBackgroundCopyCallback to the class declaration of your form
    TForm1 = class(TForm, IBackgroundCopyCallback)
  2. add these 3 functions to the Form-class declaration and implementation
    function JobTransferred(pJob: IBackgroundCopyJob): HRESULT; stdcall;
    function JobError(pJob: IBackgroundCopyJob; pError: IBackgroundCopyError): HRESULT; stdcall;
    function JobModification(pJob: IBackgroundCopyJob; dwReserved: DWORD): HRESULT; stdcall;
  3. let all 3 functions return S_OK
  4. insert into the JobTransferred procedure the line:
    pJob.Complete;

Now we can use our form as notfier for events of BITS-jobs. Just call the SetNotifyInterface function (with the form-instance as 1st param) of the Job after creating it. I put this code after the Job.Resume call. If you start the programm now and add the job again, it should disappear after getting transferred and the local file should appear in the location you have choosen.

But be aware: this notification is only working as long as the interface exists. If you start a download, shut down the programm and restart it without setting the notify interface again, you will never get informed about the complete transmission of the job.

That’s it. It remains to say: Play around with the interfaces and functions to get in touch with BITS.
Because: Playing is learning!

PS: You can download the project of this article here: example-bits-2