Chapter 18. Manipulating Files and Streams

Manipulating files, directories, drives, and pathnames has always been one of the most common requirements for every role in IT for home users. Since MS-DOS, you have probably had this necessity tons of times. In application development for the .NET Framework with Visual Basic 2012, manipulating files is even more straightforward because you have two opportunities: accessing system resources with the usual ease due to self-explanatory classes and members and because of the Common Language Runtime (CLR) as a supervisor. In this chapter, you learn how to manipulate files, directories, and drives by using specific .NET classes. Moreover, you learn about transporting such simplicity into more general data exchange objects, known as streams.

Manipulating Directories and Pathnames

The .NET Framework makes it easier to work with directories and pathnames, providing the System.IO.Directory and System.IO.Path classes. These classes offer shared methods for accessing directories and directory names, enabling deep manipulation of folders and names. To be honest, System.IO.Path also provides members for working against filenames, and due to its nature, it is included in this section. In some situations, you need to work against single directories as instances of .NET objects, and this is where the System.IO.DirectoryInfo class comes in. In this section, you learn to get the most from these classes for directory manipulation.


Tip

All the code examples provided in this chapter require an Imports System.IO directive to shorten lines of code.


The System.IO.Path Class

Often you need to work with pathnames, directory names, and filenames. The .NET Framework provides the System.IO.Path class, offering shared members that enable you to manipulate pathnames. For example, you might want to extract the filename from a pathname. This is accomplished by invoking the GetFileName method as follows:

'Returns "TextFile.txt"
Dim fileName As String = Path.GetFileName("C:TextFile.txt")

The GetExtension method returns instead only the file extension and is used as follows:

'Returns ".txt"
Dim extension As String = Path.GetExtension("C:TextFile.txt")

To ensure that a filename has an extension, you can also invoke the HasExtension method that returns True if the filename has one. In some situations you need to extract the filename without considering its extension. The GetFileNameWithoutExtension accomplishes this:

'Returns "TextFile"
Dim noExtension As String = Path.
                            GetFileNameWithoutExtension("C:TextFile.txt")

Another common situation is retrieving the directory name from a full pathname that includes a filename, too. The GetDirectoryName method enables you to perform this:

'Returns "C:UsersAlessandroMy Documents"
Dim dirName As String =

    Path. GetDirectoryName("C:UsersAlessandroMy DocumentsDocument.txt")

When working with filenames, you might want to replace the extension. This is accomplished by invoking the ChangeExtension method as follows:

'Returns "MyFile.Doc"
Dim extReplaced As String = Path.ChangeExtension("MyFile.Txt", ".doc")

Notice that such a method returns a string that contains the required modification but does not rename the file on disk (which is covered later). Path is also useful when you need to create temporary files or to store files in the Windows temporary folder. You invoke the GetTempFileName method to get a unique temporary file:

Dim temporaryFile As String = Path.GetTempFileName

The method returns the filename of the temporary file so that you can easily access it and treat it like any other file, being sure that its name is unique. If you instead need to access Windows temporary folder, you can invoke the GetTempPath method as follows:

Dim temporaryWinFolder As String = Path.GetTempPath

You could combine both temporary folder name and temporary filename to create a temporary file in the temporary folder. Combining pathnames is accomplished by invoking the Combine method, which takes two arguments such as the first pathname and the second one, or a parameter array containing strings representing pathnames. You can also generate a random filename invoking the GetRandomFileName method:

Dim randomFile As String = Path.GetRandomFileName

The difference with GetTempFileName is that this one also creates a physical file on disk, returning the full path. System.IO.Path offers two other interesting methods: GetInvalidFileNameChars and GetInvalidPathChars. These both return an array of Char storing unaccepted characters within filenames and within directory names, respectively. They are useful if you generate a string that will then be used as a filename or folder name.

The System.IO.Directory Class

To access directories, you use the System.IO.Directory class that offers shared members that enable you to perform common operations on folders. All members should be self-explanatory. For example, you can check whether a directory already exists and, if not, create a new one as follows:

If Not Directory.Exists("C:Test") Then
    Directory.CreateDirectory("C:Test")
End If

The Move method enables you to move a directory from one location to another; it takes two arguments, the source directory name and the target name:

Directory.Move("C:Test", "C:Demo")

If the target directory already exists, an IOException is thrown. You can easily get or set attribute information for directories by invoking special methods. For example, you can get the directory creation time by invoking the GetCreationTime method that returns a Date type or the SetCreationTime that requires a date specification, to modify the creation time:

Dim createdDate As Date = Directory.GetCreationTime("C:Demo")
Directory.SetCreationTime("C:Demo", New Date(2009, 5, 10))

