| File: PathUtil\FileUtility.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Common\NuGet.Common.csproj (NuGet.Common) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace NuGet.Common { /// <summary> /// File operation helpers. /// </summary> public static class FileUtility { public static readonly int MaxTries = 3; public static readonly FileShare FileSharePermissions = FileShare.ReadWrite | FileShare.Delete; /// <summary> /// Get the full path to a new temp file /// </summary> public static string GetTempFilePath(string directory) { var fileName = $"{Guid.NewGuid()}.tmp"; return Path.GetFullPath(Path.Combine(directory, fileName)); } /// <summary> /// Lock around the output path. /// Delete the existing file with retries. /// </summary> public static async Task DeleteWithLock(string filePath) { if (filePath == null) { throw new ArgumentNullException(nameof(filePath)); } await ConcurrencyUtilities.ExecuteWithFileLockedAsync(filePath, lockedToken => { Delete(filePath); return TaskResult.Zero; }, // Do not allow this to be cancelled CancellationToken.None); } /// <summary> /// Lock around the output path. /// Delete the existing file with retries. /// Move a file with retries. /// </summary> public static async Task ReplaceWithLock(Action<string> writeSourceFile, string destFilePath) { if (writeSourceFile == null) { throw new ArgumentNullException(nameof(writeSourceFile)); } if (destFilePath == null) { throw new ArgumentNullException(nameof(destFilePath)); } await ConcurrencyUtilities.ExecuteWithFileLockedAsync(destFilePath, lockedToken => { Replace(writeSourceFile, destFilePath); return TaskResult.Zero; }, // Do not allow this to be cancelled CancellationToken.None); } /// <summary> /// Delete the existing file with retries. /// Move a file with retries. /// </summary> public static void Replace(Action<string> writeSourceFile, string destFilePath) { if (writeSourceFile == null) { throw new ArgumentNullException(nameof(writeSourceFile)); } if (destFilePath == null) { throw new ArgumentNullException(nameof(destFilePath)); } string? destFileDirectory = Path.GetDirectoryName(destFilePath); if (destFileDirectory is null) { throw new ArgumentException(paramName: destFilePath, message: "Path.GetDirectoryName(destFilePath) returned null"); } var tempPath = GetTempFilePath(destFileDirectory); try { // Write to temp path writeSourceFile(tempPath); // Delete the previous file and move the temporary file // This will throw if there is a failure Replace(tempPath, destFilePath); } catch { // Clean up the temporary file Delete(tempPath); // Throw since this failed throw; } } public static async Task ReplaceAsync(Func<string, Task> writeSourceFile, string destFilePath) { if (writeSourceFile == null) { throw new ArgumentNullException(nameof(writeSourceFile)); } if (destFilePath == null) { throw new ArgumentNullException(nameof(destFilePath)); } string? destFileDirectory = Path.GetDirectoryName(destFilePath); if (destFileDirectory is null) { throw new ArgumentException(paramName: destFilePath, message: "Path.GetDirectoryName(destFilePath) returned null"); } var tempPath = GetTempFilePath(destFileDirectory); try { // Write to temp path await writeSourceFile(tempPath); // Delete the previous file and move the temporary file // This will throw if there is a failure Replace(tempPath, destFilePath); } catch { // Clean up the temporary file Delete(tempPath); // Throw since this failed throw; } } /// <summary> /// Delete the existing file with retries. /// Move a file with retries. /// </summary> public static void Replace(string sourceFileName, string destFileName) { // Remove the old file Delete(destFileName); // Move the file Move(sourceFileName, destFileName); } /// <summary> /// Move a file with retries. /// This will not overwrite /// </summary> public static void Move(string sourceFileName, string destFileName) { if (sourceFileName == null) { throw new ArgumentNullException(nameof(sourceFileName)); } if (destFileName == null) { throw new ArgumentNullException(nameof(destFileName)); } // Run up to 3 times for (var i = 0; i < MaxTries; i++) { // Ignore exceptions for the first attempts try { File.Move(sourceFileName, destFileName); break; } catch (Exception ex) when ((i < (MaxTries - 1)) && (ex is UnauthorizedAccessException || ex is IOException)) { Sleep(100); } } } /// <summary> /// Delete a file with retries. /// </summary> public static void Delete(string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } // Run up to 3 times for (var i = 0; i < MaxTries; i++) { // Ignore exceptions for the first attempts try { if (File.Exists(path)) { File.Delete(path); } break; } catch (Exception ex) when ((i < (MaxTries - 1)) && (ex is UnauthorizedAccessException || ex is IOException)) { Sleep(100); } } } public static T SafeRead<T>(string filePath, Func<FileStream, string, T> read) { var retries = MaxTries; for (var i = 1; i <= retries; i++) { // Ignore exceptions for the first attempts try { using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileSharePermissions)) { return read(stream, filePath); } } catch (Exception ex) when ((i < retries) && (ex is UnauthorizedAccessException || ex is IOException)) { Sleep(100); } } // This will never reached, but the compiler can't detect that return default!; } public static async Task<T> SafeReadAsync<T>(string filePath, Func<FileStream, string, Task<T>> read) { var retries = MaxTries; for (var i = 1; i <= retries; i++) { // Ignore exceptions for the first attempts try { using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileSharePermissions)) { return await read(stream, filePath); } } catch (Exception ex) when ((i < retries) && (ex is UnauthorizedAccessException || ex is IOException)) { await Task.Delay(100); } } // This will never reached, but the compiler can't detect that return default!; } private static void Sleep(int ms) { // Sleep sync Thread.Sleep(ms); } } }