Using IO.File Class to read a file that is in use - vb.net

I am working on improving an old class and I am stuck here.
There is a method like this which works.
Private Database_Bytes As Byte()
Private Base as String = 'Path of Some database file.
Sub New()
If File.Exists(Base) Then
FileOpen(1, Base, OpenMode.Binary, OpenAccess.Read, OpenShare.Shared)
Dim asi As String = Space(LOF(1))
FileGet(1, asi)
FileClose(1)
Database_Bytes = Encoding.Default.GetBytes(asi)
End If
'Operations
End Sub
As FileOpen is old and IO Class offers better performance, I am trying to change it to below method but. I get an IOException if the file is in use by another process.
Private Database_Bytes As Byte()
Private Base as String = 'Path of Some database file.
Sub New()
If File.Exists(Base) Then
Database_Bytes = IO.File.ReadAllBytes(Base)
End If
'Operations
End Sub
How can I avoid the IOException if the file is in use?

As you have found out, File.ReadAllBytes does not allow you to read a file that is in use. However, you can do that with FileStream.Read.
Private Database_Bytes As Byte()
Private Base as String = "Path of Some database file."
Sub New()
If File.Exists(Base) Then
Using fs As New FileStream(Base, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
ReDim Database_Bytes(CInt(fs.Length) - 1)
fs.Read(Database_Bytes, 0, Database_Bytes.Length)
End Using
End If
'Operations
End Sub
The code above assumes that you have Imports System.IO (which seems to be the case based on the code you posted). Otherwise, File.Exists, FileStream, etc. would need to be qualified (e.g. IO.File.Exists, etc.).

Related

Access to the path Log is denied randomly

when I write to my log file I sometimes get access to log path is denied C:/app/log.log
Here is my code:
Private filePath As String
Private fileStream As FileStream
Private streamWriter As StreamWriter
Public Sub OpenFile()
Dim strPath As String
strPath = My.Application.Info.DirectoryPath + "\log.log"
If System.IO.File.Exists(strPath) Then
fileStream = New FileStream(strPath, FileMode.Append, FileAccess.Write)
Else
fileStream = New FileStream(strPath, FileMode.Create, FileAccess.Write)
End If
streamWriter = New StreamWriter(fileStream)
End Sub
Public Sub WriteLog(ByVal strComments As String)
OpenFile()
streamWriter.WriteLine(Date.Now.ToString & " " & strComments)
CloseFile()
End Sub
Public Sub CloseFile()
streamWriter.Close()
fileStream.Close()
End Sub
Is there anyway to make this use admin permissions or fix this issue? It only happens sometimes, and I'm not sure why it happens, but if I delete the log.log file the application can write to log again. Very weird.
You are calling your function WriteLog from multiple threads asynchronously, you should protect this function for example with a mutex.

Showing progress of ZipFiles Class

I was wondering, how can I get the percentage of this being done, so I can display it on a progress bar?
ZipFile.CreateFromDirectory("C:\temp\folder", "C:\temp\folder.zip")
and also
ZipFile.ExtractToDirectory("C:\temp\folder.zip", "C:\temp\folder")
This doesnt have any events or callbacks that you can use to report progress. Simply means you cant with the .Net version. If you used the 7-Zip library you can do this easily.
I came across this question while checking for related questions for the identical question, asked for C# code. It is true that the .NET static ZipFile class does not offer progress reporting. However, it is not hard to do using the ZipArchive implementation, available since earlier versions of .NET.
The key is to use a Stream wrapper that will report bytes read and written, and insert that in the data pipeline while creating or extracting the archive.
I wrote a version in C# for an answer to the other question, and since I didn't find any VB.NET examples, figured it would be helpful to include a VB.NET version on this question.
(Arguably, I could include both examples in a single answer and propose closing one of the questions as a duplicate of the other. But since it's doubtful the close vote would result in an actual closure, the connection between the two questions would not be as obvious as it should be. I think for best visibility to future users trying to find the solution appropriate for their needs, leaving this as two different questions is better.)
The foundation of the solution is the Stream wrapper class:
StreamWithProgress.vb
Imports System.IO
Public Class StreamWithProgress
Inherits Stream
' NOTE For illustration purposes. For production code, one would want To
' override *all* of the virtual methods, delegating to the base _stream object,
' to ensure performance optimizations in the base _stream object aren't
' bypassed.
Private ReadOnly _stream As Stream
Private ReadOnly _readProgress As IProgress(Of Integer)
Private ReadOnly _writeProgress As IProgress(Of Integer)
Public Sub New(Stream As Stream, readProgress As IProgress(Of Integer), writeProgress As IProgress(Of Integer))
_stream = Stream
_readProgress = readProgress
_writeProgress = writeProgress
End Sub
Public Overrides ReadOnly Property CanRead As Boolean
Get
Return _stream.CanRead
End Get
End Property
Public Overrides ReadOnly Property CanSeek As Boolean
Get
Return _stream.CanSeek
End Get
End Property
Public Overrides ReadOnly Property CanWrite As Boolean
Get
Return _stream.CanWrite
End Get
End Property
Public Overrides ReadOnly Property Length As Long
Get
Return _stream.Length
End Get
End Property
Public Overrides Property Position As Long
Get
Return _stream.Position
End Get
Set(value As Long)
_stream.Position = value
End Set
End Property
Public Overrides Sub Flush()
_stream.Flush()
End Sub
Public Overrides Sub SetLength(value As Long)
_stream.SetLength(value)
End Sub
Public Overrides Function Seek(offset As Long, origin As SeekOrigin) As Long
Return _stream.Seek(offset, origin)
End Function
Public Overrides Sub Write(buffer() As Byte, offset As Integer, count As Integer)
_stream.Write(buffer, offset, count)
_writeProgress?.Report(count)
End Sub
Public Overrides Function Read(buffer() As Byte, offset As Integer, count As Integer) As Integer
Dim bytesRead As Integer = _stream.Read(buffer, offset, count)
_readProgress?.Report(bytesRead)
Return bytesRead
End Function
End Class
The wrapper class can be used to implement progress-aware versions of the ZipFile static methods:
ZipFileWithProgress.vb
Imports System.IO
Imports System.IO.Compression
NotInheritable Class ZipFileWithProgress
Private Sub New()
End Sub
Public Shared Sub CreateFromDirectory(
sourceDirectoryName As String,
destinationArchiveFileName As String,
progress As IProgress(Of Double))
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName)
Dim sourceFiles As FileInfo() = New DirectoryInfo(sourceDirectoryName).GetFiles("*", SearchOption.AllDirectories)
Dim totalBytes As Double = sourceFiles.Sum(Function(f) f.Length)
Dim currentBytes As Long = 0
Using archive As ZipArchive = ZipFile.Open(destinationArchiveFileName, ZipArchiveMode.Create)
For Each fileInfo As FileInfo In sourceFiles
' NOTE: naive method To Get Sub-path from file name, relative to
' input directory. Production code should be more robust than this.
' Either use Path class Or similar to parse directory separators And
' reconstruct output file name, Or change this entire method to be
' recursive so that it can follow the sub-directories And include them
' in the entry name as they are processed.
Dim entryName As String = fileInfo.FullName.Substring(sourceDirectoryName.Length + 1)
Dim entry As ZipArchiveEntry = archive.CreateEntry(entryName)
entry.LastWriteTime = fileInfo.LastWriteTime
Using inputStream As Stream = File.OpenRead(fileInfo.FullName)
Using outputStream As Stream = entry.Open()
Dim progressStream As Stream = New StreamWithProgress(inputStream,
New BasicProgress(Of Integer)(
Sub(i)
currentBytes += i
progress.Report(currentBytes / totalBytes)
End Sub), Nothing)
progressStream.CopyTo(outputStream)
End Using
End Using
Next
End Using
End Sub
Public Shared Sub ExtractToDirectory(
sourceArchiveFileName As String,
destinationDirectoryName As String,
progress As IProgress(Of Double))
Using archive As ZipArchive = ZipFile.OpenRead(sourceArchiveFileName)
Dim totalBytes As Double = archive.Entries.Sum(Function(e) e.Length)
Dim currentBytes As Long = 0
For Each entry As ZipArchiveEntry In archive.Entries
Dim fileName As String = Path.Combine(destinationDirectoryName, entry.FullName)
Directory.CreateDirectory(Path.GetDirectoryName(fileName))
Using inputStream As Stream = entry.Open()
Using outputStream As Stream = File.OpenWrite(fileName)
Dim progressStream As Stream = New StreamWithProgress(outputStream, Nothing,
New BasicProgress(Of Integer)(
Sub(i)
currentBytes += i
progress.Report(currentBytes / totalBytes)
End Sub))
inputStream.CopyTo(progressStream)
End Using
End Using
File.SetLastWriteTime(fileName, entry.LastWriteTime.LocalDateTime)
Next
End Using
End Sub
End Class
The .NET built-in implementation of IProgress(Of T) is intended for use in contexts where there is a UI thread where progress reporting events should be raised. As such, when used in a console program, like which I used to test this code, it will default to using the thread pool to raise the events, allowing for the possibility of out-of-order reports. To address this, the above uses a simpler implementation of IProgress(Of T), one that simply invokes the handler directly and synchronously.
BasicProgress.vb
Class BasicProgress(Of T)
Implements IProgress(Of T)
Private ReadOnly _handler As Action(Of T)
Public Sub New(handler As Action(Of T))
_handler = handler
End Sub
Private Sub Report(value As T) Implements IProgress(Of T).Report
_handler(value)
End Sub
End Class
And naturally, it's useful to have an example with which to test and demonstrate the code.
Module1.vb
Imports System.IO
Module Module1
Sub Main(args As String())
Dim sourceDirectory As String = args(0),
archive As String = args(1),
archiveDirectory As String = Path.GetDirectoryName(Path.GetFullPath(archive)),
unpackDirectoryName As String = Guid.NewGuid().ToString()
File.Delete(archive)
ZipFileWithProgress.CreateFromDirectory(sourceDirectory, archive,
New BasicProgress(Of Double)(
Sub(p)
Console.WriteLine($"{p:P2} archiving complete")
End Sub))
ZipFileWithProgress.ExtractToDirectory(archive, unpackDirectoryName,
New BasicProgress(Of Double)(
Sub(p)
Console.WriteLine($"{p:P0} extracting complete")
End Sub))
End Sub
End Module
Additional notes regarding this implementation can be found in my answer to the related question.

Create Binary file of text that notepad can't read

I am attempting to create a binary file that is not readable by notepad in windows. This file needs to contain text information. The current code I run is readable in notepad (with a few extra characters here and there, but still human readable). Any assistance is greatly appreciated.
Using writer As BinaryWriter = New BinaryWriter(File.Open("file.bin", FileMode.Create))
writer.Write(rtbWriter.Text)
End Using
All files can be read by notepad - whether it is binary or not. If you don't want the text to be readable (or to be more accurate - understandable), consider using encryption.
EDIT: For an introduction on how to use encryption, see the link below to see how to use the 3DES cryptographic service provider in VB.NET:
simple encrypting / decrypting in VB.Net
*A more sophisticated approach would chain together the File Stream and Crypto Stream...
...but here's a very simple example showing how to encrypt/decrypt individual strings so you have something to play with and learn from:
Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
Public Class Form1
Private Key As String = "SomeRandomKeyThatIsHardCoded"
Private data As New List(Of String)
Private DataFileName As String = System.IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.MyDocuments, "SomeFile.txt")
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Some data to play with:
data.Add("User X, Access Y")
data.Add("User Y, Access Z")
data.Add("User Z, Access A")
ListBox1.DataSource = data
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Write out each entry in encrypted form:
Using SW As New StreamWriter(DataFileName, False)
For Each entry As String In data
SW.WriteLine(Crypto.Encrypt(entry, Key))
Next
End Using
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
data.Clear()
ListBox1.DataSource = Nothing
' Read each encrypted line and decrypt it:
Using SR As New System.IO.StreamReader(DataFileName)
While Not SR.EndOfStream
data.Add(Crypto.Decrypt(SR.ReadLine, Key))
End While
End Using
ListBox1.DataSource = data
End Sub
End Class
Public Class Crypto
Private Shared DES As New TripleDESCryptoServiceProvider
Private Shared MD5 As New MD5CryptoServiceProvider
Public Shared Function MD5Hash(ByVal value As String) As Byte()
Return MD5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(value))
End Function
Public Shared Function Encrypt(ByVal stringToEncrypt As String, ByVal key As String) As String
DES.Key = Crypto.MD5Hash(key)
DES.Mode = CipherMode.ECB
Dim Buffer As Byte() = ASCIIEncoding.ASCII.GetBytes(stringToEncrypt)
Return Convert.ToBase64String(DES.CreateEncryptor().TransformFinalBlock(Buffer, 0, Buffer.Length))
End Function
Public Shared Function Decrypt(ByVal encryptedString As String, ByVal key As String) As String
Try
DES.Key = Crypto.MD5Hash(key)
DES.Mode = CipherMode.ECB
Dim Buffer As Byte() = Convert.FromBase64String(encryptedString)
Return ASCIIEncoding.ASCII.GetString(DES.CreateDecryptor().TransformFinalBlock(Buffer, 0, Buffer.Length))
Catch ex As Exception
Return ""
End Try
End Function
End Class