Table 18.1 summarizes members for getting/setting attributes.

Table 18.1. Members for Getting/Setting Attributes

Image

You can easily get other information, such as the list of files available within the desired directory. You can get the list of files from a directory by invoking the GetFiles method, returning an array of string (each one is a filename), which works like this:

'Second argument is optional, specifies a pattern for search
Dim filesArray() As String = Directory.GetFiles("C:", "*.exe")

As an alternative, you can invoke the EnumerateFiles that returns an IEnumerable(Of String) and that works like this:

'get files
Dim filesEnumerable As IEnumerable(Of String) = _
                       Directory.EnumerateFiles("C:", "*.exe")

For Each item In filesEnumerable
    Console.WriteLine("File name: {0}", item)
Next

This difference probably does not make much sense at this particular point of the book, but you learn later that IEnumerable objects are LINQ-enabled; therefore, you can write LINQ queries against sequences of this type. Similarly to GetFiles and EnumerateFiles, you invoke GetDirectories and EnumerateDirectories to retrieve a list of all subdirectories’ names within the specified directory. Next, the GetFiles and EnumerateFiles return a list of all filenames and subdirectory names within the specified directory. Actually there is another difference between GetXXX and EnumerateXXX methods, which is about performance: The latter starts enumerating as the Framework is still gathering files/directories, making things faster and more efficient.

To delete a directory, you invoke the Delete method. It works only if a directory is empty and requires the directory name:

'Must be empty
Directory.Delete("C:Demo")

If the folder is not empty, an IOException is thrown. Delete has an overload that accepts a Boolean value if you want to delete empty subdirectories, too. The Directory class also provides the capability of retrieving a list of available drives on your machine. This is accomplished by invoking the GetLogicalDrives method that returns an array of String, which you can then iterate:

Dim drivesOnMyMachine() As String = Directory.
                                    GetLogicalDrives
For Each drive In drivesOnMyMachine
    Console.WriteLine(drive)

Next

On my machine the preceding code produces the output shown in Figure 18.1.

Image

Figure 18.1. Enumerating the list of drives on the current system.

The last example is about retrieving the current directory, which is accomplished by invoking the GetCurrentDirectory method:

Dim currentFolder As String = Directory.GetCurrentDirectory

You can also set the current folder by invoking the shared SetCurrentDirectory method, passing the folder name as an argument. Accessing directories via the Directory class is straightforward, but in some circumstances you have no access to specific information. For this, a more flexible class that enables you to work on specific directories is DirectoryInfo.

The System.IO.DirectoryInfo Class

The System.IO.DirectoryInfo class represents a single directory. More precisely, an instance of the DirectoryInfo class handles information about the specified directory. It inherits from System.IO.FileSystemInfo, which is a base class that provides the basic infrastructure for representing directories or files. You create an instance of the DirectoryInfo class by passing the desired directory name as an argument to the constructor:

Dim di As New DirectoryInfo("C:Demo")

You have the same members that you already learned about for the Directory class, with some differences. First, now members are instance members and not shared. Second, methods summarized in Table 18.1 are now properties. Third, members are invoked directly on the instance that represents the directory; therefore, you do not need to pass the directory name as an argument. For example, you remove an empty directory by invoking the instance method Delete as follows:

di.Delete()

An interesting property is DirectoryInfo.Attributes, which enables you to specify values from the System.IO.FileAttributes enumeration and that determine directory behavior. For example, you can make a directory hidden and read-only setting bitwise flags as follows:

di.Attributes = FileAttributes.Hidden Or FileAttributes.ReadOnly

When you specify values from such enumeration, IntelliSense can help you understand what the value is about; it’s worth mentioning that such values are self-explanatory. Sometimes you do not programmatically create instances of DirectoryInfo; instead you receive an instance from some other objects. For example, the Directory.CreateDirectory shared method returns a DirectoryInfo object. In such cases, you can get further information as the directory name invoking the FullName property, which returns the full pathname of the folder, or the Name property that just returns the name without path. Both work like this:

Dim directoryFullName As String = di.FullName
Dim directoryName As String = di.Name

Use the DirectoryInfo class each time you need to store information for specific directories—for example, within collections.

The System.IO.DriveInfo Class

Similarly to System.IO.DirectoryInfo, System.IO.DriveInfo provides access to drives information. Using this class is straightforward; it provides information on the disk type, disk space (free and total), volume label, and other self-explanatory properties that you can discover with IntelliSense. The following example shows how you can create an instance of the class and retrieve information on the specified drive:

