2019-12-30 17:37:29 -05:00
/ * Copyright ( C ) 2008 - 2018 Peter Palotas , Jeffrey Jangli , Alexandr Normuradov
2022-05-21 15:42:27 -04:00
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the "Software" ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
2019-12-30 17:37:29 -05:00
* furnished to do so , subject to the following conditions :
2022-05-21 15:42:27 -04:00
*
* The above copyright notice and this permission notice shall be included in
2019-12-30 17:37:29 -05:00
* all copies or substantial portions of the Software .
2022-05-21 15:42:27 -04:00
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
2019-12-30 17:37:29 -05:00
* /
using NUnit.Framework ;
using System ;
using System.Globalization ;
2022-05-21 15:42:27 -04:00
using System.IO ;
2019-12-30 17:37:29 -05:00
using System.Security.AccessControl ;
2022-05-21 15:42:27 -04:00
using static System . IO . Path ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
namespace Vanara.PInvoke.Tests ;
/// <summary>Used to create a temporary directory that will be deleted once this instance is disposed.</summary>
public sealed class TemporaryDirectory : IDisposable
2019-12-30 17:37:29 -05:00
{
2022-09-01 15:36:37 -04:00
public const int OneMebibyte = 1 < < 20 ;
/// <summary>The path to the temporary folder, ending with a backslash.</summary>
private static readonly string TempPath = GetTempPath ( ) ;
public TemporaryDirectory ( ) : this ( false )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
}
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
public TemporaryDirectory ( bool isNetwork , string folderPrefix = null , string root = null )
{
if ( string . IsNullOrWhiteSpace ( folderPrefix ) )
folderPrefix = "Vanara.TempRoot" ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
if ( string . IsNullOrWhiteSpace ( root ) )
root = TempPath ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
//if (isNetwork)
// root = Alphaleonis.Win32.Filesystem.Path.LocalToUnc(root);
//UnitTestConstants.PrintUnitTestHeader(isNetwork);
do
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
Directory = new DirectoryInfo ( Combine ( root , folderPrefix + "." + RandomString ) ) ;
} while ( Directory . Exists ) ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
Directory . Create ( ) ;
}
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
~ TemporaryDirectory ( )
{
Dispose ( false ) ;
}
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
public DirectoryInfo Directory { get ; private set ; }
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns the full path to a non-existing directory with a random name, such as: "C:\Users\UserName\AppData\Local\Temp\AlphaFS.TempRoot.lpqdzf\Directory_wqáánmvh.z03".</summary>
public string RandomDirectoryFullPath = > Combine ( Directory . FullName , RandomDirectoryName ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a random directory name, such as: "Directory_wqáánmvh".</summary>
public string RandomDirectoryName = > string . Format ( CultureInfo . InvariantCulture , "Directory.{0}" , RandomString ) ;
/// <summary>Returns the full path to a non-existing file with a random name and without an extension, such as: "C:\Users\UserName\AppData\Local\Temp\AlphaFS.TempRoot.lpqdzf\File_wqáánmvh".</summary>
public string RandomFileNoExtensionFullPath = > Combine ( Directory . FullName , GetFileNameWithoutExtension ( RandomTxtFileName ) ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a random string of 8 characters in length, possibly with diacritic characters.</summary>
public string RandomString
{
get
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
var randomFileName = GetFileNameWithoutExtension ( GetRandomFileName ( ) ) ;
return randomFileName ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
//switch (new Random(DateTime.UtcNow.Millisecond).Next(1, 3))
//{
// case 1:
// return randomFileName.Replace("a", "ä").Replace("e", "ë").Replace("i", "ï").Replace("o", "ö").Replace("u", "ü");
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
// case 2: return randomFileName.Replace("a", "á").Replace("e", "é").Replace("i", "í").Replace("o", "ó").Replace("u", "ú");
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
// case 3: return randomFileName.Replace("a", "â").Replace("e", "ê").Replace("i", "î").Replace("o", "ô").Replace("u", "û");
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
// default:
// return randomFileName;
//}
}
}
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns the full path to a non-existing file with a random name, such as: "C:\Users\UserName\AppData\Local\Temp\AlphaFS.TempRoot.lpqdzf\File_wqáánmvh.txt".</summary>
public string RandomTxtFileFullPath = > Combine ( Directory . FullName , RandomTxtFileName ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns the full path to a non-existing file with a random name, such as: "File_wqáánmvh.txt".</summary>
public string RandomTxtFileName = > string . Format ( CultureInfo . InvariantCulture , "File_{0}.txt" , RandomString ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="DirectoryInfo"/> instance to an existing directory.</summary>
public DirectoryInfo CreateDirectory ( ) = > CreateDirectoryCore ( null ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="DirectoryInfo"/> instance to an existing directory.</summary>
public DirectoryInfo CreateDirectory ( string directoryNameSuffix ) = > CreateDirectoryCore ( Directory . FullName + directoryNameSuffix ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Returns a <see cref="DirectoryInfo"/> instance to an existing directory, possibly with read-only and/or hidden
/// attributes set.
/// </summary>
public DirectoryInfo CreateDirectoryRandomizedAttributes ( ) = > CreateDirectoryCore ( null , false , true , true ) ;
/// <summary>
/// Returns a <see cref="DirectoryInfo"/> instance to an existing directory, possibly with read-only and/or hidden
/// attributes set.
/// </summary>
public DirectoryInfo CreateDirectoryRandomizedAttributes ( string directoryNameSuffix ) = > CreateDirectoryCore ( Directory . FullName + directoryNameSuffix , false , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="FileInfo"/> instance to an existing file.</summary>
public FileInfo CreateFile ( ) = > CreateFileCore ( null ) ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="FileInfo"/> instance to an existing file of <paramref name="fileSize"/> bytes.</summary>
public FileInfo CreateFile ( int fileSize ) = > CreateFileCore ( null , fileSize : fileSize ) ;
2019-12-30 17:37:29 -05:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Returns a <see cref="FileInfo"/> instance to an existing file, possibly with read-only and/or hidden attributes set.
/// </summary>
public FileInfo CreateFileRandomizedAttributes ( ) = > CreateFileCore ( null , false , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Creates a directory structure populated with subdirectories and files of random size and possibly with read-only and/or hidden
/// attributes set.
/// </summary>
public DirectoryInfo CreateRandomizedAttributesTree ( int level = 1 ) = > CreateTreeCore ( null , level , false , false , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Creates a recursive directory structure populated with subdirectories and files of random size and possibly with read-only and/or
/// hidden attributes set.
/// </summary>
public DirectoryInfo CreateRecursiveRandomizedAttributesTree ( int level = 1 ) = > CreateTreeCore ( null , level , true , false , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Creates a recursive directory structure populated with subdirectories and files, possibly with randomized CreationTime,
/// LastAccessTime and/or LastWriteTime. The file size, read-only and/or hidden attributes might also be randomized.
/// </summary>
public DirectoryInfo CreateRecursiveRandomizedDatesAndAttributesTree ( int level = 1 ) = > CreateTreeCore ( null , level , true , true , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Creates a recursive directory structure populated with subdirectories and files, possibly with randomized CreationTime,
/// LastAccessTime and/or LastWriteTime.
/// </summary>
public DirectoryInfo CreateRecursiveRandomizedDatesTree ( int level = 1 ) = > CreateTreeCore ( null , level , true , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Creates a recursive directory structure populated with subdirectories and files of random size.</summary>
public DirectoryInfo CreateRecursiveTree ( int level = 1 ) = > CreateTreeCore ( null , level , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Creates a recursive directory structure populated with subdirectories and files of random size.</summary>
public DirectoryInfo CreateRecursiveTree ( int level , string rootFullPath ) = > CreateTreeCore ( rootFullPath , level , true ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="DirectoryInfo"/> instance to an existing directory.</summary>
public DirectoryInfo CreateSubDirectory ( string directoryName ) = > CreateDirectoryCore ( Combine ( Directory . FullName , directoryName ) ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Returns a <see cref="FileInfo"/> instance to an existing file.</summary>
public FileInfo CreateSubDirectoryFile ( DirectoryInfo directoryInfo , string fileName = null , int fileSize = OneMebibyte ) = > CreateFileCore ( Combine ( directoryInfo . FullName , fileName ? ? RandomTxtFileName ) , fileSize : fileSize ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Creates a directory structure populated with subdirectories and files of random size.</summary>
public DirectoryInfo CreateTree ( int level = 1 ) = > CreateTreeCore ( null , level ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <inheritdoc/>
public void Dispose ( )
{
Dispose ( true ) ;
GC . SuppressFinalize ( this ) ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
public DateTime GetRandomFileDate ( )
{
var rnd = new Random ( DateTime . Now . Millisecond ) ;
return new DateTime ( rnd . Next ( 1971 , DateTime . Now . Year ) , rnd . Next ( 1 , 12 ) , rnd . Next ( 1 , 28 ) , rnd . Next ( 0 , 23 ) , rnd . Next ( 0 , 59 ) , rnd . Next ( 0 , 59 ) ) ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>Enables or disables deny access for the current User.</summary>
public void SetDirectoryDenyPermission ( bool enable , string folderFullPath )
{
// ╔═════════════╦═════════════╦═══════════════════════════════╦════════════════════════╦══════════════════╦═══════════════════════╦═════════════╦═════════════╗
// ║ ║ folder only ║ folder, sub-folders and files ║ folder and sub-folders ║ folder and files ║ sub-folders and files ║
// sub-folders ║ files ║
// ╠═════════════╬═════════════╬═══════════════════════════════╬════════════════════════╬══════════════════╬═══════════════════════╬═════════════╬═════════════╣
// ║ Propagation ║ none ║ none ║ none ║ none ║ InheritOnly ║ InheritOnly ║ InheritOnly ║ ║ Inheritance ║ none ║ Container|Object
// ║ Container ║ Object ║ Container|Object ║ Container ║ Object ║ ╚═════════════╩═════════════╩═══════════════════════════════╩════════════════════════╩══════════════════╩═══════════════════════╩═════════════╩═════════════╝
var user = ( Environment . UserDomainName + @"\" + Environment . UserName ) . TrimStart ( '\\' ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
var rule = new FileSystemAccessRule ( user , FileSystemRights . FullControl , InheritanceFlags . ContainerInherit | InheritanceFlags . ObjectInherit , PropagationFlags . None , AccessControlType . Deny ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
DirectorySecurity dirSecurity ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
DirectoryInfo dirInfo = CreateDirectoryCore ( folderFullPath ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
// Set DENY for current User.
if ( enable )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
dirSecurity = dirInfo . GetAccessControl ( ) ;
dirSecurity . AddAccessRule ( rule ) ;
dirInfo . SetAccessControl ( dirSecurity ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
// Remove DENY for current User.
else
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
dirSecurity = dirInfo . GetAccessControl ( ) ;
dirSecurity . RemoveAccessRule ( rule ) ;
dirInfo . SetAccessControl ( dirSecurity ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <inheritdoc/>
public override string ToString ( ) = > Directory . FullName ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
private static void SetReadOnlyAndOrHiddenAttributes ( FileSystemInfo fsi , bool readOnly = false , bool hidden = false )
{
if ( readOnly & & new Random ( DateTime . UtcNow . Millisecond ) . Next ( 0 , 1000 ) % 2 = = 0 )
fsi . Attributes | = FileAttributes . ReadOnly ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
if ( hidden & & new Random ( DateTime . UtcNow . Millisecond ) . Next ( 0 , 1000 ) % 2 = = 0 )
fsi . Attributes | = FileAttributes . Hidden ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Returns a <see cref="DirectoryInfo"/> instance to an existing directory, possibly with read-only and/or hidden
/// attributes set.
/// </summary>
private DirectoryInfo CreateDirectoryCore ( string folderFullPath , bool randomizedDates = false , bool readOnly = false , bool hidden = false )
{
DirectoryInfo dirInfo = System . IO . Directory . CreateDirectory ( ! string . IsNullOrWhiteSpace ( folderFullPath ) ? folderFullPath : RandomDirectoryFullPath ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
SetRandomizedDates ( dirInfo , randomizedDates ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
SetReadOnlyAndOrHiddenAttributes ( dirInfo , readOnly , hidden ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
return dirInfo ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Returns a <see cref="FileInfo"/> instance to an existing file, possibly with read-only and/or hidden attributes set.
/// </summary>
private FileInfo CreateFileCore ( string fileFullPath , bool randomizedDates = false , bool readOnly = false , bool hidden = false , int fileSize = 0 )
{
var fileInfo = new FileInfo ( ! string . IsNullOrWhiteSpace ( fileFullPath ) ? fileFullPath : RandomTxtFileFullPath ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
using ( FileStream fs = fileInfo . Create ( ) )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
if ( fileSize > OneMebibyte )
fs . SetLength ( fileSize ) ;
else
fs . SetLength ( new Random ( DateTime . UtcNow . Millisecond ) . Next ( 0 , OneMebibyte ) ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
SetRandomizedDates ( fileInfo , randomizedDates ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
SetReadOnlyAndOrHiddenAttributes ( fileInfo , readOnly , hidden ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
return fileInfo ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
/// <summary>
/// Creates an, optional recursive, directory structure of <param name="level"/> levels deep, populated with subdirectories and files
/// of random size and possibly with read-only and/or hidden attributes set.
/// </summary>
private DirectoryInfo CreateTreeCore ( string rootFullPath , int level = 1 , bool recurse = false , bool randomizedDates = false , bool readOnly = false , bool hidden = false )
{
DirectoryInfo dirInfo = CreateDirectoryCore ( rootFullPath , randomizedDates , readOnly , hidden ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
var folderCount = 0 ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
for ( var fsoCount = 0 ; fsoCount < level ; fsoCount + + )
{
folderCount + + ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
var fsoName = RandomString + "-" + fsoCount ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
// Always create folder.
DirectoryInfo di = CreateDirectoryCore ( Combine ( dirInfo . FullName , $"Directory_{fsoName}_directory" ) , randomizedDates , readOnly , hidden ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
// Create file, every other iteration.
CreateFileCore ( Combine ( fsoCount % 2 = = 0 ? di . FullName : dirInfo . FullName , $"File_{fsoName}_file.txt" ) , randomizedDates , readOnly , hidden ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
if ( recurse )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
foreach ( var folder in System . IO . Directory . EnumerateDirectories ( dirInfo . FullName ) )
CreateTreeCore ( folder , level , false , randomizedDates , readOnly , hidden ) ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
Assert . AreEqual ( level , folderCount , "The number of folders does not equal level argument, but is expected to." ) ;
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
return dirInfo ;
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
private void Dispose ( bool isDisposing )
{
try
{
if ( isDisposing )
System . IO . Directory . Delete ( Directory . FullName , true ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
catch ( Exception ex )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
Console . WriteLine ( $"{nameof(TemporaryDirectory)} delete failure. Error: {ex.Message.Replace(Environment.NewLine, string.Empty)}" ) ;
2022-05-21 15:42:27 -04:00
}
2022-09-01 15:36:37 -04:00
}
2022-05-21 15:42:27 -04:00
2022-09-01 15:36:37 -04:00
private void SetRandomizedDates ( FileSystemInfo fsi , bool randomizedDates = false )
{
if ( randomizedDates & & new Random ( DateTime . UtcNow . Millisecond ) . Next ( 0 , 1000 ) % 2 = = 0 )
2022-05-21 15:42:27 -04:00
{
2022-09-01 15:36:37 -04:00
fsi . CreationTime = GetRandomFileDate ( ) ;
fsi . LastAccessTime = GetRandomFileDate ( ) ;
fsi . LastWriteTime = GetRandomFileDate ( ) ;
2022-05-21 15:42:27 -04:00
}
}
}