topcss/my-notes

长路径的问题 LongPath

topcss opened this issue · 1 comments

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

/// <summary>
/// 超长文件路径的处理
/// 参照
/// https://github.com/Dennis-Koch/ambeth/blob/9a13deba2f588b3826a3bfb75906680382fff8fe/ambeth/Ambeth.Util/ambeth/util/LongPath.cs
/// </summary>
public class LongPath
{
    #region win32 api 处理错误消息

    //// 这样使用,抛出异常
    //int errCode = Marshal.GetLastWin32Error();
    //throw new Exception(GetSysErrMsg(errCode));

    [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", SetLastError = true)]
    public static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

    [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
    public static extern bool FreeLibrary(int hModule);

    [DllImport("Kernel32.dll")]
    public extern static int FormatMessage(int flag, ref IntPtr source, int msgid, int langid, ref string buf, int size, ref IntPtr args);

    /// <summary>
    /// 获取系统错误信息描述
    /// </summary>
    /// <param name="errCode">系统错误码</param>
    /// <returns></returns>
    public static string GetSysErrMsg(int errCode)
    {
        IntPtr tempptr = IntPtr.Zero;
        string msg = null;
        FormatMessage(0x1300, ref tempptr, errCode, 0, ref msg, 255, ref tempptr);
        return msg;
    }
    #endregion

    #region win32 define
    [StructLayout(LayoutKind.Sequential)]
    public class SECURITY_ATTRIBUTES
    {
        public int nLength;
        public IntPtr pSecurityDescriptor;
        public int bInheritHandle;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    internal static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool CreateDirectory(String lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern SafeFileHandle CreateFile(String lpFileName, FileAccess dwDesiredAccess,
        FileShare dwShareMode,
        SECURITY_ATTRIBUTES lpSecurityAttributes,
        FileMode creationDisposition,
        FileAttributes dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern bool DeleteFile(String lpFileName);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern FileAttributes GetFileAttributes(String lpPathName);

    /// <summary>
    /// Verifies that a path is a valid directory.
    /// </summary>
    /// <param name="pszPath">A pointer to a null-terminated string of maximum length MAX_PATH that contains the path to verify.</param>
    /// <returns>Returns (BOOL)FILE_ATTRIBUTE_DIRECTORY if the path is a valid directory; otherwise, FALSE.</returns>
    [DllImport("shlwapi.dll", EntryPoint = "PathIsDirectoryW", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool PathIsDirectory([MarshalAs(UnmanagedType.LPTStr)]string pszPath);

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    public static extern int SHCreateDirectoryEx(IntPtr hwnd, string pszPath, IntPtr psa);
    #endregion

    #region 长地址的处理
    // http://www.cplusplus.com/forum/lounge/110597/

    // max # of characters we support using the "\\?\" syntax
    // (0x7FFF + 1 for NULL terminator)
    const int PATHCCH_MAX_CCH = 0x8000;
    const string LONG_PATH_ID = @"\\?\";
    const string UNC_PREFIX = @"\\";
    const string UNC_LONG_ID = @"\\?\UNC\";
    const string CUR_DIR_REL_PATH_ID = @".\";

    const char WILDCAR_CHAR_ASTER = '*';
    const char WILDCAR_CHAR_QUEMARK = '?';

    const string DIR_DOWN = "..";
    const string DIR_UP = ".";

    static Regex RegIP = new Regex(@"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"); // IP表达式
    static Regex RegUNC = new Regex(@"\\[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"); // UNC表达式

    /// <summary>
    /// 获取长路径的表达方式
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    static string GetLongPath(string path)
    {
        path = path.Replace('/', Path.DirectorySeparatorChar);

        if (RegIP.Match(path).Success)// 包含 IP 地址的情况
        {
            if (!path.StartsWith(UNC_LONG_ID))// 还不存在UNC地址处理
            {
                if (RegUNC.Match(path).Success)// 如果双斜杠开头的情况
                {
                    return UNC_LONG_ID + path.Substring(2);
                }
                else
                {
                    return UNC_LONG_ID + path;// 如果直接IP的情况
                }
            }
        }
        else
        {
            if (!path.StartsWith(LONG_PATH_ID))// 不存在长地址处理的情况
            {
                return LONG_PATH_ID + path;
            }
        }
        return path;
    }
    #endregion

    #region 自定义方法
    public static SafeFileHandle CreateFile(String fileName, FileAccess fileAccess, FileShare fileShare, FileMode fileMode)
    {
        String formattedName = GetLongPath(fileName);

        int fileNameIndex = formattedName.LastIndexOf(Path.DirectorySeparatorChar);
        if (fileNameIndex >= 0)
        {
            String dirName = formattedName.Substring(0, fileNameIndex);
            CreateDir(dirName);
        }

        SafeFileHandle fileHandle = CreateFile(formattedName, fileAccess, fileShare, null, fileMode, 0, IntPtr.Zero);
        if (fileHandle.IsInvalid)
        {
            int lastWin32Error = Marshal.GetLastWin32Error();
            throw new Win32Exception(lastWin32Error);
        }
        return fileHandle;
    }


    public static void DeleteDir(String dir)
    {
        FileAttributes fileAttributes = GetFileAttributes(dir);
        if (fileAttributes == 0)
        {
            return;
        }
        if (DeleteFile(dir))
        {
            return;
        }
    }

    /// <summary>
    /// 复制超长路径的文件
    /// </summary>
    /// <param name="existingFileName"></param>
    /// <param name="newFileName"></param>
    /// <returns></returns>
    public static bool CopyFile(string existingFileName, string newFileName)
    {
        existingFileName = GetLongPath(existingFileName);
        newFileName = GetLongPath(newFileName);

        // 如果目标文件夹不存在则创建
        int fileNameIndex = newFileName.LastIndexOf(Path.DirectorySeparatorChar);
        if (fileNameIndex >= 0)
        {
            string dir = newFileName.Substring(0, fileNameIndex);

            CreateDir(dir);
        }

        // 然后调用unicode版本的Windows API
        return CopyFile(existingFileName, newFileName, false);
    }

    /// <summary>
    /// 文件或文件夹是否存在
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static bool IsExists(string path)
    {
        return GetFileAttributes(path) >= 0;
    }

    private static readonly Object StaticLockObj = new object();

    /// <summary>
    /// 创建长路径的多级目录
    /// </summary>
    /// <param name="dir"></param>
    /// <returns></returns>
    public static void CreateDir(String dir)
    {
        lock (StaticLockObj)
        {
            dir = GetLongPath(dir);

            // 存在就返回
            FileAttributes fileAttributes = GetFileAttributes(dir);
            if (fileAttributes >= 0) { return; }
            // 若不存在,就创建,
            if (CreateDirectory(dir, null)) { return; }

            // 创建失败,则尝试创建上级目录
            int fileNameIndex = dir.LastIndexOf(Path.DirectorySeparatorChar);
            if (fileNameIndex >= 0)
            {
                String parentDir = dir.Substring(0, fileNameIndex);

                // 若上级目录不存在则创建
                if (!IsExists(parentDir))
                {
                    // 根路径不参加上级目录:c: -> c: | \\192.168.1.1\ -> \
                    if (parentDir.EndsWith(":") || @"\\?\\" == parentDir) { }
                    else
                    {
                        CreateDir(parentDir);
                    }
                }

                if (!CreateDirectory(dir, null))
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
        }
    }


    /// <summary>
    /// 拷贝文件夹
    /// </summary>
    /// <param name="srcdir"></param>
    /// <param name="desdir"></param>
    public static void CopyDirectory(string srcdir, string desdir)
    {
        string folderName = srcdir.Substring(srcdir.LastIndexOf("\\") + 1);

        string desfolderdir = desdir + "\\" + folderName;

        if (desdir.LastIndexOf("\\") == (desdir.Length - 1))
        {
            desfolderdir = desdir + folderName;
        }
        string[] filenames = Directory.GetFileSystemEntries(srcdir);

        foreach (string file in filenames)// 遍历所有的文件和目录
        {
            FileAttributes fileAttributes = GetFileAttributes(file);
            if (fileAttributes >= 0)
            {
                if (PathIsDirectory(file))
                {
                    string currentdir = desfolderdir + "\\" + file.Substring(file.LastIndexOf("\\") + 1);

                    CreateDir(currentdir);

                    CopyDirectory(file, desfolderdir);
                }
                else
                {
                    string srcfileName = file.Substring(file.LastIndexOf("\\") + 1);

                    srcfileName = desfolderdir + "\\" + srcfileName;

                    if (!CopyFile(file, srcfileName))
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
            }
        }
    }
    #endregion
}