Friday, September 30, 2005

Background Intelligent Transfer Service (BITS)

Summary: This article explains how to use Microsoft Background Intelligent Transfer Service (BITS) in Microsoft Visual Studio .NET through a wrapper around API calls. The code included with the download requires Visual Studio .NET and Microsoft Windows XP. (9 printed pages)

 

Contents

Introduction
Customized APIs
Using the BITS Wrapper
Items Included in Download
Conclusion

Introduction

One of the realities of the Internet is that it can take a while to download files of any significant size. Another reality is that no one wants to wait while these downloads occur, which has lead to a problem with applications that update themselves over the Internet: users choose not to download the latest patches, because it would force them to wait while the new files and application components are downloaded. In time, this problem can lead to users ending up far behind the latest version of their software.

What is needed is some way to download fixes and updates without impacting the user experience in any way. Well, that is just what Windows Update does in Microsoft® Windows® XP. Windows Update can be configured to download the latest software patches automatically (and it can be configured not to, an essential feature to give users control), and it does so without impacting the user's experience by performing these downloads in the background.

Figure 1. Windows XP Automatic Updates Feature

When I see a great new feature like this in Windows, the first thing I think is "how can I implement this in my own applications?" In this case, an answer is readily available. It turns out that the technology used by Automatic Updates is a feature called Background Intelligent Transfer Service (BITS). BITS is exposed to programmers through a set of APIs.

Customized APIs

The APIs and the associated examples are aimed at Microsoft Visual C++® programmers, so there was a little bit of work required to make them accessible to .NET languages.

Using the tools included with Microsoft Visual Studio®, a type library was generated from the Bits.idl file that is included with the Platform SDK. The Bits.idl type library was then converted into a .NET assembly by the type–library import (tlbimp) utility. For more information see Using the MIDL Compiler, and Type Library Importer.

Once the assembly was generated by tlbimp, a project could reference it just like any other .NET assembly. At this point, the features of BITS are useable and exposed using the interfaces documented in the Platform SDK. However, like many sets of APIs, BITS is not exposed in the programmer-friendly way that .NET developers are accustomed to, so I decided to write another layer, called a wrapper, on top of the BITS library, making it easier to use from .NET.

Note   You are working with a customized wrapper developed specifically for this article, instead of directly against the BITS libraries. The section in the Platform SDK discussing BITS is your best source of information for understanding exactly how to use this technology.

Using the BITS Wrapper

The balance of this article discusses how to use the wrapper to work with BITS and do the following:

  • Create a transfer job.
  • Receive notification of changes to your job(s) status.
  • Directly cancel, suspend, and complete a job.

This article is based on BITS library version 1.0. BITS library version 1.5 and later will include additional functionality that is not covered in this article.

This section discusses:

Getting Started
Creating a New Job
Adding Files to a Job
Checking the Job Status
Using Events

Getting Started

Before you can use any of the transfer service functionality, you need to reference the wrapper. Once the reference to the wrapper is added, and you have added an Imports Microsoft.Msdn.Samples.BITS statement to the top of your code, you can create an instance of the Manager object, which wraps the BackgroundCopyManager object in the underlying library. The constructor for the Manager class does not have any arguments, so creating a new instance is straightforward.

Dim myBITS As New Manager()

The Manager object is the core of any work you do with BITS:

  • Manager enables you to create new jobs.
  • With Manager, you can obtain a collection of jobs currently in the system.
  • You can retrieve a specific job using the Manager identifier.

Creating a New Job

Manager object exposes the CreateJob method, which creates a new transfer job when given a job name, with two other overloaded versions. You have the option of additionally specifying a description, or a description and two configuration values (the retry delay and the retry timeout). In its most simple form, you can create a new job by specifying only the name.

Dim myNewJob As Job = myBITS.CreateJob("Test Job Name")
Note   There is no requirement for the name of the job to be unique, it is used only for your reference, and a GUID is created to be the real unique identifier for a particular job. This identifying GUID is available through the ID property of the Job class.

Adding Files to a Job

Although the API for BITS supports adding one file or an entire group of files to a job with a single method call, the wrapper in this download only provides the ability to add a single file request at a time. For each file you add, you will need to specify the local destination and remote source file name.

myNewJob.AddFile("c:\msdnlogo.gif", _   "http://www.coldrooster.com/msdn.gif")