Sub DriveInfoDemo()
    Dim dr As New DriveInfo("C:")

    Console.WriteLine("Drive type: {0}", dr.DriveType.ToString)
    Console.WriteLine("Volume label: {0}", dr.VolumeLabel)
    Console.WriteLine("Total disk space: {0}", dr.TotalSize.ToString)
    Console.WriteLine("Available space: {0}",
                      dr.AvailableFreeSpace.ToString)
    dr = Nothing
End Sub

Handling Exceptions for Directories and Pathnames

When working with directories and pathnames, encountering exceptions is not so uncommon. Table 18.2 summarizes directory-related exceptions.

Table 18.2. Directory-Related Exceptions

Image

It is important to implement Try..Catch blocks for handling the previously described exceptions and provide the user the ability to escape from such situations.

Manipulating Files

Manipulating files is a daily task for every developer. Luckily, the .NET Framework provides an easy infrastructure for working with files. In this section, you learn about the System.IO.File and System.IO.FileInfo classes that also represent some important concepts before you go into streams.

The System.IO.File Class

The System.IO.File class provides access to files on disk exposing special shared members. For example, you can easily create a text file invoking two methods: WriteAllText and WriteAllLines. Both create a new text file, put into the file the given text, and then close the file; however, the second one enables you to write the content of an array of strings into multiple lines. The following code provides an example:

File.WriteAllText("C:TempOneFile.txt", "Test message")

Dim lines() As String = {"First", "Second", "Third"}
File.WriteAllLines("C:TempOneFile.txt", lines)

Such methods are useful because they avoid the need to manually close files on disk when you perform the writing operation. You can also easily create binary files by invoking the WriteAllBytes method that works like the previous ones but requires the specification of an array of byte instead of text. The following is a small example:

File.WriteAllBytes("C:TempOneFile.bin", New Byte() {1, 2, 3, 4})

Reading files’ content is also easy. There are reading counterparts of the previously described method. ReadAllText and ReadAllLines enable you to retrieve content from a text file; the first one returns all content as a String, whereas the second one returns the content line-by-line by putting in an array of String. This is an example:

Dim text As String = File.ReadAllText("C:TempOneFile.txt")
Dim fileLines() As String = File.ReadAllLines("C:TempOneFile.txt")

Similarly you can read data from binary files invoking ReadAllBytes, which returns an array of Byte, as follows:

Dim bytes() As Byte = File.ReadAllBytes("C:TempOneFile.bin")

For text files, you can also append text to an existing file. You accomplish this by invoking AppendAllText if you want to put an entire string or AppendAllLines if you have a sequence of strings. This is an example:

Dim lines As IEnumerable(Of String) = _
             New String() {"First", "Second", "Third"}.AsEnumerable
File.AppendAllLines("C:TemporaryTest.txt", lines)
File.AppendAllText("C:TemporaryText.txt",
                   "All text is stored within a string")

Notice how an array of strings is converted into an IEnumerable(Of String) invoking the AsEnumerable extension method, which is discussed in Chapters 20, “Advanced Language Features,” and 23, “LINQ to Objects.” AppendAllLines takes an IEnumerable(Of String) as a parameter, but you can also pass an array of strings because arrays are actually enumerable. After reading and writing, copying is also important. The Copy method enables you to create copies of files, accepting two arguments: the source file and the target file. This is an example:

File.Copy("C:OneFolderSource.txt", "C:AnotherFolderTarget.txt")

You can also move a file from a location to another by invoking Move. Such a method is also used to rename a file and can be used as follows:

File.Move("C:OneFolderSource.txt", "C:AnotherFolderTarget.txt")

Another useful method is Replace. It enables you to replace the content of a file with the content of another file, making a backup of the first file. You use it as follows:

File.Replace("C:Source.Txt", "C:Target.txt", "C:Backup.txt")

You are not limited to text files. The File class offers two important methods that provide a basic encryption service, Encrypt and Decrypt. Encrypt makes a file accessible only by the user who is currently logged into Windows. You invoke it as follows:

File.Encrypt("C:TempOneFile.txt")

If you try to log off from the system and then log on with another user profile, the encrypted file will not be accessible. You need to log on again with the user profile that encrypted the file. To reverse the result, invoke Decrypt:

File.Decrypt("C:TempOneFile.txt")

Finally, you can easily delete a file from disk. This is accomplished with the simple Delete method:

File.Delete("C:TempOneFile.txt")

The File class also has members similar to the Directory class. Consider the summarization made in Table 18.1 about the Directory class’s members. The File class exposes the same members with the same meaning; the only difference is that such members now affect files. Those members are not covered again because they behave the same on files.


Members Returning Streams

The System.IO.File class exposes methods that return or require streams, such as Create and Open. Such members are not covered here for two reasons: The first one is that the streams discussion will be offered later in this chapter; the second one is that streams provide their own members for working against files that do the same as file members and therefore a more appropriate discussion is related to streams.


