this.picasaService.AcynOperationCompleted += new AsyncOperationCompletedEventHandler(this.OnDone);
this.picasaService.AcynOperationProgress += new AsyncOperationProgressEventHandler(this.OnProgress);
QueryFeedAsync
, and it is used in the Photobrowser.cs file in the following method:
public void StartQuery(string uri, string albumTitle)
{
UserState us = new UserState();
this.states.Add(us);
us.opType = UserState.OperationType.query;
us.filename = albumTitle;
this.picasaService.QueryFeedAync(new Uri(uri), DateTime.MinValue, us);
}
UserState
object to remember what kind of operation it was (in the above sample this is indicated with the UserState.OperationType.query), as well to realize if the callback was actually meant for the object that received it.
This is due to the fact that there are several forms open that all share the same service. Each of these forms adds their event handlers as in the sample code above. This means that the SDK will notify all open forms of progress reports. To distinguish that a given event is arriving at the correct form, the forms create UserState
objects, and remember those in the states variable.
An alternative solution would have been to just create a new service object per form, that would avoid this complication, but use more resources.
private void OnProgress(object sender, AsyncOperationProgressEventArgs e)
{
if (this.states.Contains(e.UserState as UserState) == true)
{
this.progressBar.Value = e.ProgressPercentage;
}
}
All we do here is that we set a UI element on the form, the progress bar control to the given percentage value of the event, and we are done.
Note though, we will not be able to get reasonable numbers for all requests. There are situations where you donwload a resource and the server does not return a content-length header. In this case the complete size of the download is not known before it is completed, therefore having an exact progress report is not possible. The SDK will still send you notifications, and let you know how much data is already downloaded, but the number for the ProgressPercentage will be constant at 100. You can determine this situation by evaluating the CompleteSize property of the event arguments, if that property is -1, the length of the data transfer is unknown.
private void OnDone(object sender, AsyncOperationCompletedEventArgs e)
{
UserState ut = e.UserState as UserState;
if (this.states.Contains(ut) == false)
return;
this.states.Remove(ut);
if (e.Error == null)
{
So first of all, we check if the event happens to be interesting to us. If it is not, the code returns. If it is, we remove the object from our list. Then we check if the error object of the argument is null (the object might hold an exception thrown on the background thread), if it is, we continue processing.
To handle our simple query-a-feed case, we need to check the operation type:
if (ut.opType == UserState.OperationType.query ||
ut.opType == UserState.OperationType.queryForBackup)
{
if (e.Feed != null)
{
this.photoFeed = e.Feed as PicasaFeed;
this.InitializeList(ut.filename);
}
}
If the operationtype is OperationType.query, we check if the arguments feed property is not null, if that is the case, we got a feed back, and can initialize our listbox with that feed. Pretty simple, even in the case of several operations at once.
For the case that downloads a picture, this is not much different:
We check the operationtype, if it is a download operation, we get the response stream property and use this to write the data to a file.
if (ut.opType == UserState.OperationType.download ||
ut.opType == UserState.OperationType.downloadList)
{
if (e.ResponseStream != null)
{
WriteFile(ut.filename, e.ResponseStream);
this.FileInfo.Text = "Saved file: " + ut.filename;
}
}
Where it get's a little more complicated is the "Backup an album" functionallity. An album can have a lot of pictures, and just doing something like this:
would create one new background thread per picture in the album, something that can tax even the more powerful machines. So what is implemented is a queue system. Whenever one picture is downloaded, we spawn the next download.
Therefore, for a backup job, we create a complete new photobrowser dialog, fill that dialog with the initial list of photos. Then we start downloading the first photo to the target folder, remove it from the dialog, and then spawn the download of the next photo. This is done in the following piece of code, also in the OnDone event handler:
foreach (Entry e in album.Entries)
Service.QueryStreamAync(e.Uri, DateTime.MinValue, myObject);
So, we check the operation type, if it is the OperationType.downloadList, we create a new, unique user object, increment a counter, add this object to the list of userobjects, remove the photo from the photolist, and spawn another delegate by invoking the CreateAnotherSaveFile() method of the photobrowser.
if (ut.opType == UserState.OperationType.downloadList)
{
// we need to create a new object for uniqueness
UserState u = new UserState();
u.counter = ut.counter + 1;
u.feed = ut.feed;
u.foldername = ut.foldername;
u.opType = UserState.OperationType.downloadList;
if (u.feed.Entries.Count > 0)
{
u.feed.Entries.RemoveAt(0);
this.PhotoList.Items.RemoveAt(0);
}
this.states.Add(u);
SaveAnotherPictureDelegate d = new SaveAnotherPictureDelegate(this.CreateAnotherSaveFile);
this.BeginInvoke(d, u);
}
What happens here is: if in our user state object, we have a feed with entries left in it, we take the first entry, and create another background thread to query for that picture. And so the cycle continues.
The only unhandled case is chunking of the feed. If a feed of photos would arrive in chunks, we would need to handle it here to load the next set of photos. This is just a placeholder, as i do not believe picasa creates this situation.
When we are done backing up the album, the dialog closes itself.
private void CreateAnotherSaveFile(UserState us)
{
if (us.feed.Entries.Count > 0)
{
PicasaEntry p = us.feed.Entries[0] as PicasaEntry;
us.filename = us.foldername + "\\image" + us.counter.ToString() + ".jpg";
this.picasaService.QueryStreamAync(new Uri(p.Media.Content.Attributes["url"] as string), DateTime.MinValue, us);
}
else if (us.feed.Entries.Count == 0 && us.feed.NextChunk != null)
{
}
else
{
this.Close();
}
}