Read and write into a txt file using VB.NET with words in between

I want to open a txt file and write into it numbers from 1 to 100 and put between every number enter.
One way you could try is to write the numbers into a StringBuilder and then use it's ToString() method to get the resulting text:
Imports System.IO
Imports System.Text
Public Class NumberWriter
Private ReadOnly OutputPath as String = _
Path.Combine(Application.StartupPath, "out.txt")
Public Sub WriteOut()
Dim outbuffer as New StringBuilder()
For i as integer = 1 to 100
outbuffer.AppendLine(System.Convert.ToString(i))
Next i
File.WriteAllText(OutputPath, outbuffer.ToString(), true)
End Sub
Public Shared Sub Main()
Dim writer as New NumberWriter()
Try
writer.WriteOut()
Catch ex as Exception
Console.WriteLine(ex.Message)
End Try
End Sub
End Class
There's a good example over at Home and Learn
Dim FILE_NAME As String = "C:\test2.txt"
If System.IO.File.Exists(FILE_NAME) = True Then
Dim objWriter As New System.IO.StreamWriter(FILE_NAME)
objWriter.Write(TextBox1.Text)
objWriter.Close()
MsgBox("Text written to file")
Else
MsgBox("File Does Not Exist")
End If
You could also use the "My.Computer.FileSystem" namespace, like:
Dim str As String = ""
For num As Int16 = 1 To 100
str += num.ToString & vbCrLf
Next
My.Computer.FileSystem.WriteAllText("C:\Working\Output.txt", str, False)
See System.IO namespace, especially the System.IO.File class.

