Files
Common-VS2019/SacsStandardBase/Base/IO/FileUtil.cs
T
2025-11-20 17:17:40 +00:00

361 lines
14 KiB
C#

using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Sarand.Net.Standard.Base.IO
{
//=======================================================================
public static class FileUtil
{
//-------------------------------------------------------------------
// U t i l i t i e s
//-------------------------------------------------------------------
public static String GetFileLengthFmt(long length)
{
// Longs run out around EB
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
if (length == 0) return "0" + suf[0];
long bytes = Math.Abs(length);
int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
double num = Math.Round(bytes / Math.Pow(1024, place), place > 0 ? 1 : 0);
if (num >= 1000.0)
{
// Avoid values with 4 digits (and thousands separator).
place++;
num = Math.Round(bytes / Math.Pow(1024, place), place > 0 ? 1 : 0, MidpointRounding.AwayFromZero);
}
return (Math.Sign(length) * num).ToString(place > 0 ? "N1" : "N0") + " " + suf[place];
}
//-------------------------------------------------------------------
// P a t h
//-------------------------------------------------------------------
public static String GetPathDelim(String path)
{
if (!StringUtil.IsEmpty(path) && path.IndexOf(Path.AltDirectorySeparatorChar) >= 0)
return Path.AltDirectorySeparatorChar.ToString();
return Path.PathSeparator.ToString();
}
//-------------------------------------------------------------------
// D i r e c t o r y
//-------------------------------------------------------------------
public delegate bool DirectoryExistsFn(String directoryPath);
public static DirectoryExistsFn fnDirectoryExists { get; set; } = null;
public static bool IsDirectory(String directoryPath)
{
try
{
// Alternative method, if specified.
if (fnDirectoryExists != null)
return fnDirectoryExists(directoryPath);
// Default method.
return Directory.Exists(directoryPath);
}
catch (Exception exc) { SaLogger.Debug($"FileUtil.IsDirectory {ExceptionUtil.GetErrorStateMessage(exc)}"); }
return false;
}
public delegate String DirectoryGetParentFn(String filepath);
public static DirectoryGetParentFn fnDirectoryGetParent { get; set; }
public static String GetParentDirectory(String filepath)
{
try
{
// Alternative method, if specified.
if (fnDirectoryGetParent != null)
return fnDirectoryGetParent(filepath);
// Default method.
DirectoryInfo parentDirectoryInfo = Directory.GetParent(filepath);
if (parentDirectoryInfo != null)
return parentDirectoryInfo.FullName;
}
catch (Exception exc) { SaLogger.Debug($"FileUtil.GetParentDirectory {ExceptionUtil.GetErrorStateMessage(exc)}"); }
return String.Empty;
}
public delegate bool DirectoryCreateDirectoryFn(String filepath);
public static DirectoryCreateDirectoryFn fnDirectoryCreateDirectory { get; set; } = null;
public static bool CreateDirectory(String directoryPath)
{
try
{
// Alternative method, if specified.
if (fnDirectoryCreateDirectory != null)
return fnDirectoryCreateDirectory(directoryPath);
// Default method.
DirectoryInfo info = Directory.CreateDirectory(directoryPath);
return info.Exists;
}
catch (Exception exc) { SaLogger.Debug($"FileUtil.CreateDirectory {ExceptionUtil.GetErrorStateMessage(exc)}"); }
return false;
}
public static bool ValidateDirectory(String directoryPath, bool allowCreation = false)
{
// Validate parent folder first.
String parentDirectoryPath = GetParentDirectory(directoryPath);
if (!StringUtil.IsEmpty(parentDirectoryPath) && !ValidateDirectory(parentDirectoryPath, allowCreation))
return false;
// Validate folder itself.
if (!IsDirectory(directoryPath) && allowCreation)
CreateDirectory(directoryPath);
return IsDirectory(directoryPath);
}
//-------------------------------------------------------------------
// F i l e
//-------------------------------------------------------------------
public static DateTime GetFileLastWriteTime(String filePath)
{
try
{
return File.GetLastWriteTime(filePath);
}
catch (Exception exc)
{
SaLogger.Warn($"{typeof(FileUtil).Name}.GetFileLastWriteTime {ExceptionUtil.GetErrorStateMessage(exc)}");
}
return DateTime.MinValue;
}
//-------------------------------------------------------------------
// File utilities.
public delegate bool FileExistsFn(String filePath);
public static FileExistsFn fnFileExists { get; set; } = null;
/// <summary>
/// Test whether file exists.
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static bool IsFile(String filePath)
{
try
{
// Alternative method, if specified.
if (fnFileExists != null)
return fnFileExists(filePath);
// Default method.
return File.Exists(filePath);
}
catch (Exception exc) { SaLogger.Debug($"FileUtil.IsFile {ExceptionUtil.GetErrorStateMessage(exc)}"); }
return false;
}
//-------------------------------------------------------------------
public delegate String[] FileReadAllLinesFn(String filePath);
public static FileReadAllLinesFn fnFileReadAllLines { get; set; } = null;
/// <summary>
/// Read all lines from a file.
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static String[] ReadAllFileLines(String filePath)
{
try
{
// Alternative method, if specified.
if (fnFileReadAllLines != null)
return fnFileReadAllLines(filePath);
// Default method.
return File.ReadAllLines(filePath);
}
catch (Exception exc) { SaLogger.Debug($"FileUtil.ReadAllFileLines {ExceptionUtil.GetErrorStateMessage(exc)}"); }
return new String[0];
}
//-------------------------------------------------------------------
/// <summary>
/// <para>Locate the specified file in a folder path relative to the specified folder.</para>
/// <para>Search is in the fspecified folder and then each parent folder in turn.</para>
/// <para>Any subDirectorites at each level found in the suppleid list are also searched.</para>
/// </summary>
public static String LocateFileRelative(String baseFolder, String filename, List<String> subDirectories)
{
if (String.IsNullOrEmpty(filename)) return String.Empty;
String rootfolder = SacsSystem.ApplicationFolder;
#if DEBUG
SaLogger.Trace("FileUtil.LocateFileRelative Root folder = " + rootfolder);
#endif
String directory = rootfolder;
while (!directory.IsEmpty())
{
// Does this directory contain the specified file:
String filepath = directory + Path.AltDirectorySeparatorChar + filename;
if (IsFile(filepath)) return filepath;
// Check sub-directories:
if (subDirectories != null)
{
foreach (var subDirectory in subDirectories)
{
filepath = directory + Path.AltDirectorySeparatorChar + subDirectory + Path.AltDirectorySeparatorChar + filename;
if (IsFile(filepath)) return filepath;
}
}
directory = GetParentDirectory(directory);
}
return String.Empty;
}
/// <summary>Locate file in path relative to application folder.</summary>
public static String LocateFileRelative(String filename, List<String> subDirectories)
{
return LocateFileRelative(SacsSystem.ApplicationFolder, filename, subDirectories);
}
//-------------------------------------------------------------------
// L o c a t e F i l e s
//-------------------------------------------------------------------
// Locate files in the folder that match the file and one of the specified extensions.
// Returned list is the full path for each file.
public static List<String> LocateFiles(String folder, String file, List<String> extensions, int maxSubFolders)
{
List<String> files = new List<string>();
if (!DirectoryUtil.DirectoryExists(folder) || StringUtil.IsEmpty(file)) return files;
// Locate matching files in the specified folder.
String fileSpec = file + "." + (extensions.Count == 1 ? extensions[0] : "*");
foreach (String filename in DirectoryUtil.EnumerateFiles(folder, fileSpec))
{
String filePath = folder + GetPathDelim(folder) + filename;
bool validFile = true;
// Verify the extension:
if (extensions.Count > 0)
{
String extension = Path.GetExtension(filePath);
validFile = extensions.Any(item => item.EqualsCI(extension));
}
if (validFile)
files.Add(filePath);
}
if (maxSubFolders > 0)
{
// Locate sub folders:
foreach (String subFolder in DirectoryUtil.EnumerateDirectories(folder))
{
if (subFolder == "." || subFolder == "..") continue;
files.AddRange(LocateFiles(subFolder, file, extensions, maxSubFolders - 1));
}
}
return files;
}
//-------------------------------------------------------------------
/// <summary>
/// Replace invalid characters with '_'.
/// <para>Invalid characters: \/:*?\"&lt;>|</para>
/// </summary>
/// <param name="filename"></param>
/// <returns></returns>
public static String FormatFilename(String filename)
{
if (StringUtil.IsEmpty(filename)) return String.Empty;
// Replace invalid characters with '_'.
filename = filename.ReplaceAny("\\/:*?\"<>|", '_');
filename = StringUtil.ReplaceAll(filename, "__", "_");
filename = StringUtil.ReplaceAll(filename, "..", ".");
filename = filename.ReplaceAll(new string[] { "_ ", " _", " " }, " ");
return filename;
}
//-------------------------------------------------------------------
// Shell32 alternatives to file utilities to avoid 'path too long' exceptions etc.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode/*Auto*/)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
[DllImport("shell32.dll", CharSet = CharSet.Unicode/*Auto*/)]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
//-------------------------------------------------------------------
// Create/Open file handle.
// Ref. http://www.pinvoke.net/default.aspx/kernel32.getfiletime
private const uint GENERIC_READ = 0x80000000;
private const uint FILE_SHARE_READ = 0x1;
private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
private const int INVALID_HANDLE_VALUE = -1;
private const uint OPEN_EXISTING = 3;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode/*Auto*/, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr SecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
//-------------------------------------------------------------------
// File date/times.
// Ref. http://www.pinvoke.net/default.aspx/kernel32.getfiletime
// REf. https://msdn.microsoft.com/en-us/library/windows/desktop/ms724320(v=vs.85).aspx
public class FileTimes
{
public DateTime CreationTime { get; internal set; } = DateTime.MinValue;
public DateTime LastAccessTime { get; internal set; } = DateTime.MinValue;
public DateTime LastWriteTime { get; internal set; } = DateTime.MinValue;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileTime(IntPtr hFile, ref FILETIME lpCreationTime, ref FILETIME lpLastAccessTime, ref FILETIME lpLastWriteTime);
[StructLayout(LayoutKind.Sequential)]
private struct FILETIME
{
public uint dwLowDateTime;
public uint dwHighDateTime;
}
public static FileTimes GetFileTimes(String filePath)
{
FileTimes fileTimes = new FileTimes();
try
{
// .Net versions first.
// These fail on long file paths.
fileTimes.CreationTime = File.GetCreationTime(filePath).Date;
fileTimes.LastAccessTime = File.GetLastAccessTime(filePath).Date;
fileTimes.LastWriteTime = File.GetLastWriteTime(filePath).Date;
return fileTimes;
}
catch (Exception) { }
// If .Net methods fail, try P/Invoke via kernel32.
IntPtr ptr = IntPtr.Zero;
FILETIME ftCreationTime = new FILETIME();
FILETIME ftLastAccessTime = new FILETIME();
FILETIME ftLastWriteTime = new FILETIME();
try
{
// NB Opening file will fail if it's open elsewhere.
ptr = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
if (ptr.ToInt32() == INVALID_HANDLE_VALUE)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
if (GetFileTime(ptr, ref ftCreationTime, ref ftLastAccessTime, ref ftLastWriteTime) != true)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
fileTimes.CreationTime = DateTime.FromFileTimeUtc((((long)ftCreationTime.dwHighDateTime) << 32) | ((uint)ftCreationTime.dwLowDateTime));
fileTimes.LastAccessTime = DateTime.FromFileTimeUtc((((long)ftLastAccessTime.dwHighDateTime) << 32) | ((uint)ftLastAccessTime.dwLowDateTime));
fileTimes.LastWriteTime = DateTime.FromFileTimeUtc((((long)ftLastWriteTime.dwHighDateTime) << 32) | ((uint)ftLastWriteTime.dwLowDateTime));
}
catch (Exception) { }
finally
{
if (ptr != IntPtr.Zero && ptr.ToInt32() != INVALID_HANDLE_VALUE) CloseHandle(ptr);
}
return fileTimes;
}
//-------------------------------------------------------------------
}
//=======================================================================
}