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; /// /// Test whether file exists. /// /// /// 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; /// /// Read all lines from a file. /// /// /// 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]; } //------------------------------------------------------------------- /// /// Locate the specified file in a folder path relative to the specified folder. /// Search is in the fspecified folder and then each parent folder in turn. /// Any subDirectorites at each level found in the suppleid list are also searched. /// public static String LocateFileRelative(String baseFolder, String filename, List 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; } /// Locate file in path relative to application folder. public static String LocateFileRelative(String filename, List 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 LocateFiles(String folder, String file, List extensions, int maxSubFolders) { List files = new List(); 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; } //------------------------------------------------------------------- /// /// Replace invalid characters with '_'. /// Invalid characters: \/:*?\"<>| /// /// /// 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; } //------------------------------------------------------------------- } //======================================================================= }