|
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
Option Strict On
Option Explicit On
Imports System
Imports System.Collections
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Globalization
Imports System.Security
Imports System.Text
Imports Microsoft.VisualBasic.CompilerServices
#If TARGET_WINDOWS Then
Imports Microsoft.VisualBasic.CompilerServices.NativeMethods
Imports Microsoft.VisualBasic.CompilerServices.NativeTypes
#End If
Imports ExUtils = Microsoft.VisualBasic.CompilerServices.ExceptionUtils
Namespace Microsoft.VisualBasic.FileIO
''' <summary>
''' This class represents the file system on a computer. It allows browsing the existing drives, special directories;
''' and also contains some commonly use methods for IO tasks.
''' </summary>
Public Class FileSystem
''' <summary>
''' Return the names of all available drives on the computer.
''' </summary>
''' <value>A ReadOnlyCollection(Of DriveInfo) containing all the current drives' names.</value>
Public Shared ReadOnly Property Drives() As ObjectModel.ReadOnlyCollection(Of System.IO.DriveInfo)
Get
' NOTE: Don't cache the collection since it may change without us knowing.
' The performance hit will be small since it's a small collection.
Dim DriveInfoCollection As New ObjectModel.Collection(Of System.IO.DriveInfo)
For Each DriveInfo As System.IO.DriveInfo In IO.DriveInfo.GetDrives()
DriveInfoCollection.Add(DriveInfo)
Next
Return New ObjectModel.ReadOnlyCollection(Of System.IO.DriveInfo)(DriveInfoCollection)
End Get
End Property
''' <summary>
''' Get or set the current working directory.
''' </summary>
''' <value>A String containing the path to the directory.</value>
Public Shared Property CurrentDirectory() As String
Get
Return NormalizePath(IO.Directory.GetCurrentDirectory())
End Get
Set(ByVal value As String)
IO.Directory.SetCurrentDirectory(value)
End Set
End Property
''' <summary>
''' Combines two path strings by adding a path separator.
''' </summary>
''' <param name="baseDirectory">The first part of the path.</param>
''' <param name="relativePath">The second part of the path, must be a relative path.</param>
''' <returns>A String contains the combined path.</returns>
Public Shared Function CombinePath(ByVal baseDirectory As String, ByVal relativePath As String) As String
If baseDirectory = "" Then
Throw ExUtils.GetArgumentNullException("baseDirectory", SR.General_ArgumentEmptyOrNothing_Name, "baseDirectory")
End If
If relativePath = "" Then
Return baseDirectory
End If
baseDirectory = IO.Path.GetFullPath(baseDirectory) ' Throw exceptions if BaseDirectoryPath is invalid.
Return NormalizePath(IO.Path.Combine(baseDirectory, relativePath))
End Function
''' <summary>
''' Determines whether the given path refers to an existing directory on disk.
''' </summary>
''' <param name="directory">The path to verify.</param>
''' <returns>True if DirectoryPath refers to an existing directory. Otherwise, False.</returns>
Public Shared Function DirectoryExists(ByVal directory As String) As Boolean
Return IO.Directory.Exists(directory)
End Function
''' <summary>
''' Determines whether the given path refers to an existing file on disk.
''' </summary>
''' <param name="file">The path to verify.</param>
''' <returns>True if FilePath refers to an existing file on disk. Otherwise, False.</returns>
Public Shared Function FileExists(ByVal file As String) As Boolean
If Not String.IsNullOrEmpty(file) AndAlso
(file.EndsWith(IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) Or
file.EndsWith(IO.Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) Then
Return False
End If
Return IO.File.Exists(file)
End Function
''' <summary>
''' Find files in the given folder that contain the given text.
''' </summary>
''' <param name="directory">The folder path to start from.</param>
''' <param name="containsText">The text to be found in file.</param>
''' <param name="ignoreCase">True to ignore case. Otherwise, False.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <returns>A string array containing the files that match the search condition.</returns>
Public Shared Function FindInFiles(ByVal directory As String,
ByVal containsText As String, ByVal ignoreCase As Boolean, ByVal searchType As SearchOption) As ObjectModel.ReadOnlyCollection(Of String)
Return FindInFiles(directory, containsText, ignoreCase, searchType, Nothing)
End Function
''' <summary>
''' Find files in the given folder that contain the given text.
''' </summary>
''' <param name="directory">The folder path to start from.</param>
''' <param name="containsText">The text to be found in file.</param>
''' <param name="ignoreCase">True to ignore case. Otherwise, False.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <param name="fileWildcards">The search patterns to use for the file name ("*.*")</param>
''' <returns>A string array containing the files that match the search condition.</returns>
''' <exception cref="System.ArgumentNullException">If one of the pattern is Null, Empty or all-spaces string.</exception>
Public Shared Function FindInFiles(ByVal directory As String, ByVal containsText As String, ByVal ignoreCase As Boolean,
ByVal searchType As SearchOption, ByVal ParamArray fileWildcards() As String) As ObjectModel.ReadOnlyCollection(Of String)
' Find the files with matching name.
Dim NameMatchFiles As ObjectModel.ReadOnlyCollection(Of String) = FindFilesOrDirectories(
FileOrDirectory.File, directory, searchType, fileWildcards)
' Find the files containing the given text.
If containsText <> "" Then
Dim ContainTextFiles As New ObjectModel.Collection(Of String)
For Each FilePath As String In NameMatchFiles
If (FileContainsText(FilePath, containsText, ignoreCase)) Then
ContainTextFiles.Add(FilePath)
End If
Next
Return New ObjectModel.ReadOnlyCollection(Of String)(ContainTextFiles)
Else
Return NameMatchFiles
End If
End Function
''' <summary>
''' Return the paths of sub directories found directly under a directory.
''' </summary>
''' <param name="directory">The directory to find the sub directories inside.</param>
''' <returns>A ReadOnlyCollection(Of String) containing the matched directories' paths.</returns>
Public Shared Function GetDirectories(ByVal directory As String) As ObjectModel.ReadOnlyCollection(Of String)
Return FindFilesOrDirectories(FileOrDirectory.Directory, directory, SearchOption.SearchTopLevelOnly, Nothing)
End Function
''' <summary>
''' Return the paths of sub directories found under a directory with the specified name patterns.
''' </summary>
''' <param name="directory">The directory to find the sub directories inside.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <param name="wildcards">The wildcards for the file name, for example "*.bmp", "*.txt"</param>
''' <returns>A ReadOnlyCollection(Of String) containing the matched directories' paths.</returns>
Public Shared Function GetDirectories(ByVal directory As String, ByVal searchType As SearchOption,
ByVal ParamArray wildcards() As String) As ObjectModel.ReadOnlyCollection(Of String)
Return FindFilesOrDirectories(FileOrDirectory.Directory, directory, searchType, wildcards)
End Function
''' <summary>
''' Returns the information object about the specified directory.
''' </summary>
''' <param name="directory">The path to the directory.</param>
''' <returns>A DirectoryInfo object containing the information about the specified directory.</returns>
Public Shared Function GetDirectoryInfo(ByVal directory As String) As System.IO.DirectoryInfo
Return New System.IO.DirectoryInfo(directory)
End Function
''' <summary>
''' Return the information about the specified drive.
''' </summary>
''' <param name="drive">The path to the drive.</param>
''' <returns>A DriveInfo object containing the information about the specified drive.</returns>
Public Shared Function GetDriveInfo(ByVal drive As String) As System.IO.DriveInfo
Return New System.IO.DriveInfo(drive)
End Function
''' <summary>
''' Returns the information about the specified file.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <returns>A FileInfo object containing the information about the specified file.</returns>
Public Shared Function GetFileInfo(ByVal file As String) As System.IO.FileInfo
file = NormalizeFilePath(file, "file")
Return New System.IO.FileInfo(file)
End Function
''' <summary>
''' Return an unordered collection of file paths found directly under a directory.
''' </summary>
''' <param name="directory">The directory to find the files inside.</param>
''' <returns>A ReadOnlyCollection(Of String) containing the matched files' paths.</returns>
Public Shared Function GetFiles(ByVal directory As String) As ObjectModel.ReadOnlyCollection(Of String)
Return FindFilesOrDirectories(FileOrDirectory.File, directory, SearchOption.SearchTopLevelOnly, Nothing)
End Function
''' <summary>
''' Return an unordered collection of file paths found under a directory with the specified name patterns and containing the specified text.
''' </summary>
''' <param name="directory">The directory to find the files inside.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <param name="wildcards">The wildcards for the file name, for example "*.bmp", "*.txt"</param>
''' <returns>A ReadOnlyCollection(Of String) containing the matched files' paths.</returns>
Public Shared Function GetFiles(ByVal directory As String, ByVal searchType As SearchOption,
ByVal ParamArray wildcards() As String) As ObjectModel.ReadOnlyCollection(Of String)
Return FindFilesOrDirectories(FileOrDirectory.File, directory, searchType, wildcards)
End Function
''' <summary>
''' Return the name (and extension) from the given path string.
''' </summary>
''' <param name="path">The path string from which to obtain the file name (and extension).</param>
''' <returns>A String containing the name of the file or directory.</returns>
''' <exception cref="ArgumentException">path contains one or more of the invalid characters defined in InvalidPathChars.</exception>
Public Shared Function GetName(ByVal path As String) As String
Return IO.Path.GetFileName(path)
End Function
''' <summary>
''' Returns the parent directory's path from a specified path.
''' </summary>
''' <param name="path">The path to a file or directory, this can be absolute or relative.</param>
''' <returns>
''' The path to the parent directory of that file or directory (whether absolute or relative depends on the input),
''' or an empty string if Path is a root directory.
''' </returns>
''' <exception cref="IO.Path.GetFullPath">See IO.Path.GetFullPath: If path is an invalid path.</exception>
''' <remarks>
''' The path will be normalized (for example: C:\Dir1////\\\Dir2 will become C:\Dir1\Dir2)
''' but will not be resolved (for example: C:\Dir1\Dir2\..\Dir3 WILL NOT become C:\Dir1\Dir3). Use CombinePath.
''' </remarks>
Public Shared Function GetParentPath(ByVal path As String) As String
' Call IO.Path.GetFullPath to handle exception cases. Don't use the full path returned.
IO.Path.GetFullPath(path)
If IsRoot(path) Then
Throw ExUtils.GetArgumentExceptionWithArgName("path", SR.IO_GetParentPathIsRoot_Path, path)
Else
Return IO.Path.GetDirectoryName(path.TrimEnd(
IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar))
End If
End Function
''' <summary>
''' Create a uniquely named zero-byte temporary file on disk and return the full path to that file.
''' </summary>
''' <returns>A String containing the name of the temporary file.</returns>
Public Shared Function GetTempFileName() As String
Return System.IO.Path.GetTempFileName()
End Function
''' <summary>
''' Return an instance of a TextFieldParser for the given file.
''' </summary>
''' <param name="file">The path to the file to parse.</param>
''' <returns>An instance of a TextFieldParser.</returns>
Public Shared Function OpenTextFieldParser(ByVal file As String) As TextFieldParser
Return New TextFieldParser(file)
End Function
''' <summary>
''' Return an instance of a TextFieldParser for the given file using the given delimiters.
''' </summary>
''' <param name="file">The path to the file to parse.</param>
''' <param name="delimiters">A list of delimiters.</param>
''' <returns>An instance of a TextFieldParser</returns>
Public Shared Function OpenTextFieldParser(ByVal file As String, ByVal ParamArray delimiters As String()) As TextFieldParser
Dim Result As New TextFieldParser(file)
Result.SetDelimiters(delimiters)
Result.TextFieldType = FieldType.Delimited
Return Result
End Function
''' <summary>
''' Return an instance of a TextFieldParser for the given file using the given field widths.
''' </summary>
''' <param name="file">The path to the file to parse.</param>
''' <param name="fieldWidths">A list of field widths.</param>
''' <returns>An instance of a TextFieldParser</returns>
Public Shared Function OpenTextFieldParser(ByVal file As String, ByVal ParamArray fieldWidths As Integer()) As TextFieldParser
Dim Result As New TextFieldParser(file)
Result.SetFieldWidths(fieldWidths)
Result.TextFieldType = FieldType.FixedWidth
Return Result
End Function
''' <summary>
''' Return a StreamReader for reading the given file using UTF-8 as preferred encoding.
''' </summary>
''' <param name="file">The file to open the StreamReader on.</param>
''' <returns>An instance of System.IO.StreamReader opened on the file (with FileShare.Read).</returns>
Public Shared Function OpenTextFileReader(ByVal file As String) As IO.StreamReader
Return OpenTextFileReader(file, Encoding.UTF8)
End Function
''' <summary>
''' Return a StreamReader for reading the given file using the given encoding as preferred encoding.
''' </summary>
''' <param name="file">The file to open the StreamReader on.</param>
''' <param name="Encoding">The preferred encoding that will be used if the encoding of the file could not be detected.</param>
''' <returns>An instance of System.IO.StreamReader opened on the file (with FileShare.Read).</returns>
Public Shared Function OpenTextFileReader(ByVal file As String, ByVal encoding As Encoding) As IO.StreamReader
file = NormalizeFilePath(file, "file")
Return New IO.StreamReader(file, encoding, detectEncodingFromByteOrderMarks:=True)
End Function
''' <summary>
''' Return a StreamWriter for writing to the given file using UTF-8 encoding.
''' </summary>
''' <param name="file">The file to write to.</param>
''' <param name="Append">True to append to the content of the file. False to overwrite the content of the file.</param>
''' <returns>An instance of StreamWriter opened on the file (with FileShare.Read).</returns>
Public Shared Function OpenTextFileWriter(ByVal file As String, ByVal append As Boolean) As IO.StreamWriter
Return OpenTextFileWriter(file, append, Encoding.UTF8)
End Function
''' <summary>
''' Return a StreamWriter for writing to the given file using the given encoding.
''' </summary>
''' <param name="file">The file to write to.</param>
''' <param name="Append">True to append to the content of the file. False to overwrite the content of the file.</param>
''' <param name="Encoding">The encoding to use to write to the file.</param>
''' <returns>An instance of StreamWriter opened on the file (with FileShare.Read).</returns>
Public Shared Function OpenTextFileWriter(ByVal file As String, ByVal append As Boolean,
ByVal encoding As Encoding) As IO.StreamWriter
file = NormalizeFilePath(file, "file")
Return New IO.StreamWriter(file, append, encoding)
End Function
''' <summary>
''' Read the whole content of a file into a byte array.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <returns>A byte array contains the content of the file.</returns>
''' <exception cref="IO.IOException">If the length of the file is larger than Integer.MaxValue (~2GB).</exception>
''' <exception cref="IO.FileStream">See FileStream constructor and Read: for other exceptions.</exception>
Public Shared Function ReadAllBytes(ByVal file As String) As Byte()
Return IO.File.ReadAllBytes(file)
End Function
''' <summary>
''' Read the whole content of a text file into a string using UTF-8 encoding.
''' </summary>
''' <param name="file">The path to the text file.</param>
''' <returns>A String contains the content of the given file.</returns>
''' <exception cref="IO.StreamReader">See StreamReader constructor and ReadToEnd.</exception>
Public Shared Function ReadAllText(ByVal file As String) As String
Return IO.File.ReadAllText(file)
End Function
''' <summary>
''' Read the whole content of a text file into a string using the given encoding.
''' </summary>
''' <param name="file">The path to the text file.</param>
''' <param name="encoding">The character encoding to use if the encoding was not detected.</param>
''' <returns>A String contains the content of the given file.</returns>
''' <exception cref="IO.StreamReader">See StreamReader constructor and ReadToEnd.</exception>
Public Shared Function ReadAllText(ByVal file As String, ByVal encoding As Encoding) As String
Return IO.File.ReadAllText(file, encoding)
End Function
''' <summary>
''' Copy an existing directory to a new directory,
''' throwing exception if there are existing files with the same name.
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
Public Shared Sub CopyDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String)
CopyOrMoveDirectory(CopyOrMove.Copy, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing directory to a new directory,
''' overwriting existing files with the same name if specified.
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="overwrite">True to overwrite existing files with the same name. Otherwise False.</param>
Public Shared Sub CopyDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal overwrite As Boolean)
CopyOrMoveDirectory(CopyOrMove.Copy, sourceDirectoryName, destinationDirectoryName,
overwrite, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing directory to a new directory,
''' displaying progress dialog and confirmation dialogs if specified,
''' throwing exception if user cancels the operation (only applies if displaying progress dialog and confirmation dialogs).
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
Public Shared Sub CopyDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal showUI As UIOption)
CopyOrMoveDirectory(CopyOrMove.Copy, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, ToUIOptionInternal(showUI), UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing directory to a new directory,
''' displaying progress dialog and confirmation dialogs if specified,
''' throwing exception if user cancels the operation if specified. (only applies if displaying progress dialog and confirmation dialogs).
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="onUserCancel">ThrowException to throw exception if user cancels the operation. Otherwise DoNothing.</param>
Public Shared Sub CopyDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal showUI As UIOption, ByVal onUserCancel As UICancelOption)
CopyOrMoveDirectory(CopyOrMove.Copy, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, ToUIOptionInternal(showUI), onUserCancel)
End Sub
''' <summary>
''' Copy an existing file to a new file. Overwriting a file of the same name is not allowed.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String)
CopyOrMoveFile(CopyOrMove.Copy, sourceFileName, destinationFileName,
overwrite:=False, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing file to a new file. Overwriting a file of the same name if specified.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="overwrite">True to overwrite existing file with the same name. Otherwise False.</param>
Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal overwrite As Boolean)
CopyOrMoveFile(CopyOrMove.Copy, sourceFileName, destinationFileName,
overwrite, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing file to a new file,
''' displaying progress dialog and confirmation dialogs if specified,
''' will throw exception if user cancels the operation.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal showUI As UIOption)
CopyOrMoveFile(CopyOrMove.Copy, sourceFileName, destinationFileName,
overwrite:=False, ToUIOptionInternal(showUI), UICancelOption.ThrowException)
End Sub
''' <summary>
''' Copy an existing file to a new file,
''' displaying progress dialog and confirmation dialogs if specified,
''' will throw exception if user cancels the operation if specified.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="onUserCancel">ThrowException to throw exception if user cancels the operation. Otherwise DoNothing.</param>
''' <remarks>onUserCancel will be ignored if showUI = HideDialogs.</remarks>
Public Shared Sub CopyFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal showUI As UIOption, ByVal onUserCancel As UICancelOption)
CopyOrMoveFile(CopyOrMove.Copy, sourceFileName, destinationFileName,
overwrite:=False, ToUIOptionInternal(showUI), onUserCancel)
End Sub
''' <summary>
''' Creates a directory from the given path (including all parent directories).
''' </summary>
''' <param name="directory">The path to create the directory at.</param>
Public Shared Sub CreateDirectory(ByVal directory As String)
' Get the full path. GetFullPath will throw if invalid path.
directory = IO.Path.GetFullPath(directory)
If IO.File.Exists(directory) Then
Throw ExUtils.GetIOException(SR.IO_FileExists_Path, directory)
End If
' CreateDirectory will create the full structure and not throw if directory exists.
System.IO.Directory.CreateDirectory(directory)
End Sub
''' <summary>
''' Delete the given directory, with options to recursively delete.
''' </summary>
''' <param name="directory">The path to the directory.</param>
''' <param name="onDirectoryNotEmpty">DeleteAllContents to delete everything. ThrowIfDirectoryNonEmpty to throw exception if the directory is not empty.</param>
Public Shared Sub DeleteDirectory(ByVal directory As String, ByVal onDirectoryNotEmpty As DeleteDirectoryOption)
DeleteDirectoryInternal(directory, onDirectoryNotEmpty,
UIOptionInternal.NoUI, RecycleOption.DeletePermanently, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Delete the given directory, with options to recursively delete, show progress UI, send file to Recycle Bin; throwing exception if user cancels.
''' </summary>
''' <param name="directory">The path to the directory.</param>
''' <param name="showUI">True to shows progress window. Otherwise, False.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
Public Shared Sub DeleteDirectory(ByVal directory As String, ByVal showUI As UIOption, ByVal recycle As RecycleOption)
DeleteDirectoryInternal(directory, DeleteDirectoryOption.DeleteAllContents,
ToUIOptionInternal(showUI), recycle, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Delete the given directory, with options to recursively delete, show progress UI, send file to Recycle Bin, and whether to throw exception if user cancels.
''' </summary>
''' <param name="directory">The path to the directory.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
''' <param name="onUserCancel">Throw exception when user cancel the UI operation or not.</param>
Public Shared Sub DeleteDirectory(ByVal directory As String,
ByVal showUI As UIOption, ByVal recycle As RecycleOption, ByVal onUserCancel As UICancelOption)
DeleteDirectoryInternal(directory, DeleteDirectoryOption.DeleteAllContents,
ToUIOptionInternal(showUI), recycle, onUserCancel)
End Sub
''' <summary>
''' Delete the given file.
''' </summary>
''' <param name="file">The path to the file.</param>
Public Shared Sub DeleteFile(ByVal file As String)
DeleteFileInternal(file, UIOptionInternal.NoUI, RecycleOption.DeletePermanently, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Delete the given file, with options to show progress UI, delete to recycle bin.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
Public Shared Sub DeleteFile(ByVal file As String, ByVal showUI As UIOption, ByVal recycle As RecycleOption)
DeleteFileInternal(file, ToUIOptionInternal(showUI), recycle, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Delete the given file, with options to show progress UI, delete to recycle bin, and whether to throw exception if user cancels.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
''' <param name="onUserCancel">Throw exception when user cancel the UI operation or not.</param>
''' <exception cref="IO.Path.GetFullPath">IO.Path.GetFullPath() exceptions: if FilePath is invalid.</exception>
''' <exception cref="IO.FileNotFoundException">if a file does not exist at FilePath</exception>
Public Shared Sub DeleteFile(ByVal file As String, ByVal showUI As UIOption, ByVal recycle As RecycleOption,
ByVal onUserCancel As UICancelOption)
DeleteFileInternal(file, ToUIOptionInternal(showUI), recycle, onUserCancel)
End Sub
''' <summary>
''' Move an existing directory to a new directory,
''' throwing exception if there are existing files with the same name.
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
Public Shared Sub MoveDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String)
CopyOrMoveDirectory(CopyOrMove.Move, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing directory to a new directory,
''' overwriting existing files with the same name if specified.
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param> ''' <param name="overwrite">True to overwrite existing files with the same name. Otherwise False.</param>
Public Shared Sub MoveDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal overwrite As Boolean)
CopyOrMoveDirectory(CopyOrMove.Move, sourceDirectoryName, destinationDirectoryName,
overwrite, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing directory to a new directory,
''' displaying progress dialog and confirmation dialogs if specified,
''' throwing exception if user cancels the operation (only applies if displaying progress dialog and confirmation dialogs).
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
Public Shared Sub MoveDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal showUI As UIOption)
CopyOrMoveDirectory(CopyOrMove.Move, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, ToUIOptionInternal(showUI), UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing directory to a new directory,
''' displaying progress dialog and confirmation dialogs if specified,
''' throwing exception if user cancels the operation if specified. (only applies if displaying progress dialog and confirmation dialogs).
''' </summary>
''' <param name="sourceDirectoryName">The path to the source directory, can be relative or absolute.</param>
''' <param name="destinationDirectoryName">The path to the target directory, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="onUserCancel">ThrowException to throw exception if user cancels the operation. Otherwise DoNothing.</param>
Public Shared Sub MoveDirectory(ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String, ByVal showUI As UIOption, ByVal onUserCancel As UICancelOption)
CopyOrMoveDirectory(CopyOrMove.Move, sourceDirectoryName, destinationDirectoryName,
overwrite:=False, ToUIOptionInternal(showUI), onUserCancel)
End Sub
''' <summary>
''' Move an existing file to a new file. Overwriting a file of the same name is not allowed.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
Public Shared Sub MoveFile(ByVal sourceFileName As String, ByVal destinationFileName As String)
CopyOrMoveFile(CopyOrMove.Move, sourceFileName, destinationFileName,
overwrite:=False, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing file to a new file. Overwriting a file of the same name if specified.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="overwrite">True to overwrite existing file with the same name. Otherwise False.</param>
Public Shared Sub MoveFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal overwrite As Boolean)
CopyOrMoveFile(CopyOrMove.Move, sourceFileName, destinationFileName,
overwrite, UIOptionInternal.NoUI, UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing file to a new file,
''' displaying progress dialog and confirmation dialogs if specified,
''' will throw exception if user cancels the operation.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
Public Shared Sub MoveFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal showUI As UIOption)
CopyOrMoveFile(CopyOrMove.Move, sourceFileName, destinationFileName,
overwrite:=False, ToUIOptionInternal(showUI), UICancelOption.ThrowException)
End Sub
''' <summary>
''' Move an existing file to a new file,
''' displaying progress dialog and confirmation dialogs if specified,
''' will throw exception if user cancels the operation if specified.
''' </summary>
''' <param name="sourceFileName">The path to the source file, can be relative or absolute.</param>
''' <param name="destinationFileName">The path to the destination file, can be relative or absolute. Parent directory will always be created.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="onUserCancel">ThrowException to throw exception if user cancels the operation. Otherwise DoNothing.</param>
''' <remarks>onUserCancel will be ignored if showUI = HideDialogs.</remarks>
Public Shared Sub MoveFile(ByVal sourceFileName As String, ByVal destinationFileName As String, ByVal showUI As UIOption, ByVal onUserCancel As UICancelOption)
CopyOrMoveFile(CopyOrMove.Move, sourceFileName, destinationFileName,
overwrite:=False, ToUIOptionInternal(showUI), onUserCancel)
End Sub
''' <summary>
''' Rename a directory, does not act like a move.
''' </summary>
''' <param name="directory">The path of the directory to be renamed.</param>
''' <param name="newName">The new name to change to. This must not contain path information.</param>
''' <exception cref="IO.Path.GetFullPath">IO.Path.GetFullPath exceptions: If directory is invalid.</exception>
''' <exception cref="System.ArgumentException">If newName is Nothing or Empty String or contains path information.</exception>
''' <exception cref="IO.FileNotFoundException">If directory does not point to an existing directory.</exception>
''' <exception cref="IO.IOException">If directory points to a root directory.
''' Or if there's an existing directory or an existing file with the same name.</exception>
Public Shared Sub RenameDirectory(ByVal directory As String, ByVal newName As String)
' Get the full path. This will handle invalid path exceptions.
directory = IO.Path.GetFullPath(directory)
' Throw if device path.
ThrowIfDevicePath(directory)
' Directory is a root directory. This does not require IO access so it's cheaper up front.
If IsRoot(directory) Then
Throw ExUtils.GetIOException(SR.IO_DirectoryIsRoot_Path, directory)
End If
' Throw if directory does not exist.
If Not IO.Directory.Exists(directory) Then
Throw ExUtils.GetDirectoryNotFoundException(SR.IO_DirectoryNotFound_Path, directory)
End If
' Verify newName is not null.
If newName = "" Then
Throw ExUtils.GetArgumentNullException(
"newName", SR.General_ArgumentEmptyOrNothing_Name, "newName")
End If
' Calculate new path. GetFullPathFromNewName will verify newName is only a name.
Dim FullNewPath As String = GetFullPathFromNewName(GetParentPath(directory), newName, "newName")
Debug.Assert(GetParentPath(FullNewPath).Equals(GetParentPath(directory),
StringComparison.OrdinalIgnoreCase), "Invalid FullNewPath")
' Verify that the new path does not conflict.
EnsurePathNotExist(FullNewPath)
IO.Directory.Move(directory, FullNewPath)
End Sub
''' <summary>
''' Renames a file, does not change the file location.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="newName">The new name to change to. This must not contain path information.</param>
''' <exception cref="IO.Path.GetFullPath">IO.Path.GetFullPath exceptions: If file is invalid.</exception>
''' <exception cref="System.ArgumentException">If newName is Nothing or Empty String or contains path information.</exception>
''' <exception cref="IO.FileNotFoundException">If file does not point to an existing file.</exception>
''' <exception cref="IO.IOException">If there's an existing directory or an existing file with the same name.</exception>
Public Shared Sub RenameFile(ByVal file As String, ByVal newName As String)
' Get the full path. This will handle invalid path exceptions.
file = NormalizeFilePath(file, "file")
' Throw if device path.
ThrowIfDevicePath(file)
' Throw if file does not exist.
If Not IO.File.Exists(file) Then
Throw ExUtils.GetFileNotFoundException(file, SR.IO_FileNotFound_Path, file)
End If
' Verify newName is not null.
If newName = "" Then
Throw ExUtils.GetArgumentNullException(
"newName", SR.General_ArgumentEmptyOrNothing_Name, "newName")
End If
' Calculate new path. GetFullPathFromNewName will verify that newName is only a name.
Dim FullNewPath As String = GetFullPathFromNewName(GetParentPath(file), newName, "newName")
Debug.Assert(GetParentPath(FullNewPath).Equals(GetParentPath(file),
StringComparison.OrdinalIgnoreCase), "Invalid FullNewPath")
' Verify that the new path does not conflict.
EnsurePathNotExist(FullNewPath)
IO.File.Move(file, FullNewPath)
End Sub
''' <summary>
''' Overwrites or appends the specified byte array to the specified file,
''' creating the file if it does not exist.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="data">The byte array to write to the file.</param>
''' <param name="append">True to append the text to the existing content. False to overwrite the existing content.</param>
''' <exception cref="IO.FileStream">See FileStream constructor and Write: For other exceptions.</exception>
Public Shared Sub WriteAllBytes(ByVal file As String, ByVal data() As Byte, ByVal append As Boolean)
' Cannot call through IO.File.WriteAllBytes (since they don't support append)
' so only check for trailing separator
CheckFilePathTrailingSeparator(file, NameOf(file))
Dim FileStream As IO.FileStream = Nothing
Try
Dim IOFileMode As IO.FileMode
If append Then
IOFileMode = IO.FileMode.Append
Else
IOFileMode = IO.FileMode.Create ' CreateNew or Truncate.
End If
FileStream = New IO.FileStream(file,
mode:=IOFileMode, access:=IO.FileAccess.Write, share:=IO.FileShare.Read)
FileStream.Write(data, 0, data.Length)
Finally
If Not FileStream Is Nothing Then
FileStream.Close()
End If
End Try
End Sub
''' <summary>
''' Overwrites or appends the given text using UTF-8 encoding to the given file,
''' creating the file if it does not exist.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="text">The text to write to the file.</param>
''' <param name="append">True to append the text to the existing content. False to overwrite the existing content.</param>
''' <exception cref="IO.StreamWriter">See StreamWriter constructor and Write: For other exceptions.</exception>
Public Shared Sub WriteAllText(ByVal file As String, ByVal text As String, ByVal append As Boolean)
WriteAllText(file, text, append, Encoding.UTF8)
End Sub
''' <summary>
''' Overwrites or appends the given text using the given encoding to the given file,
''' creating the file if it does not exist.
''' </summary>
''' <param name="file">The path to the file.</param>
''' <param name="text">The text to write to the file.</param>
''' <param name="append">True to append the text to the existing content. False to overwrite the existing content.</param>
''' <param name="encoding">The encoding to use.</param>
''' <exception cref="IO.StreamWriter">See StreamWriter constructor and Write: For other exceptions.</exception>
Public Shared Sub WriteAllText(ByVal file As String, ByVal text As String, ByVal append As Boolean,
ByVal encoding As Encoding)
'Cannot call through IO.File.WriteAllText (since they don't support: append, prefer current encoding than specified one)
' so only check for trailing separator.
CheckFilePathTrailingSeparator(file, NameOf(file))
Dim StreamWriter As IO.StreamWriter = Nothing
Try
' If appending to a file and it exists, attempt to detect the current encoding and use it.
If append AndAlso IO.File.Exists(file) Then
Dim StreamReader As IO.StreamReader = Nothing
Try
StreamReader = New IO.StreamReader(file, encoding, detectEncodingFromByteOrderMarks:=True)
Dim Chars(10 - 1) As Char
StreamReader.Read(Chars, 0, 10) ' Read the next 10 characters to activate auto detect encoding.
encoding = StreamReader.CurrentEncoding ' Set encoding to the detected encoding.
Catch ex As IO.IOException
' Ignore IOException.
Finally
If StreamReader IsNot Nothing Then
StreamReader.Close()
End If
End Try
End If
' StreamWriter uses FileShare.Read by default.
StreamWriter = New IO.StreamWriter(file, append, encoding)
StreamWriter.Write(text)
Finally
If Not StreamWriter Is Nothing Then
StreamWriter.Close()
End If
End Try
End Sub
''' <summary>
''' Normalize the path, but throw exception if the path ends with separator.
''' </summary>
''' <param name="Path">The input path.</param>
''' <param name="ParamName">The parameter name to include in the exception if one is raised.</param>
''' <returns>The normalized path.</returns>
Friend Shared Function NormalizeFilePath(ByVal Path As String, ByVal ParamName As String) As String
CheckFilePathTrailingSeparator(Path, ParamName)
Return NormalizePath(Path)
End Function
''' <summary>
''' Get full path, get long format, and remove any pending separator.
''' </summary>
''' <param name="Path">The path to be normalized.</param>
''' <returns>The normalized path.</returns>
''' <exception cref="IO.Path.GetFullPath">See IO.Path.GetFullPath for possible exceptions.</exception>
''' <remarks>Keep this function since we might change the implementation / behavior later.</remarks>
Friend Shared Function NormalizePath(ByVal Path As String) As String
Return GetLongPath(RemoveEndingSeparator(IO.Path.GetFullPath(Path)))
End Function
''' <summary>
''' Throw ArgumentException if the file path ends with a separator.
''' </summary>
''' <param name="path">The file path.</param>
''' <param name="paramName">The parameter name to include in ArgumentException.</param>
Friend Shared Sub CheckFilePathTrailingSeparator(ByVal path As String, ByVal paramName As String)
If path = "" Then ' Check for argument null
Throw ExUtils.GetArgumentNullException(paramName)
End If
If path.EndsWith(IO.Path.DirectorySeparatorChar, StringComparison.Ordinal) Or
path.EndsWith(IO.Path.AltDirectorySeparatorChar, StringComparison.Ordinal) Then
Throw ExUtils.GetArgumentExceptionWithArgName(paramName, SR.IO_FilePathException)
End If
End Sub
''' <summary>
''' Add an array of string into a Generic Collection of String.
''' </summary>
Private Shared Sub AddToStringCollection(ByVal StrCollection As ObjectModel.Collection(Of String), ByVal StrArray() As String)
' CONSIDER: : BCL to support adding an array of string directly into a generic string collection?
Debug.Assert(StrCollection IsNot Nothing, "StrCollection is NULL")
If StrArray IsNot Nothing Then
For Each Str As String In StrArray
If Not StrCollection.Contains(Str) Then
StrCollection.Add(Str)
End If
Next
End If
End Sub
''' <summary>
''' Handles exception cases and calls shell or framework to copy / move directory.
''' </summary>
''' <param name="operation">select Copy or Move operation.</param>
''' <param name="sourceDirectoryName">the source directory</param>
''' <param name="destinationDirectoryName">the target directory</param>
''' <param name="overwrite">overwrite files</param>
''' <param name="showUI">calls into shell to copy / move directory</param>
''' <param name="onUserCancel">throw exception if user cancels the operation or not.</param>
''' <exception cref="IO.Path.GetFullPath">IO.Path.GetFullPath exceptions: If SourceDirectoryPath or TargetDirectoryPath is invalid.
''' Or if NewName contains path information.</exception>
''' <exception cref="System.ArgumentException">If Source or Target is device path (\\.\).</exception>
''' <exception cref="IO.DirectoryNotFoundException">Source directory does not exist as a directory.</exception>
''' <exception cref="System.ArgumentNullException">If NewName = "".</exception>
''' <exception cref="IO.IOException">SourceDirectoryPath and TargetDirectoryPath are the same.
''' IOException: Target directory is under source directory - cyclic operation.
''' IOException: TargetDirectoryPath points to an existing file.
''' IOException: Some files and directories can not be copied.</exception>
Private Shared Sub CopyOrMoveDirectory(ByVal operation As CopyOrMove,
ByVal sourceDirectoryName As String, ByVal destinationDirectoryName As String,
ByVal overwrite As Boolean, ByVal showUI As UIOptionInternal, ByVal onUserCancel As UICancelOption)
Debug.Assert([Enum].IsDefined(operation), "Invalid Operation")
' Verify enums.
VerifyUICancelOption("onUserCancel", onUserCancel)
' Get the full path and remove any separators at the end. This will handle invalid path exceptions.
' IMPORTANT: sourceDirectoryName and destinationDirectoryName should be used for exception throwing ONLY.
Dim SourceDirectoryFullPath As String = NormalizePath(sourceDirectoryName)
Dim TargetDirectoryFullPath As String = NormalizePath(destinationDirectoryName)
' Throw if device path.
ThrowIfDevicePath(SourceDirectoryFullPath)
ThrowIfDevicePath(TargetDirectoryFullPath)
' Throw if source directory does not exist.
If Not IO.Directory.Exists(SourceDirectoryFullPath) Then
Throw ExUtils.GetDirectoryNotFoundException(SR.IO_DirectoryNotFound_Path, sourceDirectoryName)
End If
' Throw if source directory is a root directory.
If IsRoot(SourceDirectoryFullPath) Then
Throw ExUtils.GetIOException(SR.IO_DirectoryIsRoot_Path, sourceDirectoryName)
End If
' Throw if there's a file at TargetDirectoryFullPath.
If IO.File.Exists(TargetDirectoryFullPath) Then
Throw ExUtils.GetIOException(SR.IO_FileExists_Path, destinationDirectoryName)
End If
' Throw if source and target are the same.
If TargetDirectoryFullPath.Equals(SourceDirectoryFullPath, StringComparison.OrdinalIgnoreCase) Then
Throw ExUtils.GetIOException(SR.IO_SourceEqualsTargetDirectory)
End If
' Throw if cyclic operation (target is under source). A sample case is
' Source = C:\Dir1\Dir2
' Target = C:\Dir1\Dir2\Dir3\Dir4.
' NOTE: Do not use StartWith since it does not allow specifying InvariantCultureIgnoreCase.
If TargetDirectoryFullPath.Length > SourceDirectoryFullPath.Length AndAlso
TargetDirectoryFullPath.Substring(0, SourceDirectoryFullPath.Length).Equals(
SourceDirectoryFullPath, StringComparison.OrdinalIgnoreCase) Then
Debug.Assert(TargetDirectoryFullPath.Length > SourceDirectoryFullPath.Length, "Target path should be longer")
If TargetDirectoryFullPath.Chars(SourceDirectoryFullPath.Length) = IO.Path.DirectorySeparatorChar Then
Throw ExUtils.GetInvalidOperationException(SR.IO_CyclicOperation)
End If
End If
' NOTE: Decision to create target directory is different for Shell and Framework call.
If showUI <> UIOptionInternal.NoUI AndAlso Environment.UserInteractive Then
ShellCopyOrMove(operation, FileOrDirectory.Directory, SourceDirectoryFullPath, TargetDirectoryFullPath, showUI, onUserCancel)
Else
' Otherwise, copy the directory using System.IO.
FxCopyOrMoveDirectory(operation, SourceDirectoryFullPath, TargetDirectoryFullPath, overwrite)
End If
End Sub
''' <summary>
''' Copies or moves the directory using Framework.
''' </summary>
''' <param name="operation">Copy or Move.</param>
''' <param name="sourceDirectoryPath">Source path - must be full path.</param>
''' <param name="targetDirectoryPath">Target path - must be full path.</param>
''' <param name="Overwrite">True to overwrite the files. Otherwise, False.</param>
''' <exception cref="IO.IOException">Some files or directories cannot be copied or moved.</exception>
Private Shared Sub FxCopyOrMoveDirectory(ByVal operation As CopyOrMove,
ByVal sourceDirectoryPath As String, ByVal targetDirectoryPath As String, ByVal overwrite As Boolean)
Debug.Assert([Enum].IsDefined(operation), "Invalid Operation")
Debug.Assert(sourceDirectoryPath <> "" And IO.Path.IsPathRooted(sourceDirectoryPath), "Invalid Source")
Debug.Assert(targetDirectoryPath <> "" And IO.Path.IsPathRooted(targetDirectoryPath), "Invalid Target")
' Special case for moving: If target directory does not exist, AND both directories are on same drive,
' use IO.Directory.Move for performance gain (not copying).
If operation = CopyOrMove.Move And Not IO.Directory.Exists(targetDirectoryPath) And
IsOnSameDrive(sourceDirectoryPath, targetDirectoryPath) Then
' Create the target's parent. IO.Directory.CreateDirectory won't throw if it exists.
IO.Directory.CreateDirectory(GetParentPath(targetDirectoryPath))
Try
IO.Directory.Move(sourceDirectoryPath, targetDirectoryPath)
Exit Sub
Catch ex As IO.IOException
Catch ex As UnauthorizedAccessException
' Ignore IO.Directory.Move specific exceptions here. Try to do as much as possible later.
End Try
End If
' Create the target, create the root node, and call the recursive function.
System.IO.Directory.CreateDirectory(targetDirectoryPath)
Debug.Assert(IO.Directory.Exists(targetDirectoryPath), "Should be able to create Target Directory")
Dim SourceDirectoryNode As New DirectoryNode(sourceDirectoryPath, targetDirectoryPath)
Dim Exceptions As New ListDictionary
CopyOrMoveDirectoryNode(operation, SourceDirectoryNode, overwrite, Exceptions)
' Throw the final exception if there were exceptions during copy / move.
If Exceptions.Count > 0 Then
Dim IOException As New IO.IOException(SR.IO_CopyMoveRecursive)
For Each Entry As DictionaryEntry In Exceptions
IOException.Data.Add(Entry.Key, Entry.Value)
Next
Throw IOException
End If
End Sub
''' <summary>
''' Given a directory node, copy or move that directory tree.
''' </summary>
''' <param name="Operation">Specify whether to move or copy the directories.</param>
''' <param name="SourceDirectoryNode">The source node. Only copy / move directories contained in the source node.</param>
''' <param name="Overwrite">True to overwrite sub-files. Otherwise False.</param>
''' <param name="Exceptions">The list of accumulated exceptions while doing the copy / move</param>
Private Shared Sub CopyOrMoveDirectoryNode(ByVal Operation As CopyOrMove,
ByVal SourceDirectoryNode As DirectoryNode, ByVal Overwrite As Boolean, ByVal Exceptions As ListDictionary)
Debug.Assert([Enum].IsDefined(Operation), "Invalid Operation")
Debug.Assert(Exceptions IsNot Nothing, "Null exception list")
Debug.Assert(SourceDirectoryNode IsNot Nothing, "Null source node")
' Create the target directory. If we encounter known exceptions, add the exception to the exception list and quit.
Try
If Not IO.Directory.Exists(SourceDirectoryNode.TargetPath) Then
IO.Directory.CreateDirectory(SourceDirectoryNode.TargetPath)
End If
Catch ex As Exception
If (TypeOf ex Is IO.IOException OrElse TypeOf ex Is UnauthorizedAccessException OrElse
TypeOf ex Is IO.DirectoryNotFoundException OrElse TypeOf ex Is NotSupportedException OrElse
TypeOf ex Is SecurityException) Then
Exceptions.Add(SourceDirectoryNode.Path, ex.Message)
Exit Sub
Else
Throw
End If
End Try
Debug.Assert(IO.Directory.Exists(SourceDirectoryNode.TargetPath), "TargetPath should have existed or exception should be thrown")
If Not IO.Directory.Exists(SourceDirectoryNode.TargetPath) Then
Exceptions.Add(SourceDirectoryNode.TargetPath, ExUtils.GetDirectoryNotFoundException(SR.IO_DirectoryNotFound_Path, SourceDirectoryNode.TargetPath))
Exit Sub
End If
' Copy / move all the files under this directory to target directory.
For Each SubFilePath As String In IO.Directory.GetFiles(SourceDirectoryNode.Path)
Try
CopyOrMoveFile(Operation, SubFilePath, IO.Path.Combine(SourceDirectoryNode.TargetPath, IO.Path.GetFileName(SubFilePath)),
Overwrite, UIOptionInternal.NoUI, UICancelOption.ThrowException)
Catch ex As Exception
If (TypeOf ex Is IO.IOException OrElse TypeOf ex Is UnauthorizedAccessException OrElse
TypeOf ex Is SecurityException OrElse TypeOf ex Is NotSupportedException) Then
Exceptions.Add(SubFilePath, ex.Message)
Else
Throw
End If
End Try
Next
' Copy / move all the sub directories under this directory to target directory.
For Each SubDirectoryNode As DirectoryNode In SourceDirectoryNode.SubDirs
CopyOrMoveDirectoryNode(Operation, SubDirectoryNode, Overwrite, Exceptions)
Next
' If this is a move, try to delete the current directory.
' Using recursive:=False since we expect the content should be emptied by now.
If Operation = CopyOrMove.Move Then
Try
IO.Directory.Delete(SourceDirectoryNode.Path, recursive:=False)
Catch ex As Exception
If (TypeOf ex Is IO.IOException OrElse TypeOf ex Is UnauthorizedAccessException OrElse
TypeOf ex Is SecurityException OrElse TypeOf ex Is IO.DirectoryNotFoundException) Then
Exceptions.Add(SourceDirectoryNode.Path, ex.Message)
Else
Throw
End If
End Try
End If
End Sub
''' <summary>
''' Copies or move files. This will be called from CopyFile and MoveFile.
''' </summary>
''' <param name="Operation">Copy or Move.</param>
''' <param name="sourceFileName">Path to source file.</param>
''' <param name="destinationFileName">Path to target file.</param>
''' <param name="Overwrite">True = Overwrite. This flag will be ignored if ShowUI.</param>
''' <param name="ShowUI">Hide or show the UIDialogs.</param>
''' <param name="OnUserCancel">Throw exception in case user cancel using UI or not.</param>
''' <exception cref="IO.Path.GetFullPath">
''' IO.Path.GetFullPath exceptions: If SourceFilePath or TargetFilePath is invalid.
''' ArgumentException: If Source or Target is device path (\\.\).
''' FileNotFoundException: If SourceFilePath does not exist (including pointing to an existing directory).
''' IOException: If TargetFilePath points to an existing directory.
''' ArgumentNullException: If NewName = "".
''' ArgumentException: If NewName contains path information.
''' </exception>
Private Shared Sub CopyOrMoveFile(ByVal operation As CopyOrMove,
ByVal sourceFileName As String, ByVal destinationFileName As String,
ByVal overwrite As Boolean, ByVal showUI As UIOptionInternal, ByVal onUserCancel As UICancelOption
)
Debug.Assert([Enum].IsDefined(operation), "Invalid Operation")
' Verify enums.
VerifyUICancelOption("onUserCancel", onUserCancel)
' Get the full path and remove any separator at the end. This will handle invalid path exceptions.
' IMPORTANT: sourceFileName and destinationFileName should be used for throwing user exceptions ONLY.
Dim sourceFileFullPath As String = NormalizeFilePath(sourceFileName, "sourceFileName")
Dim destinationFileFullPath As String = NormalizeFilePath(destinationFileName, "destinationFileName")
' Throw if device path.
ThrowIfDevicePath(sourceFileFullPath)
ThrowIfDevicePath(destinationFileFullPath)
' Throw exception if SourceFilePath does not exist.
If Not IO.File.Exists(sourceFileFullPath) Then
Throw ExUtils.GetFileNotFoundException(sourceFileName, SR.IO_FileNotFound_Path, sourceFileName)
End If
' Throw exception if TargetFilePath is an existing directory.
If IO.Directory.Exists(destinationFileFullPath) Then
Throw ExUtils.GetIOException(SR.IO_DirectoryExists_Path, destinationFileName)
End If
' Always create the target's parent directory(s).
IO.Directory.CreateDirectory(GetParentPath(destinationFileFullPath))
' If ShowUI, attempt to call Shell function.
If showUI <> UIOptionInternal.NoUI AndAlso System.Environment.UserInteractive Then
ShellCopyOrMove(operation, FileOrDirectory.File, sourceFileFullPath, destinationFileFullPath, showUI, onUserCancel)
Exit Sub
End If
' Use Framework.
If operation = CopyOrMove.Copy OrElse
sourceFileFullPath.Equals(destinationFileFullPath, StringComparison.OrdinalIgnoreCase) Then
' Call IO.File.Copy if this is a copy operation.
' In addition, if sourceFileFullPath is the same as destinationFileFullPath,
' IO.File.Copy will throw, IO.File.Move will not.
' Whatever overwrite flag is passed in, IO.File.Move should throw exception,
' so call IO.File.Copy to get the exception as well.
IO.File.Copy(sourceFileFullPath, destinationFileFullPath, overwrite)
Else ' MoveFile with support for overwrite flag.
If overwrite Then ' User wants to overwrite destination.
' Why not checking for destination existence: user may not have read permission / ACL,
' but have write permission / ACL thus cannot see but can delete / overwrite destination.
If Environment.OSVersion.Platform = PlatformID.Win32NT Then ' Platforms supporting MoveFileEx.
#If TARGET_WINDOWS Then
Try
Dim succeed As Boolean = NativeMethods.MoveFileEx(
sourceFileFullPath, destinationFileFullPath, m_MOVEFILEEX_FLAGS)
' GetLastWin32Error has to be close to PInvoke call. FxCop rule.
If Not succeed Then
ThrowWinIOError(System.Runtime.InteropServices.Marshal.GetLastWin32Error())
End If
Catch
Throw
End Try
#End If
Else ' Non Windows
' IO.File.Delete will not throw if destinationFileFullPath does not exist
' (user may not have permission to discover this, but have permission to overwrite),
' so always delete the destination.
IO.File.Delete(destinationFileFullPath)
IO.File.Move(sourceFileFullPath, destinationFileFullPath)
End If
Else ' Overwrite = False, call Framework.
IO.File.Move(sourceFileFullPath, destinationFileFullPath)
End If ' Overwrite
End If
End Sub
''' <summary>
''' Delete the given directory, with options to recursively delete, show progress UI, send file to Recycle Bin, and whether to throw exception if user cancels.
''' </summary>
''' <param name="directory">The path to the directory.</param>
''' <param name="onDirectoryNotEmpty">DeleteAllContents to delete everything. ThrowIfDirectoryNonEmpty to throw exception if the directory is not empty.</param>
''' <param name="showUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
''' <param name="onUserCancel">Throw exception when user cancel the UI operation or not.</param>
''' <remarks>If user wants shell features, onDirectoryNotEmpty is ignored.</remarks>
Private Shared Sub DeleteDirectoryInternal(ByVal directory As String, ByVal onDirectoryNotEmpty As DeleteDirectoryOption,
ByVal showUI As UIOptionInternal, ByVal recycle As RecycleOption, ByVal onUserCancel As UICancelOption)
VerifyDeleteDirectoryOption("onDirectoryNotEmpty", onDirectoryNotEmpty)
VerifyRecycleOption("recycle", recycle)
VerifyUICancelOption("onUserCancel", onUserCancel)
' Get the full path. This will handle invalid paths exceptions.
Dim directoryFullPath As String = IO.Path.GetFullPath(directory)
' Throw if device path.
ThrowIfDevicePath(directoryFullPath)
If Not IO.Directory.Exists(directoryFullPath) Then
Throw ExUtils.GetDirectoryNotFoundException(SR.IO_DirectoryNotFound_Path, directory)
End If
If IsRoot(directoryFullPath) Then
Throw ExUtils.GetIOException(SR.IO_DirectoryIsRoot_Path, directory)
End If
' If user want shell features (Progress, Recycle Bin), call shell operation.
' We don't need to consider onDirectoryNotEmpty here.
If (showUI <> UIOptionInternal.NoUI) AndAlso Environment.UserInteractive Then
ShellDelete(directoryFullPath, showUI, recycle, onUserCancel, FileOrDirectory.Directory)
Exit Sub
End If
' Otherwise, call Framework's method.
IO.Directory.Delete(directoryFullPath, onDirectoryNotEmpty = DeleteDirectoryOption.DeleteAllContents)
End Sub
''' <summary>
''' Delete the given file, with options to show progress UI, send file to Recycle Bin, throw exception if user cancels.
''' </summary>
''' <param name="file">the path to the file</param>
''' <param name="showUI">AllDialogs, OnlyErrorDialogs, or NoUI</param>
''' <param name="recycle">DeletePermanently or SendToRecycleBin</param>
''' <param name="onUserCancel">DoNothing or ThrowException</param>
''' <remarks></remarks>
Private Shared Sub DeleteFileInternal(ByVal file As String, ByVal showUI As UIOptionInternal, ByVal recycle As RecycleOption,
ByVal onUserCancel As UICancelOption)
' Verify enums
VerifyRecycleOption("recycle", recycle)
VerifyUICancelOption("onUserCancel", onUserCancel)
' Get the full path. This will handle invalid path exceptions.
Dim fileFullPath As String = NormalizeFilePath(file, "file")
' Throw if device path.
ThrowIfDevicePath(fileFullPath)
If Not IO.File.Exists(fileFullPath) Then
Throw ExUtils.GetFileNotFoundException(file, SR.IO_FileNotFound_Path, file)
End If
' If user want shell features (Progress, Recycle Bin), call shell operation.
If (showUI <> UIOptionInternal.NoUI) AndAlso Environment.UserInteractive Then
ShellDelete(fileFullPath, showUI, recycle, onUserCancel, FileOrDirectory.File)
Exit Sub
End If
IO.File.Delete(fileFullPath)
End Sub
''' <summary>
''' Verify that a path does not refer to an existing directory or file. Throw exception otherwise.
''' </summary>
''' <param name="Path">The path to verify.</param>
''' <remarks>This is used for RenameFile and RenameDirectory.</remarks>
Private Shared Sub EnsurePathNotExist(ByVal Path As String)
If IO.File.Exists(Path) Then
Throw ExUtils.GetIOException(SR.IO_FileExists_Path, Path)
End If
If IO.Directory.Exists(Path) Then
Throw ExUtils.GetIOException(SR.IO_DirectoryExists_Path, Path)
End If
End Sub
''' <summary>
''' Determines if the given file in the path contains the given text.
''' </summary>
''' <param name="FilePath">The file to check for.</param>
''' <param name="Text">The text to search for.</param>
''' <returns>True if the file contains the text. Otherwise False.</returns>
Private Shared Function FileContainsText(ByVal FilePath As String, ByVal Text As String, ByVal IgnoreCase As Boolean) _
As Boolean
Debug.Assert(FilePath <> "" AndAlso IO.Path.IsPathRooted(FilePath), FilePath)
Debug.Assert(Text <> "", "Empty text")
' To support different encoding (UTF-8, ASCII).
' Read the file in byte, then use Decoder classes to get a string from those bytes and compare.
' Decoder class maintains state between the conversion, allowing it to correctly decode
' byte sequences that span adjacent blocks. (sources\ndp\clr\src\BCL\System\Text\Decoder.cs).
Dim DEFAULT_BUFFER_SIZE As Integer = 1024 ' default buffer size to read each time.
Dim FileStream As IO.FileStream = Nothing
Try
' Open the file with ReadWrite share, least possibility to fail.
FileStream = New IO.FileStream(FilePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.ReadWrite)
' Read in a byte buffer with default size, then open a StreamReader to detect the encoding of the file.
Dim DetectedEncoding As System.Text.Encoding = System.Text.Encoding.Default ' Use Default encoding as the fall back.
Dim ByteBuffer(DEFAULT_BUFFER_SIZE - 1) As Byte
Dim ByteCount As Integer = 0
ByteCount = FileStream.Read(ByteBuffer, 0, ByteBuffer.Length)
If ByteCount > 0 Then
' Only take the number of bytes returned to avoid false detection.
Dim MemoryStream As New IO.MemoryStream(ByteBuffer, 0, ByteCount)
Dim StreamReader As New IO.StreamReader(MemoryStream, DetectedEncoding, detectEncodingFromByteOrderMarks:=True)
StreamReader.ReadLine()
DetectedEncoding = StreamReader.CurrentEncoding
End If
' Calculate the real buffer size to read in each time to ensure read in at least a character array
' as long as or longer than the given text.
' 1. Calculate the maximum number of bytes required to encode the given text in the detected encoding.
' 2. If it's larger than DEFAULT_BUFFER_SIZE, use it. Otherwise, use DEFAULT_BUFFER_SIZE.
Dim MaxByteDetectedEncoding As Integer = DetectedEncoding.GetMaxByteCount(Text.Length)
Dim BufferSize As Integer = Math.Max(MaxByteDetectedEncoding, DEFAULT_BUFFER_SIZE)
' Dim up the byte buffer and the search helpers (See TextSearchHelper).
Dim SearchHelper As New TextSearchHelper(DetectedEncoding, Text, IgnoreCase)
' If the buffer size is larger than DEFAULT_BUFFER_SIZE, read more from the file stream
' to fill up the byte buffer.
If BufferSize > DEFAULT_BUFFER_SIZE Then
ReDim Preserve ByteBuffer(BufferSize - 1)
' Read maximum ByteBuffer.Length - ByteCount (from the initial read) bytes from the stream
' into the ByteBuffer, starting at ByteCount position.
Dim AdditionalByteCount As Integer = FileStream.Read(ByteBuffer, ByteCount, ByteBuffer.Length - ByteCount)
ByteCount += AdditionalByteCount ' The total byte count now is ByteCount + AdditionalByteCount
Debug.Assert(ByteCount <= ByteBuffer.Length)
End If
' Start the search and read until end of file.
Do
If ByteCount > 0 Then
If SearchHelper.IsTextFound(ByteBuffer, ByteCount) Then
Return True
End If
End If
ByteCount = FileStream.Read(ByteBuffer, 0, ByteBuffer.Length)
Loop While (ByteCount > 0)
Return False
Catch ex As Exception
' We don't expect the following types of exceptions, so we'll re-throw it together with Yukon's exceptions.
Debug.Assert(Not (TypeOf ex Is ArgumentException Or TypeOf ex Is ArgumentOutOfRangeException Or
TypeOf ex Is ArgumentNullException Or TypeOf ex Is IO.DirectoryNotFoundException Or
TypeOf ex Is IO.FileNotFoundException Or TypeOf ex Is ObjectDisposedException Or
TypeOf ex Is RankException Or TypeOf ex Is ArrayTypeMismatchException Or
TypeOf ex Is InvalidCastException), "Unexpected exception: " & ex.ToString())
' These exceptions may happen and we'll return False here.
If TypeOf ex Is IO.IOException Or
TypeOf ex Is NotSupportedException Or
TypeOf ex Is SecurityException Or
TypeOf ex Is UnauthorizedAccessException Then
Return False
Else
' Re-throw Yukon's exceptions, PathTooLong exception (linked directory) and others.
Throw
End If
Finally
If FileStream IsNot Nothing Then
FileStream.Close()
End If
End Try
End Function
''' <summary>
''' Find files or directories in a directory and return them in a string collection.
''' </summary>
''' <param name="FileOrDirectory">Specify to search for file or directory.</param>
''' <param name="directory">The directory path to start from.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <param name="wildcards">The search patterns to use for the file name ("*.*")</param>
''' <returns>A ReadOnlyCollection(Of String) containing the files that match the search condition.</returns>
''' <exception cref="System.ArgumentException">ArgumentNullException: If one of the pattern is Null, Empty or all-spaces string.</exception>
Private Shared Function FindFilesOrDirectories(ByVal FileOrDirectory As FileOrDirectory, ByVal directory As String,
ByVal searchType As SearchOption, ByVal wildcards() As String) As ObjectModel.ReadOnlyCollection(Of String)
Dim Results As New ObjectModel.Collection(Of String)
FindFilesOrDirectories(FileOrDirectory, directory, searchType, wildcards, Results)
Return New ObjectModel.ReadOnlyCollection(Of String)(Results)
End Function
''' <summary>
''' Find files or directories in a directory and return them in a string collection.
''' </summary>
''' <param name="FileOrDirectory">Specify to search for file or directory.</param>
''' <param name="directory">The directory path to start from.</param>
''' <param name="searchType">SearchAllSubDirectories to find recursively. Otherwise, SearchTopLevelOnly.</param>
''' <param name="wildcards">The search patterns to use for the file name ("*.*")</param>
''' <param name="Results">A ReadOnlyCollection(Of String) containing the files that match the search condition.</param>
Private Shared Sub FindFilesOrDirectories(ByVal FileOrDirectory As FileOrDirectory, ByVal directory As String,
ByVal searchType As SearchOption, ByVal wildcards() As String, ByVal Results As ObjectModel.Collection(Of String))
Debug.Assert(Results IsNot Nothing, "Results is NULL")
' Verify enums.
VerifySearchOption("searchType", searchType)
directory = NormalizePath(directory)
' Verify wild cards. Only TrimEnd since empty space is allowed at the start of file / directory name.
If wildcards IsNot Nothing Then
For Each wildcard As String In wildcards
' Throw if empty string or Nothing.
If wildcard.TrimEnd() = "" Then
Throw ExUtils.GetArgumentNullException("wildcards", SR.IO_GetFiles_NullPattern)
End If
Next
End If
' Search for files / directories directly under given directory (based on wildcards).
If wildcards Is Nothing OrElse wildcards.Length = 0 Then
AddToStringCollection(Results, FindPaths(FileOrDirectory, directory, Nothing))
Else
For Each wildcard As String In wildcards
AddToStringCollection(Results, FindPaths(FileOrDirectory, directory, wildcard))
Next
End If
' Search in sub directories if specified.
If searchType = SearchOption.SearchAllSubDirectories Then
For Each SubDirectoryPath As String In IO.Directory.GetDirectories(directory)
FindFilesOrDirectories(FileOrDirectory, SubDirectoryPath, searchType, wildcards, Results)
Next
End If
End Sub
''' <summary>
''' Given a directory, a pattern, find the files or directories directly under the given directory that match the pattern.
''' </summary>
''' <param name="FileOrDirectory">Specify whether to find files or directories.</param>
''' <param name="directory">The directory to look under.</param>
''' <param name="wildCard">*.bmp, *.txt, ... Nothing to search for every thing.</param>
''' <returns>An array of String containing the paths found.</returns>
Private Shared Function FindPaths(ByVal FileOrDirectory As FileOrDirectory, ByVal directory As String, ByVal wildCard As String) As String()
If FileOrDirectory = FileSystem.FileOrDirectory.Directory Then
If wildCard = "" Then
Return IO.Directory.GetDirectories(directory)
Else
Return IO.Directory.GetDirectories(directory, wildCard)
End If
Else
If wildCard = "" Then
Return IO.Directory.GetFiles(directory)
Else
Return IO.Directory.GetFiles(directory, wildCard)
End If
End If
End Function
''' <summary>
''' Returns the fullpath from a directory path and a new name. Throws exception if the new name contains path information.
''' </summary>
''' <param name="Path">The directory path.</param>
''' <param name="NewName">The new name to combine to the directory path.</param>
''' <param name="ArgumentName">The argument name to throw in the exception.</param>
''' <returns>A String contains the full path.</returns>
''' <remarks>This function is used for CopyFile, RenameFile and RenameDirectory.</remarks>
Private Shared Function GetFullPathFromNewName(ByVal Path As String,
ByVal NewName As String, ByVal ArgumentName As String) As String
Debug.Assert(Path <> "" AndAlso IO.Path.IsPathRooted(Path), Path)
Debug.Assert(Path.Equals(IO.Path.GetFullPath(Path)), Path)
Debug.Assert(NewName <> "", "Null NewName")
Debug.Assert(ArgumentName <> "", "Null argument name")
' In copy file, rename file and rename directory, the new name must be a name only.
' Enforce that by combine the path, normalize it, then compare the new parent directory with the old parent directory.
' These two directories must be the same.
' Throw exception if NewName contains any separator characters.
If NewName.IndexOfAny(m_SeparatorChars) >= 0 Then
Throw ExUtils.GetArgumentExceptionWithArgName(ArgumentName, SR.IO_ArgumentIsPath_Name_Path, ArgumentName, NewName)
End If
' Call GetFullPath again to catch invalid characters in NewName.
Dim FullPath As String = RemoveEndingSeparator(IO.Path.GetFullPath(IO.Path.Combine(Path, NewName)))
' If the new parent directory path does not equal the parent directory passed in, throw exception.
' Use this to check for cases like "..", checking for separators will not block this case.
If Not GetParentPath(FullPath).Equals(Path, StringComparison.OrdinalIgnoreCase) Then
Throw ExUtils.GetArgumentExceptionWithArgName(ArgumentName, SR.IO_ArgumentIsPath_Name_Path, ArgumentName, NewName)
End If
Return FullPath
End Function
''' <summary>
''' Returns the given path in long format (v.s 8.3 format) if the path exists.
''' </summary>
''' <param name="FullPath">The path to resolve to long format.</param>
''' <returns>The given path in long format if the path exists.</returns>
''' <remarks>
''' GetLongPathName is a PInvoke call and requires unmanaged code permission.
''' Use DirectoryInfo.GetFiles and GetDirectories (which call FindFirstFile) so that we always have permission.
'''</remarks>
Private Shared Function GetLongPath(ByVal FullPath As String) As String
Debug.Assert(Not FullPath = "" AndAlso IO.Path.IsPathRooted(FullPath), "Must be full path")
Try
' If root path, return itself. UNC path do not recognize 8.3 format in root path, so this is fine.
If IsRoot(FullPath) Then
Return FullPath
End If
' DirectoryInfo.GetFiles and GetDirectories call FindFirstFile which resolves 8.3 path.
' Get the DirectoryInfo (user must have code permission or access permission).
Dim DInfo As New IO.DirectoryInfo(GetParentPath(FullPath))
If IO.File.Exists(FullPath) Then
Debug.Assert(DInfo.GetFiles(IO.Path.GetFileName(FullPath)).Length = 1, "Must found exactly 1")
Return DInfo.GetFiles(IO.Path.GetFileName(FullPath))(0).FullName
ElseIf IO.Directory.Exists(FullPath) Then
Debug.Assert(DInfo.GetDirectories(IO.Path.GetFileName(FullPath)).Length = 1,
"Must found exactly 1")
Return DInfo.GetDirectories(IO.Path.GetFileName(FullPath))(0).FullName
Else
Return FullPath ' Path does not exist, cannot resolve.
End If
Catch ex As Exception
' Ignore these type of exceptions and return FullPath. These type of exceptions should either be caught by calling functions
' or indicate that caller does not have enough permission and should get back the 8.3 path.
If TypeOf ex Is ArgumentException OrElse
TypeOf ex Is ArgumentNullException OrElse
TypeOf ex Is IO.PathTooLongException OrElse
TypeOf ex Is NotSupportedException OrElse
TypeOf ex Is IO.DirectoryNotFoundException OrElse
TypeOf ex Is SecurityException OrElse
TypeOf ex Is UnauthorizedAccessException Then
Debug.Assert(Not (TypeOf ex Is ArgumentException OrElse
TypeOf ex Is ArgumentNullException OrElse
TypeOf ex Is IO.PathTooLongException OrElse
TypeOf ex Is NotSupportedException), "These exceptions should be caught above")
Return FullPath
Else
Throw
End If
End Try
End Function
''' <summary>
''' Checks to see if the two paths is on the same drive.
''' </summary>
''' <param name="Path1"></param>
''' <param name="Path2"></param>
''' <returns>True if the 2 paths is on the same drive. False otherwise.</returns>
''' <remarks>Just a string comparison.</remarks>
Private Shared Function IsOnSameDrive(ByVal Path1 As String, ByVal Path2 As String) As Boolean
' Remove any separators at the end for the same reason in IsRoot.
Path1 = Path1.TrimEnd(IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar)
Path2 = Path2.TrimEnd(IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar)
Return String.Equals(IO.Path.GetPathRoot(Path1), IO.Path.GetPathRoot(Path2), StringComparison.OrdinalIgnoreCase)
End Function
''' <summary>
''' Checks if the full path is a root path.
''' </summary>
''' <param name="Path">The path to check.</param>
''' <returns>True if FullPath is a root path, False otherwise.</returns>
''' <remarks>
''' IO.Path.GetPathRoot: C: -> C:, C:\ -> C:\, \\machine\share -> \\machine\share,
''' BUT \\machine\share\ -> \\machine\share (No separator here).
''' Therefore, remove any separators at the end to have correct result.
''' </remarks>
Private Shared Function IsRoot(ByVal Path As String) As Boolean
' This function accepts a relative path since GetParentPath will call this,
' and GetParentPath accept relative paths.
If Not IO.Path.IsPathRooted(Path) Then
Return False
End If
Path = Path.TrimEnd(IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar)
Return String.Equals(Path, IO.Path.GetPathRoot(Path), StringComparison.OrdinalIgnoreCase)
End Function
''' <summary>
''' Removes all directory separators at the end of a path.
''' </summary>
''' <param name="Path">a full or relative path.</param>
''' <returns>If Path is a root path, the same value. Otherwise, removes any directory separators at the end.</returns>
''' <remarks>We decided not to return path with separators at the end.</remarks>
Private Shared Function RemoveEndingSeparator(ByVal Path As String) As String
If IO.Path.IsPathRooted(Path) Then
' If the path is rooted, attempt to check if it is a root path.
' Note: IO.Path.GetPathRoot: C: -> C:, C:\ -> C:\, \\myshare\mydir -> \\myshare\mydir
' BUT \\myshare\mydir\ -> \\myshare\mydir!!! This function will remove the ending separator of
' \\myshare\mydir\ as well. Do not use IsRoot here.
If Path.Equals(IO.Path.GetPathRoot(Path), StringComparison.OrdinalIgnoreCase) Then
Return Path
End If
End If
' Otherwise, remove all separators at the end.
Return Path.TrimEnd(IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar)
End Function
''' <summary>
''' Sets relevant flags on the SHFILEOPSTRUCT and calls SHFileOperation to copy move file / directory.
''' </summary>
''' <param name="Operation">Copy or move.</param>
''' <param name="TargetType">The target is a file or directory?</param>
''' <param name="FullSourcePath">Full path to source directory / file.</param>
''' <param name="FullTargetPath">Full path to target directory / file.</param>
''' <param name="ShowUI">Show all dialogs or just the error dialogs.</param>
''' <param name="OnUserCancel">Throw exception or ignore if user cancels the operation.</param>
''' <remarks>
''' Copy/MoveFile will call this directly. Copy/MoveDirectory will call ShellCopyOrMoveDirectory first
''' to change the path if needed.
''' </remarks>
Private Shared Sub ShellCopyOrMove(ByVal Operation As CopyOrMove, ByVal TargetType As FileOrDirectory,
ByVal FullSourcePath As String, ByVal FullTargetPath As String, ByVal ShowUI As UIOptionInternal, ByVal OnUserCancel As UICancelOption)
Debug.Assert([Enum].IsDefined(Operation))
Debug.Assert([Enum].IsDefined(TargetType))
Debug.Assert(FullSourcePath <> "" And IO.Path.IsPathRooted(FullSourcePath), "Invalid FullSourcePath")
Debug.Assert(FullTargetPath <> "" And IO.Path.IsPathRooted(FullTargetPath), "Invalid FullTargetPath")
Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
#If TARGET_WINDOWS Then
' Set operation type.
Dim OperationType As SHFileOperationType
If Operation = CopyOrMove.Copy Then
OperationType = SHFileOperationType.FO_COPY
Else
OperationType = SHFileOperationType.FO_MOVE
End If
' Set operation details.
Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
' *** Special action for Directory only. ***
Dim FinalSourcePath As String = FullSourcePath
If TargetType = FileOrDirectory.Directory Then
' Shell behavior: If target does not exist, create target and copy / move source CONTENT into target.
' If target exists, copy / move source into target.
' To have our behavior:
' If target does not exist, create target parent (or shell will throw) and call ShellCopyOrMove.
' If target exists, attach "\*" to FullSourcePath and call ShellCopyOrMove.
' In case of Move, since moving the directory, just create the target parent.
If IO.Directory.Exists(FullTargetPath) Then
FinalSourcePath = IO.Path.Combine(FullSourcePath, "*")
Else
IO.Directory.CreateDirectory(GetParentPath(FullTargetPath))
End If
End If
' Call into ShellFileOperation.
ShellFileOperation(OperationType, OperationFlags, FinalSourcePath, FullTargetPath, OnUserCancel, TargetType)
' *** Special action for Directory only. ***
' In case target does exist, and it's a move, we actually move content and leave the source directory.
' Clean up here.
If Operation = CopyOrMove.Move And TargetType = FileOrDirectory.Directory Then
If IO.Directory.Exists(FullSourcePath) Then
If IO.Directory.GetDirectories(FullSourcePath).Length = 0 _
AndAlso IO.Directory.GetFiles(FullSourcePath).Length = 0 Then
IO.Directory.Delete(FullSourcePath, recursive:=False)
End If
End If
End If
#Else
Throw New PlatformNotSupportedException(SR.NoShellCopyOrMove)
#End If
End Sub
''' <summary>
''' Sets relevant flags on the SHFILEOPSTRUCT and calls into SHFileOperation to delete file / directory.
''' </summary>
''' <param name="FullPath">Full path to the file / directory.</param>
''' <param name="ShowUI">ShowDialogs to display progress and confirmation dialogs. Otherwise HideDialogs.</param>
''' <param name="recycle">SendToRecycleBin to delete to Recycle Bin. Otherwise DeletePermanently.</param>
''' <param name="OnUserCancel">Throw exception or not if the operation was canceled (by user or errors in the system).</param>
''' <remarks>
''' We don't need to consider Recursive flag here since we already verify that in DeleteDirectory.
''' </remarks>
Private Shared Sub ShellDelete(ByVal FullPath As String,
ByVal ShowUI As UIOptionInternal, ByVal recycle As RecycleOption, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "FullPath must be a full path")
Debug.Assert(ShowUI <> UIOptionInternal.NoUI, "Why call ShellDelete if ShowUI is NoUI???")
#If TARGET_WINDOWS Then
' Set fFlags to control the operation details.
Dim OperationFlags As ShFileOperationFlags = GetOperationFlags(ShowUI)
If (recycle = RecycleOption.SendToRecycleBin) Then
OperationFlags = OperationFlags Or ShFileOperationFlags.FOF_ALLOWUNDO
End If
ShellFileOperation(SHFileOperationType.FO_DELETE, OperationFlags, FullPath, Nothing, OnUserCancel, FileOrDirectory)
#Else
Throw New PlatformNotSupportedException(SR.NoShellCopyOrMove)
#End If
End Sub
#If TARGET_WINDOWS Then
''' <summary>
''' Calls NativeMethods.SHFileOperation with the given SHFILEOPSTRUCT, notifies the shell of change,
''' and throw exceptions if needed.
''' </summary>
''' <param name="OperationType">Value from SHFileOperationType, specifying Copy / Move / Delete</param>
''' <param name="OperationFlags">Value from ShFileOperationFlags, specifying overwrite, recycle bin, etc...</param>
''' <param name="FullSource">The full path to the source.</param>
''' <param name="FullTarget">The full path to the target. Nothing if this is a Delete operation.</param>
''' <param name="OnUserCancel">Value from UICancelOption, specifying to throw or not when user cancels the operation.</param>
Private Shared Sub ShellFileOperation(ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
ByVal FullSource As String, ByVal FullTarget As String, ByVal OnUserCancel As UICancelOption, ByVal FileOrDirectory As FileOrDirectory)
Debug.Assert([Enum].IsDefined(OperationType))
Debug.Assert(OperationType <> SHFileOperationType.FO_RENAME, "Don't call Shell to rename")
Debug.Assert(FullSource <> "" And IO.Path.IsPathRooted(FullSource), "Invalid FullSource path")
Debug.Assert(OperationType = SHFileOperationType.FO_DELETE OrElse (FullTarget <> "" And IO.Path.IsPathRooted(FullTarget)), "Invalid FullTarget path")
' Get the SHFILEOPSTRUCT
Dim OperationInfo As SHFILEOPSTRUCT = GetShellOperationInfo(OperationType, OperationFlags, FullSource, FullTarget)
Dim Result As Integer
Try
Result = NativeMethods.SHFileOperation(OperationInfo)
' Notify the shell in case some changes happened.
NativeMethods.SHChangeNotify(SHChangeEventTypes.SHCNE_DISKEVENTS,
SHChangeEventParameterFlags.SHCNF_DWORD, IntPtr.Zero, IntPtr.Zero)
Catch
Throw
Finally
End Try
' If the operation was canceled, check OnUserCancel and throw OperationCanceledException if needed.
' Otherwise, check the result and throw the appropriate exception if there is an error code.
If OperationInfo.fAnyOperationsAborted Then
If OnUserCancel = UICancelOption.ThrowException Then
Throw New OperationCanceledException()
End If
ElseIf Result <> 0 Then
ThrowWinIOError(ToWinIOErrorCode(Result))
End If
End Sub
''' <summary>
''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
''' </summary>
''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
''' <param name="SourcePath">The source file / directory path.</param>
''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
Private Shared Function GetShellOperationInfo(
ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
ByVal SourcePath As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
Debug.Assert(SourcePath <> "" And IO.Path.IsPathRooted(SourcePath), "Invalid SourcePath")
Return GetShellOperationInfo(OperationType, OperationFlags, New String() {SourcePath}, TargetPath)
End Function
''' <summary>
''' Returns an SHFILEOPSTRUCT used by SHFileOperation based on the given parameters.
''' </summary>
''' <param name="OperationType">One of the SHFileOperationType value: copy, move or delete.</param>
''' <param name="OperationFlags">Combination SHFileOperationFlags values: details of the operation.</param>
''' <param name="SourcePaths">A string array containing the paths of source files. Must not be empty.</param>
''' <param name="TargetPath">The target file / directory path. Nothing in case of delete.</param>
''' <returns>A fully initialized SHFILEOPSTRUCT.</returns>
Private Shared Function GetShellOperationInfo(
ByVal OperationType As SHFileOperationType, ByVal OperationFlags As ShFileOperationFlags,
ByVal SourcePaths() As String, Optional ByVal TargetPath As String = Nothing) As SHFILEOPSTRUCT
Debug.Assert([Enum].IsDefined(OperationType), "Invalid OperationType")
Debug.Assert(TargetPath = "" Or IO.Path.IsPathRooted(TargetPath), "Invalid TargetPath")
Debug.Assert(SourcePaths IsNot Nothing AndAlso SourcePaths.Length > 0, "Invalid SourcePaths")
Dim OperationInfo As SHFILEOPSTRUCT
' Set wFunc - the operation.
OperationInfo.wFunc = CType(OperationType, UInteger)
' Set fFlags - the operation details.
OperationInfo.fFlags = CType(OperationFlags, UShort)
' Set pFrom and pTo - the paths.
OperationInfo.pFrom = GetShellPath(SourcePaths)
If TargetPath Is Nothing Then
OperationInfo.pTo = Nothing
Else
OperationInfo.pTo = GetShellPath(TargetPath)
End If
' Set other fields.
OperationInfo.hNameMappings = IntPtr.Zero
' Try to set hwnd to the process's MainWindowHandle. If exception occurs, use IntPtr.Zero, which is desktop.
Try
OperationInfo.hwnd = Process.GetCurrentProcess.MainWindowHandle
Catch ex As Exception
If TypeOf (ex) Is SecurityException OrElse
TypeOf (ex) Is InvalidOperationException OrElse
TypeOf (ex) Is NotSupportedException Then
' GetCurrentProcess can throw SecurityException. MainWindowHandle can throw InvalidOperationException or NotSupportedException.
OperationInfo.hwnd = IntPtr.Zero
Else
Throw
End If
End Try
OperationInfo.lpszProgressTitle = String.Empty ' We don't set this since we don't have any FOF_SIMPLEPROGRESS.
Return OperationInfo
End Function
''' <summary>
''' Return the ShFileOperationFlags based on the ShowUI option.
''' </summary>
''' <param name="ShowUI">UIOptionInternal value.</param>
Private Shared Function GetOperationFlags(ByVal ShowUI As UIOptionInternal) As ShFileOperationFlags
Dim OperationFlags As ShFileOperationFlags = m_SHELL_OPERATION_FLAGS_BASE
If (ShowUI = UIOptionInternal.OnlyErrorDialogs) Then
OperationFlags = OperationFlags Or m_SHELL_OPERATION_FLAGS_HIDE_UI
End If
Return OperationFlags
End Function
''' <summary>
''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
''' </summary>
''' <param name="FullPath">The full path to be converted.</param>
''' <returns>A string in the required format.</returns>
Private Shared Function GetShellPath(ByVal FullPath As String) As String
Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), "Must be full path")
Return GetShellPath(New String() {FullPath})
End Function
''' <summary>
''' Returns the special path format required for pFrom and pTo of SHFILEOPSTRUCT. See NativeMethod.
''' </summary>
''' <param name="FullPaths">A string array containing the paths for the operation.</param>
''' <returns>A string in the required format.</returns>
Private Shared Function GetShellPath(ByVal FullPaths() As String) As String
#If DEBUG Then
Debug.Assert(FullPaths IsNot Nothing, "FullPaths is NULL")
Debug.Assert(FullPaths.Length > 0, "FullPaths() is empty array")
For Each FullPath As String In FullPaths
Debug.Assert(FullPath <> "" And IO.Path.IsPathRooted(FullPath), FullPath)
Next
#End If
' Each path will end with a Null character.
Dim MultiString As New StringBuilder()
For Each FullPath As String In FullPaths
MultiString.Append(FullPath & ControlChars.NullChar)
Next
' Don't need to append another Null character since String always end with Null character by default.
Debug.Assert(MultiString.ToString.EndsWith(ControlChars.NullChar, StringComparison.Ordinal))
Return MultiString.ToString()
End Function
#End If
''' <summary>
''' Throw an argument exception if the given path starts with "\\.\" (device path).
''' </summary>
''' <param name="path">The path to check.</param>
''' <remarks>
''' FileStream already throws exception with device path, so our code only check for device path in Copy / Move / Delete / Rename.
''' </remarks>
Private Shared Sub ThrowIfDevicePath(ByVal path As String)
If path.StartsWith("\\.\", StringComparison.Ordinal) Then
Throw ExceptionUtils.GetArgumentExceptionWithArgName("path", SR.IO_DevicePath)
End If
End Sub
#If TARGET_WINDOWS Then
''' <summary>
''' Given an error code from winerror.h, throw the appropriate exception.
''' </summary>
''' <param name="errorCode">An error code from winerror.h.</param>
''' <remarks>
''' - This method is based on sources\ndp\clr\src\BCL\System\IO\_Error.cs::WinIOError, except the following.
''' - Exception message does not contain the path since at this point it is normalized.
''' - Instead of using PInvoke of GetMessage and MakeHRFromErrorCode, use managed code.
''' </remarks>
Private Shared Sub ThrowWinIOError(ByVal errorCode As Integer)
Select Case errorCode
Case NativeTypes.ERROR_FILE_NOT_FOUND
Throw New IO.FileNotFoundException()
Case NativeTypes.ERROR_PATH_NOT_FOUND
Throw New IO.DirectoryNotFoundException()
Case NativeTypes.ERROR_ACCESS_DENIED
Throw New UnauthorizedAccessException()
Case NativeTypes.ERROR_FILENAME_EXCED_RANGE
Throw New IO.PathTooLongException()
Case NativeTypes.ERROR_INVALID_DRIVE
Throw New IO.DriveNotFoundException()
Case NativeTypes.ERROR_OPERATION_ABORTED, NativeTypes.ERROR_CANCELLED
Throw New OperationCanceledException()
Case Else
' Including these from _Error.cs::WinIOError.
'Case NativeTypes.ERROR_ALREADY_EXISTS
'Case NativeTypes.ERROR_INVALID_PARAMETER
'Case NativeTypes.ERROR_SHARING_VIOLATION
'Case NativeTypes.ERROR_FILE_EXISTS
Throw New IO.IOException((New Win32Exception(errorCode)).Message,
System.Runtime.InteropServices.Marshal.GetHRForLastWin32Error())
End Select
End Sub
#End If
''' <summary>
''' Convert UIOption to UIOptionInternal to use internally.
''' </summary>
''' <remarks>
''' Only accept valid UIOption values.
''' </remarks>
Private Shared Function ToUIOptionInternal(ByVal showUI As UIOption) As UIOptionInternal
Select Case showUI
Case FileIO.UIOption.AllDialogs
Return UIOptionInternal.AllDialogs
Case FileIO.UIOption.OnlyErrorDialogs
Return UIOptionInternal.OnlyErrorDialogs
Case Else
Throw New System.ComponentModel.InvalidEnumArgumentException("showUI", showUI, GetType(UIOption))
End Select
End Function
#If TARGET_WINDOWS Then
''' <summary>
''' Convert SHFileOperation error code to Win IO error code.
''' </summary>
''' <param name="errorCode">SHFileOperation error code.</param>
''' <returns>Win IO error code.</returns>
Private Shared Function ToWinIOErrorCode(ByVal errorCode As Integer) As Integer
Select Case errorCode
Case NativeTypes.DE_SAMEFILE
Return NativeTypes.ERROR_ALREADY_EXISTS
Case NativeTypes.DE_MANYSRC1DEST
Return NativeTypes.ERROR_INVALID_PARAMETER
Case NativeTypes.DE_DIFFDIR
Return NativeTypes.ERROR_NOT_SAME_DEVICE
Case NativeTypes.DE_ROOTDIR
Return NativeTypes.ERROR_ACCESS_DENIED
Case NativeTypes.DE_OPCANCELLED
Return NativeTypes.ERROR_CANCELLED
Case NativeTypes.DE_DESTSUBTREE
Return NativeTypes.ERROR_BAD_PATHNAME
Case NativeTypes.DE_ACCESSDENIEDSRC
Return NativeTypes.ERROR_ACCESS_DENIED
Case NativeTypes.DE_PATHTOODEEP
Return NativeTypes.ERROR_FILENAME_EXCED_RANGE
Case NativeTypes.DE_MANYDEST
Return NativeTypes.ERROR_INVALID_PARAMETER
Case NativeTypes.DE_INVALIDFILES
Return NativeTypes.ERROR_BAD_PATHNAME
Case NativeTypes.DE_DESTSAMETREE
Return NativeTypes.ERROR_INVALID_PARAMETER
Case NativeTypes.DE_FLDDESTISFILE
Return NativeTypes.ERROR_ALREADY_EXISTS
Case NativeTypes.DE_FILEDESTISFLD
Return NativeTypes.ERROR_ALREADY_EXISTS
Case NativeTypes.DE_FILENAMETOOLONG
Return NativeTypes.ERROR_FILENAME_EXCED_RANGE
Case NativeTypes.DE_DEST_IS_CDROM
Return NativeTypes.ERROR_WRITE_FAULT
Case NativeTypes.DE_DEST_IS_DVD
Return NativeTypes.ERROR_WRITE_FAULT
Case NativeTypes.DE_DEST_IS_CDRECORD
Return NativeTypes.ERROR_WRITE_FAULT
Case NativeTypes.DE_FILE_TOO_LARGE
Return NativeTypes.ERROR_FILE_TOO_LARGE
Case NativeTypes.DE_SRC_IS_CDROM
Return NativeTypes.ERROR_READ_FAULT
Case NativeTypes.DE_SRC_IS_DVD
Return NativeTypes.ERROR_READ_FAULT
Case NativeTypes.DE_SRC_IS_CDRECORD
Return NativeTypes.ERROR_READ_FAULT
Case NativeTypes.DE_ERROR_MAX
Return NativeTypes.ERROR_FILENAME_EXCED_RANGE
Case NativeTypes.DE_ERROR_UNKNOWN
Return NativeTypes.ERROR_PATH_NOT_FOUND
Case NativeTypes.ERRORONDEST
Return NativeTypes.ERROR_GEN_FAILURE
Case NativeTypes.DE_ROOTDIR_ERRORONDEST
Return NativeTypes.ERROR_ACCESS_DENIED
Case Else
Return errorCode
End Select
End Function
#End If
''' <summary>
''' Verify that the given argument value is a valid DeleteDirectoryOption. If not, throw InvalidEnumArgumentException.
''' </summary>
''' <param name="argName">The argument name.</param>
''' <param name="argValue">The argument value.</param>
''' <remarks></remarks>
Private Shared Sub VerifyDeleteDirectoryOption(ByVal argName As String, ByVal argValue As DeleteDirectoryOption)
If argValue = FileIO.DeleteDirectoryOption.DeleteAllContents OrElse
argValue = FileIO.DeleteDirectoryOption.ThrowIfDirectoryNonEmpty Then
Exit Sub
End If
Throw New InvalidEnumArgumentException(argName, argValue, GetType(DeleteDirectoryOption))
End Sub
''' <summary>
''' Verify that the given argument value is a valid RecycleOption. If not, throw InvalidEnumArgumentException.
''' </summary>
''' <param name="argName">The argument name.</param>
''' <param name="argValue">The argument value.</param>
Private Shared Sub VerifyRecycleOption(ByVal argName As String, ByVal argValue As RecycleOption)
If argValue = RecycleOption.DeletePermanently OrElse
argValue = RecycleOption.SendToRecycleBin Then
Exit Sub
End If
Throw New InvalidEnumArgumentException(argName, argValue, GetType(RecycleOption))
End Sub
''' <summary>
''' Verify that the given argument value is a valid SearchOption. If not, throw InvalidEnumArgumentException.
''' </summary>
''' <param name="argName">The argument name.</param>
''' <param name="argValue">The argument value.</param>
Private Shared Sub VerifySearchOption(ByVal argName As String, ByVal argValue As SearchOption)
If argValue = SearchOption.SearchAllSubDirectories OrElse
argValue = SearchOption.SearchTopLevelOnly Then
Exit Sub
End If
Throw New InvalidEnumArgumentException(argName, argValue, GetType(SearchOption))
End Sub
''' <summary>
''' Verify that the given argument value is a valid UICancelOption. If not, throw InvalidEnumArgumentException.
''' </summary>
''' <param name="argName">The argument name.</param>
''' <param name="argValue">The argument value.</param>
Private Shared Sub VerifyUICancelOption(ByVal argName As String, ByVal argValue As UICancelOption)
If argValue = UICancelOption.DoNothing OrElse
argValue = UICancelOption.ThrowException Then
Exit Sub
End If
Throw New InvalidEnumArgumentException(argName, argValue, GetType(UICancelOption))
End Sub
#If TARGET_WINDOWS Then
' Base operation flags used in shell IO operation.
' - DON'T move connected files as a group.
' - DON'T confirm directory creation - our silent copy / move do not.
Private Const m_SHELL_OPERATION_FLAGS_BASE As ShFileOperationFlags =
ShFileOperationFlags.FOF_NO_CONNECTED_ELEMENTS Or
ShFileOperationFlags.FOF_NOCONFIRMMKDIR
' Hide UI operation flags for Delete.
' - DON'T show progress bar.
' - DON'T confirm (answer yes to everything). NOTE: In exception cases (read-only file), shell still asks.
Private Const m_SHELL_OPERATION_FLAGS_HIDE_UI As ShFileOperationFlags =
ShFileOperationFlags.FOF_SILENT Or
ShFileOperationFlags.FOF_NOCONFIRMATION
' When calling MoveFileEx, set the following flags:
' - Simulate CopyFile and DeleteFile if copied to a different volume.
' - Replace contents of existing target with the contents of source file.
' - Do not return until the file has actually been moved on the disk.
Private Const m_MOVEFILEEX_FLAGS As Integer = CInt(
MoveFileExFlags.MOVEFILE_COPY_ALLOWED Or
MoveFileExFlags.MOVEFILE_REPLACE_EXISTING Or
MoveFileExFlags.MOVEFILE_WRITE_THROUGH)
#End If
' Array containing all the path separator chars. Used to verify that input is a name, not a path.
Private Shared ReadOnly m_SeparatorChars() As Char = {
IO.Path.DirectorySeparatorChar, IO.Path.AltDirectorySeparatorChar, IO.Path.VolumeSeparatorChar}
''' <summary>
''' Private enumeration: The operation is a Copy or Move.
''' </summary>
Private Enum CopyOrMove
Copy
Move
End Enum
''' <summary>
''' Private enumeration: Target of the operation is a File or Directory.
''' </summary>
''' <remarks></remarks>
Private Enum FileOrDirectory
File
Directory
End Enum
''' <summary>
''' Private enumeration: Indicate the options of ShowUI to use internally.
''' This includes NoUI so that we can base the decision on 1 variable.
''' </summary>
''' <remarks></remarks>
Private Enum UIOptionInternal
OnlyErrorDialogs = UIOption.OnlyErrorDialogs
AllDialogs = UIOption.AllDialogs
NoUI
End Enum
''' <summary>
''' A simple tree node to build up the directory structure used for a snapshot in Copy / Move Directory.
''' </summary>
Private NotInheritable Class DirectoryNode
''' <summary>
''' Given a DirectoryPath, create the node and add the sub-directory nodes.
''' </summary>
''' <param name="DirectoryPath">Path to the directory. NOTE: must exist.</param>
''' <param name="TargetDirectoryPath">Path to the target directory of the move / copy. NOTE: must be a full path.</param>
Friend Sub New(ByVal DirectoryPath As String, ByVal TargetDirectoryPath As String)
Debug.Assert(IO.Directory.Exists(DirectoryPath), "Directory does not exist")
Debug.Assert(TargetDirectoryPath <> "" And IO.Path.IsPathRooted(TargetDirectoryPath), "Invalid TargetPath")
m_Path = DirectoryPath
m_TargetPath = TargetDirectoryPath
m_SubDirs = New ObjectModel.Collection(Of DirectoryNode)
For Each SubDirPath As String In IO.Directory.GetDirectories(m_Path)
Dim SubTargetDirPath As String = IO.Path.Combine(m_TargetPath, IO.Path.GetFileName(SubDirPath))
m_SubDirs.Add(New DirectoryNode(SubDirPath, SubTargetDirPath))
Next
End Sub
''' <summary>
''' Return the Path of the current node.
''' </summary>
''' <value>A String containing the Path of the current node.</value>
Friend ReadOnly Property Path() As String
Get
Return m_Path
End Get
End Property
''' <summary>
''' Return the TargetPath for copy / move.
''' </summary>
''' <value>A String containing the copy / move target path of the current node.</value>
Friend ReadOnly Property TargetPath() As String
Get
Return m_TargetPath
End Get
End Property
''' <summary>
''' Return the sub directories of the current node.
''' </summary>
''' <value>A Collection(Of DirectoryNode) containing the sub-directory nodes.</value>
Friend ReadOnly Property SubDirs() As ObjectModel.Collection(Of DirectoryNode)
Get
Return m_SubDirs
End Get
End Property
Private m_Path As String
Private m_TargetPath As String
Private m_SubDirs As ObjectModel.Collection(Of DirectoryNode)
End Class 'Private Class DirectoryNode
''' <summary>
''' Helper class to search for text in an array of byte using a specific Decoder.
''' </summary>
''' <remarks>
''' To search for text that might exist in an encoding, construct this class with the text and Decoder.
''' Then call IsTextFound() and pass in byte arrays.
''' This class will take care of text spanning byte arrays by caching a part of the array and use it in
''' the next IsTextFound() call.
''' </remarks>
Private NotInheritable Class TextSearchHelper
''' <summary>
''' Constructs a new helper with a given encoding and a text to search for.
''' </summary>
''' <param name="Encoding">The Encoding to use to convert byte to text.</param>
''' <param name="Text">The text to search for in subsequent byte array.</param>
Friend Sub New(ByVal Encoding As Text.Encoding, ByVal Text As String, ByVal IgnoreCase As Boolean)
Debug.Assert(Encoding IsNot Nothing, "Null Decoder")
Debug.Assert(Text <> "", "Empty Text")
m_Decoder = Encoding.GetDecoder
m_Preamble = Encoding.GetPreamble
m_IgnoreCase = IgnoreCase
' If use wants to ignore case, convert search text to lower case.
If m_IgnoreCase Then
m_SearchText = Text.ToUpper(CultureInfo.CurrentCulture)
Else
m_SearchText = Text
End If
End Sub
''' <summary>
''' Determines whether the text is found in the given byte array.
''' </summary>
''' <param name="ByteBuffer">The byte array to find the text in</param>
''' <param name="Count">The number of valid bytes in the byte array</param>
''' <returns>True if the text is found. Otherwise, False.</returns>
Friend Function IsTextFound(ByVal ByteBuffer() As Byte, ByVal Count As Integer) As Boolean
Debug.Assert(ByteBuffer IsNot Nothing, "Null ByteBuffer")
Debug.Assert(Count > 0, Count.ToString(CultureInfo.InvariantCulture))
Debug.Assert(m_Decoder IsNot Nothing, "Null Decoder")
Debug.Assert(m_Preamble IsNot Nothing, "Null Preamble")
Dim ByteBufferStartIndex As Integer = 0 ' If need to handle BOM, ByteBufferStartIndex will increase.
' Check for the preamble the first time IsTextFound is called. If find it, shrink ByteBuffer.
If m_CheckPreamble Then
If BytesMatch(ByteBuffer, m_Preamble) Then
ByteBufferStartIndex = m_Preamble.Length
Count -= m_Preamble.Length ' Reduce the valid byte count if ByteBuffer was shrunk.
End If
m_CheckPreamble = False
' In case of an empty file with BOM at the beginning return FALSE.
If Count <= 0 Then
Return False
End If
End If
' Get the number of characters in the byte array.
Dim ExpectedCharCount As Integer = m_Decoder.GetCharCount(ByteBuffer, ByteBufferStartIndex, Count)
' The character buffer used to search will be a combination of the cached buffer and the current one.
Dim CharBuffer(m_PreviousCharBuffer.Length + ExpectedCharCount - 1) As Char
' Start the buffer with the cached buffer.
Array.Copy(sourceArray:=m_PreviousCharBuffer, sourceIndex:=0,
destinationArray:=CharBuffer, destinationIndex:=0, length:=m_PreviousCharBuffer.Length)
' And fill the rest with the ones from byte array.
Dim CharCount As Integer = m_Decoder.GetChars(
bytes:=ByteBuffer, byteIndex:=ByteBufferStartIndex, byteCount:=Count,
chars:=CharBuffer, charIndex:=m_PreviousCharBuffer.Length)
Debug.Assert(CharCount = ExpectedCharCount, "Should read all characters")
' Refresh the cached buffer for the possible next search.
If CharBuffer.Length > m_SearchText.Length Then
If m_PreviousCharBuffer.Length <> m_SearchText.Length Then
ReDim m_PreviousCharBuffer(m_SearchText.Length - 1)
End If
Array.Copy(sourceArray:=CharBuffer, sourceIndex:=(CharBuffer.Length - m_SearchText.Length),
destinationArray:=m_PreviousCharBuffer, destinationIndex:=0, length:=m_SearchText.Length)
Else
m_PreviousCharBuffer = CharBuffer
End If
' If user wants to ignore case, convert new string to lower case. m_SearchText was converted in constructor.
If m_IgnoreCase Then
Return New String(CharBuffer).Contains(m_SearchText, StringComparison.OrdinalIgnoreCase)
Else
Return New String(CharBuffer).Contains(m_SearchText)
End If
End Function
''' <summary>
''' No default constructor.
''' </summary>
Private Sub New()
End Sub
''' <summary>
''' Returns whether the big buffer starts with the small buffer.
''' </summary>
''' <param name="BigBuffer"></param>
''' <param name="SmallBuffer"></param>
''' <returns>True if BigBuffer starts with SmallBuffer.Otherwise, False.</returns>
Private Shared Function BytesMatch(ByVal BigBuffer() As Byte, ByVal SmallBuffer() As Byte) As Boolean
Debug.Assert(BigBuffer.Length > SmallBuffer.Length, "BigBuffer should be longer")
If BigBuffer.Length < SmallBuffer.Length Or SmallBuffer.Length = 0 Then
Return False
End If
For i As Integer = 0 To SmallBuffer.Length - 1
If BigBuffer(i) <> SmallBuffer(i) Then
Return False
End If
Next
Return True
End Function
Private m_SearchText As String ' The text to search.
Private m_IgnoreCase As Boolean ' Should we ignore case?
Private m_Decoder As Text.Decoder ' The Decoder to use.
Private m_PreviousCharBuffer() As Char = Array.Empty(Of Char)() ' The cached character array from previous call to IsTextExist.
Private m_CheckPreamble As Boolean = True ' True to check for preamble. False otherwise.
Private m_Preamble() As Byte ' The byte order mark we need to consider.
End Class 'Private Class TextSearchHelper
End Class 'Public Class FileSystem
''' <summary>
''' Specify the action to do when deleting a directory and it is not empty.
''' </summary>
''' <remarks>
''' Again, avoid Integer values that VB Compiler will convert Boolean to (0 and -1).
''' IMPORTANT: Change VerifyDeleteDirectoryOption if this enum is changed.
''' Also, values in DeleteDirectoryOption must be different from UIOption.
''' </remarks>
Public Enum DeleteDirectoryOption As Integer
ThrowIfDirectoryNonEmpty = 4
DeleteAllContents = 5
End Enum
''' <summary>
''' Specify whether to delete a file / directory to Recycle Bin or not.
''' </summary>
Public Enum RecycleOption As Integer
DeletePermanently = 2
SendToRecycleBin = 3
End Enum
''' <summary>
''' Specify whether to perform the search for files/directories recursively or not.
''' </summary>
Public Enum SearchOption As Integer
SearchTopLevelOnly = 2
SearchAllSubDirectories = 3
End Enum
''' <summary>
''' Defines option whether to throw exception when user cancels a UI operation or not.
''' </summary>
Public Enum UICancelOption As Integer
DoNothing = 2
ThrowException = 3
End Enum
''' <summary>
''' Specify which UI dialogs to show.
''' </summary>
''' <remarks>
''' To fix common issues; avoid Integer values that VB Compiler
''' will convert Boolean to (0 and -1).
''' </remarks>
Public Enum UIOption As Integer
OnlyErrorDialogs = 2
AllDialogs = 3
End Enum
End Namespace
' NOTE:
' - All path returned by us will NOT have the Directory Separator Character ('\') at the end.
' - All path accepted by us will NOT consider the meaning of Directory Separator Character ('\') at the end.
' - Parameter accepting path will accept both relative and absolute paths unless specified.
' Relative paths will be resolved using the current working directory.
' - IO.Path.GetFullPath is used to normalized the path. It will only throw in case of not well-formed path.
' - Hidden Files and Directories will be moved / copied by Framework code.
'
' - On both Read and Write, we use the default Share mode that FX uses for the StreamReader/Writer, which is Share.Read.
' Details on what share mode means:
' When a call is made to open the file, the share mode not only means that the caller wants to restrict every call
' afterwards, but also every call before as well, which means that the caller will fail if any calls before it
' already obtained a conflict right.
' For example: if this call succeeds,
' Open(FileA, OpenMode.Write, ShareMode.Read)
' Although it is sharing FileA for reading, if the 2nd call is
' Open(FileA, OpenMode.Read, ShareMode.Read)
' the 2nd call will fail since it wants to restrict everybody else to read only, but 1st caller has already obtained
' write access.
' So the default behavior is fine since novice Mort can't run into trouble using it.
'
' - All IO functions involving ShowUI have dependency on Windows Shell and sometimes have different behavior.
' - CopyDirectory will attempt to copy all the files in the directory. If there are files or sub-directories
' that cause exception, CopyDirectory will not stop, since that will leave the result in unknown state.
' Instead, an exception will be thrown at the end containing a list of exception files in Data property.
' - MoveDirectory behaves the same so MoveDirectory is not equal to calling CopyDirectory and DeleteDirectory.
' - Overwrite in directory case means overwrite sub files. Sub directories will always be merged.
'
' Shell behavior in exception cases:
' - Copy / Move File
' . Existing target:
' Overwrite = True: Overwrite target.
' Overwrite = False: Dialog Yes: Overwrite target.
' No: Error code 7. ERROR_ARENA_TRASHED
' . Existing target and Read-Only (Framework will throw).
' Always ask. No: Error code 7. ERROR_ARENA_TRASHED
' . OS access denied: Error code 1223. ERROR_CANCELLED
' - Copy / Move Directory Existing target:
' . Has an existing file:
' Overwrite = True: Overwrite file.
' Overwrite = False: Dialog Yes / Yes to all : Overwrite target.
' No: Leave and copy the rest.
' Cancel: Error code 2. ERROR_FILE_NOT_FOUND.
' . Has an existing file and Read-Only (Framework will throw).
' Behave as when Overwrite = False.
' . File in source same name with directory in target:
' * Copy: Error code 1223 ERROR_CANCELLED.
' * Move: Overwrite = True: Error code 183. ERROR_ALREADY_EXISTS.
' Overwrite = False: Ask question Yes: Error code 183.
' Cancel: Error code 2.
' . Directory in source same name with file in target:
' Error code 183 in all cases.
'
' NOTE: Some different behavior when deleting files / directories.
' ShowUI RecycleBin Normal file. Read-only file.
' F F Gone Exception. *
' T F Question + UI + Gone Question + UI + Gone
' F T Bin Question + Bin *
' T T Question + UI + Bin Question + UI + Bin
|