Download di file in .Net visualizzando tutte le informazioni (progressbar, riprendere download interrotti ecc…)

In questo articolo vi spiegherò come creare un semplice ma efficace download manager che mostra tutte le informazioni riguardo il download del file: la velocità di download, la dimensione del file, la progressbar con la percentuale di completamento e permette il pause&resume download.

Le basi per questo programma sono le classi WebRequest e WebResponse del namespace System.Net.
La classe WebRequest rappresenta un richiesta web che dopo eseguita ritorna un oggetto WebResponse che è lo stream di dati ricevuto in output.

Il programma contiene solamente una form con qualche label, una progressbar, e una textbox dove inserire l’ URL del file da scaricare. Ovviamente contiene anche due pulsanti (uno per avviare il download, uno per stopparlo).

Invece, il cuore dell’ applicazione è in un BackgroundWorker in modo l’ UI non si freeza durante il download.
Questo è il codice contenuto nell’ evento BackgroundWorker_DoWork:

Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) _
 Handles BackgroundWorker1.DoWork

        'Crea la richiesta ed ottiene la risposta
        Dim theResponse As HttpWebResponse
        Dim theRequest As HttpWebRequest
        Try 'Controlla che il file esista

            theRequest = WebRequest.Create(Me.txtFileName.Text)
            theResponse = theRequest.GetResponse
        Catch ex As Exception

            MessageBox.Show("An error occurred while downloading file. Possibe causes:" & ControlChars.CrLf & _
                            "1) File doesn't exist" & ControlChars.CrLf & _
                            "2) Remote server error", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)

            Dim cancelDelegate As New DownloadCompleteSafe(AddressOf DownloadComplete)

            Me.Invoke(cancelDelegate, True)

            Exit Sub
        End Try
        Dim length As Long = theResponse.ContentLength 'Dimensione (in Byte) dello stream di risposta

        Dim safedelegate As New ChangeTextsSafe(AddressOf ChangeTexts)
        Me.Invoke(safedelegate, length, 0, 0, 0) 'Invoca la TreadsafeDelegate

        Dim writeStream As New IO.FileStream(Me.whereToSave, IO.FileMode.Create)

        'Per rimpiazzare la proprietà Stream.Position (il webstream non supporta il Seek)
        Dim nRead As Integer

        'Per calcolare la velocità di download
        Dim speedtimer As New Stopwatch
        Dim currentspeed As Double = -1
        Dim readings As Integer = 0

        Do

            If BackgroundWorker1.CancellationPending Then 'If user abort download
                Exit Do
            End If

            speedtimer.Start()

            Dim readBytes(4095) As Byte
            Dim bytesread As Integer = theResponse.GetResponseStream.Read(readBytes, 0, 4096)

            nRead += bytesread
            Dim percent As Short = (nRead * 100) / length

            Me.Invoke(safedelegate, length, nRead, percent, currentspeed)

            If bytesread = 0 Then Exit Do

            writeStream.Write(readBytes, 0, bytesread)

            speedtimer.Stop()

            readings += 1
            If readings >= 5 Then 'Per migliorare la precisione calcola la velocità di download solo ogni cinque cicli
                currentspeed = 20480 / (speedtimer.ElapsedMilliseconds / 1000)
                speedtimer.Reset()
                readings = 0
            End If
        Loop

        'Chiude gli stream
        theResponse.GetResponseStream.Close()
        writeStream.Close()

        If Me.BackgroundWorker1.CancellationPending Then

            IO.File.Delete(Me.whereToSave)

            Dim cancelDelegate As New DownloadCompleteSafe(AddressOf DownloadComplete)

            Me.Invoke(cancelDelegate, True)

            Exit Sub

        End If

        Dim completeDelegate As New DownloadCompleteSafe(AddressOf DownloadComplete)

        Me.Invoke(completeDelegate, False)

End Sub

Comunque, è necessario usare alcune delegate per accedere agli eventi UI (in quanto si trovano nell’ altro thread, non in quello del backgroundworker).

Attualmente il programma non supporta il download dei file via FTP, ma comunque non penso sia difficile aggiungere questa funzionalità.

Riprendere i download

Per cominciare il download da un punto preciso (ad esempio quando si vuole riprendere un download messo in pausa) è sufficiente aggiungere il seguente codice prima di usare il metodo GetResponse della WebRequest.

Dim theRequest As HttpWebRequest

theRequest.AddRange(daDoveVuoiCominciare) '<- aggiungere questo

"daDoveVuoiCominciare" è un valore Integer che rappresente il punto dal quale partire (in byte).
Attenzione! Bisogna anche aprire lo stream di scrittura dove è contenuta la prima parte del file e impostare la posizione alla fine del file:

Dim writeStream As New IO.FileStream(Me.whereToSave, IO.FileMode.Open)
writeStream.Position = daDoveVuoiCominciare

Calcolare la velocità di download

Per calcolare la velocità di download questo programma usa un metodo semplice: calcola (usando la classe StopWatch) il tempo per eseguire un ciclo (ovvero 4 kb da scaricare) e ogni cinque cicli (per aumentare la precisione) divide il buffer * 5 (in questa applicazione il buffer è di 4096 bytes, quindi questo valore è di 20480) per il tempo per eseguire un ciclo (in secondi) e si ottiene la velocità in byte/secondo.

currentspeed = 20480 / (speedtimer.ElapsedMilliseconds / 1000)

Però, la classe StopWatch non è molto precisa e quindi la velocità può oscillare di 5-6 kb in più o in meno rispetto a quella reale.

E' tutto! Spero che questo articolo vi sia di aiuto nei vostri futuri programmi.

5 risposte

  1. Vincenzo ha detto:

    Molto interessante.

    Ciao.

  2. Shane ha detto:

    Grazie – molto utile!

  3. Thunder ha detto:

    Molto buono. L’unico peccato è che fa errore qui:

    Me.Invoke(safedelegate, length, nRead, percent, currentspeed)

    Messaggio dell’eccezione: (ArgumentOutOfRangeException)
    ‘-10300’ non è un valore valido per ‘Value’. ‘Value’ deve essere compreso tra ‘minimum’ e ‘maximum’. Nome parametro: Value

  4. Prova ha detto:

    Copioni, ho visto gli STESSI codici in un sito inglese, avete solo tradotto in italiano.
    Ecco il link del sito: http://www.codeproject.com/Articles/17979/Downloading-Files-in-NET-With-All-Information-Prog

    • Carmine ha detto:

      Ciao,
      Sono contento che tu frequenti CodeProject 🙂
      E no, non siamo copioni; quell’articolo è il mio, solo tradotto da me in inglese. Io sono registrato con nickname “Carmine_XX” su CodeProject 😉
      E se cerchi in “about the authors”, in fondo all’articolo di CodeProject, troverai il mio nome ed il link a http://www.thetotalsite.it.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *