// Copyright (C) Stichting Deltares 2021. 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);
}
}
}