How to download .exe file with progress bar - VB 2012 - vb.net

I am trying to create an updater for my program which automatically download's the latest version of my program from the web. Now I want this process to be done using a progress bar (so when the download progress is at 50% the progress bar is half-way through). This is my code:
Private Sub client_ProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Dim bytesIn As Double = Double.Parse(e.BytesReceived.ToString())
Dim totalBytes As Double = Double.Parse(e.TotalBytesToReceive.ToString())
Dim percentage As Double = bytesIn / totalBytes * 100
client.Value = Int32.Parse(Math.Truncate(percentage).ToString())
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim url As String = "MY DOWNLOAD LINK"
'Download
Dim client As WebClient = New WebClient
AddHandler client.DownloadProgressChanged, AddressOf client_ProgressChanged
AddHandler client.DownloadFileCompleted, AddressOf client_DownloadCompleted
client.DownloadFileAsync(New Uri(url), "C:\Users\User\Desktop\BACKUP\TESTING\New folder\1.exe")
End Sub
End Class
Now I know that the place where the file is saved has been inputed manually by me , but I will change that later. My problem currently is that the file is not being downloaded. However when I change the DownloadFileAsync method to DownloadFile , my program downloads the file. However with the DownloadFile method I will not be able to use the progress bar to track the download progress. Any help is much appreciated :-)

Don't know what you mean, when you say that the file isn't downloaded? You get an Error/Exception? Nothing happens at all? Did you place breakpoints, debug.prints etc?
With VS2012 and asyn/await you can put everything into one method an keep a "linear code-flow". Import System.Threading and System.Threading.Tasks
Private Async Function DownloadWithProgress(ByVal url As String, ByVal p As ProgressBar) As Task(Of Integer)
Dim wc As Net.HttpWebRequest = DirectCast(Net.HttpWebRequest.Create(url), Net.HttpWebRequest)
Dim resp = Await (New TaskFactory(Of Net.WebResponse)).StartNew(AddressOf wc.GetResponse)
p.Value = 0
p.Maximum = CInt(resp.ContentLength)
Dim rqs = resp.GetResponseStream
Dim bufsize As Integer = 1 << 16
Dim buffer(bufsize) As Byte
Dim got As Integer = 0
Dim total As Integer = 0
Do
got = Await (New TaskFactory(Of Integer)).FromAsync(AddressOf rqs.BeginRead, AddressOf rqs.EndRead, buffer, 0, bufsize, Nothing)
total += got
Me.Label1.Text = "got: " & total.ToString
p.Increment(got)
Loop Until got = 0
Return total
End Function
In this sample, the data from the web is downloaded into an array, but you can of course also write it into a file, or do whatever you want with the data.
Sample for usage:
Private running As Boolean = False
Private Async Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If running Then
MessageBox.Show("Cant you see, I'm working?")
Exit Sub
Else
running = True
End If
Await DownloadWithProgress("http://download.thinkbroadband.com/5MB.zip", ProgressBar1)
running = False
End Sub

Related

The GUI is not moving properly