The System.IO.FileInfo Class

Similarly to what I explained about the System.IO.DirectoryInfo class, there is also a System.IO.FileInfo counterpart for the System.IO.File class. An instance of the FileInfo class is therefore a representation of a single file, providing members that enable you to perform operations on that particular file or get/set information. Because FileInfo inherits from System.IO.FileSystemInfo like DirectoryInfo, you can find the same members. You create an instance of the FileInfo class by passing the filename to the constructor, as demonstrated here:

Dim fi As New FileInfo("C:MyFile.txt")

You can set attributes for the specified file assigning the Attributes property, which receives a value from the System.IO.FileAttributes enumeration:

fi.Attributes = FileAttributes.System Or FileAttributes.Hidden

You can still perform operations by invoking instance members that do not require the filename specification, such as CopyTo, Delete, Encrypt, Decrypt, or MoveTo: the FileInfo, such as Length (of type Long) that returns the file size in bytes; Name (of type String) that returns the filename and that is useful when you receive a FileInfo instance from somewhere else; FullName that is the same as Name but also includes the full path; Exists that determines if the file exists; and IsReadOnly that determines if the file is read-only. Using FileInfo can be useful if you need to create collections of objects, each representing a file on disk. Consider the following custom collections that stores series of FileInfo objects:

Class MyFileList
    Inherits List(Of FileInfo)

End Class

Now consider the following code that retrieves the list of executable filenames in the specified folder and creates an instance of the FileInfo class for each file, pushing it into the collection:

Module FileInfoDemo

    Sub FileInfoDemo()

        'An instance of the collection
        Dim customList As New MyFileList

        'Create a FileInfo for each .exe file
        'in the specified directory
        For Each itemName As String In _
            Directory.EnumerateFiles("C:", "*.exe")

            Dim fileReference As New FileInfo(itemName)
            customList.Add(fileReference)
        Next

        'Iterate the collection
        For Each item In customList
            Console.WriteLine("File: {0}, length: {1}, created on: {2}",
                              item.Name, item.Length, item.CreationTime)
        Next
    End Sub
End Module

In this particular case enclosing the code within a module is just for demonstration purposes. Notice how you can access properties for each file that you could not know in advance. Just like DirectoryInfo, FileInfo also exposes properties that are counterparts for methods summarized in Table 18.1 and that this time are related to files. Refer to that table for further information.

Handling File Exceptions

Refer to Table 18.2 for exceptions that can occur when working with files. Other than those exceptions, you may encounter a FileNotFoundException if the specified file does not exist.

Understanding Permissions

The .NET Framework provides a high-level security mechanism over system resources, so it can happen that you attempt to access, in both reading or writing, directories or files but you do not have the required rights. To prevent your code from failing at runtime, you can check whether you have permissions. When working with files and directories, you need to check the availability of the System.Security.FileIOPermission object. For example, the following code asks the system (Demand method) if it has permissions to read local files:

Dim fp As New FileIOPermission(PermissionState.None)
fp.AllLocalFiles = FileIOPermissionAccess.Read
Try
    fp.Demand()
Catch ex As Security.SecurityException
    Console.WriteLine("You have no permission for local files")
Catch ex As Exception
End Try

If your code has no sufficient permissions, a SecurityException is thrown. Checking for permission is absolutely a best practice and should be applied where possible. In Chapter 45, “Working with Assemblies,” you get some more information about the security model in the .NET Framework.

Introducing Streams

Streams are sequences of bytes exchanged with some kind of sources, such as files, memory, and network. A stream is represented by the abstract System.IO.Stream class that is the base class for different kinds of streams and that implements the IDisposable interface. The Stream class exposes some common members that you find in all other streams. Table 18.3 summarizes the most important common members.

Table 18.3. Streams Common Members

Image

Now that you have a summarization of common members, you are ready to discover specific kinds of streams that inherit from Stream.

Reading and Writing Text Files

You create text files by instantiating the StreamWriter class, which is a specific stream implementation for writing to text files. The following code, which will be explained, provides an example:

Dim ts As New StreamWriter("C:TemporaryOneFile.txt",
                           False, System.Text.Encoding.UTF8)

ts.WriteLine("This is a text file")
ts.WriteLine("with multi-line example")
ts.Close()

The constructor provides several overloads; the one used in the code receives the file name to be created—a Boolean value indicated whether the text must be appended if the file already exists and how the text is encoded. WriteLine is a method that writes a string and then puts a line terminator character. When you are done, you must close the stream invoking Close. You can also invoke Write to put in just one character. The reading counterpart is the StreamReader that works in a similar way, as demonstrated here:

Dim rf As New StreamReader("C:TemporaryOneFile.txt",
                           System.Text.Encoding.UTF8)
Dim readALine As String = rf.ReadLine
Dim allContent As String = rf.ReadToEnd

rf.Close()

StreamReader provides the ability to read one line (ReadLine method), one character per time (Read method), or all the content of the stream (ReadToEnd method) putting such content into a variable of type String. In both StreamWriter and StreamReader, the constructor can receive an existing stream instead of a string. This is exemplified by the following code:

Dim fs As New FileStream("C:TemporaryOneFile.txt", FileMode.Create)
Dim ts As New StreamWriter(fs)

'Work on your file here..
ts.Close()

fs.Close()

First, you need an instance of the FileStream class, which enables you to open a communication with the specified file and with the mode specified by a value of the FileMode enumeration (such as Create, Append, CreateNew, Open, and OpenOrTruncate). This class provides support for both synchronous and asynchronous operations. Then you point to the FileStream instance in the constructor of the StreamWriter/StreamReader class. Remember to close both streams when you are done.

Reading and Writing Binary Files

You can read and write data to binary files using the BinaryReader and BinaryWriter classes. Both require a FileStream instance and enable you to read and write arrays of bytes. The following is an example of creating a binary stream:

Dim fs As New FileStream("C:TemporaryOneFile.bin", FileMode.CreateNew)
Dim bs As New BinaryWriter(fs)

Dim bytesToWrite() As Byte = New Byte() {128, 64, 32, 16}

bs.Write(bytesToWrite)
bs.Close()
fs.Close()

The Write method enables you to write information as binary, but it also accepts base .NET types such as integers and strings, all written as binary. It provides several overloads so that you can also specify the offset and the number of bytes to be written. To read a binary file, you instantiate the BinaryReader class. The following example retrieves information from a file utilizing a Using..End Using block to ensure that resources are correctly freed up when no longer necessary:

fs = New FileStream("C:TemporaryOneFile.bin", FileMode.Open)
Using br As New BinaryReader(fs)
    If fs IsNot Nothing AndAlso fs.Length > 0 Then
        Dim buffer() As Byte = br.ReadBytes(CInt(fs.Length))
    End If
End Using
fs.Close()

In this case the ReadBytes method, which is used to retrieve data, reads a number of bytes corresponding to the file length. Because binary data can have different forms, ReadBytes is just one of a series of methods for reading .NET types such as ReadChar, ReadInt32, ReadString, ReadDouble, and so on.


Asynchronous Programming

The .NET Framework 4.5 introduces a new pattern for asynchronous programming techniques based on the Async/Await keywords. This pattern has some new features for working with streams asynchronously as well. Because you first need to know how this pattern works, examples on asynchronous operations over streams will be provided in Chapter 44, “Asynchronous Programming.”


Using Memory Streams

Memory streams are special objects that act like file streams but that work in memory, providing the ability to manipulate binary data. The following code creates a MemoryStream with 2 Kbytes capacity and puts in a string:

Dim ms As New MemoryStream(2048)

Dim bs As New BinaryWriter(ms)
bs.Write("Some text written as binary")
bs.Close()
ms.Close()

To retrieve data, you use a BinaryReader pointing to the MemoryStream as you saw in the paragraph for binary files. So, in this example, you can invoke ReadString as follows:

'The stream must be still open
Using br As New BinaryReader(ms)
    If ms IsNot Nothing AndAlso ms.Length > 0 Then
        Dim data As String = br.ReadString

    End If
End Using
ms.Close()

Using Streams with Strings

Although not often utilized, you can use StringReader and StringWriter for manipulating strings. The following example generates a new StringBuilder and associates it to a new StringWriter. Then it retrieves the list of filenames in the C: directory and puts each string into the writer. You notice that because of the association between the two objects, changes are reflected to the StringBuilder. Try this:

Dim sBuilder As New Text.StringBuilder
Dim sWriter As New StringWriter(sBuilder)

For Each name As String In Directory.GetFiles("C:")
    sWriter.WriteLine(name)
Next
sWriter.Close()

Console.WriteLine(sBuilder.ToString)

To read strings, you can use the StringReader object, whose constructor requires a string to be read. To continue with the example, we can read the previously created StringBuilder line-by-line:

Dim sReader As New StringReader(sBuilder.ToString)
Do Until sReader.Peek = -1
    Console.WriteLine(sReader.ReadLine)

Loop

You notice lots of similarities between string streams and StreamWriter/StreamReader because both work with text.

Compressing Data with Streams

