Beispiel #1
0
 /// <summary>
 /// Replaces the contents of this file with the contents of another file, deleting the original file, and creating a backup of the replaced file and optionally ignores merge errors.
 /// </summary>
 /// <param name="destPath">The path of the file being replaced.</param>
 /// <param name="destBackupPath">The path of the backup file (maybe null, in that case, it doesn't create any backup)</param>
 /// <param name="ignoreMetadataErrors"><c>true</c> to ignore merge errors (such as attributes and access control lists (ACLs)) from the replaced file to the replacement file; otherwise, <c>false</c>.</param>
 public void ReplaceTo(UPath destPath, UPath destBackupPath, bool ignoreMetadataErrors)
 {
     FileSystem.ReplaceFile(Path, destPath, destBackupPath, ignoreMetadataErrors);
 }
Beispiel #2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="FileEntry"/> class.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The file path.</param>
 public FileEntry(IFileSystem fileSystem, UPath path) : base(fileSystem, path)
 {
 }
Beispiel #3
0
        /// <summary>
        /// Tries to get a <see cref="FileSystemEntry"/> for the specified path. If the file or directory does not exist, returns null.
        /// </summary>
        /// <param name="fileSystem">The file system.</param>
        /// <param name="path">The file or directory path.</param>
        /// <returns>A new <see cref="FileSystemEntry"/> from the specified path.</returns>
        public static FileSystemEntry TryGetFileSystemEntry(this IFileSystem fileSystem, UPath path)
        {
            var fileExists = fileSystem.FileExists(path);

            if (fileExists)
            {
                return(new FileEntry(fileSystem, path));
            }
            var directoryExists = fileSystem.DirectoryExists(path);

            if (directoryExists)
            {
                return(new DirectoryEntry(fileSystem, path));
            }

            return(null);
        }
Beispiel #4
0
 /// <summary>Moves a specified file to a new location, providing the option to specify a new file name.</summary>
 /// <param name="destFileName">The path to move the file to, which can specify a different file name. </param>
 /// <exception cref="T:System.IO.IOException">
 ///     An I/O error occurs, such as the destination file already exists or the
 ///     destination device is not ready.
 /// </exception>
 /// <exception cref="T:System.ArgumentNullException">
 ///     <paramref name="destFileName" /> is null.
 /// </exception>
 /// <exception cref="T:System.ArgumentException">
 ///     <paramref name="destFileName" /> is empty, contains only white spaces, or contains invalid characters.
 /// </exception>
 /// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
 /// <exception cref="T:System.UnauthorizedAccessException">
 ///     <paramref name="destFileName" /> is read-only or is a directory.
 /// </exception>
 /// <exception cref="T:System.IO.FileNotFoundException">The file is not found. </exception>
 /// <exception cref="T:System.IO.DirectoryNotFoundException">
 ///     The specified path is invalid, such as being on an unmapped
 ///     drive.
 /// </exception>
 /// <exception cref="T:System.IO.PathTooLongException">
 ///     The specified path, file name, or both exceed the system-defined
 ///     maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names
 ///     must be less than 260 characters.
 /// </exception>
 /// <exception cref="T:System.NotSupportedException">
 ///     <paramref name="destFileName" /> contains a colon (:) in the middle of the string.
 /// </exception>
 public void MoveTo(UPath destFileName)
 {
     FileSystem.MoveFile(Path, destFileName);
 }
