How do I delay a vb.net program until a file operation completes? - vb.net

I have this:
Dim myTemp As String
myTemp = System.DateTime.Now().ToString("MMMddyyyy_HHmmss") & ".pdf"
System.IO.File.Copy(myFile, "c:\" & myTemp)
Application.DoEvents()
OpenFile(myTemp)
The problem is that when I call OpenFile, which is just a call to a sub that opens a file, it cannot find the file. This is because it is calling it so quickly that the program doesn't have time to actually create the file before the open takes place.
I thought that DoEvents() would rectify this but it does not. I need to wait until the file is created before I open the file. How can I do that?

I don't really know much VB.NET, but isn't Copy a blocking call? Are you sure you're not just trying to open the file from the wrong location (or the unescaped backslash invalidates the path)?
What about this? I've added the drive letter to OpenFile, and escaped the backslash both places.
Dim myTemp As String
myTemp = System.DateTime.Now().ToString("MMMddyyyy_HHmmss") & ".pdf"
System.IO.File.Copy(myFile, "c:\\" & myTemp)
OpenFile("c:\\" & myTemp)

Ideally you should perform the copy on a separate thread that informs the main GUI thread when it is done so it can then perform the open through an Invoke call.

Use FileSystemWatcher to alert you when the file is created. No loops.
https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-6165137.html

This is ugly but it works for me
Function WaitForFile(fullPath, wdelay)
Dim vd_start As Date
vd_start = Now()
Dim vd_end As Date
Dim wsec, wmin, whour, wt5string As Integer
Dim wtstring As String
Dim count As Integer
Dim wscale As Integer
Dim vd_1 As Date
Dim Vo_fileinfo As FileInfo
Dim fs As FileStream
wsec = Format(wdelay Mod 60, "00")
wmin = Format(Int(wdelay / 60), "00")
whour = Format(Int(wdelay / (60 * 60)), "00")
wtstring = CStr(whour) + ":" + CStr(wmin) + ":" + CStr(wsec)
Dim duration = New System.TimeSpan(0, whour, wmin, wsec)
vd_end = vd_start.Add(duration)
On Error GoTo error1
Dim vsize1, vsize2 As Long
While vd_start < vd_end
fs = New FileStream(fullPath, FileMode.Open)
fs.ReadByte()
fs.Seek(0, SeekOrigin.Begin)
fs.Close()
Vo_fileinfo = New FileInfo(fullPath)
vsize1 = Vo_fileinfo.Length
Threading.Thread.Sleep(500)
Vo_fileinfo = New FileInfo(fullPath)
vsize2 = Vo_fileinfo.Length
If vsize1 <> vsize2 Then GoTo error1
GoTo finalgoto
error1:
Err.Clear()
vd_start = Now()
End While
WaitForFile = False
GoTo Endgoto
finalgoto: WaitForFile = True
Endgoto:
End Function

This is a bit hacky, but it should work.
Do Until (System.IO.File.Exists("C:\" & myTemp))
Threading.Thread.Sleep(1)
Loop

That isn't really what Doevents is used for. It is most frequently used to let the UI message queue clear out (let the UI have some CPU time to refresh). It is slightly more complex than I am describing, but that isn't the point of your question so I will move on.
Try this to make the critical section of your code block:
SyncLock Me
System.IO.File.Copy(myFile, "c:\" & myTemp)
Application.DoEvents()
End SyncLock
OpenFile(myTemp)

In addition to Tom's answer, isnt it better to put a Application.DoEvents() rather then making the thread sleep?

First, you should not call DoEvents anywhere. For the most part, when it is used, it is a hack to circumvent what should really be an asynchronous operation.
That being said, the Copy method is a synchronous operation. The call to OpenFile will not occur until the call to Copy completes.
That being said when the call to OpenFile occurs, if the file does not exist, it is because you copied it to the wrong place, or because some other process is working on the file in question.

I thinf Synclock is not good for this case
for explaination, MSDN can help me
The SyncLock statement ensures that multiple threads do not execute the same statements at the same time. When the thread reaches the SyncLock block, it evaluates the expression and maintains this exclusivity until it has a lock on the object that is returned by the expression. This prevents an expression from changing values during the running of several threads, which can give unexpected results from your code.
in my opinion, copy is blocking method, so thread waits until copying is done
can`t be problem in another place?

dim SourceFile as string
dim DestinationFile as string
SourceFile = "c:/archivo.txt"
DestinationFile = "c:/destino/archivo.txt"
If System.IO.File.Exists(SourceFile) = True Then
System.IO.File.Copy(SourceFile, DestinationFile, True)
'or
'My.Computer.FileSystem.CopyFile(SourceFile, DestinationFile, FileIO.UIOption.AllDialogs, FileIO.UICancelOption.DoNothing)
SourceFile = ""
DestinationFile = ""
else
MessageBox.Show("the file don't copy!")
end if

System.Threading.Thread.Sleep(1000);

Related

Can't Multi Download With WebClient

I know this question has been asked before, but I have a special way I want to download the files through a webclient, I want to download each file at a time through a Do While statement, but it just adds the file to download and moves on to the next task that also downloads another file so it cancels each other out and crashes the application.
Private Function StartDownloads()
Dim wc As New WebClient
AddHandler wc.DownloadProgressChanged, AddressOf DownloadProgressChanged
AddHandler wc.DownloadFileCompleted, AddressOf DownloadFileCompleted
Dim delimiterChars As Char() = {"+"c}
Dim words As String() = RichTextBox1.Text.Split(delimiterChars)
Dim i As Integer = 1
Do While (i < words.Length)
Dim delimiterChars1 As Char() = {"|"c}
Dim words1 As String() = words(i).Split(delimiterChars1)
Dim name As String = words1(0)
Dim fileurl As String = words1(2)
ToolStripStatusLabel1.Text = "Downloading " & name & ":"
wc.DownloadFileAsync(New System.Uri(fileurl), Path.GetTempPath() & "\" & name)
i = (i + 1)
Loop
End Function
AND
Private Sub DownloadProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs)
ProgressBar1.Value = e.ProgressPercentage
ToolStripStatusLabel2.Text = String.Format("{0} MB's / {1} MB's",
(e.BytesReceived / 1024D / 1024D).ToString("0.00"),
(e.TotalBytesToReceive / 1024D / 1024D).ToString("0.00"))
End Sub
So basically in the Do While statement it starts the download then continues without waiting for the download to finish and then downloads both at once, which then crashes the program/interferes with the labels displaying the download name, the reason this is a different question from others is I need it to specify the download name as I can't do with other tutorials, by adding the downloads to a list...
So if anyone can help me make the Do While statement wait for the download to finish then continue with the next without loosing the file name and stuff like that, please give me some tips, thanks!
I know that DownloadFile waits for the download to finish then continues, but I need it to show download progress and download bytes and stuff like that...
P.S. Sorry if this is confusing as it's hard to explain, thanks.
By adding await wc.DownloadFileTaskAsync(...), it worked perfectly

Many instances of the same process writing to the same log file

I am kicking off a number of instances of the same process and the issue is that they all write to the same log file. I know it is not a good practice and was wondering what can I do to avoid possible issues. Here is the procedure I use to write to file:
Sub WriteToErrorLog(ByVal Msg As String)
Dim path As String
path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
Dim strFile As String = System.IO.Path.Combine(path, "Log_" & DateTime.Today.ToString("dd-MMM-yyyy") & ".txt")
Dim sw As StreamWriter
Dim fs As FileStream = Nothing
Try
If (Not File.Exists(strFile)) Then
fs = File.Create(strFile)
fs.Close()
End If
sw = File.AppendText(strFile)
sw.WriteLine(Msg & vbcrlf)
Catch ex As Exception
MsgBox("Error Creating Log File")
MsgBox(ex.Message & " - " & ex.StackTrace)
Finally
sw.Close()
End Try
End Sub
I would appreciate any suggestions/improvements. thanks!
As I have said in my comment, the scenario of multiple access to the same file resource should be handled carefully and probably the best solution is to use a well tested log library like Log4Net or NLog.
In any case you could improve your code in a couple of point
Sub WriteToErrorLog(ByVal Msg As String)
Dim path As String
path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
Dim strFile As String = System.IO.Path.Combine(path, "Log_" & DateTime.Today.ToString("dd-MMM-yyyy") & ".txt")
Dim retry as Integer = 3 ' this could be changed if you experience a lot of collisions.'
Dim sw As StreamWriter = Nothing
While retry > 0
Try
Using sw = File.AppendText(strFile)
sw.WriteLine(Msg & vbcrlf)
End Using
Exit While
Catch ex as Exception
retry -= 1
End Try
End While
' If retry has reached zero then we have exausted our tentatives and give up....'
if retry = 0 Then
MessageBox.Show("Error writing to Log File")
End if
End Sub
I have removed all the part that check if file exists and then create it. This is not necessary because as the documentation explains, File.Append is the same that calling StreamWriter(file, true) and this means that if the file doesn't exist it will be created.
Next, to try to handle possible collision with other process writing to the same file concurrently, I have added a retry loop that could get access to the log file just after another process finishes.
(this is really a poor-man solution but then it is better to use a well tested library)
It is important to enclose the opening and writing of the file inside a using statement that closes and disposes the Stream also in case of exceptions. This is mandatory to be sure to leave the file always closed for the other processes to work.

Better way to check and update line in file VB.NET

I am trying to check a bunch of files that should have a leading | delimiter but sometimes don't. If the first line doesn't have the |, the rest of the file won't, if it does have it, then the rest of the file will.
So I'm checking the first line which works and enters the update logic, which advances to the next row once I read the line in. So I added a writer.Write before I go into the Do While loop and it works, but it seems like there would be a better way of doing this.
Here is my code:
Dim fileList As FileInfo() = dirList.GetFiles
For Each fiName In fileList
sFullName = fiName.FullName
sFileName = Path.GetFileNameWithoutExtension(fiName.ToString)
sDir = fiName.DirectoryName
Dim reader As New StreamReader(sFullName)
Dim line As String = reader.ReadLine()
If Not line.StartsWith("|") Then
Dim sNewFullName As String = sDir + "\" + sFileName + "_Temp.txt"
Dim writer As New StreamWriter(sNewFullName)
Dim sLine As String
sLine = "|" + line + System.Environment.NewLine
writer.Write(sLine)
Do While reader.Peek() <> -1
sLine = "|" + reader.ReadLine() + System.Environment.NewLine
writer.Write(sLine)
Loop
writer.Close()
reader.Close()
File.Delete(sFullName)
File.Move(sNewFullName, sFullName)
End If
reader.Close()
Next fiName
Any help or guidance would be much appreciated, Thank you in advance...
This way works just fine. There are several other ways to do it, but it's a matter of opinion on which is better, based on simplicity, portability, performance, maintainability, etc.
For example, you could read the entire file into a string using file.readalltext, then change all VBCRLF (or whether the line end character is) to VBCRLF & "|", then write it all out with file.writealltext.
If it was me, and if it works, I'd leave it unless I just wanted to learn some other methods. Some would argue for hours for another method that is better, but it may be more productive to move on to the next project.

Deleting file if not just created (or being used)

I am creating a console app that will delete pictures from a directory every 30 minutes. Problem is that its being populated by files every minute or so. So if I go and delete files in that directory then it may cause an error trying to delete a file thats being created just then or opened.
I currently have this code to copy the files to another directory and then delete them from the source directory.
Dim f() As String = Directory.GetFiles(sourceDir)
For i As Integer = 0 To UBound(f)
'Check file date here in IF statement FIRST...
File.Copy(f(i), destDir & f(i).Replace(sourceDir, ""))
If File.Exists(f(i)) = True Then
File.Delete(f(i))
End If
Debug.Print(f(i) & " to >>> " & destDir & f(i).Replace(sourceDir, ""))
Next
How can I use:
File.GetCreationTime(f(i))
in an IF statement checking IF the currently file its on is newer than 30 seconds ago?
OR
Is there a way of only populating:
Dim f() As String = Directory.GetFiles(sourceDir)
with only those files that are more than 30 seconds old?
There isn't a reliable way to detect if a file is locked or not. Even if you did find out (it is technically possible), it could be locked before you tried to delete it. There are other reasons a delete may fail. In your case, I don't think it matters what the reason was.
The only way is to put the call to delete in a try/catch and trap IOException, and then retry if you want.
You need to use a FileInfo object to get the CreatedTime and compare to Now. You can also use LastAccessTime or LastWriteTime, but since these are all new files being written then, you don't need to.
Private Sub DeleteFiles()
Dim files = From f In Directory.GetFiles("c:\temp")
Let fi = New FileInfo(f)
Where fi.Exists AndAlso fi.CreationTime <= DateTime.Now.AddSeconds(-30)
For Each f In files
Try
f.Delete()
Catch ex As Exception
If TypeOf ex Is IOException AndAlso IsFileLocked(ex) Then
' do something?
End If
'otherwise we just ignore it. we will pick it up on the next pass
End Try
Next
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
IsFileLocked function lifted from this other thread on SO
Dim NewFileDate As DateTime = DateTime.Now.AddSeconds(-30)
' get the list of all files in FileDir
Dim PicFiles As List(Of String) = System.IO.Directory.GetFiles("C:\", "*.txt").ToList()
' filter the list to only include files older than NewFileDate
Dim OutputList As List(Of String) = PicFiles.Where(Function(x) System.IO.File.GetCreationTime(x) < NewFileDate).ToList()
' delete files in the list
For Each PicFile As String In OutputList
'wrap this next line in a Try-Catch if you find there is file locking.
System.IO.File.Delete(PicFile)
Next
Obviously targeting .Net 3.5 or 4.0

ASP.NET How do I wait for file upload/release?

I've got ASP.NET intranet application written in VB. It gets a file from the user, and then depending on a few different cases it may create a few copies of the file as well as move the original.
Unfortunately I've come across a case where I get this error:
Exception Details: System.IO.IOException: The process cannot access the file
'\\some\dir\D09_03_5_180_0.000-6.788.png' because it is being used by
another process.
Which is thrown by My.Computer.FileSystem.CopyFile. And that's fine that it's being used by another process - it may still be saving/downloading from the user or trying to copy while another thread(?) is copying, I don't really care about that, what I want to know:
Is there any way that I can tell VB to wait to copy (also move) the file until the file is no longer in use?
Thanks
Test if the file is in use and the do what you need to do.
Public Sub WriteLogFile(ByVal pText As String, ByVal psPath As String, ByVal psName As String)
Dim strFullFileName As String
Dim Writer As System.IO.StreamWriter
Dim Fs As System.IO.FileStream
Try
Dim DirectoryHandler As New System.IO.DirectoryInfo(psPath)
strFullFileName = psPath & "\" & psName & Date.Today.Month.ToString & "-" & Date.Today.Day.ToString & "-" & Date.Today.Year.ToString & ".txt"
If Not DirectoryHandler.Exists() Then
Try
Monitor.Enter(fsLocker)
DirectoryHandler.Create()
Finally
Monitor.Exit(fsLocker)
End Try
End If
Try
If CheckIfFileIsInUse(strFullFileName) = True Then
Thread.Sleep(500) ' wait for .5 second
WriteLogFile(pText, psPath, psName)
If Not Fs Is Nothing Then Fs.Close()
If Not Writer Is Nothing Then Writer.Close()
Exit Sub
End If
Monitor.Enter(fsLocker)
Fs = New System.IO.FileStream(strFullFileName, IO.FileMode.Append, IO.FileAccess.Write, IO.FileShare.Write)
Writer = New System.IO.StreamWriter(Fs)
Writer.WriteLine(Date.Now.ToString & vbTab & "ProcessID: " & Process.GetCurrentProcess.Id.ToString() & vbTab & pText)
Writer.Close()
Fs.Close()
Finally
Monitor.Exit(fsLocker)
End Try
Catch ex As Exception
Dim evtEMailLog As System.Diagnostics.EventLog = New System.Diagnostics.EventLog()
evtEMailLog.Source = Process.GetCurrentProcess.ProcessName.ToString()
evtEMailLog.WriteEntry(ex.Message, System.Diagnostics.EventLogEntryType.Error)
Finally
If Not Fs Is Nothing Then Fs.Close()
If Not Writer Is Nothing Then Writer.Close()
End Try
End Sub
Public Function CheckIfFileIsInUse(ByVal sFile As String) As Boolean
If System.IO.File.Exists(sFile) Then
Try
Dim F As Short = FreeFile()
FileOpen(F, sFile, OpenMode.Append, OpenAccess.Write, OpenShare.Shared)
FileClose(F)
Catch
Return True
End Try
End If
End Function
Hmm... not directly.
What most implementations are doing, is making a retry of copying the file, with a small timeframe (some seconds)
if you want to make a nice UI, you check via Ajax, if the copying process went well.
Well, it turns out that waiting would not work in this case:
When trying to copy a file you cannot copy a file from one location to the same location or it will throw an error (apparently). Rather than just pretending to copy the file, VB actually tries to copy the file and fails because the copy operation is trying to copy to the file it's copying from (with overwrite:=True at least).
Whoops!