One of the most interesting features of streams is the ability to compress and decompress data utilizing the GZipStream and DeflateStream objects. Both are exposed by the System.IO.Compression namespace, and they both compress data using the GZip algorithm. The only difference is that the GZipStream writes a small header to compressed data. The interesting thing is that they work similarly to other streams, and when you write or read data into the stream, data is automatically compressed or decompressed by the runtime. The good news is that you are not limited to compressing files, but any other kind of stream. Compressing and decompressing data is quite a simple task. In some situations you need more attention according to the kind of data you need to access for files. To make comprehension easier, take a look at the code example provided in Listing 18.1 that contains comments that explain how such streams work. The purpose of the example is to provide the ability to compress and decompress files that is a common requirement in applications. You are encouraged to read comments within the code that can help you get started with the GZipStream.

Listing 18.1. Compressing and Decompressing Streams


Imports System.IO
Imports System.IO.Compression

Module Compression

    Sub TestCompress()
        Try
            Compress("C:TempSource.Txt",
                     "C:TempCompressed.gzp")
        Catch ex As FileNotFoundException
            Console.WriteLine("File not found!")
        Catch ex As IOException
            Console.WriteLine("An input/output error has occurred:")
            Console.WriteLine(ex.Message)
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Sub TestDecompress()
        Try
            Decompress("C:TempCompressed.gzp",
                       "C:TempOriginal.txt")

        Catch ex As FileNotFoundException
            Console.WriteLine("File not found!")
        Catch ex As IOException
            Console.WriteLine("An input/output error has occurred:")
            Console.WriteLine(ex.Message)
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try
    End Sub

    Public Sub Compress(ByVal inputName As String, ByVal outputName As String)

        'Instantiates a new FileStream
        Dim infile As FileStream

        Try
            'The Stream points to the specified input file
            infile = New FileStream(inputName, FileMode.Open, FileAccess.Read,
                                    FileShare.Read)

            'Stores the file length in a buffer
            Dim buffer(CInt(infile.Length - 1)) As Byte

            'Checks if the file can be read and assigns to the "count"
            'variable the result of reading the file
            Dim count As Integer = infile.Read(buffer, 0, buffer.Length)

            'If the number of read byte is different from the file length
            'throws an exception
            If count <> buffer.Length Then
                infile.Close()
                Throw New IOException
            End If
            'closes the stream
            infile.Close()
            infile = Nothing

            'Creates a new stream pointing to the output file
            Dim ms As New FileStream(outputName, FileMode.CreateNew,
                                     FileAccess.Write)

            'Creates a new GZipStream for compressing, pointing to
            'the output stream above leaving it open
            Dim compressedzipStream As New GZipStream(ms,
                                                      CompressionMode.Compress,
                                                      True)
            'Puts the buffer into the new stream, which is
            'automatically compressed
            compressedzipStream.Write(buffer, 0, buffer.Length)

            compressedzipStream.Close()
            ms.Close()
            Exit Sub
        Catch ex As IO.FileNotFoundException
            Throw
        Catch ex As IOException
            Throw
        Catch ex As Exception
            Throw
        End Try
    End Sub

    Public Sub Decompress(ByVal fileName As String, ByVal originalName As String)

        Dim inputFile As FileStream

        'Defining the stream for decompression
        Dim compressedZipStream As GZipStream

        'Defining a variable for storing compressed file size
        Dim compressedFileSize As Integer

        Try

            'Reads the input file
            inputFile = New FileStream(fileName,
                                       FileMode.Open,
                                       FileAccess.Read,
                                       FileShare.Read)

            'Reads input file's size
            compressedFileSize = CInt(inputFile.Length)

            'Creates a new GZipStream in Decompress mode
            compressedZipStream = New GZipStream(inputFile,
                                                 CompressionMode.Decompress)

            'In compressed data the first 100 bytes store the original
            'data size, so let's get it
            Dim offset As Integer = 0
            Dim totalBytes As Integer = 0

            Dim SmallBuffer(100) As Byte

            'Reads until there are available bytes in the first 100
            'and increments variables that we'll need for sizing
            'the buffer that will store the decompressed file
            Do While True

                Dim bytesRead As Integer = compressedZipStream.
                                           Read(SmallBuffer, 0, 100)
                If bytesRead = 0 Then
                    Exit Do
                End If

                offset += bytesRead
                totalBytes += bytesRead
            Loop

            compressedZipStream.Close()
            compressedZipStream = Nothing

            'Creates a new FileStream for reading the input file
            inputFile = New FileStream(fileName,
                                       FileMode.Open,
                                       FileAccess.Read,
                                       FileShare.Read)

            'and decompress its content
            compressedZipStream = New GZipStream(inputFile,
                                                 CompressionMode.Decompress)

            'Declares the buffer that will store uncompressed data
            Dim buffer(totalBytes) As Byte

            'Reads from the source file the number of bytes
            'representing the buffer length, taking advantage
            'of the original size
            compressedZipStream.Read(buffer, 0, totalBytes)

            compressedZipStream.Close()
            compressedZipStream = Nothing

            'Creates a new file for putting uncompressed
            'data
            Dim ms As New FileStream(originalName,
                                     FileMode.Create,
                                     FileAccess.Write)

            'Writes uncompressed data to file
            ms.Write(buffer, 0, buffer.Length)
            ms.Close()
            ms = Nothing
            Exit Sub

            'General IO error
        Catch ex As IOException
            Throw
        Catch ex As Exception
            Throw
            Exit Try
        End Try
    End Sub