Compressing a database to a single file?

In a contact manager program I've been storing data in CSV files for each contact and would like a way to compress this data into a single file.
I have attempted using data entry tools in the visual studio toolbox and template class, though I have never quite figured out how to use them. What would be especially convenient is if I could somehow store a generic class instance as opposed to having to come up with a string representation of it, and then parse it.
I'd also need to figure out how to tell the program what to do when a file is opened (I've noticed in the properties how to associate a file type with the program though am not sure how to tell it what to do when it's opened).
IMHO, switch to sqlite. You'll be able to query faster, compress it, and much more then working with a csv file.
Found this article about Serialization and it works very well!
http://www.vbdotnetheaven.com/UploadFile/Nimusoft/Serialization105022008052559AM/Serialization1.aspx
edit: Figured I should probably post more. I have a class IOwner, and list of this class contains all of my database. So I added serialization tags to this class and others it references then substitute in those properties with the ones shown on the article:
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.IO
Namespace BinarySerialization
<Serializable()> _
Public Class IFile
Implements ISerializable
Public Contacts As List(Of IOwner)
Public Self As IOwner
Public Cars As List(Of Vehicle)
Public path As String
Public Sub New()
End Sub
Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
Data.Self = info.GetValue("Self", GetType(IOwner))
Data.Contacts = info.GetValue("Contacts", GetType(List(Of IOwner)))
Data.Cars = info.GetValue("Cars", GetType(List(Of Vehicle)))
End Sub
Public Sub WriteFile()
Dim s As New FileStream(path, FileMode.Create, FileAccess.ReadWrite)
Dim B As New BinaryFormatter()
B.Serialize(s, Me)
s.Close()
End Sub
Public Function ReadFile() As IFile
Dim Fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Dim F As New BinaryFormatter()
Dim s1 As IFile = DirectCast(F.Deserialize(Fs), IFile)
Fs.Close()
Return s1
End Function
#Region "ISerializable Members"
Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) Implements ISerializable.GetObjectData
info.AddValue("Self", Data.Self)
info.AddValue("Contacts", Data.Contacts)
info.AddValue("Cars", Data.Cars)
End Sub
#End Region
End Class
End Namespace
UPDATE 2016
Binary serialization is not advisable for complex structures that are subject to change. Using a database storage method such as SQLite and using an ODB (object database model) would have been preferable.