The directory specified for the local path must exist, and the account that creates this job must have permission to create files in it. If the local file already exists, it is replaced with the newly downloaded file once the job is completed.

NOTE   An error occurs if the local file cannot be replaced because it is read–only, or the job owner does not have permission to delete it once the job is completed. The remote file must also exist, and be static. Dynamic content such as ASP or CGI cannot be downloaded using this technology because BITS uses the Range request header to restart file downloads, which only supports static content. For more information on the Range request header, see section 14.3.5 of the HTTP 1.1 specification (RFC2616.txt).

Checking the Job Status

A new job starts in a suspended state, which is good because the job itself is not very useful until you have added the appropriate files. You must force the job to resume before any actual transfers will occur.

myNewJob.ResumeJob()

Resuming a job without any files will cause an error, as will trying to resume a job that is already completed or cancelled. Jobs automatically suspend when the user that owns the job is not logged on. You can also suspend the job using the Suspend method.

Note   Jobs only run when the account that created them is logged on. If a job is created by an application running in the Local System context (such as a Windows Service) then it will run at all times, even if no one is logged on.

Once a job has completed transferring file(s) down to the local machine, those files are not available to the user until the job is acknowledged through a call to the Complete method. You can use the State property to determine the current status of a job, as shown in this code:

If currentJob.State = JobState.Transferred Then    currentJob.Complete()End If

The possible states, and what each state indicates, are documented as part of the BITS reference material.

Regardless of the specific status, you obtain the current progress of a job through the Progress property, which returns the bytes transferred/bytes total and files transferred/files total values, enabling you to determine a percentage of work that has been done.

Ideally, to implement all concepts discussed so far in this article, set up a BITS job that transfers an extremely large file and will not expire before you have implemented the concepts in a test environment. One particularly large file, perfect for trying a long-running BITS transfer and available on the web for downloading, is the Windows 2000 SP2 install file.

The following code:

  1. Creates an instance of the Manager object.
  2. Creates a new job.
  3. Then adds the SP2 install file to the new job and starts the job with ResumeJob.

A loop, through all of the jobs that are currently queued for the user, displays the current progress and status of each job. The user should now have an option to continue the loop or stop the loop. Once the user declines to continue the loop, the transfer job for the SP2 executable is cancelled, and the temporary file created for this file transfer is deleted.

Imports Microsoft.Msdn.Samples.BITSModule Module1    Sub Main()        Dim myBITS As New BITS.Manager()        Dim sp2Job As Job = myBITS.CreateJob("SP2 Job")        sp2Job.AddFile("c:\bigfile.exe", _         "http://download.microsoft.com/download/.../W2KSP2.exe")        'Note that this is not the complete URL,        'the full URL to any large file will do        sp2Job.ResumeJob()        Dim Jobs As BITS.JobList        Dim currentJob As BITS.Job        Dim currentProgress As BITS.JobProgress        Dim exitLoop As Boolean = False        Jobs = myBITS.GetListofJobs()        Do            For Each currentJob In Jobs                currentProgress = currentJob.Progress()                Console.WriteLine("{0} {1}/{2} ({3})", _                    currentJob.DisplayName, _                    currentProgress.BytesTransferred, _                    currentProgress.BytesTotal, _                    currentJob.StateString)            Next            Console.Write("Continue (Y/N)?")            If Console.ReadLine() = "N" Then                exitLoop = True                sp2Job.Cancel()                Console.WriteLine("Job Cancelled")            End If        Loop Until exitLoop = True        Console.ReadLine()    End SubEnd Module

Every complete loop should produce console output like this example:

SP2 Job 2627516/106278016 (Transferring)Continue (Y/N)?

If you decide not to continue, the loop will stop and the job will be cancelled.

If your job is in the transferring state and you remove your network connection by disconnecting a dial-up connection, disabling a LAN connection, or removing your network cable from your network card, the job should enter transient error mode, indicating that it has encountered a network problem. Once network connectivity is restored it will resume in the transferring state.

The amount of time before BITS tries to resume a transfer after a transient error occurs is controlled by the MinimumRetryDelay property of the Job object. The length of time after which BITS will not continue to retry, if transferring does not resume, is controlled by the NoProgressTimeout property. These timing values default to ten minutes and fourteen days respectively. At least ten minutes will expire after you disconnect from the network before the transfer will attempt to resume.

