// Copyright (C) Stichting Deltares and State of the Netherlands 2024. All rights reserved. // // This file is part of Riskeer. // // Riskeer is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using Core.Common.Util; namespace Core.Common.TestUtil { /// /// This class can be used to set temporary files while testing. /// Disposing an instance of this class will delete the files. /// /// /// The following is an example for how to use this class: /// /// using(new FileDisposeHelper(new[]{"pathToFile"})) { /// // Perform tests with files /// } /// /// public class FileDisposeHelper : IDisposable { private const int numberOfAdditionalDeleteAttempts = 3; private readonly Dictionary filePathStreams; private bool disposed; /// /// Creates a new instance of . /// /// Paths of the files that will be created, if the path is valid. /// Thrown when one of the files in could /// not be created by the system. public FileDisposeHelper(IEnumerable filePaths) { filePathStreams = new Dictionary(); foreach (string filePath in filePaths) { filePathStreams.Add(filePath, null); } Create(); } /// /// Creates a new instance of . /// /// Path of the file that will be created, if valid. /// Thrown when the file could not be created by the system. public FileDisposeHelper(string filePath) : this(new[] { filePath }) {} /// /// Declines sharing of the files specified in the constructor. /// /// Thrown when one of the files could not be locked. /// /// Files are unlocked when disposing the instance. public void LockFiles() { IEnumerable> notLockedFiles = filePathStreams.Where(f => f.Value == null).ToArray(); foreach (KeyValuePair filePathStream in notLockedFiles) { LockFile(filePathStream.Key); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Frees the unmanaged resources and deletes the files defined in . /// When is true, the managed resources are freed as well. /// /// Indicates whether the method call comes from the method. /// Thrown when any of the files in is in use. /// -or- There is an open handle on the file, /// and the operating system is Windows XP or earlier. This open handle can result from enumerating /// directories and files. For more information, see How to: Enumerate Directories and Files. protected virtual void Dispose(bool disposing) { if (disposed) { return; } KeyValuePair[] dictionary = filePathStreams.ToArray(); foreach (KeyValuePair filePathStream in dictionary) { filePathStream.Value?.Dispose(); filePathStreams[filePathStream.Key] = null; var attempts = 0; while (!TryDeleteFile(filePathStream.Key) && attempts < numberOfAdditionalDeleteAttempts) { attempts++; GC.WaitForPendingFinalizers(); Thread.Sleep(10); } } if (disposing) { filePathStreams.Clear(); } disposed = true; } private static bool TryDeleteFile(string filePath) { try { DeleteFile(filePath); } catch (Exception e) { if (e is IOException) { return false; } // Ignore other exceptions } return true; } private void LockFile(string filePath) { try { filePathStreams[filePath] = File.OpenWrite(filePath); } catch (IOException exception) { throw new InvalidOperationException($"Unable to lock '{filePath}'.", exception); } } /// /// Creates the temporary files. /// /// Thrown when the file could not be created by the system. private void Create() { foreach (string filePath in filePathStreams.Keys.ToArray()) { CreateFile(filePath); } } /// /// Creates a file at at the given file path. If the is /// invalid, no file is created. /// /// Path of the new file. /// Thrown when either: /// /// The caller does not have the required permission.-or- path specified a file that is read-only. /// 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. /// The specified path is invalid (for example, it is on an unmapped drive). /// An I/O error occurred while creating the file. /// /// private static void CreateFile(string filePath) { if (IOUtils.IsValidFilePath(filePath)) { try { using (File.Create(filePath)) {} } catch (Exception e) { if (e is DirectoryNotFoundException || e is IOException || e is NotSupportedException || e is UnauthorizedAccessException) { throw new ArgumentException(e.Message); } throw; } } } /// /// Deletes a file at the given file path. If the is /// invalid, no file is deleted (obviously). /// /// Path of the file to delete. /// Thrown when either: /// /// The specified is invalid (for example, it is on an unmapped drive). /// is in an invalid format. /// The specified 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. /// The caller does not have the required permission.-or- /// is a directory.-or- specified a read-only file. /// /// /// Thrown when the specified is in use. -or- There is an open handle on the file, /// and the operating system is Windows XP or earlier. This open handle can result from enumerating /// directories and files. For more information, see How to: Enumerate Directories and Files. private static void DeleteFile(string filePath) { if (IOUtils.IsValidFilePath(filePath)) { try { File.Delete(filePath); } catch (Exception e) when (e is DirectoryNotFoundException || e is NotSupportedException || e is UnauthorizedAccessException) { throw new ArgumentException(e.Message); } } } ~FileDisposeHelper() { Dispose(false); } } }