Beispiel #5
0
        private SearchPattern(ref UPath path, ref string searchPattern)
        {
            path.AssertAbsolute();
            if (searchPattern == null)
            {
                throw new ArgumentNullException(nameof(searchPattern));
            }

            this._exactMatch = null;
            this._regexMatch = null;

            // Optimized path, most common case
            if (searchPattern == "*")
            {
                return;
            }

            if (searchPattern.StartsWith("/"))
            {
                throw new ArgumentException($"The search pattern `{searchPattern}` cannot start by an absolute path `/`");
            }

            searchPattern = searchPattern.Replace('\\', '/');

            // If the path contains any directory, we need to concatenate the directory part with the input path
            if (searchPattern.IndexOf('/') > 0)
            {
                var pathPattern = new UPath(searchPattern);
                var directory   = pathPattern.GetDirectory();
                if (!directory.IsNull && !directory.IsEmpty)
                {
                    path /= directory;
                }
                searchPattern = pathPattern.GetName();

                // If the search pattern is again a plain any, optimized path
                if (searchPattern == "*")
                {
                    return;
                }
            }

            var           startIndex = 0;
            StringBuilder builder    = null;

            try
            {
                int nextIndex;
                while ((nextIndex = searchPattern.IndexOfAny(SpecialChars, startIndex)) >= 0)
                {
                    if (builder == null)
                    {
                        builder = UPath.GetSharedStringBuilder();
                        builder.Append("^");
                    }

                    var lengthToEscape = nextIndex - startIndex;
                    if (lengthToEscape > 0)
                    {
                        var toEscape = Regex.Escape(searchPattern.Substring(startIndex, lengthToEscape));
                        builder.Append(toEscape);
                    }

                    var c = searchPattern[nextIndex];
                    var regexPatternPart = c == '*' ? "[^/]*" : "[^/]";
                    builder.Append(regexPatternPart);

                    startIndex = nextIndex + 1;
                }
                if (builder == null)
                {
                    this._exactMatch = searchPattern;
                }
                else
                {
                    var length = searchPattern.Length - startIndex;
                    if (length > 0)
                    {
                        var toEscape = Regex.Escape(searchPattern.Substring(startIndex, length));
                        builder.Append(toEscape);
                    }

                    builder.Append("$");

                    var regexPattern = builder.ToString();
                    this._regexMatch = new Regex(regexPattern);
                }
            }
            finally
            {
                if (builder != null)
                {
                    builder.Length = 0;
                }
            }
        }
