Download a file from the web periodically - vb.net

I am creating an application which on startup (MainWindow loaded) starts a BackgroundWorker, which on DoWork checks whether there is a newer version of the file (DatasSource for an Autocompletebox) available. If so, I download and merge this with the existing file and create a new file.
Now I want to do this on startup and also periodically (like 30 minutes). So I created a threading.Timer [it's a private member in MainWindow class] and initialize it in RunWorkerCompleted of the backgroundWorker (as mentioned above). The timer goes to the callback successfully but at the file download code (just a fyi, a different namespace and different class) it just terminates and I can't figure out why?
I have tried using Windows.Timers.Timer, ThreadPool.RegisterWaitForSingleObject() but no luck...
Can anyone point me to the right direction? I am open to any solution.
Download code:
Public Sub MergeHistoryFile()
/*Check the directory if there are any downloaded files(.tmp);if there are;just delete them*/
/*some code which checks if file on web is modified;if yes download file*/
Try
Dim waiter As Threading.AutoResetEvent = New AutoResetEvent(False)
_downloader = New WebClient()
AddHandler _downloader.DownloadDataCompleted, AddressOf Me.DownloaderFileCompleted
_downloader.DownloadDataAsync(New Uri(path_file), waiter)
waiter.WaitOne()
Catch ex As Exception
Throw ex
End Try
/*some more code which checks if there something new in the downloaded file;if yes merge the local and the downloaded file reinitialize the autocomplebox*/
End Sub
Private _downloadCancelled As Boolean = False
Private Sub DownloaderFileCompleted(ByVal sender As Object, ByVal e As System.Net.DownloadDataCompletedEventArgs)
If IsNothing(e.Error) Then
If Not (IsNothing(e.Result)) Then
Using fs As New FileStream(Path.Combine(HistoryPath, "_tempDownladedFile.tmp"), FileMode.CreateNew)
fs.Write(e.Result, 0, e.Result.Count)
End Using
CType(e.UserState, Threading.AutoResetEvent).Set()
End If
Else
_downloadCancelled = True
_downloader.CancelAsync()
End If
End Sub

There are several problems with this code, as I pointed out in my comment.
I think your primary problem is that when you create the file, you're passing FileMode.CreateNew, which is going to fail if the file already exists. As the documentation says:
CreateNew Specifies that the operating system should create a new file. This requires FileIOPermissionAccess.Write permission. If the file already exists, an IOException exception is thrown.
You probably want FileMode.Create.
So what happens is that the FileStream constructor throws an exception, which causes your DownloadFileCompleted method to exit without ever setting the event that tells the caller to stop waiting.

Related

Hangman System.IO.IOException' occurred in mscorlib.dll

i am creating a hangman game that is to be used on a few computers, i have created the hangman game itself but i am using the "load form" function to create the list when the program first starts, but i am having this issue.
An unhandled exception of type 'System.IO.IOException' occurred in mscorlib.dll
Additional information: The process cannot access the file 'h:\Bryson\words.txt' because it is being used by another process.
Using sw As StreamWriter = File.CreateText("h:\Bryson\words.txt")
^^that line is where the error pops up^^
I have inserted some in code Comments to make life easier. If anyone can help thanks in advance :)
'USED TO CREATE HANGMAN FILE IF NOT FOUND
Private Sub main_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fofound = False
fifound = False
MsgBox("remove this and change file path and fix qu2 quiz")
'DESIGNER USE
Dim path As String = "h:\Bryson\words.txt"
'CREATE VAR FOR PATH
If System.IO.Directory.Exists("h:\Bryson") Then
'CHECKS IF FOLDER EXISTS
fofound = True
Else
'IF IT DOES THEN IT MOVES ON
System.IO.Directory.CreateDirectory("h:\Bryson")
'IF NOT IT CREATES THE FOLDER
fofound = True
If File.Exists("h:\Bryson\test\words.txt") Then
'CHECKS IF FILE EXISTS
fifound = True
Else
'IF IT DOES IT MOVES ON
IO.File.Create("h:\Bryson\words.txt")
'IF NOT IT CREATES IT
FileClose()
End If
End If
If fofound And fifound = True Then
Else
Using sw As StreamWriter = File.CreateText("h:\Bryson\words.txt")
'CRASH POINT The process cannot access the file 'C:\Bryson\words.txt'
'because it Is being used by another process.
sw.WriteLine("Hangman")
sw.WriteLine("computer")
sw.WriteLine("electrode")
sw.WriteLine("independent")
sw.WriteLine("stream")
sw.WriteLine("enforcing")
End Using
'WRITES TO FILE
MsgBox("file created")
'DESIGNER USE
FileClose()
'CLOSES FILE
End If
End Sub
FileClose() is a legacy function from VB6 and will not affect anything in the System.IO namespace. To close a file you need to call .Close() or .Dispose() on the stream that has opened the file (wrapping the stream in a Using block does this automatically).
Your problem is this line:
IO.File.Create("h:\Bryson\words.txt")
The method creates a new file and opens a FileStream to it which locks the file. Since you never close the returned FileStream your file will remain locked until you close your application.
The File.Create() call is completely unnecessary though because File.CreateText() will create the file if it doesn't exist. So you should just remove the above line.

