362 lines
14 KiB
C#
362 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Sarand.Net.Standard.Base.App;
|
|
using Sarand.Net.Standard.Base.Core;
|
|
using Sarand.Net.Standard.Base.Utils;
|
|
|
|
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 " + exc.GetType().Name + ": " + exc.Message); }
|
|
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 " + exc.GetType().Name + ": " + exc.Message); }
|
|
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 " + exc.GetType().Name + ": " + exc.Message); }
|
|
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 " + exc.GetType().Name + ": " + exc.Message);
|
|
}
|
|
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 " + exc.GetType().Name + ": " + exc.Message); }
|
|
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 " + exc.GetType().Name + ": " + exc.Message); }
|
|
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: \/:*?\"<>|</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;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|