// See https://aka.ms/new-console-template for more information using Microsoft.Win32.SafeHandles; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; Console.WriteLine("Installer is suck"); if (args.Length == 0) { Console.WriteLine("Usage: "); return; } var dirIn = Path.GetFullPath(args[0]); var dirOut = Path.GetFullPath(args[1]); var files = Directory.GetFiles(dirIn); foreach (var file in files) { try { Console.WriteLine($"> {file}"); var hl = HardLinkHelper.GetFileLinkCount(file); if (hl > 1) { Console.WriteLine(" SKIP"); continue; } var info = new FileInfo(file); Console.WriteLine($" Len: {info.Length:N0}"); Console.Write(" SHA256..."); string hexHash; { using var inputStream = info.OpenRead(); using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(inputStream); hexHash = Convert.ToHexString(hash); } Console.WriteLine($" {hexHash}"); var targetPath = Path.Combine(dirOut, $"{info.Length:0-000-000-000}_{hexHash}{info.Extension}"); if (File.Exists(targetPath)) { try { File.Delete(file); } catch (UnauthorizedAccessException) { Console.WriteLine($" Warn: Failure to delete {file}, moved to append ._del"); File.Move(file, file + "._del"); } } else { File.Move(file, targetPath); } HardLinkHelper.CreateHardLink(file, targetPath); Console.WriteLine($" Collapse to {targetPath} <"); } catch (Exception e) { Console.WriteLine($"Error:{e.Message}"); } Console.WriteLine(); } public static class HardLinkHelper { // P/Invoke 声明 [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr FindFirstFileNameW( string lpFileName, uint dwFlags, ref uint lpdwBufferSize, StringBuilder lpLinkName ); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FindNextFileNameW( IntPtr hFindStream, ref uint lpdwBufferSize, StringBuilder lpLinkName ); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FindClose(IntPtr hFindStream); [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] private static extern bool CreateHardLink( string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes ); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool GetFileInformationByHandle( SafeFileHandle hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation ); [StructLayout(LayoutKind.Sequential)] private struct BY_HANDLE_FILE_INFORMATION { public uint FileAttributes; public uint FileCreationTimeLow; public uint FileCreationTimeHigh; public uint LastAccessTimeLow; public uint LastAccessTimeHigh; public uint LastWriteTimeLow; public uint LastWriteTimeHigh; public uint VolumeSerialNumber; public uint FileSizeHigh; public uint FileSizeLow; public uint NumberOfLinks; // 硬链接数量 public uint FileIndexHigh; public uint FileIndexLow; } [DllImport("kernel32.dll")] private static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern SafeFileHandle CreateFile( string lpFileName, [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, IntPtr lpSecurityAttributes, [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(SafeHandle hObject); [DllImport("shlwapi.dll", CharSet = CharSet.Auto)] private static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore); public static bool CreateHardLink(string link, string to) { return CreateHardLink(link, to, nint.Zero); } public static int GetFileLinkCount(string filepath) { int result = 0; SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION(); if (GetFileInformationByHandle(handle, out fileInfo)) result = (int)fileInfo.NumberOfLinks; CloseHandle(handle); return result; } //public static string[] GetFileSiblingHardLinks(string filepath) //{ // List result = new List(); // uint stringLength = 256; // StringBuilder sb = new StringBuilder(1024); // GetVolumePathName(filepath, sb, stringLength); // string volume = sb.ToString(); // sb.Length = 0; stringLength = 1024; // IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb); // if (findHandle.ToInt64() != -1) // { // do // { // StringBuilder pathSb = new StringBuilder(volume, 1024); // PathAppend(pathSb, sb.ToString()); // result.Add(pathSb.ToString()); // sb.Length = 0; stringLength = 1024; // } while (FindNextFileNameW(findHandle, ref stringLength, sb)); // FindClose(findHandle); // return result.ToArray(); // } // return null; //} ///// ///// 获取指定文件的所有硬链接路径 ///// ///// 目标文件的路径 ///// 文件的所有硬链接路径集合 //public static IEnumerable GetHardLinks(string filePath) //{ // var hardLinks = new List(); // try // { // // 获取文件信息 // using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read)) // { // BY_HANDLE_FILE_INFORMATION fileInfo; // if (GetFileInformationByHandle(fileStream.SafeFileHandle, out fileInfo)) // { // Console.WriteLine($"硬链接数量: {fileInfo.NumberOfLinks}"); // if (fileInfo.NumberOfLinks > 1) // { // uint bufferSize = 1024; // StringBuilder linkName = new StringBuilder((int)bufferSize); // IntPtr handle = FindFirstFileNameW(filePath, 0, ref bufferSize, linkName); // if (handle != IntPtr.Zero) // { // do // { // hardLinks.Add(linkName.ToString()); // bufferSize = 1024; // linkName.Clear(); // } while (FindNextFileNameW(handle, ref bufferSize, linkName)); // FindClose(handle); // } // } // } // else // { // Console.WriteLine("无法获取文件信息."); // } // } // } // catch (Exception ex) // { // Console.WriteLine($"错误: {ex.Message}"); // } // return hardLinks; //} }