Rx: Fusionner plusieurs Observable en un seul
By Michael DELVA on Wednesday 12 May 2010, 11:00 - C# - Permalink
TweetCet article est un prolongement de l'article précédent, puisqu'après avoir vu comment lancer le téléchargement de toutes les mises à jour et être notifié quand tout est terminé, nous allons cette fois voir comment récupérer une liste d'observables (chacun d'entre eux correspondant à l'installation d'une mise à jour), les fusionner en un seul, afin de pouvoir exécuter les mises à jour les unes après les autres, avec un arrête automatique dès lors qu'une mise à jour se passe mal.
La dernière fonction que nous avons vu dans l'article précédent correspondait au lancement du téléchargement des mises à jour:
public void Install()
{
Download()
.ObserveOn(scheduler)
.Subscribe(_ => { },
error =>
{
HasError = true;
DisplayErrorInStatusBar(ShellViewModelRes.DownloadError, error);
IsWorking = false;
},
DoInstall);
}
Avant de voir comment est réalisée cette "fusion" dans la fonction DoInstall, nous allons prendre quelques secondes pour d'abord regarder comment la classe AssemblyUpdater va retourner les observables correspondant à l'exécution des mises à jour:
public Dictionary<AssemblyUpdateVersion, IObservable<Unit>> InstallUpdates()
{
var enumerable = from downloadedUpdate in downloadedUpdates
select new
{
Key = downloadedUpdate.Key,
Value = CreateInstallationObservable(downloadedUpdate.Value)
};
var result = new Dictionary<AssemblyUpdateVersion, IObservable<Unit>>();
enumerable.ForEach(x => result.Add(x.Key, x.Value));
return result;
}
private IObservable<Unit> CreateInstallationObservable(string filePath)
{
return FileInstaller.Install(filePath)
.Do(_ => Logger.Verbose("Install update {0}", filePath),
error => Logger.Error("Error during installation of {0} - Error message : {1}", filePath, error.Message),
() => Logger.Verbose("Update successfully installed - {0}", filePath));
}
Rien de bien compliqué, puisqu'on va parcourir le champ downloadedUpdates, auquel un élément a été ajouté dès qu'un téléchargement s'est terminé, puis on crée le Dictionary en utilisant la fonction CreateInstallationObservable, qui appelle FileInstaller.Install, dont voici la déclaration de l'interface qu'elle implémente:
public interface IFileInstaller
{
IObservable<Unit> Install(string filePath);
}
Revenons maintenant dans le ViewModel, et plus particulièrement dans la fonction DoInstall, où se trouve véritablement ce qui nous intéresse. Le principe de la fonction est assez similaire à la fonction Download vue dans l'article précédent.
On récupère l'instance correspondant à la mise à jour dans InstalledAssemblies (qui est je le rappelle de type ObservableCollection<InstalledAssembly>), et on appelle la fonction InstallUpdate sur cette instance. Cette dernière fonction va retourner un IObservable<Unit>, qui sera en fait l'observable qu'on va lui passer en paramètre (et qui est donc l'observable retourné par AssemblyUpdater et qui correspond à l'installation en elle-même. Vous suivez toujours? :D) sur lequel on va simplement appeler la méthode d'extension Do() afin de mettre à jour le GUI en fonction du résultat de l'installation. Toujours dans la fonction DoInstall du ViewModel, on va ainsi récupérer les observables modifiés par les classes InstalledAssembly et on va les fusionner en un seul observable grâce à la méthode d'extension Concat, avant de souscrire un observateur à cet observable concaténé. Si je n'ai pas été très clair, j'espère que le code le sera un peu plus :p
Commençons par la fonction InstalledAssembly.InstallUpdate:
public IObservable<Unit> Install(IObservable<Unit> installObservable)
{
UpdateStatus = UpdateStatus.Installing;
StatusMessage = ShellViewModelRes.InstallingUpdate;
return from observable in installObservable
.Do(_ => { },
error =>
{
UpdateStatus = UpdateStatus.InstallError;
StatusMessage = string.Format(CultureInfo.CurrentUICulture, "{0} : {1}", ShellViewModelRes.InstallError, error.Message);
},
() =>
{
UpdateStatus = UpdateStatus.Installed;
StatusMessage = ShellViewModelRes.UpdateInstalled;
})
select observable;
}
Donc comme je le disais, on se contente d'ajouter à l'observable l'exécution de code grâce à la méthode d'extension Do, afin de mettre à jour la vue.
Vient maintenant le code de la fonction DoInstall du ViewModel:
private void DoInstall()
{
StatusText = ShellViewModelRes.InstallingUpdates;
var installObservables = assemblyUpdater.InstallUpdates();
var allObservableInstalls = from installObservable in installObservables
let installedAssembly = InstalledAssemblies.First(ia => ia.AssemblyName == installObservable.Key.AssemblyName)
let observable = installedAssembly.InstallUpdate(installObservable.Key, installObservable.Value)
select observable;
allObservableInstalls
.Concat()
.ObserveOn(scheduler)
.Finally(() =>
{
IsWorking = false;
CanQuit = true;
})
.Subscribe(_ => { },
error =>
{
HasError = true;
DisplayErrorInStatusBar(ShellViewModelRes.InstallError, error);
},
() =>
{
StatusText = ShellViewModelRes.AllUpdatesInstalled;
});
}
On voit donc ici:
- l'appel à InstalledAssembly.InstallUpdate pour chacune des mise à jour
- la création de allObservableInstalls, qui est de type IEnumerable<IObservable<Unit>>, et qui va contenir tous les observables de toutes les installations de mise à jour
- la concaténation de cet IEnumerable<IObservable<Unit>> en un seul IObservable<Unit>
- l'utilisation de la méthode d'extension Finally qui, vous vous en doutez, permet l'exécution de code dès que l'observable se termine (avec succès ou non)
- la souscription d'un observateur à cet observable concaténé, qui va donc (enfin) lancer les mises à jours, les unes après les autres, et finir de mettre à jour l'interface quand ce sera terminé
Il nous restera à voir comment exécuter la mise à jour, via la classe implémentant IFileInstaller, afin de terminer ce petit tour dans les méandres de mon application de mise à jour. Mais rassurez-vous (ou pas), je n'en aurai pas encore terminé avec Rx, puisque j'ai encore quelques articles prévus sur ce (vaste) sujet. Sachez que le prochain article vous montrera notamment comment exécuter une fonction en asynchrone, puisqu'il sera important de ne pas bloquer la vue et son rafraichissement lorsque la mise à jour sera en train d'être installée.
A bientôt !!!