Updating Variable in Multithreading in VB.NET

I've wrote a program which on startup loads the computer list from Active Directory. This takes about 10 seconds. If the user has started the program with a specific host as parameter, it should be usable immediately.
So to don't interrupt the user I want to load the computer list in a different thread. The problem is that it writes to a variable (the computer list) which is also used in the main thread.
You may think, I could simply use a temporary variable and when its done overwrite the main variable. But I have to keep existing data of the main variable.
'hosts list
Private Shared hosts As New SortedDictionary(Of String, HostEntry)
'Get all computers in Active Directory
'Will run in a extra thread
Private Delegate Sub GetADcomputersDelegate()
Private Sub GetADcomputers()
If Me.InvokeRequired Then
Me.Invoke(New GetADcomputersDelegate(AddressOf GetADcomputers), Nothing)
Else
lblStatusAD.Text = "Getting Computers..."
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End If
End Sub
So when GetADcomputers() runs in its own thread, the main thread is also blocked. I guess because auf the hosts variable.
So what could I change to make the thread do it's work and after that apply the updated computer list without losing data of entries in old hosts list? And all this in a fast and efficient way.
That code is very wrong. If you call that method on a secondary thread then it immediately marshals a call back to the UI thread and does EVERYTHING on the UI thread. What you should be doing is executing all the background work on the secondary thread and then marshalling to the UI thread ONLY to update the UI.
Get rid of that If...Else block and just make the entire body of the method what's current ly in the Else block. Next, identify all the lines that specifically interact with the UI and remove each of those to their own method. You then add If...Else blocks to each of those methods so that only the code that actually touches the UI is executed on the UI thread.
Here's a start:
Private Sub GetADcomputers()
UpdateStatusADLabel("Getting Computers...")
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End Sub
Private Sub UpdateStatusADLabel(text As String)
If lblStatusAD.InvokeRequired Then
lblStatusAD.Invoke(New Action(Of String)(AddressOf UpdateStatusADLabel), text)
Else
lblStatusAD.Text = text
End If
End Sub

Print to XPS without a Save As dialog

How can I save an xps file by printing to a virtual printer without using the Save File As dialog? When I call the print method, a dialog automatically pops up asking the user to specify the file name and path. This only works when creating brand new files; it throws an error 'you do not have permission to write to that file...' if I attempt to overwrite an existing file. Anyways, I want the user to be able to specify the file name in my own dialog, not the one that is autamatically called by the printDocument's Print method.
Public Event PrintPage As System.Drawing.Printing.PrintPageEventHandler
Private WithEvents Doc As New Printing.PrintDocument
Public Sub SaveXPSFile()
Doc.PrinterSettings.PrinterName = "Microsoft XPS Document Writer"
Doc.PrinterSettings.PrintFileName = "C:\Users\POConnell\Documents\t.xps"
Doc.Print()
Doc.Dispose()
End Sub
Private Sub PrintDocument1_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles Doc.PrintPage
'drawing commands go here
End sub
It's a couple of months late, but here we go.
2 questions, two answers.
Question 1:
How can I save an xps file by printing to a virtual printer without using the Save File As dialog?
Answer 1: You were close. I think you're looking for
Doc.DefaultPageSettings.PrinterSettings.PrintToFile = True
Doc.DefaultPageSettings.PrinterSettings.PrintFileName = "C:\Users\POConnell\Documents\t.xps"
Here is my implementation:
(Legal paper size, landscape mode)
Using prn As New PrintDocument
With prn
.PrinterSettings.PrinterName = "Microsoft XPS Document Writer"
AddHandler .PrintPage, _
AddressOf Me.PrintPageHandler
.DefaultPageSettings.Landscape = landscape
.DefaultPageSettings.PaperSize = New PaperSize("Legal", 850, 1400)
If My.Computer.FileSystem.FileExists("C:\temp\Log.oxps") Then My.Computer.FileSystem.DeleteFile("C:\temp\Log.oxps")
.DefaultPageSettings.PrinterSettings.PrintToFile = True
.DefaultPageSettings.PrinterSettings.PrintFileName = "C:\temp\Log.oxps"
.Print()
RemoveHandler .PrintPage, _
AddressOf Me.PrintPageHandler
End With
End Using
As you can see, I use the oxps file format, but it should still work just the same for you.
Question 2: it throws an error 'you do not have permission to write to that file...' if I attempt to overwrite an existing file.
Answer 2: Check if the file already exists prior to printing the file, and delete it if it does. Of course it will fail attempting to create a file that already exists.
For some reason using My.Computer.FileSystem.DeleteFile is faster than the traditional Kill() and System.IO.File.Delete, which both require the thread to sleep for ~1-200ms prior to recreating the file, or else a different access denied error will occur.
Hopefully this helps someone in the future!

