Télécharger simplement un fichier via Internet en tâche de fond

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.


Mug Toucan DX dans la baie de RioMug Chinese New Year 2023 : year of the rabbit