End Module



Tip

You use GZipStream and DeflateStream the identical way. The only difference is about the header in the compressed stream. If you need further information on the difference, here is the official MSDN page: http://msdn.microsoft.com/en-us/library/system.io.compression.deflatestream(VS.110).aspx.


Notice how, at a higher level, you just instantiate the stream the same way in both compression and decompression tasks. The difference is the CompressionMode enumeration value that determines whether a stream is for compression or decompression. With this technique, you can invoke just the two custom methods for compressing and decompressing files, meaning that you could apply it to other kinds of data, too.

New in Visual Basic 2012: Working with Zip Archives

The .NET Framework 4.5 introduces new features to the System.IO.Compression namespace, which provide an opportunity of working with classic Zip archives. Until this version of the Framework, it was not possible to work directly with the famous Zip algorithm because only the GZip one was supported. Technically speaking, in .NET 4.5 applications you add a reference to the System.IO.Compression.FileSystem.dll assembly. This extends the System.IO.Compression namespace with new types that will be described shortly. The real thing you have to take care about is that the new features offer basic ways for working with Zip archives and so there could be some situations in which you might need more advanced libraries, such as the famous DotNetZip (http://dotnetzip.codeplex.com) or SharpZipLib (http://sharpziplib.com) available for free on the Internet. For example, currently the new classes do not enable you to enter passwords when extracting archives or specify one to protect archives. Also, full compatibility is not always guaranteed. Notice that these new types are not available for Metro-style apps in Windows 8, where you will still use streams described previously. In the .NET Framework 4.5, you can now use the ZipFile class, which exposes two shared methods, CreateFromDirectory and ExtractToDirectory. The first method creates a Zip archive containing all files in the specified directory, whereas the second one extracts the content of a Zip archive into the specified directory. The following example demonstrates how to create and extract a Zip archive:

ZipFile.CreateFromDirectory("C: emp", "C:ippedTemp.zip",
        CompressionLevel.Optimal, True)
ZipFile.ExtractToDirectory("C:ippedDemo.zip", "C: emp")

In the previous example, CreateFromDirectory generates a compressed archive called ZippedTemp.zip, which stores the content of a folder called C:Temp. The third argument (called compressionLevel) represents the compression level and requires one value from the CompressionLevel enumeration. You choose among Optimal, NoCompression, and Fastest. The fourth argument (called includeBaseDirectory) is instead a Boolean value that, when True, enables you to include the base directory in the archive. Using ExtractToDirectory is even simpler because you just specify the archive to decompress and the target directory. Both methods have an overload that provides an argument called entryNameEncoding of type System.Text.Encoding which enables you to specify the encoding to use with file entries for compatibility with Zip archives that do not support UTF-8. The System.IO.Compression namespace also offers the ZipArchive and ZipArchiveEntry classes, representing a Zip archive and one entry inside the compressed archive, respectively. These classes provide better control over Zip files. The following code demonstrates how to show the list of files inside a Zip archive:

Using zipArc As ZipArchive = ZipFile.Open("C: empNorthwind.zip", ZipArchiveMode.Read)
    For Each item As ZipArchiveEntry In zipArc.Entries
        Console.WriteLine("{0}, Compressed size: {1}, ",
                          item.FullName,
                          item.CompressedLength.ToString)
    Next
End Using

Because the ZipArchive class implements the IDisposable interface, it can be convenient to enclose the code within a Using..End Using block. Notice that the ZipArchive instance receives the result of another method from the ZipFile class, Open. Every item in the archive is an instance of the ZipArchiveEntry class; a collection called Entries, of type ReadOnlyCollection(Of ZipArchiveEntry), can be iterated to get the list of items that are stored inside the Zip archive. It is also easy to create a new Zip file with ZipArchive and ZipFile.Open. The following code demonstrates this:

Using zipArc As ZipArchive = ZipFile.Open("C:TempNewZipped.zip",
                ZipArchiveMode.Create)
      zipArc.CreateEntry("C:TempNorthwind.sdf", CompressionLevel.NoCompression)
End Using

Here you use the ZipArchiveMode.Create mode for opening the Zip file, since you want to create a new one. Then you use the CreateEntry instance method of the ZipArchive class to add entries to the zipped archive. Notice that the entry is actually added to the archive when the Using block gets finalized. You can also update an existing archive by adding or deleting entries. The following code demonstrates how to add an entry to an existing archive and how to remove an existing entry:

Using zipArc As ZipArchive = ZipFile.Open("C:TempNewZipped.zip",
                ZipArchiveMode.Update)
   zipArc.CreateEntry("C:TempAnotherFile.txt", CompressionLevel.Fastest)
   'Get the specified entry
   Dim entry As ZipArchiveEntry = zipArc.GetEntry("Northwind.sdf")
   'Delete the entry from the archive
   entry.Delete()
End Using

You use GetEntry to get the instance of the ZipArchiveEntry that you want to remove, by specifying the name. Then you invoke the Delete method on the retrieved instance.

Networking with Streams

The .NET Framework provides functionalities for data exchange through networks using streams; in particular, it exposes the System.Net.Sockets.NetworkStream class. Reading and writing data via a NetworkStream instance passes through a System.Net.Sockets.TcpClient class’s instance. Code in Listing 18.2 shows how you can both write and read data in such a scenario. See comments in code for explanations.

Listing 18.2. Networking with NetworkStream


Imports System.Net.Sockets
Imports System.Text

Module Network

    Sub NetStreamDemo()
        'Instantiating TcpClient and NetworkStream
        Dim customTcpClient As New TcpClient()
        Dim customNetworkStream As NetworkStream

        Try
            'Attempt to connect to socket
            '127.0.0.1 is the local machine address
            customTcpClient.Connect("127.0.0.1", 587) 'Port
            'Gets the instance of the stream for
            'data exchange
            customNetworkStream = customTcpClient.GetStream()

            'The port is not available
        Catch ex As ArgumentOutOfRangeException
            Console.WriteLine(ex.Message)
            'Connection problem
        Catch ex As SocketException
            Console.WriteLine(ex.Message)
        End Try

        'Gets an array of byte from a value, which is
        'encoded via System.Text.Encoding.Ascii.GetBytes
        Dim bytesToWrite() As Byte = _
            Encoding.ASCII.GetBytes("Something to exchange via TCP")

        'Gets the stream instance
        customNetworkStream = customTcpClient.GetStream()
        'Writes the bytes to the stream; this
        'means sending data to the network
        customNetworkStream.Write(bytesToWrite, 0,
                                  bytesToWrite.Length)

        'Establishes the buffer size for receiving data
        Dim bufferSize As Integer = customTcpClient.
                                    ReceiveBufferSize
        Dim bufferForReceivedBytes(bufferSize) As Byte

        'Gets data from the stream, meaning by the network
        customNetworkStream.Read(bufferForReceivedBytes, 0,
                                 bufferSize)

        Dim result As String = Encoding.ASCII.GetString(bufferForReceivedBytes,
                               0, bufferSize)
    End Sub
End Module


There are several ways for data exchange, and this is probably one of the most basic ones in the era of Windows Communication Foundation. This topic is related to streams, so an overview was necessary.

Summary

Working with files, directories, and drives is a common requirement for each application. The .NET Framework provides two main classes for working with directories: System.IO.Directory and System.IO.Path. The first one enables you to perform operations such as creating, moving, renaming, and investigating for filenames. The second one is about directory and filename manipulation other than gaining access to Windows temporary folder. Similar to Directory, the System.IO.DirectoryInfo class provides access to directory operations and information, but the difference is that an instance of such a class represents a single directory. If you instead need to get information on physical drives on your machine, create an instance of the System.IO.DriveInfo class. Similar to Directory and DirectoryInfo, the System.IO.File and System.IO.FileInfo classes provide access to files on disk, and their members are the same as for directory classes except that they enable you to work with files. The last part of the chapter is about streams. Streams are sequences of bytes that enable you to exchange different kinds of data. All stream classes inherit from System.IO.Stream. StreamReader and StreamWriter enable you to read and write text files. BinaryReader and BinaryWriter enable you to read and write binary files. MemoryStream enables you to read and write in-memory binary data. StringReader and StringWriter enable you to manage in-memory strings. GZipStream and DeflateStream enable you to compress data according to the GZip algorithm (you saw new features in .NET 4.5 about Zip compression). NetworkStream enables you to exchange data through a network. Writing and reading data can often require several lines of code. Luckily the Visual Basic language offers an important alternative known as the My namespace that is covered in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.212.102