Dans ma série sur les processus je vous ai montré comment faire des traitements non bloquants en tâche de fond. Voici un cas pratique : le téléchargement d'un fichier depuis Internet.
Lorsqu'on développe des applications mobiles ou des applications bureautiques en lien avec des sites Internet ou des API tierces il n'est pas rare de devoir accéder à des fichiers distants. Le plus simple pour les télécharger est de faire un GET à l'aide des composants de l'unité System.Net.HttpClient
Voici donc une classe permettant de rapatrier un fichier depuis une URL et le stocker en local.
unit u_download;
interface
uses system.SysUtils;
type
tdownload_file = class(tobject)
class procedure download(from_url, to_filename: string;
success: tproc = nil; error: tproc = nil);
class function temporaryFileName(appName: string): string;
end;
implementation
{ download_file }
uses system.ioutils, system.Net.HttpClient, system.Classes;
class procedure tdownload_file.download(from_url, to_filename: string;
success: tproc = nil; error: tproc = nil);
begin
tthread.CreateAnonymousThread(
procedure
var
serveur: THTTPClient;
serveur_reponse: IHTTPResponse;
fichier: tfilestream;
begin
try
serveur := THTTPClient.Create;
try
serveur_reponse := serveur.Get(from_url);
if serveur_reponse.StatusCode = 200 then
begin
fichier := tfilestream.Create(to_filename,
fmCreate or fmOpenWrite or fmShareDenyWrite);
try
fichier.CopyFrom(serveur_reponse.ContentStream,
serveur_reponse.ContentStream.Size);
finally
fichier.Free;
end;
if assigned(success) then
tthread.queue(nil,
procedure
begin
success;
end);
end
else
begin
raise Exception.CreateFmt
('Cannot get distant file. Please contact us or retry later. HTTP %d - %s',
[serveur_reponse.StatusCode, serveur_reponse.StatusText]);
end;
finally
serveur.Free;
end;
except
if assigned(error) then
tthread.queue(nil,
procedure
begin
error;
end);
end;
end).Start;
end;
class function tdownload_file.temporaryFileName(appName: string): string;
begin
result := tpath.Combine(tpath.gettempPath,
appName + '-' + datetimetotimestamp(now).Time.ToString + '.tmp')
end;
end.
Et comme on désire connaître le résultat du téléchargement et surtout le moment où il se termine, quoi de mieux que de passer également des procedures qui seront ainsi appelées lorsque c'est nécessaire ?
L'appel se fait donc de la façon suivante :
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := false;
tdownload_file.download('http://gamolf.fr/images/gamolf-500x500.png',
tpath.Combine(tpath.GetDocumentsPath(), '__monimage.png'),
procedure
begin
showMessage('Fichier téléchargé');
Button1.Enabled := true;
end,
procedure
begin
showMessage('Erreur lors du téléchargement');
Button1.Enabled := true;
end);
end;
Dans cet exemple j'ai juste mis un TButton sur une fiche vide. L'événement onclick de ce bouton est lié à la méthode Button1Click. L'unité utilise deux autres unités : u_download pour le téléchargement et System.IOUtils dont la classe TPath permet d'obtenir entre autres choses la liste des dossiers utiles en fonction de la plateforme sur laquelle on exécute le programme.
Lors du clic sur le bouton, il se désactive pour éviter qu'on reclique dessus tant que l'opération n'est pas terminée puis appelle le téléchargement d'une image dont on spécifie l'URL (ici le logo de l'un de mes sites) et le dossier de stockage en local. Cet appel passe également deux procedures anonymes. Dans ces procédures on fait un affichage pour prévenir l'utilisateur de l'état du transfert et on réactive l'état du bouton.
Ici réactiver le bouton ne sert à rien puisque cliquer à nouveau dessus téléchargerait le même fichier, mais vous voyez le principe.
La première procédure passée en paramètre est appelée lorsque le téléchargement se termine avec succès. La seconde est appelée en cas d'erreur : lorsqu'une exception est déclenchée ou s'il y a un code d'erreur HTTP différent de 200 puisqu'on déclenche nous-mêmes une exception dans ce cas.
Elles sont exécutées dans le processus principal et permettent donc d'accéder aux fonctions graphiques comme les composants visuels ou les boites de dialogue.
Notez comment leur appel est traité dans le source :
class procedure tdownload_file.download(from_url, to_filename: string;
success: tproc = nil; error: tproc = nil);
On commence par déclarer deux paramètres de type TProc dans la méthode faisant le téléchargement et on les met par défaut à nil si le paramètre n'est pas spécifié. Il peut en effet arriver que l'on n'ait rien à faire en cas de succès ou d'erreur, autant laisser au programmeur final le choix de les utiliser ou pas. Notez que si vous voulez utiliser la fonction d'erreur mais pas celle du succès il faudra quand même passer le paramètre à nil puisque le compilateur ne pourra pas deviner que vous voulez sauter ce paramètre.
Lorsqu'on désire les appeler, on teste si le paramètre est renseigné. La méthode assigned() permet de savoir si un pointeur pointe quelque part, mais pas forcément si ce quelque part est valide, donc méfiance et pensez à utilier FreeAndNil(monObjet) plutôt que juste monObjet.Free() lorsque vous désallouez des objets. Enfin on appelle la procédure success ou error dans une procédure soumise au thread principal par la méthode TThread.Queue().
if assigned(success) then
tthread.queue(nil,
procedure
begin
success;
end);
On obtient ainsi un code qui permet de simplement télécharger n'importe quel fichier distant sans interrompre le fonctionnement du programme et tout en pouvant agir lorsque le téléchargement se termine.