VB.NET Checking if a File is Open before proceeding with a Read/Write?

Is there a method to verify that a file is open? The only thing I can think of is the Try/Catch to see if i can catch the file-open exception but I figured that a method be available to return true/false if file is open.
Currently using System.IO and the following code under class named Wallet.
Private holdPath As String = "defaultLog.txt"
Private _file As New FileStream(holdPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)
Private file As New StreamWriter(_file)
Public Function Check(ByVal CheckNumber As Integer, ByVal CheckAmount As Decimal) As Decimal
Try
file.WriteLine("testing")
file.Close()
Catch e As IOException
'Note sure if this is the proper way.
End Try
Return 0D
End Function
Any pointers will be appreciated! Thank you!!
Private Sub IsFileOpen(ByVal file As FileInfo)
Dim stream As FileStream = Nothing
Try
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)
stream.Close()
Catch ex As Exception
If TypeOf ex Is IOException AndAlso IsFileLocked(ex) Then
' do something here, either close the file if you have a handle, show a msgbox, retry or as a last resort terminate the process - which could cause corruption and lose data
End If
End Try
End Sub
Private Shared Function IsFileLocked(exception As Exception) As Boolean
Dim errorCode As Integer = Marshal.GetHRForException(exception) And ((1 << 16) - 1)
Return errorCode = 32 OrElse errorCode = 33
End Function
Call it like this:
Call IsFileOpen(new FileInfo(filePath))
There is really no point using a 'is file in use check' function since you will still need to have try catch to handle the case that the file fails to open. The file open can fail for many more reasons than it just being already open.
Also using a function to do a check is no guarantee of success. The 'is file in use check' might return false only for the file open to fail with a file already open error, because in time between the check and trying to open the file it was opened by someone else.
It looks like the two suggestions from this MSDN forum posting both involve trying to open the file.
The first one is similar to what you are doing now, and the second involves using a Windows API function (CreateFile) and checking for a invalid handle signifying the file is in use. In both cases they are relying on an error condition to determine if the file is open or not. In short, in my opinion the method you are using is correct since there is not a System.IO.File.IsOpen property.

Using the Exited event in vb.net

Ok, I'm making a very basic vb.net winforms app, essentially you can drag files into it, and it then uses a batch file to process the files.
It's pretty simple and everything is going to plan so far, it accepts the right files, it uses the batch file to process them and the batch file does what it is supposed to.
The only problem is that I don't know how to hook into the Exited event that can/should be raised by the batch file process when the process completes...
I want the DOS window of the batch file to remain hidden while it is running, so I have used ProcessStartInfo to specify the batch file, then set the WindowStyle property of the process to ProcessWindowStyle.Minimised, then used System.Diagnostics.Process.Start(myBatch) to start the process at the appropriate moment.
This is fine, it works and does what I want. However, the only way to tell when a process ends is to use the Exited event. But the Exited event apparently only works with a Process not a ProcessStartInfo. I could switch to use Process instead but then I couldn't (AFAIK) run the DOS window minimised...
Is there a way around this? I've only been writing .net for a few days. This is how I'm running the batch file:
Dim myBatch As New ProcessStartInfo("C:\\batchFiles\\test.bat")
myBatch.WindowStyle = ProcessWindowStyle.Minimized
system.Diagnostics.Process.Start(myBatch)
Any ideas?
Thanks
Try creating a process object and setting the StartInfo property. You can then call WaitForExit instead of waiting for the event. EG:
using(var process = new Process
{
StartInfo =
new ProcessStartInfo("Foo.exe")
{WindowStyle = ProcessWindowStyle.Minimized}
})
{
process.Start();
process.WaitForExit();
}
Not sure of the syntax in VB but I am almost sure that what you have to do is actually use the WIN API inline with managed code, and then you can use the MainWindowHandle of the Process Object.
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
The commands it takes, I would recommend reference to the win api library for this method. But what you want to do I would think is very feasible with the interop.
Andrew
From the documentation: This event can occur only if the value of the EnableRaisingEvents property is true.
So the following should work:
Dim procStart As New ProcessStartInfo
Dim WithEvents proc As New Process
Private Sub Button21_Click(sender As System.Object, e As System.EventArgs) Handles Button21.Click
procStart.FileName = "C:\PTL\Bin\xxxxxx.exe"
proc.StartInfo = procStart
proc.EnableRaisingEvents = True
proc.Start()
End Sub
Private Sub proc_Exited(sender As Object, e As System.EventArgs) Handles proc.Exited
Debug.WriteLine("Process Ended " + proc.ExitCode.ToString + " " + DateTime.Now.ToString)
End Sub