Skip to content

Commit

Permalink
WIP to address issue dotnet#9807
Browse files Browse the repository at this point in the history
  • Loading branch information
paul1956 committed Apr 13, 2024
1 parent 4417b39 commit e8d68ba
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,174 @@ Namespace Microsoft.VisualBasic.MyServices.Internal
End Sub

End Class

''' <summary>
''' Class that controls the thread that does the actual work of downloading or uploading.
''' </summary>
Friend NotInheritable Class HttpClientCopy

''' <summary>
''' Creates an instance of a HttpClientCopy, used to download or upload a file
''' </summary>
''' <param name="client">The HttpClient used to do the downloading or uploading</param>
''' <param name="dialog">UI for indicating progress</param>
Public Sub New(client As HttpClient, dialog As ProgressDialog)

Debug.Assert(client IsNot Nothing, "No HttpClient")

_httpClient = client
m_ProgressDialog = dialog

End Sub

''' <summary>
''' Downloads a file
''' </summary>
''' <param name="address">The source for the file</param>
''' <param name="destinationFileName">The path and name where the file is saved</param>
Public Async Function DownloadFileAsync(address As Uri, destinationFileName As String) As Task
Debug.Assert(_httpClient IsNot Nothing, "No HttpClient")
Debug.Assert(address IsNot Nothing, "No address")
Debug.Assert((Not String.IsNullOrWhiteSpace(destinationFileName)) AndAlso Directory.Exists(Path.GetDirectoryName(Path.GetFullPath(destinationFileName))), "Invalid path")

Using response As HttpResponseMessage = Await _httpClient.GetAsync(address, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(False)
Using responseStream As Stream = Await response.Content.ReadAsStreamAsync().ConfigureAwait(False)
Using fileStream As New FileStream(destinationFileName, FileMode.Create, FileAccess.Write, FileShare.None)

Dim buffer(8191) As Byte
Dim totalBytesRead As Long = 0
Dim bytesRead As Integer
Try
m_ProgressDialog?.ShowProgressDialog() 'returns when the download sequence is over, whether due to success, error, or being canceled
While (Await responseStream.ReadAsync(buffer.AsMemory(0, buffer.Length), _cancelTokenRead).ConfigureAwait(False)) > 0
Dim contentLength? As Long = response.Content.Headers.ContentLength
If Not contentLength.HasValue Then
Continue While
Else
bytesRead = CInt(contentLength.Value)
totalBytesRead += bytesRead

Dim cancelTokenWrite As CancellationToken = _cancelSourceWrite.Token

Await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), _cancelTokenWrite).ConfigureAwait(False)
If m_ProgressDialog IsNot Nothing Then
Dim percentage As Integer = CInt(totalBytesRead / contentLength.Value * 100)
InvokeIncrement(percentage)
End If
End If
_cancelSourceRead = New CancellationTokenSource()
_cancelTokenRead = _cancelSourceRead.Token
_cancelSourceWrite = New CancellationTokenSource()
_cancelTokenWrite = _cancelSourceWrite.Token
End While
Catch ex As Exception
Throw
End Try
End Using
End Using
End Using
CloseProgressDialog()

End Function

#If False Then
''' <summary>
''' Uploads a file
''' </summary>
''' <param name="sourceFileName">The name and path of the source file</param>
''' <param name="address">The address to which the file is uploaded</param>
Public Sub UploadFile(sourceFileName As String, address As Uri)
Debug.Assert(m_WebClient IsNot Nothing, "No WebClient")
Debug.Assert(address IsNot Nothing, "No address")
Debug.Assert((Not String.IsNullOrWhiteSpace(sourceFileName)) AndAlso File.Exists(sourceFileName), "Invalid file")

' If we have a dialog we need to set up an async download
If m_ProgressDialog IsNot Nothing Then
m_WebClient.UploadFileAsync(address, sourceFileName)
m_ProgressDialog.ShowProgressDialog() 'returns when the download sequence is over, whether due to success, error, or being canceled
Else
m_WebClient.UploadFile(address, sourceFileName)
End If

'Now that we are back on the main thread, throw the exception we encountered if the user didn't cancel.
If _exceptionEncounteredDuringFileTransfer IsNot Nothing Then
If m_ProgressDialog Is Nothing OrElse Not m_ProgressDialog.UserCanceledTheDialog Then
Throw _exceptionEncounteredDuringFileTransfer
End If
End If
End Sub

#End If

''' <summary>
''' Notifies the progress dialog to increment the progress bar
''' </summary>
''' <param name="progressPercentage">The percentage of bytes read</param>
Private Sub InvokeIncrement(progressPercentage As Integer)
' Don't invoke unless dialog is up and running
If m_ProgressDialog IsNot Nothing Then
If m_ProgressDialog.IsHandleCreated Then

' For performance, don't invoke if increment is 0
Dim increment As Integer = progressPercentage - _percentage
_percentage = progressPercentage
If increment > 0 Then
m_ProgressDialog.BeginInvoke(New DoIncrement(AddressOf m_ProgressDialog.Increment), increment)
End If

End If
End If
End Sub

