Search code examples
.netvb.netwinformstimezonewebclient

Downloading an image from the Internet with specific DateTime in the URL


I am trying to develop an application that will fetch a file from a web server using a generated Url path.
A new file on the web server is created every second, and I am trying to access that file and display it in a PictureBox (old files are not deleted).
I have ran into some problems, the server is returning a 404 error, but I can't figure out why.
The application seems to be unable to download the file using the generated image source Url, but when I visit the generated link in a web browser (e.g Chrome, Internet Explorer), it works just fine.
I also ran into some problems along the way of having the URL not formatted correctly.

Try 1: I tried to download the file using a string outputted by my URl generator. The URl must be in a date form: yyyyMMdd/yyyyMMddHHmmss in Tokyo Standard Time. Generating this part worked fine and no problems arose. This is my code:

' Convert the time to Tokyo Standard Time
Dim japanTime = System.TimeZoneInfo.ConvertTime(Now, TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time")) 

'Convert date to url that can be used in the Monitor url
Dim jTime_url As String = Convert.ToDateTime(japanTime.ToString()).ToString("yyyyMMdd/yyyyMMddHHmmss") 

'BYTE ARRAY HOLDS THE DATA
Try
    PictureBox1.Load("www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/" + jTime_url + ".jma_s.gif")
    Console.WriteLine("http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/" + jTime_url + ".jma_s.gif")
Catch ex As Exception
    Console.WriteLine(ex.Message)
End Try

The problem I ran into here was that I may have requested the latest file too quickly, so the web server would always report "404 file not found".

Try 2: I tried delaying the file capture by 4 seconds. This worked, most of the time. The problems were that the file obtained from the web server only worked for most of the time due to that fact that once the "second" value reached 0, the outputted string would be -4 instead of 56.
The second problem was that the entire code wouldn't work sometimes randomly, just returning the error "404 file not found".
I tried outputting the Urls to the console and I viewed those online images in my web browser, and they worked just fine every time. The third problem was that I needed the "seconds" converted to be outputted as 00, 01, 02 etc.

Dim japanTime = System.TimeZoneInfo.ConvertTime(Now, TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time")) 'Convert user computer time to TokyoTime (japan)

'For debugging purposes only (not enabled)
' MessageBox.Show(japanTime.ToString()) 

'FORMAT OUTPUTTED: 1/19/2020 1:47:18 PM

'Needed format: yyyyMMdd/yyyyMMddHHmmss
Dim oldsecond As String = Convert.ToDateTime(japanTime.ToString()).ToString("ss") 'Create the old second to make a delay (source code not right)
' Dim oldminute As String = Convert.ToDateTime(japanTime.ToString()).ToString("mm")

'The -1 Explains the code delay
Dim newsecond As String = oldsecond - 5 

Dim newnewsecond As String
If newsecond = -4 Then
    newnewsecond = "56"
Else
    If newsecond = -3 Then
        newnewsecond = "57"
    Else
        If newsecond = -2 Then
            newnewsecond = "58"
        Else
            If newsecond = -1 Then
                newnewsecond = "59"
            Else
                If newsecond = 0 Then
                    newnewsecond = "00"
                Else
                    newnewsecond = newsecond
                End If
            End If
        End If
    End If
End If
Label4.Text = newnewsecond

Dim jTime_url As String = Convert.ToDateTime(japanTime.ToString()).ToString("yyyyMMdd/yyyyMMddHHmm" & newnewsecond) 'Convert date to url that can be used in the Monitor url
Dim MyWebClient As New System.Net.WebClient

Try
    PictureBox1.Load("www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/" + jTime_url + ".jma_s.gif")
    Console.WriteLine("http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/" + jTime_url + ".jma_s.gif")
  Catch ex as Exception
    Console.Writeline(ex.message)
End Try

What I am trying to achieve here is that I want to be able to display this image source Url using the latest date information replacing yyyyMMdd/yyyyMMddHHmmss and displaying it in a PictureBox.

Is there an error in my code, can I improvement it or do I have to re-write it in another way, shape, or form?

http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/yyyyMMdd/yyyyMMddHHmmss.jma_s.gif

Sample url image source you can look at:
http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/20200304/20200304081359.jma_s.gif


Solution

  • I changed the DateTime conversion method, using TimeZoneInfo.ConvertTimeBySystemTimeZoneId, passing my Local TimeZone.Id and "Tokyo Standard Time" as parameters, to generate a DateTimeOffset that represents the current Tokyo DateTime.

    Using a Timer, subtracting 4 seconds from the calculated DateTimeOffset (DateTimeOffset.AddSeconds(-4)), the Images are loaded correctly.

    ► Note that the System clock must be synchronized with a NTP Server. 4 seconds is a relatively loose gap, but an unsynched clock will of course compromise the result anyway.

    EDIT:
    Changed System.Windows.Forms.Timer to System.Timers.Timer, since what takes more time here is the image download.
    Using BeginInvoke() to set the PictureBox.Image, which takes almost nothing, prevents the UI from stuttering when the Form is moved around.

    Private tokyoTimer As System.Timers.Timer = Nothing
    Private tokyoClient As WebClient
    
    Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        tokyoTimer = New System.Timers.Timer() With {.Interval = 1000}
        tokyoClient = New WebClient()
        AddHandler tokyoTimer.Elapsed,
            Sub()
                Dim TokyoOffset = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(Date.Now, TimeZoneInfo.Local.Id, "Tokyo Standard Time")
                Dim currentImage As String = TokyoOffset.AddSeconds(-4).ToString("yyyyMMdd/yyyyMMddHHmmss") & ".jma_s.gif"
                Try
                    Dim data = tokyoClient.DownloadData(New Uri($"http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/{currentImage}"))
                    BeginInvoke(New MethodInvoker(
                        Sub()
                            PictureBox1.Image?.Dispose()
                            PictureBox1.Image = Image.FromStream(New MemoryStream(data))
                        End Sub))
                Catch ex As Exception
                    ' The exception hadling can be quite extensive here, since many factor can cause it: 
                    ' No server response, no Internet connection, internal server (500+) faults etc.
                    Console.WriteLine(ex.Message)
                End Try
            End Sub
        tokyoTimer.Enabled = True
    End Sub
    
    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        tokyoTimer.Enabled = False
        tokyoTimer.Dispose()
        tokyoClient?.Dispose()
    End Sub
    

    Tokyo Images



    An asynchronous version of the same procedure, all contained in a class object.
    The TokyoImagesDownloader class exposes two public methods:

    StartDownload() expects as arguments:

    1. The PicureBox control used to show the downloaded images
    2. The interval, in seconds, between downloads.
    3. A delay value, in seconds, to subtract to the current Tokyo Local Time, to prevent the Server from returning 404 - Not found because the requested image is not yet ready.

    A StopWatch is used to synchronize the requested Interval between downloads, considering the time needed to download and show an image, so the clock shown in the Image itself should reflect the Interval requested.

    StopDownload() can be called at any time to stop the download of the images.

    With:

    StartDownload(PictureBox1, 1, 8)
    

    the class is instructed to show the images in PictureBox1, download an image each second and delay the current Tokyo Time by 8 seconds.

    Dim imageDonwloder As TokyoImagesDownloader = Nothing
    
    Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        imageDonwloder = New TokyoImagesDownloader()
        imageDonwloder.StartDownload(PictureBox1, 1, 8)
    End Sub
    
    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        imageDonwloder.StopDownload()
    End Sub
    

    Imports System.IO
    Imports System.Net
    
    Public Class TokyoImagesDownloader
    
        Private tokyoClient As WebClient
        Private cts As CancellationTokenSource = Nothing
    
        Public Sub StartDownload(canvas As PictureBox, intervalSeconds As Integer, serverTimeDelaySeconds As Integer)
            cts = New CancellationTokenSource()
            tokyoClient = New WebClient()
            Task.Run(Function() DownloadAsync(canvas, intervalSeconds, serverTimeDelaySeconds))
        End Sub
    
        Private Async Function DownloadAsync(canvas As PictureBox, intervalSeconds As Integer, serverTimeDelaySeconds As Integer) As Task
            Dim downloadTimeWatch As Stopwatch = New Stopwatch()
            downloadTimeWatch.Start()
            Do
                If cts.IsCancellationRequested Then Return
                Dim TokyoOffset = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(Date.Now, TimeZoneInfo.Local.Id, "Tokyo Standard Time")
                Dim currentImage As String = TokyoOffset.AddSeconds(-serverTimeDelaySeconds).ToString("yyyyMMdd/yyyyMMddHHmmss")
                Dim url = New Uri($"http://www.kmoni.bosai.go.jp/data/map_img/RealTimeImg/jma_s/{currentImage}.jma_s.gif")
                Try
                    Dim data = Await tokyoClient.DownloadDataTaskAsync(url)
                    canvas.BeginInvoke(New MethodInvoker(
                        Sub()
                            canvas.Image?.Dispose()
                            canvas.Image = Image.FromStream(New MemoryStream(data))
                        End Sub))
                    Await Task.Delay((intervalSeconds * 1000) - CInt(downloadTimeWatch.ElapsedMilliseconds))
                    downloadTimeWatch.Restart()
                Catch wEx As WebException
                    Console.WriteLine(wEx.Message)
                End Try
            Loop
        End Function
    
        Public Sub StopDownload()
            cts.Cancel()
            tokyoClient?.CancelAsync()
            tokyoClient?.Dispose()
            cts?.Dispose()
        End Sub
    End Class