mercredi 18 mars 2015

Télécharger des fichiers en file d'attente avec progressbar et label

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+
















dimanche 1 mars 2015

DotNet Patcher : MAJ vers 4.3.2

BugFix : Le fichier protégé ne se lançait plus à cause du Packer !
BugFix : L'utilisation des styles visuels de la Progressbar avec affichage de texte causait une exception !