using MediaBrowser.Common.Logging; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; namespace MediaBrowser.Controller.IO { /// /// Provides low level File access that is much faster than the File/Directory api's /// public static class FileData { public const int MAX_PATH = 260; public const int MAX_ALTERNATE = 14; public static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); /// /// Gets information about a path /// public static WIN32_FIND_DATA GetFileData(string path) { WIN32_FIND_DATA data; IntPtr handle = FindFirstFile(path, out data); bool getFilename = false; if (handle == INVALID_HANDLE_VALUE && !Path.HasExtension(path)) { if (!path.EndsWith("*")) { Logger.LogInfo("Handle came back invalid for {0}. Since this is a directory we'll try appending \\*.", path); FindClose(handle); handle = FindFirstFile(Path.Combine(path, "*"), out data); getFilename = true; } } if (handle == IntPtr.Zero) { throw new IOException("FindFirstFile failed"); } if (getFilename) { data.cFileName = Path.GetFileName(path); } FindClose(handle); data.Path = path; return data; } /// /// Gets all file system entries within a foler /// public static IEnumerable GetFileSystemEntries(string path, string searchPattern) { return GetFileSystemEntries(path, searchPattern, true, true); } /// /// Gets all files within a folder /// public static IEnumerable GetFiles(string path, string searchPattern) { return GetFileSystemEntries(path, searchPattern, true, false); } /// /// Gets all sub-directories within a folder /// public static IEnumerable GetDirectories(string path, string searchPattern) { return GetFileSystemEntries(path, searchPattern, false, true); } /// /// Gets all file system entries within a foler /// public static IEnumerable GetFileSystemEntries(string path, string searchPattern, bool includeFiles, bool includeDirectories) { string lpFileName = Path.Combine(path, searchPattern); WIN32_FIND_DATA lpFindFileData; var handle = FindFirstFile(lpFileName, out lpFindFileData); if (handle == IntPtr.Zero) { int hr = Marshal.GetLastWin32Error(); if (hr != 2 && hr != 0x12) { throw new IOException("GetFileSystemEntries failed"); } yield break; } if (IncludeInOutput(lpFindFileData.cFileName, lpFindFileData.dwFileAttributes, includeFiles, includeDirectories)) { yield return lpFindFileData; } while (FindNextFile(handle, out lpFindFileData) != IntPtr.Zero) { if (IncludeInOutput(lpFindFileData.cFileName, lpFindFileData.dwFileAttributes, includeFiles, includeDirectories)) { lpFindFileData.Path = Path.Combine(path, lpFindFileData.cFileName); yield return lpFindFileData; } } FindClose(handle); } private static bool IncludeInOutput(string cFileName, FileAttributes attributes, bool includeFiles, bool includeDirectories) { if (cFileName.Equals(".", StringComparison.OrdinalIgnoreCase)) { return false; } if (cFileName.Equals("..", StringComparison.OrdinalIgnoreCase)) { return false; } if (!includeFiles && !attributes.HasFlag(FileAttributes.Directory)) { return false; } if (!includeDirectories && attributes.HasFlag(FileAttributes.Directory)) { return false; } return true; } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr FindFirstFile(string fileName, out WIN32_FIND_DATA data); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA data); [DllImport("kernel32")] private static extern bool FindClose(IntPtr hFindFile); private const char SpaceChar = ' '; private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars(); /// /// Takes a filename and removes invalid characters /// public static string GetValidFilename(string filename) { foreach (char c in InvalidFileNameChars) { filename = filename.Replace(c, SpaceChar); } return filename; } } [StructLayout(LayoutKind.Sequential)] public struct FILETIME { public uint dwLowDateTime; public uint dwHighDateTime; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct WIN32_FIND_DATA { public FileAttributes dwFileAttributes; public FILETIME ftCreationTime; public FILETIME ftLastAccessTime; public FILETIME ftLastWriteTime; public int nFileSizeHigh; public int nFileSizeLow; public int dwReserved0; public int dwReserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = FileData.MAX_PATH)] public string cFileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = FileData.MAX_ALTERNATE)] public string cAlternate; public bool IsHidden { get { return dwFileAttributes.HasFlag(FileAttributes.Hidden); } } public bool IsSystemFile { get { return dwFileAttributes.HasFlag(FileAttributes.System); } } public bool IsDirectory { get { return dwFileAttributes.HasFlag(FileAttributes.Directory); } } public DateTime CreationTimeUtc { get { return ParseFileTime(ftCreationTime); } } public DateTime LastAccessTimeUtc { get { return ParseFileTime(ftLastAccessTime); } } public DateTime LastWriteTimeUtc { get { return ParseFileTime(ftLastWriteTime); } } private DateTime ParseFileTime(FILETIME filetime) { long highBits = filetime.dwHighDateTime; highBits = highBits << 32; return DateTime.FromFileTimeUtc(highBits + (long)filetime.dwLowDateTime); } public string Path { get; set; } } }