Using Events

The previous example showed you how to poll for the current status of a job. However, BITS also has a notification–based system. Through the .NET wrapper, the notification-based system is exposed as the JobEvents class where you add any number of jobs that you wish to monitor. Events are raised when a monitored job encounters an error, is modified (including status changes), or finishes transferring. The next code sample shows how this notification interface is used behind a Form to react to events occurring with any of the current jobs.

Form code that is not directly related to the BITS example has been omitted. See the downloadable code for the complete version.

Note   By default, the wrapper does not register for the JobModification event since it is called often and expensive. If you wish to register for this event, you must use the overloaded version of JobEvents.AddJob and specify the desired events yourself. In any event handler, you should be careful to not do too much work, since you are blocking the thread that raised the event. If you have a lot of unfinished work in response to a raised event, you should spin off a new thread to handle it.
Imports Microsoft.Msdn.Samples.BITSPublic Class Form1    Inherits System.Windows.Forms.Form    Dim WithEvents myEvents As New JobEvents()    Dim myJobs As JobList    Private Sub Form1_Load(ByVal sender As System.Object, _                           ByVal e As System.EventArgs) _                           Handles MyBase.Load        Dim myBITS As New BITS.Manager()        myJobs = myBITS.GetListofJobs(JobType.CurrentUser)        Dim currentJob As Job        For Each currentJob In myJobs            myEvents.AddJob(currentJob)        Next        ListBox1.DataSource = myJobs    End Sub    Private Sub myEvents_JobModification(ByVal sender As Object, _                                         ByVal e As JobEventArgs) _                                         Handles myEvents.JobModification        Dim sb As New System.Text.StringBuilder()        sb.AppendFormat("{0} ({1})", _                e.JobName, _                e.Job.StateString)        sb.Append(Environment.NewLine)        sb.Append(TextBox1.Text)        TextBox1.Text = sb.ToString    End Sub    Private Sub myEvents_JobError(ByVal sender As Object, _                                  ByVal e As BITS.JobErrorEventArgs) _                                  Handles myEvents.JobError        Dim sb As New System.Text.StringBuilder()        sb.AppendFormat("{0} ({1})", _                e.JobName, _                e.GetErrorDescription())        sb.Append(Environment.NewLine)        sb.Append(TextBox1.Text)        TextBox1.Text = sb.ToString    End Sub    Private Sub myEvents_JobTransferred(ByVal sender As Object, _                                        ByVal e As BITS.JobEventArgs) _                                        Handles myEvents.JobTransferred        Dim sb As New System.Text.StringBuilder()        sb.AppendFormat("{0} ({1})", _                e.JobName, _                "Transferred")        sb.Append(Environment.NewLine)        sb.Append(TextBox1.Text)        TextBox1.Text = sb.ToString    End SubEnd Class

BITS runs in the background, so your programs can start jobs and then periodically check status, instead of having to run throughout the entire download. Remember that BITS is using the user's network connection (although it has minimal impact) and storing files onto their computer, so it is essential that if you use this technology you provide the user with the option of turning it off completely or agreeing to each download before it occurs, just like Windows XP does with the Automatic Updates feature.

Included in Download

In addition to the source for the samples used in this article, the download also includes these required items:

  • The .NET assembly (BackgroundCopyManager.dll) created by importing the BITS type library, and the type library BITS.tlb that was created from BITS.idl.
  • The SIDUtilities project (and compiled assembly), a Microsoft Visual Basic® .NET library used to convert a Security ID (SID) into the string representation of a user's account name. This item is required by the wrapper project.
  • The wrapper for the BITS library, Microsoft.Msdn.Samples.BITS.dll, ready for you to reference in your applications. The code for this wrapper, written in Visual Basic .NET, is also included in the download. If you use Microsoft C#, or any other .NET language other than Visual Basic .NET, you only need to reference the assembly.

The BITS library includes much more functionality than what is covered in this article and most of that functionality is included in the .NET wrapper. For more information, see the section in the Platform SDK that discusses BITS.     WrappingBITS.msi.                                                 

           

Posted using illegal copy of BlogJet.
Please purchase a license here.

0 Comments:

Post a Comment

<< Home