I'm doing a little widget that shows the price of bitcoin using Binance API here
I'm not using Json format as I Just need to parse one string, eventhough I know many of you will say to use json. Anyway, I want to keep the software as simple as possible, but there is a little problem.
I'm downloading the source with webclient and Updating it using a timer.
I think I'm doing a mistake creating every time the new webclient because when I want to move the form, Is not properly mooving even if its not freezing.
The code I'm using is:
Private Sub webclientbtc()
Dim wc As New Net.WebClient
Dim WBTC As IO.Stream = Nothing
wc.Encoding = Encoding.UTF8
WBTC = wc.OpenRead("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
Dim btc As String
Using rd As New IO.StreamReader(WBTC)
btc = rd.ReadToEnd
End Using
'---------BTC PRICE---------'
Dim textBefore As String = """lastPrice"":"""
Dim textAfter As String = ""","
Dim startPosition As Integer = btc.IndexOf(textBefore)
startPosition += textBefore.Length
Dim endPosition As Integer = btc.IndexOf(textAfter, startPosition)
Dim textFound As String = btc.Substring(startPosition, endPosition - startPosition)
Dim dNumber As Double = Val(textFound.ToString)
Label1.Text = dNumber.ToString("n2")
'-------------------------------------'
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
webclientbtc()
End Sub
Timer interval is on 1000 ms, which is great to keep me update.
Any idea on how I can avoid the creations of new webclient at every update?
Thanks
Simplified, and using TAP:
Private wc as New WebClient()
Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim s = Await wc.DownloadStringTaskAsync("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
Dim d = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(s)
Label1.Text = d("lastPrice")
End Sub
You need to reference newtonsoft json package and imports it, as well as imports system.collections.generic
If the answer by Caius Jard is too good, you can avoid the use of a JSON deserialiser by using a regex:
Imports System.Net
Imports System.Text.RegularExpressions
Public Class Form1
Dim tim As New Timer()
Private Async Sub UpdateBtc(sender As Object, e As EventArgs)
' temporarily disable the timer in case the web request takes a long time
tim.Enabled = False
' using New Uri() makes sure it is a proper URI:
Dim url = New Uri("https://api.binance.com/api/v1/ticker/24hr?symbol=BTCEUR")
Dim rawJson As String
Using wb As New WebClient()
rawJson = Await wb.DownloadStringTaskAsync(url)
End Using
Dim re = New Regex("""lastPrice"":\s*""([0-9.-]+)""")
Dim lastPrice = re.Match(rawJson)?.Groups(1)?.Value
Dim p As Decimal
lblLastPrice.Text = If(Decimal.TryParse(lastPrice, p), p.ToString("N2"), "Fetch error.")
tim.Enabled = True
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
UpdateBtc(Nothing, EventArgs.Empty)
tim.Interval = 3000
AddHandler tim.Tick, AddressOf UpdateBtc
tim.Start()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
If tim IsNot Nothing Then
tim.Stop()
RemoveHandler tim.Tick, AddressOf UpdateBtc
tim.Dispose()
End If
End Sub
End Class
There's no need to re-use the WebClient, creating it is not what is taking up the time.
I prefer to instantiate timers myself: there is no requirement to do so.
It is better to use descriptive names for controls: "Label1" tells you nothing.

How long the PC has been started?

I would like to know how long the PC has been started.
That's why I made the following routine:
Public Function LipPCIsOn() As String
Dim iTempoPC As Integer
Dim tTempoPC As TimeSpan
Dim strTempoPC As String
iTempoPC = System.Environment.TickCount
tTempoPC = TimeSpan.FromMilliseconds(iTimePC)
strTempoPC = tTempoPC.Duration.ToString("hh:mm:ss")
Return strTempoPC
End Function
But I do not understand, the PC despite having been started by 3 minutes it tells me:
7:54:36
Where's the mistake?
Thank you all
There may be some other source of the last power-on time, but you can use the Windows System Event Log to get the last event from Kernel-Boot:
Function GetLastPowerOn() As DateTime?
Dim systemEventLog = New EventLog()
systemEventLog.Log = "System"
Dim lastPowerOn = systemEventLog.Entries.Cast(Of EventLogEntry).
Where(Function(eu) eu.Source = "Microsoft-Windows-Kernel-Boot").
OrderByDescending(Function(ev) ev.TimeGenerated).FirstOrDefault()
Return lastPowerOn?.TimeGenerated
End Function
I do not know the behaviour for if there is no entry, so I assumed that a Nullable(Of DateTime) would do. If you want to clear your System event log, you could let us know what happens; I don't want to do that.
Unfortunately, it takes ages to return a value (e.g. about 7 seconds on this computer), so you might want to call it asynchronously. Here is an example which uses one button and two labels on a form:
Public Class Form1
Dim tim As Timer
Friend Async Function GetLastPowerOnAsync() As Task(Of DateTime?)
Dim systemEventLog = New EventLog() With {.Log = "System"}
Dim tsk = Await Task.Factory.StartNew(Function()
Return systemEventLog.Entries.Cast(Of EventLogEntry).
Where(Function(eu) eu.Source = "Microsoft-Windows-Kernel-Boot").
OrderByDescending(Function(ev) ev.TimeGenerated).
FirstOrDefault()
End Function)
Return tsk?.TimeGenerated
End Function
Sub timTick(sender As Object, e As EventArgs)
Label1.Text = DateTime.Now.ToString("HH:mm:ss")
End Sub
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lpo = Await GetLastPowerOnAsync()
If lpo.HasValue Then
Label2.Text = lpo.Value.ToString("yyyy-MM-dd HH:mm:ss")
Else
Label2.Text = "No System event log entry with a source of Microsoft-Windows-Kernel-Boot entry found."
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
tim = New Timer() With {.Interval = 500}
AddHandler tim.Tick, AddressOf timTick
tim.Start()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
RemoveHandler tim.Tick, AddressOf timTick
tim.Dispose()
End Sub
End Class
Thank you all.
I just wanted to point out that:
1) my PC did not make a real shutdown but a suspension;
2) the correct code I rewrote is:
Public Function LipPCIsOnNew() As String
Dim EventoLogApp As New System.Diagnostics.EventLog("System")
Dim OraACCENSIONE As Date, stMachineName As String
' search from the end, to find the last boot faster
For i = EventoLogApp.Entries.Count - 1 To 1 Step -1
If EventoLogApp.Entries(i).InstanceId.ToString = 1 Then
OraACCENSIONE = EventoLogApp.Entries(i).TimeGenerated
stMachineName = EventoLogApp.Entries(i).MachineName.ToString
Exit For
End If
Next
Return OraACCENSIONE.ToString
End Function
Now everything is ok
Thank you all

FTP download files larger than 2GB (VB.net)

Missing last few bytes and file gets corrupted - bounty
I now added a bounty to solve this problem. I changed the integer types to int64 which seem to have solved part of the problem, but now when ever it finishes the download it sometimes misses the last 1-5 bytes, which in return corrupts the file, so it can't be unzipped. Is there another way of closing the stream so it ensures the files are fully downloaded, and avoid getting corrupted? I've since tried this simple code, but same problem happens.
Imports System.ComponentModel
Imports System.Net
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
End Sub
Dim WithEvents WC As New WebClient
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WC.DownloadFileAsync(New Uri("ftp://dmr-ftp-user:dmrpassword#5.44.137.84/ESStatistikListeModtag/ESStatistikListeModtag-20160327-094743.zip"), "C:\XML\ESStatistikListeModtag-20160327-094743.zip.zip")
End Sub
Private Sub WC_DownloadProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs) Handles WC.DownloadProgressChanged
ProgressBar1.Value = e.ProgressPercentage
If e.ProgressPercentage = 100 Then
MsgBox("File download - 100%") 'This message box does trigger once the download is complete, but file is still corrupted.
End If
End Sub
Private Sub WC_DownloadFileCompleted(sender As Object, e As AsyncCompletedEventArgs) Handles WC.DownloadFileCompleted
MsgBox("Complete") ' This message box doesn't trigger!
End Sub
End Class
OLD QUESTION:
I'm trying to download a zip file from an FTP server with my vb.net application. My current source code is posted below. This works fine for smaller files, but when I exceed a limit of 2GB I get the following exception:
"Arithmetic operation resulted in an overflow"
It's a file with the size of about 2.5 GB and increasing slightly each weak (about 20 MB), so I need a solution which can handle large files, hopefully with no limit. Eventually I would like to unzip the file with the program too, so if you have any ideas for doing this, you can post this as well. Thanks!
Private Sub Download(ByVal filePath As String, ByVal fileName As String)
FTPSettings.IP = "0.0.0.0"
FTPSettings.UserID = "ftp-user"
FTPSettings.Password = "ftp-pass"
Dim reqFTP As FtpWebRequest = Nothing
Dim ftpStream As Stream = Nothing
Try
Dim outputStream As New FileStream(filePath + "\" + fileName, FileMode.Create)
reqFTP = DirectCast(FtpWebRequest.Create(New Uri("ftp://" + FTPSettings.IP + "/" + fileName)), FtpWebRequest)
reqFTP.Method = WebRequestMethods.Ftp.DownloadFile
reqFTP.UseBinary = True
reqFTP.Credentials = New NetworkCredential(FTPSettings.UserID, FTPSettings.Password)
Dim response As FtpWebResponse = DirectCast(reqFTP.GetResponse(), FtpWebResponse)
ftpStream = response.GetResponseStream()
Dim cl As Long = response.ContentLength
Dim bufferSize As Integer = 2048
Dim readCount As Int64
Dim buffer As Byte() = New Byte(bufferSize - 1) {}
Dim size As Int64
readCount = ftpStream.Read(buffer, 0, bufferSize)
While readCount > 0
outputStream.Write(buffer, 0, readCount)
readCount = ftpStream.Read(buffer, 0, bufferSize)
If readCount = bufferSize Then
size += readCount
Label1.Text = size
Label1.Refresh()
End If
End While
ftpStream.Close()
outputStream.Close()
response.Close()
Catch ex As Exception
MsgBox(ex.Message)
If ftpStream IsNot Nothing Then
ftpStream.Close()
ftpStream.Dispose()
End If
Throw New Exception(ex.Message.ToString())
End Try
End Sub
Public NotInheritable Class FTPSettings
Private Sub New()
End Sub
Public Shared Property IP() As String
Get
Return m_IP
End Get
Set(ByVal value As String)
m_IP = value
End Set
End Property
Private Shared m_IP As String
Public Shared Property UserID() As String
Get
Return m_UserID
End Get
Set(ByVal value As String)
m_UserID = value
End Set
End Property
Private Shared m_UserID As String
Public Shared Property Password() As String
Get
Return m_Password
End Get
Set(ByVal value As String)
m_Password = value
End Set
End Property
Private Shared m_Password As String
End Class
End Class
I've had similar problems with WebClient before, specially if using it with the WithEvents statement.
See if re-writing your code like this solves the problem:
Imports System.ComponentModel
Imports System.Net
Public Class Form1
Private wc As New WebClient()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
wc = New WebClient()
AddHandler wc.DownloadProgressChanged, Sub(s As Object, ByVal e As DownloadProgressChangedEventArgs)
Me.Invoke(New MethodInvoker(Sub() ProgressBar1.Value = e.ProgressPercentage))
End Sub
AddHandler wc.DownloadFileCompleted, Sub(s As Object, e As ComponentModel.AsyncCompletedEventArgs)
MsgBox("Complete")
End Sub
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
wc.DownloadFileAsync(New Uri("ftp://dmr-ftp-user:dmrpassword#5.44.137.84/ESStatistikListeModtag/ESStatistikListeModtag-20160327-094743.zip"), "C:\XML\ESStatistikListeModtag-20160327-094743.zip.zip")
End Sub
End Class

How to wait for get request without freezing client

I have a simple button which sends a get request to retrieve a txt file on my website. Problem is it freezes the application while retrieving the info. How can I make it so the application doesn't freeze while waiting for the result?
Private Sub cmd_ClickMe_Click(sender As Object, e As EventArgs) Handles cmd_ClickMe.Click
Dim request As String = String.Format("http://www.*****/database/test.txt")
Dim webClient As New System.Net.WebClient
Dim result As String = webClient.DownloadString(request)
MessageBox.Show(result)
End Sub
I've also tried the following but it doesn't work (says "webClient.DownloadStringAsync(myUri)" does not produce a value:
Private Sub cmd_ClickMe_Click_1(sender As Object, e As EventArgs) Handles cmd_ClickMe.Click
Dim request As String = String.Format("http://www.****.com/database/test.txt")
Dim webClient As New System.Net.WebClient
Dim myUri As Uri = New Uri(request)
Dim result As String = webClient.DownloadStringAsync(myUri)
MessageBox.Show(result)
End Sub
Use DownloadStringAsync(Uri) instead of the DownloadString(uri).
The DownloadStringAsync method does not block the calling thread.
Here is an example how to use it:
Dim wc As New WebClient
' Specify that you get alerted
' when the download completes.
AddHandler wc.DownloadStringCompleted, AddressOf AlertStringDownloaded
Dim uri As New Uri("http:\\changeMe.com") 'Pass the URL to here. This is just an example
wc.DownloadStringAsync(uri)
End Sub
Public Shared Sub AlertStringDownloaded(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
' If the string request went as planned and wasn't cancelled:
If e.Cancelled = False AndAlso e.Error Is Nothing Then
Dim myString As String = CStr(e.Result) 'Use e.Result to get the String
MessageBox.Show(myString)
End If
End Sub

Measuring download speed

I'm downloading a file using the following:
Dim client As WebClient = New WebClient()
client.DownloadFileAsync(New Uri("http://cachefly.cachefly.net/100mb.test"), "C:\Users\Dir\100mb.test")
The file downloads and saves into the C://Users/Dir/100mb.test, but while its downloading I would like to display the download speed in a label. How can I do this? I have read many tutorials, but most of them doesn't works or are outdated. I'm a newbie with vb.net, so I can't really write something on my own, could you give me any solutions?
May I suggest something different?
Imports System.Net
Public Class Form1
Private tmp = IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "snafu.fubar")
Private Downloading As Boolean = False
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If Downloading Then Exit Sub
Downloading = True
Dim wc As New WebClient
AddHandler wc.DownloadProgressChanged, AddressOf wc_ProgressChanged
AddHandler wc.DownloadFileCompleted, AddressOf wc_DownloadDone
wc.DownloadFileAsync(New Uri("http://cachefly.cachefly.net/100mb.test"), tmp, Stopwatch.StartNew)
End Sub
Private Sub wc_DownloadDone(sender As Object, e As System.ComponentModel.AsyncCompletedEventArgs)
Downloading = False
End Sub
Private Sub wc_ProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs)
Me.Label1.Text = (e.BytesReceived / (DirectCast(e.UserState, Stopwatch).ElapsedMilliseconds / 1000.0#)).ToString("#")
End Sub
End Class
Because it makes no sense to determine the speed by the last chunk of bytes received, but instead you measure the total number of bytes and divide by the TOTAL time. Passing a stopwatch instance to the eventhandler has the advantage that it doesnt 'spoil' your class code - it''s visible only where it's needed.
This will give you percentage in the mean time. You could probably calculate from the percentage given a start time using DateTime.Now the speed in which it the download is taking:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Try
Dim client As WebClient = New WebClient()
AddHandler client.DownloadProgressChanged, AddressOf ProgChanged
client.DownloadFileAsync(New Uri("http://cachefly.cachefly.net/100mb.test"), "C:\Users\Dir\100mb.test")
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub ProgChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Dim progressPercentage As Integer = e.ProgressPercentage
End Sub
Define globals:
Dim lastUpdate As DateTime
Dim lastBytes As Long = 0
You'll need to assign an Event for progress:
Dim client As WebClient = New WebClient()
client.DownloadFileAsync(New Uri("http://cachefly.cachefly.net/100mb.test"), "C:\Users\Dir\100mb.test")
client.DownloadProgressChanged += Function(sender, e) progressChanged(e.BytesReceived)
The Event:
Private Sub progressChanged(bytes As Long)
If lastBytes = 0 Then
lastUpdate = DateTime.Now
lastBytes = bytes
Return
End If
Dim now = DateTime.Now
Dim timeSpan = now - lastUpdate
If Not timeSpan.Seconds = 0
Dim bytesChange = bytes - lastBytes
Dim bytesPerSecond = bytesChange / timeSpan.Seconds
lastBytes = bytes
lastUpdate = now
End If
End Sub
And you have the calculations of bytes per second.
label.Text = bytesPerSecond.ToString() + "B/s";
Try this:
Try this only when you're downloading through a backgroundworker
Private Sub worker_DoWork(Byval sender As Object, Byval e As DoWorkEventArgs)
Dim req As WebRequest = WebRequest.Create(downloadUrl) 'Make a request for the url of the file to be downloaded
Dim resp As WebResponse = req.GetResponse 'Ask for the response
Dim fs As New FileStream(path to download to, FileMode.CreateNew) 'Create a new FileStream that writes to the desired download path
Dim buffer(8192) As Byte 'Make a buffer
Dim downloadedsize As Long = 0
Dim downloadedTime As Long = 0
Dim dlSpeed As Long = 0
Dim currentSize As Long = 0 'Size that has been downloaded
Dim totalSize As Long = req.ContentLength 'Total size of the file that has to be downloaded
Do While currentSize < totalSize
Dim read As Integer = resp.GetResponseStream.Read(buffer, 0, 8192) 'Read the buffer from the response the WebRequest gave you
fs.Write(buffer, 0, read) 'Write to filestream that you declared at the beginning of the DoWork sub
currentSize += read
downloadedSize += read
downloadedTime += 1 'Add 1 millisecond for every cycle the While field makes
If downloadedTime = 1000 Then 'Then, if downloadedTime reaches 1000 then it will call this part
dlSpeed = (downloadedSize / TimeSpan.FromMilliseconds(downloadedTime).TotalSeconds) 'Calculate the download speed by dividing the downloadedSize by the total formatted seconds of the downloadedTime
downloadedTime = 0 'Reset downloadedTime and downloadedSize
downloadedSize = 0
End If
End While
fs.Close() 'Close the FileStream first, or the FileStream will crash.
resp.Close() 'Close the response
End Sub
Sorry for not using proper formatting, but this is a solution in itself.