Télécharger des fichiers peut, à première vue, sembler facile à coder..... mais lorsqu'on souhaite y apporter des fonctionnalités dans le but d'obtenir un gestionnaire "avancé" de téléchargement de fichiers, cela peut s'avérer plus compliquer !
Dans ce
billet il était question de créer un tel gestionnaire mais pour un soucis de ré-utilisabilité, le débat m'a amené à développer en toute modestie une classe en tenant compte des spécificités suivantes :
- Télécharger des fichiers et les enregistrer dans un répertoire de destination de son choix
- Affichage de la progression dans une progressbar et un label (indiquant l'url en cours)
- A la toute fin du téléchargement, afficher le résultat des réussites et des échecs !
- Séparer la mise à jour de l'UI des traitements
- Créer une librairie de classe pouvant être utilisée dans n'importe quel projet
Voilà à quoi pourrait ressembler une telle librairie :
1- Création d'une structure permettant de définir les informations du fichier à télécharger :
Public Structure DownloadInfos
Public Name As String
Public Url As String
Public DestDir As String
End Structure
2- Création d'une classe permettant d'ajouter des urls (+ infos) en liste d'attente pour le téléchargement :
Public Class DownloadList
#Region " Fields "
Private m_list As Queue(Of DownloadInfos)
#End Region
#Region " Methods "
Sub New()
m_list = New Queue(Of DownloadInfos)
End Sub
Public Sub Enqueue(item As DownloadInfos)
m_list.Enqueue(item)
End Sub
Public Function Any() As Boolean
Return Enumerable.Any(Of DownloadInfos)(m_list)
End Function
Public Function Dequeue() As DownloadInfos
Return m_list.Dequeue()
End Function
Public Sub Clear()
m_list.Clear()
End Sub
#End Region
End Class
3- Création d'une classe qui permettra de connaitre le statut (réussite ou échec + message d'erreur) de chaque fichier téléchargé à la toute fin de tache :
Imports System.Text
Public Class DownloadResult
#Region " Enumerations "
Enum State
Erreur = 0
Completed = 1
End Enum
#End Region
#Region " Fields "
Private ReadOnly m_url As String
Private ReadOnly m_etat As State
Private ReadOnly m_message As String
#End Region
#Region " Properties "
Public ReadOnly Property Url As String
Get
Return m_url
End Get
End Property
Public ReadOnly Property Etat As State
Get
Return m_etat
End Get
End Property
Public ReadOnly Property Message As String
Get
Return m_message
End Get
End Property
#End Region
#Region " Methods "
Sub New(url$, etat As State, message$)
m_url = url
m_etat = etat
m_message = message
End Sub
Public Overrides Function ToString() As String
If (Me.Etat = State.Completed) Then
Return ("Téléchargement réussi : " & Me.m_url)
End If
Return ("Téléchargement en échec : " & Me.m_url)
End Function
#End Region
End Class
4- Création d'une classe qui hérite de la classe EventArgs pour connaitre le statut des fichiers téléchargés à la toute fin de la tache :
Imports System.Text
Public Class TerminatedEventArgs
Inherits EventArgs
#Region " Fields "
Private m_Result As IEnumerable(Of DownloadResult)
#End Region
#Region " Properties "
Public ReadOnly Property Result As IEnumerable(Of DownloadResult)
Get
Return m_Result
End Get
End Property
#End Region
#Region " Methods "
Public Sub New(Result As IEnumerable(Of DownloadResult))
m_Result = Result
End Sub
Public Overrides Function ToString() As String
Return String.Join(Environment.NewLine, m_Result)
End Function
#End Region
End Class
5- Création de la classe principale dans laquelle on permet la mise à jour de la partie UI côté utilisateur au moyen de 2 eventHandler :
Imports System.Net
Imports System.IO
Imports System.ComponentModel
Public Class Downloader
Implements IDisposable
#Region " Fields "
Private WithEvents wc As WebClient
Private downloadUrls As DownloadList
Private results As List(Of DownloadResult)
Private m_filesCount As Integer
#End Region
#Region " Properties "
Public ReadOnly Property FilesCount As Integer
Get
Return m_filesCount
End Get
End Property
#End Region
#Region " Events "
Public Event ProgressChanged As ProgressChangedDelegate
Public Event Terminated(sender As Object, e As TerminatedEventArgs)
#End Region
#Region " Delegates "
Public Delegate Sub ProgressChangedDelegate(sender As Object, e As DownloadProgressChangedEventArgs)
#End Region
#Region " Methods "
Public Sub New()
wc = New WebClient
downloadUrls = New DownloadList
results = New List(Of DownloadResult)
m_filesCount = 0
End Sub
Public Sub AddInfos(Infos As DownloadInfos)
downloadUrls.Enqueue(Infos)
m_filesCount += 1
End Sub
Public Sub DownloadFiles()
'On vérifie que la liste des urls n'est pas vide avant de lancer le téléchargement (vérification basée sur la taille de la liste intiale donc après l'appel de la méthode Enqueue)
If m_filesCount <> 0 Then
' On vérifie que la liste n'est pas vide à chaque lancement de la routine
If downloadUrls.Any() Then
With wc
'L'url "s" est retournée et ensuite supprimée de la liste avec la méthode Dequeue
Dim s = downloadUrls.Dequeue()
' On s'attarde ici à tout ce qui pourrait être utile en vue de constituer le chemin de destination du fichier téléchargé !
Dim fiName As New FileInfo(New Uri(s.Url).AbsolutePath)
' A noter que le 3ème argument permet de savoir côté UI quel fichier est en cours de téléchargement !
.DownloadFileAsync(New Uri(s.Url), s.DestDir & "\" & fiName.Name, s)
End With
Return
Else
' C'est ici qu'on déclenche un event pour dire que le téléchargement de tous les fichiers est bien terminé !
' On en profite pour passer le résultat final des téléchargements (réussi et échec) en argument de l'event Terminated !
RaiseEvent Terminated(Me, New TerminatedEventArgs(results))
ClearLists()
End If
End If
End Sub
Private Sub ClearLists()
'On vide les listes pour la prochaine utilisation de la fonction DownloadFiles
downloadUrls.Clear()
results.Clear()
End Sub
Private Sub DownloadFiles_DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs) Handles wc.DownloadProgressChanged
' On déclenche l'event ProgressChangedEvent pour permettre la MAJ de la progressbar côté UI
RaiseEvent ProgressChanged(sender, e)
End Sub
Private Sub DownloadFiles_DownloadFileCompleted(sender As Object, e As AsyncCompletedEventArgs) Handles wc.DownloadFileCompleted
' On masque cet event côté UI car il permet de savoir à quel moment se termine la fin du téléchargement de chaque fichier !
If Not e.UserState Is Nothing Then
Dim Infos = DirectCast(e.UserState, DownloadInfos)
If e.Error IsNot Nothing Then
results.Add(New DownloadResult(Infos.Url, DownloadResult.State.Erreur, e.Error.ToString))
Else
results.Add(New DownloadResult(Infos.Url, DownloadResult.State.Completed, "Completed"))
End If
End If
DownloadFiles()
End Sub
#End Region
#Region "IDisposable Support"
Private disposedValue As Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
wc.Dispose()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
6- Enfin l'appel de la classe ,depuis l'application cliente ou formulaire principal (nécessite 1 bouton, 1 progressbar, 1 Label et bien sur le référencement de la librairie portant l'espace de nom "Download" ) :
Imports System.Net
Imports System.ComponentModel
Imports Download
Public Class Form1
Private WithEvents DL As New Downloader
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using DL
Dim url1 As New DownloadInfos() With {.Name = "GC", .Url = "http://wallace87000.upd.fr/Gif Animation Creator.exe", .DestDir = "D:\Sauvegardes"}
DL.AddInfos(url1)
Dim url2 As New DownloadInfos() With {.Name = "IU", .Url = "http://wallace87000.upd.fr/ImaboxUploader.exe", .DestDir = "D:\Sauvegardes"}
DL.AddInfos(url2)
DL.DownloadFiles()
End Using
End Sub
Private Sub DL_DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs) Handles DL.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
Label1.Text = DirectCast(e.UserState, DownloadInfos).Url
End Sub
Private Sub DL_DownloadTerminated(sender As Object, e As TerminatedEventArgs) Handles DL.Terminated
MsgBox(e.ToString)
End Sub
End Class
A+