''' <summary>
''' Posts a message to close the progress dialog
''' </summary>
Private Sub CloseProgressDialog()
' Don't invoke unless dialog is up and running
If m_ProgressDialog IsNot Nothing Then
m_ProgressDialog.IndicateClosing()

If m_ProgressDialog.IsHandleCreated Then
m_ProgressDialog.BeginInvoke(New MethodInvoker(AddressOf m_ProgressDialog.CloseDialog))
Else
' Ensure dialog is closed. If we get here it means the file was copied before the handle for
' the progress dialog was created.
m_ProgressDialog.Close()
End If
End If
End Sub

''' <summary>
''' If the user clicks cancel on the Progress dialog, we need to cancel
''' the current async file transfer operation
''' </summary>
''' <remarks>
''' Note that we don't want to close the progress dialog here. Wait until
''' the actual file transfer cancel event comes through and do it there.
''' </remarks>
Private Sub m_ProgressDialog_UserCancelledEvent() Handles m_ProgressDialog.UserHitCancel
_cancelSourceRead.Cancel() 'cancel the upload/download transfer. We'll close the ProgressDialog as soon as the WebClient cancels the xfer.
_cancelSourceWrite.Cancel() 'cancel the upload/download transfer. We'll close the ProgressDialog as soon as the WebClient cancels the xfer.
End Sub

Private _cancelSourceRead As New CancellationTokenSource()
Private _cancelTokenRead As CancellationToken = _cancelSourceRead.Token

Private _cancelSourceWrite As New CancellationTokenSource()
Private _cancelTokenWrite As CancellationToken = _cancelSourceRead.Token

' The WebClient performs the downloading or uploading operations for us
Private ReadOnly _httpClient As HttpClient

' Dialog shown if user wants to see progress UI. Allows the user to cancel the file transfer.
Private WithEvents m_ProgressDialog As ProgressDialog

' Used for invoking ProgressDialog.Increment
Private Delegate Sub DoIncrement(Increment As Integer)

' The percentage of the operation completed
Private _percentage As Integer

End Class

End Namespace
8 changes: 8 additions & 0 deletions src/Microsoft.VisualBasic.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As String, destinationFileName As String) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As String, destinationFileName As String, userName As String, password As String, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As String, destinationFileName As String, userName As String, password As String, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean, onUserCancel As Microsoft.VisualBasic.FileIO.UICancelOption) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As System.Uri, destinationFileName As String, networkCredentials As System.Net.ICredentials, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As System.Uri, destinationFileName As String, networkCredentials As System.Net.ICredentials, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean, onUserCancel As Microsoft.VisualBasic.FileIO.UICancelOption) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As System.Uri, destinationFileName As String, userName As String, password As String) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As System.Uri, destinationFileName As String, userName As String, password As String, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean) -> System.Threading.Tasks.Task
Microsoft.VisualBasic.Devices.Network.DownloadFileAsync(address As System.Uri, destinationFileName As String, userName As String, password As String, showUI As Boolean, connectionTimeout As Integer, overwrite As Boolean, onUserCancel As Microsoft.VisualBasic.FileIO.UICancelOption) -> System.Threading.Tasks.Task
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Imports Microsoft.VisualBasic.ApplicationServices

Namespace My
' The following events are available for MyApplication:
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.

' **NEW** ApplyApplicationDefaults: Raised when the application queries default values to be set for the application.

' Example:
' Private Sub MyApplication_ApplyApplicationDefaults(sender As Object, e As ApplyApplicationDefaultsEventArgs) Handles Me.ApplyApplicationDefaults
'
' ' Setting the application-wide default Font:
' e.Font = New Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular)
'
' ' Setting the HighDpiMode for the Application:
' e.HighDpiMode = HighDpiMode.PerMonitorV2
'
' ' If a splash dialog is used, this sets the minimum display time:
' e.MinimumSplashScreenDisplayTime = 4000
' End Sub

Partial Friend Class MyApplication

End Class
End Namespace

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.

Option Explicit On
Option Strict On

Imports System.IO

Public Class Form1

Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Dim tmpFilePath = GetTempFolderGuid()
My.Computer.Network.DownloadFile("https://raw.githubusercontent.com/dotnet/winforms/main/README.md", tmpFilePath)
End Sub

Private Function GetTempFolderGuid() As String
Dim folder As String = Path.Combine(Path.GetTempPath, Guid.NewGuid.ToString)
Do While Directory.Exists(folder) Or File.Exists(folder)
folder = Path.Combine(Path.GetTempPath, Guid.NewGuid.ToString)
Loop

Return folder
End Function

End Class

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<MyApplicationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MySubMain>true</MySubMain>
<MainForm>Form1</MainForm>
<SingleInstance>false</SingleInstance>
<ShutdownMode>0</ShutdownMode>
<EnableVisualStyles>true</EnableVisualStyles>
<AuthenticationMode>0</AuthenticationMode>
<SaveMySettingsOnExit>true</SaveMySettingsOnExit>
</MyApplicationData>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<RootNamespace>ScratchProjectVB</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<!-- These are needed to suppress the localization step picked up from Arcade targets -->
<LangVersion>15.0</LangVersion>
<EnableXlfLocalization>false</EnableXlfLocalization>
<UpdateXlfOnBuild>false</UpdateXlfOnBuild>
<StartupObject>ScratchProjectVB.Form1</StartupObject>
<TargetFramework>net8.0-windows7.0</TargetFramework>
</PropertyGroup>

<!-- These normally come from $(UseWindowsForms) when $(ImplicitUsings) is enabled -->

<ItemGroup>
<ProjectReference Include="..\..\..\..\Microsoft.VisualBasic.Forms\src\Microsoft.VisualBasic.Forms.vbproj" />
<ProjectReference Include="..\..\..\..\System.Drawing.Common\src\System.Drawing.Common.csproj" />
<ProjectReference Include="..\..\..\src\System.Windows.Forms.csproj" />
</ItemGroup>

<ItemGroup>
<Import Include="System.Drawing" />
<Import Include="System.Windows.Forms" />
</ItemGroup>

<ItemGroup>
<None Update="My Project\Application.myapp">
<Generator>MyApplicationCodeGenerator</Generator>
<LastGenOutput>Application.Designer.vb</LastGenOutput>
</None>
</ItemGroup>

</Project>

0 comments on commit e8d68ba

Please sign in to comment.