Welcome to Xceed .NET, .NET Standard and Xamarin Libraries! > Basic Concepts > Events > PathException event |
The PathException event is raised when an exception is caught during the processing of a path System.String, allowing the handler to correct the path and retry the operation.
The PathException event provides a convenient way to react when a path is deemed invalid for the FileSystem type.
While the FileSystem API provides common properties and methods for different mediums, the path limits for a DiskFile/DiskFolder (260 characters) is different than for a ZippedFile/ZippedFolder (>60000 characters).
Because of this, problems can occur when copying files from one medium to another. If, for example, you unzip a file that has a name longer than 260 characters to a DiskFolder, the DiskFolder class will not be able to create a DiskFile in that folder that represents the target file because the source name is invalid for that medium. By default, an exception will be thrown and the operation will fail.
Before an exception is thrown, the PathException event is raised. It provides event handlers with a modifiable path System.String that caused the failure, the type of FileSystem class involved in the attempt and an action code that tells the component how to proceed (abort, retry, ignore).
An event handler can therefore handle path situations, manipulate the offending to something acceptable for the target FileSystem type and continue with the operation.
To subscribe to the PathException event, the following steps must be performed:
Create a reference to a FileSystemEvents object.
Subscribe to the PathException event of the FileSystemEvents object using the PathExceptionEventHandler delegate class.
Create a new method that will handle the events that are raised.
Place the desired code in the newly created event handler.
This example demonstrates how to subscribe to the PathException event, a situation where the event will be raised and a possible implementation of an event handler.
static void PathExceptionExample() { // Get a reference to a source file AbstractFile sourceFile = new DiskFile( "SomeFile.dat" ); // Get a reference to a zip archive AbstractFile zipFile = new DiskFile( "PathExceptionExample.zip" ); Xceed.Zip.ZipArchive zip = new Xceed.Zip.ZipArchive( zipFile ); // Compute a very long file name (512 characters) StringBuilder stringBuilder = new StringBuilder( ); for( int i = 0; i < 32; i++ ) stringBuilder.Append( "VeryLongFilename" ); stringBuilder.Append( ".dat" ); using( AutoBatchUpdate batch = new AutoBatchUpdate( zip ) ) { // Get a reference to a zipped file that will have the very long file name AbstractFile zippedFile = zip.GetFile( stringBuilder.ToString() ); // Zip the source file to that zipped file sourceFile.CopyTo( zippedFile, true ); // Get a reference to a zipped file that has invalid characters zippedFile = zip.GetFile( "P\"ath*Exception?Example.dat" ); // Zip the source file to that zipped file sourceFile.CopyTo( zippedFile, true ); } /* Create a reference to a FileSystemEvents object */ FileSystemEvents events = new FileSystemEvents(); /* Subscribe to the PathException event of the FileSystemEvents object using the PathExceptionEventHandler delegate */ events.PathException += new PathExceptionEventHandler( OnPathException ); // Get a reference to a folder on a hard disk AbstractFolder destinationFolder = new DiskFolder( "SomeFolder" ); /* Here we extract the files from the archive. But the file in the archive * with the long filename can't be expressed on the Windows file system as it is * implemented by the .NET framework. The PathException event handler will allow * us to intervene before the operation throws an exception. */ // Extract the files from the archive zip.CopyFilesTo( events, null, destinationFolder, true, true ); } /// <summary> /// Finds an exception amongst the supplied exception stack /// </summary> /// <typeparam name="T">The exception type to find</typeparam> /// <param name="exception">The exception stack</param> /// <returns>An exception object of the specified Type or null</returns> static T FindException<T>( Exception exception ) where T : Exception { T target = default( T ); // While we have an exception while( exception != null ) { // If we can express the current exception as the target type if( ( target = exception as T ) != null ) { // We're done break; } // Go to the next exception exception = exception.InnerException; } // Return what we found return target; } /// <summary> /// Replaces a single character in the supplied String object at the specified index /// </summary> /// <param name="s"></param> /// <param name="index"></param> /// <param name="newChar"></param> /// <returns>A String object with the replaced character</returns> static string ReplaceChar( string s, int index, char newChar ) { // Put the path in a string builder System.Text.StringBuilder builder = new System.Text.StringBuilder( s ); // Replace that invalid character with 'newChar' builder[ index ] = newChar; // Update the path with the new value without the offending character s = builder.ToString(); return s; } static string ComputeSmallerName( string path, PathExceptionEventArgs e ) { // TODO: Implement logic to compute a smaller name depending on your scenario and needs return null; } static void OnPathException( object sender, PathExceptionEventArgs e ) { // Try to find a InvalidCharacterInPathException in the exception stack we got from the event InvalidCharacterInPathException invalidCharacterInPathException = FindException<InvalidCharacterInPathException>( e.Exception ); PathTooLongException pathTooLongException = FindException<PathTooLongException>( e.Exception ); ItemIsFolderException itemIsFolderException = FindException<ItemIsFolderException>( e.Exception ); ItemIsFileException itemIsFileException = FindException<ItemIsFileException>( e.Exception ); ArgumentException argumentException = FindException<ArgumentException>( e.Exception ); // If part of the path failure was caused by an invalid character if( invalidCharacterInPathException != null ) { /* The InvalidCharacterInPathException gives us exactly what character is invalid */ e.Path = ReplaceChar( e.Path, invalidCharacterInPathException.InvalidCharIndex, '_' ); // Have the FileSystem retry using the path e.Action = ItemExceptionAction.Retry; } else if( pathTooLongException != null ) { // If the exception occurred for a folder if( e.FileSystemType == typeof( AbstractFolder ) && e.Path.Length < 248 ) { /* It's the combined length of all the preceding folder names that makes * this folder name bust the limit */ // Try to create 'space' in the path by not using the folder name at all // and use the parent folder instead e.Path = ".."; // Tell the component to retry using the new value we will put in e.Path e.Action = ItemExceptionAction.Retry; } else { /* TODO: Implement ComputeSmallerName() to compute a small folder name and assign it for trial */ string smallerName = ComputeSmallerName( e.Path, e ); // If a smaller name could be computed if( String.IsNullOrEmpty( smallerName ) ) { // Skip the item e.Action = ItemExceptionAction.Ignore; } else { e.Path = smallerName; // Tell the component to retry using the new value we will put in e.Path e.Action = ItemExceptionAction.Retry; } } } else if( itemIsFolderException != null ) { /* Here, we are creating an AbstractFile object but the path represents an existing folder. There's not much we can do to fix that. If it makes sense in your scenario, you can always try to ignore the item and move on to the next one. Note that 'ignore' only applies when processing a list of items. */ //e.Action = ItemExceptionAction.Ignore; } else if( itemIsFileException != null ) { /* Here, we are creating an AbstractFolder object but the path represents an existing file. There's not much we can do to fix that. If it makes sense in your scenario, you can always try to ignore the item and move on to the next one. Note that 'ignore' only applies when processing a list of items. */ //e.Action = ItemExceptionAction.Ignore; } /* NOTE: Order is important here. ArgumentException is the more general exception that usually contains more specific inner exceptions. We test for these specific exceptions above and handle the more general exceptions last. */ else if( argumentException != null ) { /* ArgumentException is more complicated as it is less defined. It is an umbrella exception that is usually thrown when a .NET framework method or Windows itself can't process the path for 'some' reason. One thing that is worth checking is more invalid characters. System.IO.Path.GetInvalidPathChars() is what is used by the component to check the path with. But it doesn't contain things like '?', '*'. Most likely because in some contexts, they are valid. But not when you want to manipulate an actual file or folder. */ // If the problem argument is the filename if( argumentException.ParamName == "fileName" ) { // Common 'invalid' characters that are not caught by System.IO.Path.GetInvalidPathChars() char[] furtherInvalidChars = new char[] { '?', '*' }; int index; if( ( index = e.Path.IndexOfAny( furtherInvalidChars ) ) != -1 ) { e.Path = ReplaceChar( e.Path, index, '_' ); // Have the FileSystem retry using the path e.Action = ItemExceptionAction.Retry; } } } }
Private Shared Sub PathExceptionExample() ' Get a reference to a source file Dim sourceFile As AbstractFile = New DiskFile("SomeFile.dat") ' Get a reference to a zip archive Dim zipFile As AbstractFile = New DiskFile("PathExceptionExample.zip") Dim zip As New Xceed.Zip.ZipArchive(zipFile) ' Compute a very long file name (512 characters) Dim stringBuilder As New StringBuilder() For i As Integer = 0 To 31 stringBuilder.Append("VeryLongFilename") Next i stringBuilder.Append(".dat") Using batch As New AutoBatchUpdate(zip) ' Get a reference to a zipped file that will have the very long file name Dim zippedFile As AbstractFile = zip.GetFile(stringBuilder.ToString()) ' Zip the source file to that zipped file sourceFile.CopyTo(zippedFile, True) ' Get a reference to a zipped file that has invalid characters zippedFile = zip.GetFile("P""ath*Exception?Example.dat") ' Zip the source file to that zipped file sourceFile.CopyTo(zippedFile, True) End Using ' Create a reference to a FileSystemEvents object Dim events As New FileSystemEvents() ' Subscribe to the PathException event of the FileSystemEvents object using the PathExceptionEventHandler delegate AddHandler events.PathException, AddressOf OnPathException ' Get a reference to a folder on a hard disk Dim destinationFolder As AbstractFolder = New DiskFolder("SomeFolder") ' Here we extract the files from the archive. But the file in the archive ' * with the long filename can't be expressed on the Windows file system as it is ' * implemented by the .NET framework. The PathException event handler will allow ' * us to intervene before the operation throws an exception. ' Extract the files from the archive zip.CopyFilesTo(events, Nothing, destinationFolder, True, True) End Sub ''' <summary> ''' Finds an exception amongst the supplied exception stack ''' </summary> ''' <typeparam name="T">The exception type to find</typeparam> ''' <param name="exception">The exception stack</param> ''' <returns>An exception object of the specified Type or null</returns> Private Shared Function FindException(Of T As Exception)(ByVal exception As Exception) As T Dim target As T = Nothing ' While we have an exception Do While exception IsNot Nothing ' If we can express the current exception as the target type target = TryCast(exception, T) If target IsNot Nothing Then ' We're done Exit Do End If ' Go to the next exception exception = exception.InnerException Loop ' Return what we found Return target End Function ''' <summary> ''' Replaces a single character in the supplied String object at the specified index ''' </summary> ''' <param name="s"></param> ''' <param name="index"></param> ''' <param name="newChar"></param> ''' <returns>A String object with the replaced character</returns> Private Shared Function ReplaceChar(ByVal s As String, ByVal index As Integer, ByVal newChar As Char) As String ' Put the path in a string builder Dim builder As New System.Text.StringBuilder(s) ' Replace that invalid character with 'newChar' builder(index) = newChar ' Update the path with the new value without the offending character s = builder.ToString() Return s End Function Private Shared Function ComputeSmallerName(ByVal path As String, ByVal e As PathExceptionEventArgs) As String ' TODO: Implement logic to compute a smaller name depending on your scenario and needs Return Nothing End Function Private Shared Sub OnPathException(ByVal sender As Object, ByVal e As PathExceptionEventArgs) ' Try to find a InvalidCharacterInPathException in the exception stack we got from the event Dim invalidCharacterInPathException As InvalidCharacterInPathException = FindException(Of InvalidCharacterInPathException)(e.Exception) Dim pathTooLongException As PathTooLongException = FindException(Of PathTooLongException)(e.Exception) Dim itemIsFolderException As ItemIsFolderException = FindException(Of ItemIsFolderException)(e.Exception) Dim itemIsFileException As ItemIsFileException = FindException(Of ItemIsFileException)(e.Exception) Dim argumentException As ArgumentException = FindException(Of ArgumentException)(e.Exception) ' If part of the path failure was caused by an invalid character If invalidCharacterInPathException IsNot Nothing Then ' The InvalidCharacterInPathException gives us exactly what character is invalid e.Path = ReplaceChar(e.Path, invalidCharacterInPathException.InvalidCharIndex, "_"c) ' Have the FileSystem retry using the path e.Action = ItemExceptionAction.Retry ElseIf pathTooLongException IsNot Nothing Then ' If the exception occurred for a folder If e.FileSystemType Is GetType(AbstractFolder) AndAlso e.Path.Length < 248 Then ' It's the combined length of all the preceding folder names that makes ' * this folder name bust the limit ' Try to create 'space' in the path by not using the folder name at all ' and use the parent folder instead e.Path = ".." ' Tell the component to retry using the new value we will put in e.Path e.Action = ItemExceptionAction.Retry Else ' TODO: Implement ComputeSmallerName() to compute a small folder name and assign it for trial Dim smallerName As String = ComputeSmallerName(e.Path, e) ' If a smaller name could be computed If String.IsNullOrEmpty(smallerName) Then ' Skip the item e.Action = ItemExceptionAction.Ignore Else e.Path = smallerName ' Tell the component to retry using the new value we will put in e.Path e.Action = ItemExceptionAction.Retry End If End If ElseIf itemIsFolderException IsNot Nothing Then ' Here, we are creating an AbstractFile object but the path represents an existing ' folder. ' ' There's not much we can do to fix that. If it makes sense in your scenario, you ' can always try to ignore the item and move on to the next one. Note that 'ignore' ' only applies when processing a list of items. 'e.Action = ItemExceptionAction.Ignore; ElseIf itemIsFileException IsNot Nothing Then ' Here, we are creating an AbstractFolder object but the path represents an existing ' file. ' ' There's not much we can do to fix that. If it makes sense in your scenario, you ' can always try to ignore the item and move on to the next one. Note that 'ignore' ' only applies when processing a list of items. 'e.Action = ItemExceptionAction.Ignore; ' NOTE: Order is important here. ArgumentException is the more general exception that usually ' contains more specific inner exceptions. We test for these specific exceptions above and ' handle the more general exceptions last. ElseIf argumentException IsNot Nothing Then ' ArgumentException is more complicated as it is less defined. It is an umbrella exception ' that is usually thrown when a .NET framework method or Windows itself can't process the path ' for 'some' reason. ' ' One thing that is worth checking is more invalid characters. System.IO.Path.GetInvalidPathChars() ' is what is used by the component to check the path with. But it doesn't contain things like ' '?', '*'. Most likely because in some contexts, they are valid. But not when you want to ' manipulate an actual file or folder. ' If the problem argument is the filename If argumentException.ParamName = "fileName" Then ' Common 'invalid' characters that are not caught by System.IO.Path.GetInvalidPathChars() Dim furtherInvalidChars() As Char = { "?"c, "*"c } Dim index As Integer index = e.Path.IndexOfAny(furtherInvalidChars) If index <> -1 Then e.Path = ReplaceChar(e.Path, index, "_"c) ' Have the FileSystem retry using the path e.Action = ItemExceptionAction.Retry End If End If End If End Sub