Beispiel #6
0
        /// <summary>
        ///     Copies a file between two filesystems.
        /// </summary>
        /// <param name="fs">The source filesystem</param>
        /// <param name="destFileSystem">The destination filesystem</param>
        /// <param name="srcPath">The source path of the file to copy from the source filesystem</param>
        /// <param name="destPath">The destination path of the file in the destination filesystem</param>
        /// <param name="overwrite"><c>true</c> to overwrite an existing destination file</param>
        public static void CopyFileCross(this IFileSystem fs, IFileSystem destFileSystem, UPath srcPath, UPath destPath, bool overwrite)
        {
            if (destFileSystem == null)
            {
                throw new ArgumentNullException(nameof(destFileSystem));
            }

            // If this is the same filesystem, use the file system directly to perform the action
            if (fs == destFileSystem)
            {
                fs.CopyFile(srcPath, destPath, overwrite);
                return;
            }

            srcPath.AssertAbsolute(nameof(srcPath));
            if (!fs.FileExists(srcPath))
            {
                throw NewFileNotFoundException(srcPath);
            }

            destPath.AssertAbsolute(nameof(destPath));
            var destDirectory = destPath.GetDirectory();

            if (!destFileSystem.DirectoryExists(destDirectory))
            {
                throw NewDirectoryNotFoundException(destDirectory);
            }

            if (destFileSystem.FileExists(destPath) && !overwrite)
            {
                throw new IOException($"The destination file path `{destPath}` already exist and overwrite is false");
            }

            using (var sourceStream = fs.OpenFile(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var copied = false;
                try
                {
                    using (var destStream = destFileSystem.OpenFile(destPath, FileMode.Create, FileAccess.Write, FileShare.Read))
                    {
                        sourceStream.CopyTo(destStream);
                    }

                    // NOTE: For some reasons, we can sometimes get an Unauthorized access if we try to set the LastWriteTime after the SetAttributes
                    // So we setup it here.
                    destFileSystem.SetLastWriteTime(destPath, fs.GetLastWriteTime(srcPath));

                    // Preserve attributes and LastWriteTime as a regular File.Copy
                    destFileSystem.SetAttributes(destPath, fs.GetAttributes(srcPath));

                    copied = true;
                }
                finally
                {
                    if (!copied)
                    {
                        try
                        {
                            destFileSystem.DeleteFile(destPath);
                        }
                        catch
                        {
                            // ignored
                        }
                    }
                }
            }
        }
Beispiel #7
0
 /// <summary>
 /// Returns an enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files and directories.</param>
 /// <returns>An enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.</returns>
 public static IEnumerable <FileSystemEntry> EnumerateFileSystemEntries(this IFileSystem fileSystem, UPath path)
 {
     return(EnumerateFileSystemEntries(fileSystem, path, "*"));
 }
Beispiel #8
0
 /// <summary>
 /// Parses and normalize the specified path and <see cref="SearchPattern"/>.
 /// </summary>
 /// <param name="path">The path.</param>
 /// <param name="searchPattern">The search pattern.</param>
 /// <returns>An instance of <see cref="SearchPattern"/> in order to use <see cref="Match(Zio.UPath)"/> on a path.</returns>
 public static SearchPattern Parse(ref UPath path, ref string searchPattern)
 {
     return(new SearchPattern(ref path, ref searchPattern));
 }
Beispiel #9
0
 /// <summary>
 /// Returns an enumerable collection of file or directory names in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files or directories.</param>
 /// <param name="searchPattern">The search string to match against the names of directories in path. This parameter can contain a combination
 /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
 /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory
 /// or should include all subdirectories.
 /// The default value is TopDirectoryOnly.</param>
 /// <returns>An enumerable collection of the full names (including paths) for the files and directories in the directory specified by path.</returns>
 public static IEnumerable <UPath> EnumeratePaths(this IFileSystem fileSystem, UPath path, string searchPattern, SearchOption searchOption)
 {
     if (searchPattern == null)
     {
         throw new ArgumentNullException(nameof(searchPattern));
     }
     return(fileSystem.EnumeratePaths(path, searchPattern, searchOption, SearchTarget.Both));
 }
Beispiel #10
0
 /// <summary>
 /// Returns an enumerable collection of <see cref="DirectoryEntry"/> that match a search pattern in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for directories.</param>
 /// <param name="searchPattern">The search string to match against the names of directories in path. This parameter can contain a combination
 /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
 /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory
 /// or should include all subdirectories.
 /// The default value is TopDirectoryOnly.</param>
 /// <returns>An enumerable collection of <see cref="DirectoryEntry"/> from the specified path.</returns>
 public static IEnumerable <DirectoryEntry> EnumerateDirectoryEntries(this IFileSystem fileSystem, UPath path, string searchPattern, SearchOption searchOption)
 {
     if (searchPattern == null)
     {
         throw new ArgumentNullException(nameof(searchPattern));
     }
     foreach (var subPath in EnumerateDirectories(fileSystem, path, searchPattern, searchOption))
     {
         yield return(new DirectoryEntry(fileSystem, subPath));
     }
 }
Beispiel #11
0
 /// <summary>
 /// Returns an enumerable collection of file or directory names in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files or directories.</param>
 /// <returns>An enumerable collection of the full names (including paths) for the files and directories in the directory specified by path.</returns>
 public static IEnumerable <UPath> EnumeratePaths(this IFileSystem fileSystem, UPath path)
 {
     return(EnumeratePaths(fileSystem, path, "*"));
 }
Beispiel #12
0
 /// <summary>
 /// Returns an enumerable collection of file names in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files.</param>
 /// <param name="searchPattern">The search string to match against the names of directories in path. This parameter can contain a combination
 /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
 /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory
 /// or should include all subdirectories.
 /// The default value is TopDirectoryOnly.</param>
 /// <returns>An enumerable collection of the full names (including paths) for the files in the directory specified by path.</returns>
 public static IEnumerable <UPath> EnumerateFiles(this IFileSystem fileSystem, UPath path, string searchPattern, SearchOption searchOption)
 {
     if (searchPattern == null)
     {
         throw new ArgumentNullException(nameof(searchPattern));
     }
     foreach (var subPath in fileSystem.EnumeratePaths(path, searchPattern, searchOption, SearchTarget.File))
     {
         yield return(subPath);
     }
 }
Beispiel #13
0
 /// <summary>
 /// Creates or overwrites a file in the specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path and name of the file to create.</param>
 /// <returns>A stream that provides read/write access to the file specified in path.</returns>
 public static Stream CreateFile(this IFileSystem fileSystem, UPath path)
 {
     path.AssertAbsolute();
     return(fileSystem.OpenFile(path, FileMode.Create, FileAccess.ReadWrite));
 }
Beispiel #14
0
 /// <summary>Copies an existing file to a new file, allowing the overwriting of an existing file.</summary>
 /// <returns>
 ///     A new file, or an overwrite of an existing file if <paramref name="overwrite" /> is true. If the file exists
 ///     and <paramref name="overwrite" /> is false, an <see cref="T:System.IO.IOException" /> is thrown.
 /// </returns>
 /// <param name="destFileName">The name of the new file to copy to. </param>
 /// <param name="overwrite">true to allow an existing file to be overwritten; otherwise, false. </param>
 /// <exception cref="T:System.ArgumentException">
 ///     <paramref name="destFileName" /> is empty, contains only white spaces, or contains invalid characters.
 /// </exception>
 /// <exception cref="T:System.IO.IOException">
 ///     An error occurs, or the destination file already exists and
 ///     <paramref name="overwrite" /> is false.
 /// </exception>
 /// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
 /// <exception cref="T:System.ArgumentNullException">
 ///     <paramref name="destFileName" /> is null.
 /// </exception>
 /// <exception cref="T:System.IO.DirectoryNotFoundException">
 ///     The directory specified in <paramref name="destFileName" />
 ///     does not exist.
 /// </exception>
 /// <exception cref="T:System.UnauthorizedAccessException">
 ///     A directory path is passed in, or the file is being moved to a
 ///     different drive.
 /// </exception>
 /// <exception cref="T:System.IO.PathTooLongException">
 ///     The specified path, file name, or both exceed the system-defined
 ///     maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names
 ///     must be less than 260 characters.
 /// </exception>
 /// <exception cref="T:System.NotSupportedException">
 ///     <paramref name="destFileName" /> contains a colon (:) in the middle of the string.
 /// </exception>
 public FileEntry CopyTo(UPath destFileName, bool overwrite)
 {
     FileSystem.CopyFile(Path, destFileName, overwrite);
     return(new FileEntry(FileSystem, destFileName));
 }
Beispiel #15
0
 /// <summary>
 /// Returns an enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files and directories.</param>
 /// <param name="searchPattern">The search string to match against the names of directories in path. This parameter can contain a combination
 /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
 /// <returns>An enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.</returns>
 public static IEnumerable <FileSystemEntry> EnumerateFileSystemEntries(this IFileSystem fileSystem, UPath path, string searchPattern)
 {
     if (searchPattern == null)
     {
         throw new ArgumentNullException(nameof(searchPattern));
     }
     return(EnumerateFileSystemEntries(fileSystem, path, searchPattern, SearchOption.TopDirectoryOnly));
 }
Beispiel #16
0
        private SearchPattern(ref UPath path, ref string searchPattern)
        {
            path.AssertAbsolute();
            if (searchPattern == null)
            {
                throw new ArgumentNullException(nameof(searchPattern));
            }

            _exactMatch = null;
            _regexMatch = null;

            // Optimized path, most common case
            if (searchPattern == "*")
            {
                return;
            }

            if (searchPattern.StartsWith("/"))
            {
                throw new ArgumentException($"The search pattern `{searchPattern}` cannot start by an absolute path `/`");
            }

            searchPattern = searchPattern.Replace('\\', '/');

            // If the path contains any directory, we need to concatenate the directory part with the input path
            if (searchPattern.IndexOf('/') > 0)
            {
                var pathPattern = new UPath(searchPattern);
                var directory   = pathPattern.GetDirectory();
                if (!directory.IsNull && !directory.IsEmpty)
                {
                    path = path / directory;
                }
                searchPattern = pathPattern.GetName();

                // If the search pattern is again a plain any, optimized path
                if (searchPattern == "*")
                {
                    return;
                }
            }

            bool          appendSpecialCaseForExt3Chars = false;
            var           startIndex = 0;
            int           nextIndex;
            StringBuilder builder = null;

            try
            {
                while ((nextIndex = searchPattern.IndexOfAny(SpecialChars, startIndex)) >= 0)
                {
                    if (builder == null)
                    {
                        builder = UPath.GetSharedStringBuilder();
                        builder.Append("^");
                    }

                    var lengthToEscape = nextIndex - startIndex;
                    if (lengthToEscape > 0)
                    {
                        var toEscape = Regex.Escape(searchPattern.Substring(startIndex, lengthToEscape));
                        builder.Append(toEscape);
                    }

                    var c = searchPattern[nextIndex];
                    var regexPatternPart = c == '*' ? "[^/]*" : "[^/]";
                    builder.Append(regexPatternPart);

                    // If the specified extension is exactly three characters long,
                    // the method returns files with extensions that begin with the specified extension.
                    // For example, "*.xls" returns both "book.xls" and "book.xlsx".
                    // 012345
                    // *.txt
                    if (c == '*' && nextIndex + 5 == searchPattern.Length && searchPattern[nextIndex + 1] == '.' && searchPattern.IndexOf('.', nextIndex + 2) < 0)
                    {
                        appendSpecialCaseForExt3Chars = true;
                    }

                    startIndex = nextIndex + 1;
                }
                if (builder == null)
                {
                    _exactMatch = searchPattern;
                }
                else
                {
                    var length = searchPattern.Length - startIndex;
                    if (length > 0)
                    {
                        var toEscape = Regex.Escape(searchPattern.Substring(startIndex, length));
                        builder.Append(toEscape);
                    }

                    if (appendSpecialCaseForExt3Chars)
                    {
                        builder.Append("[^/]*");
                    }

                    builder.Append("$");

                    var regexPattern = builder.ToString();
                    _regexMatch = new Regex(regexPattern);
                }
            }
            finally
            {
                if (builder != null)
                {
                    builder.Length = 0;
                }
            }
        }
Beispiel #17
0
 /// <summary>
 /// Returns an enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.
 /// </summary>
 /// <param name="fileSystem">The file system.</param>
 /// <param name="path">The path of the directory to look for files and directories.</param>
 /// <param name="searchPattern">The search string to match against the names of directories in path. This parameter can contain a combination
 /// of valid literal path and wildcard (* and ?) characters (see Remarks), but doesn't support regular expressions.</param>
 /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory
 /// or should include all subdirectories.
 /// The default value is TopDirectoryOnly.</param>
 /// <param name="searchTarget">The search target either <see cref="SearchTarget.Both"/> or only <see cref="SearchTarget.Directory"/> or <see cref="SearchTarget.File"/>. Default is <see cref="SearchTarget.Both"/></param>
 /// <returns>An enumerable collection of <see cref="FileSystemEntry"/> that match a search pattern in a specified path.</returns>
 public static IEnumerable <FileSystemEntry> EnumerateFileSystemEntries(this IFileSystem fileSystem, UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget = SearchTarget.Both)
 {
     if (searchPattern == null)
     {
         throw new ArgumentNullException(nameof(searchPattern));
     }
     foreach (var subPath in fileSystem.EnumeratePaths(path, searchPattern, searchOption, searchTarget))
     {
         yield return(fileSystem.DirectoryExists(subPath) ? (FileSystemEntry) new DirectoryEntry(fileSystem, subPath) : new FileEntry(fileSystem, subPath));
     }
 }
Beispiel #18
0
 /// <summary>
 /// Normalizes the specified path and <see cref="SearchPattern"/>.
 /// </summary>
 /// <param name="path">The path.</param>
 /// <param name="searchPattern">The search pattern.</param>
 public static void Normalize(ref UPath path, ref string searchPattern)
 {
     Parse(ref path, ref searchPattern);
 }
Beispiel #19
0
        /// <summary>
        /// Gets a <see cref="FileSystemEntry"/> for the specified path. If the file or directory does not exist, throws a <see cref="FileNotFoundException"/>
        /// </summary>
        /// <param name="fileSystem">The file system.</param>
        /// <param name="path">The file or directory path.</param>
        /// <returns>A new <see cref="FileSystemEntry"/> from the specified path.</returns>
        public static FileSystemEntry GetFileSystemEntry(this IFileSystem fileSystem, UPath path)
        {
            var fileExists = fileSystem.FileExists(path);

            if (fileExists)
            {
                return(new FileEntry(fileSystem, path));
            }
            var directoryExists = fileSystem.DirectoryExists(path);

            if (directoryExists)
            {
                return(new DirectoryEntry(fileSystem, path));
            }

            throw NewFileNotFoundException(path);
        }
        public FileChangedEventArgs(IFileSystem fileSystem, WatcherChangeTypes changeType, UPath fullPath)
        {
            if (fileSystem is null)
            {
                throw new ArgumentNullException(nameof(fileSystem));
            }
            fullPath.AssertNotNull(nameof(fullPath));
            fullPath.AssertAbsolute(nameof(fullPath));

            FileSystem = fileSystem;
            ChangeType = changeType;
            FullPath   = fullPath;
            Name       = fullPath.GetName();
        }
Beispiel #21
0
        /// <summary>
        ///     Moves a file between two filesystems.
        /// </summary>
        /// <param name="fs">The source filesystem</param>
        /// <param name="destFileSystem">The destination filesystem</param>
        /// <param name="srcPath">The source path of the file to move from the source filesystem</param>
        /// <param name="destPath">The destination path of the file in the destination filesystem</param>
        public static void MoveFileCross(this IFileSystem fs, IFileSystem destFileSystem, UPath srcPath, UPath destPath)
        {
            if (destFileSystem == null)
            {
                throw new ArgumentNullException(nameof(destFileSystem));
            }

            // If this is the same filesystem, use the file system directly to perform the action
            if (fs == destFileSystem)
            {
                fs.MoveFile(srcPath, destPath);
                return;
            }

            // Check source
            srcPath.AssertAbsolute(nameof(srcPath));
            if (!fs.FileExists(srcPath))
            {
                throw NewFileNotFoundException(srcPath);
            }

            // Check destination
            destPath.AssertAbsolute(nameof(destPath));
            var destDirectory = destPath.GetDirectory();

            if (!destFileSystem.DirectoryExists(destDirectory))
            {
                throw NewDirectoryNotFoundException(destPath);
            }

            if (destFileSystem.DirectoryExists(destPath))
            {
                throw NewDestinationDirectoryExistException(destPath);
            }

            if (destFileSystem.FileExists(destPath))
            {
                throw NewDestinationFileExistException(destPath);
            }

            using (var sourceStream = fs.OpenFile(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var copied = false;
                try
                {
                    using (var destStream = destFileSystem.OpenFile(destPath, FileMode.Create, FileAccess.Write, FileShare.Read))
                    {
                        sourceStream.CopyTo(destStream);
                    }

                    // Preserve all attributes and times
                    destFileSystem.SetAttributes(destPath, fs.GetAttributes(srcPath));
                    destFileSystem.SetCreationTime(destPath, fs.GetCreationTime(srcPath));
                    destFileSystem.SetLastAccessTime(destPath, fs.GetLastAccessTime(srcPath));
                    destFileSystem.SetLastWriteTime(destPath, fs.GetLastWriteTime(srcPath));
                    copied = true;
                }
                finally
                {
                    if (!copied)
                    {
                        try
                        {
                            destFileSystem.DeleteFile(destPath);
                        }
                        catch
                        {
                            // ignored
                        }
                    }
                }
            }

            var deleted = false;

            try
            {
                fs.DeleteFile(srcPath);
                deleted = true;
            }
            finally
            {
                if (!deleted)
                {
                    try
                    {
                        destFileSystem.DeleteFile(destPath);
                    }
                    catch
                    {
                        // ignored
                    }
                }
            }
        }