Program.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // See https://aka.ms/new-console-template for more information
  2. using Microsoft.Win32.SafeHandles;
  3. using System.Runtime.InteropServices;
  4. using System.Security.Cryptography;
  5. using System.Text;
  6. Console.WriteLine("Installer is suck");
  7. if (args.Length == 0)
  8. {
  9. Console.WriteLine("Usage: <inDir> <outDir>");
  10. return;
  11. }
  12. var dirIn = Path.GetFullPath(args[0]);
  13. var dirOut = Path.GetFullPath(args[1]);
  14. var files = Directory.GetFiles(dirIn);
  15. foreach (var file in files)
  16. {
  17. try
  18. {
  19. Console.WriteLine($"> {file}");
  20. var hl = HardLinkHelper.GetFileLinkCount(file);
  21. if (hl > 1)
  22. {
  23. Console.WriteLine(" SKIP");
  24. continue;
  25. }
  26. var info = new FileInfo(file);
  27. Console.WriteLine($" Len: {info.Length:N0}");
  28. Console.Write(" SHA256...");
  29. string hexHash;
  30. {
  31. using var inputStream = info.OpenRead();
  32. using var sha256 = SHA256.Create();
  33. var hash = sha256.ComputeHash(inputStream);
  34. hexHash = Convert.ToHexString(hash);
  35. }
  36. Console.WriteLine($" {hexHash}");
  37. var targetPath = Path.Combine(dirOut, $"{info.Length:0-000-000-000}_{hexHash}{info.Extension}");
  38. if (File.Exists(targetPath))
  39. {
  40. try
  41. {
  42. File.Delete(file);
  43. }
  44. catch (UnauthorizedAccessException)
  45. {
  46. Console.WriteLine($" Warn: Failure to delete {file}, moved to append ._del");
  47. File.Move(file, file + "._del");
  48. }
  49. }
  50. else
  51. {
  52. File.Move(file, targetPath);
  53. }
  54. HardLinkHelper.CreateHardLink(file, targetPath);
  55. Console.WriteLine($" Collapse to {targetPath} <");
  56. }
  57. catch (Exception e)
  58. {
  59. Console.WriteLine($"Error:{e.Message}");
  60. }
  61. Console.WriteLine();
  62. }
  63. public static class HardLinkHelper
  64. {
  65. // P/Invoke 声明
  66. [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
  67. private static extern IntPtr FindFirstFileNameW(
  68. string lpFileName,
  69. uint dwFlags,
  70. ref uint lpdwBufferSize,
  71. StringBuilder lpLinkName
  72. );
  73. [DllImport("kernel32.dll", SetLastError = true)]
  74. private static extern bool FindNextFileNameW(
  75. IntPtr hFindStream,
  76. ref uint lpdwBufferSize,
  77. StringBuilder lpLinkName
  78. );
  79. [DllImport("kernel32.dll", SetLastError = true)]
  80. private static extern bool FindClose(IntPtr hFindStream);
  81. [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
  82. private static extern bool CreateHardLink(
  83. string lpFileName,
  84. string lpExistingFileName,
  85. IntPtr lpSecurityAttributes
  86. );
  87. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  88. private static extern bool GetFileInformationByHandle(
  89. SafeFileHandle hFile,
  90. out BY_HANDLE_FILE_INFORMATION lpFileInformation
  91. );
  92. [StructLayout(LayoutKind.Sequential)]
  93. private struct BY_HANDLE_FILE_INFORMATION
  94. {
  95. public uint FileAttributes;
  96. public uint FileCreationTimeLow;
  97. public uint FileCreationTimeHigh;
  98. public uint LastAccessTimeLow;
  99. public uint LastAccessTimeHigh;
  100. public uint LastWriteTimeLow;
  101. public uint LastWriteTimeHigh;
  102. public uint VolumeSerialNumber;
  103. public uint FileSizeHigh;
  104. public uint FileSizeLow;
  105. public uint NumberOfLinks; // 硬链接数量
  106. public uint FileIndexHigh;
  107. public uint FileIndexLow;
  108. }
  109. [DllImport("kernel32.dll")]
  110. private static extern bool GetVolumePathName(string lpszFileName,
  111. [Out] StringBuilder lpszVolumePathName, uint cchBufferLength);
  112. [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  113. private static extern SafeFileHandle CreateFile(
  114. string lpFileName,
  115. [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
  116. [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
  117. IntPtr lpSecurityAttributes,
  118. [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
  119. [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
  120. IntPtr hTemplateFile);
  121. [DllImport("kernel32.dll", SetLastError = true)]
  122. [return: MarshalAs(UnmanagedType.Bool)]
  123. private static extern bool CloseHandle(SafeHandle hObject);
  124. [DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
  125. private static extern bool PathAppend([In, Out] StringBuilder pszPath, string pszMore);
  126. public static bool CreateHardLink(string link, string to)
  127. {
  128. return CreateHardLink(link, to, nint.Zero);
  129. }
  130. public static int GetFileLinkCount(string filepath)
  131. {
  132. int result = 0;
  133. SafeFileHandle handle = CreateFile(filepath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero);
  134. BY_HANDLE_FILE_INFORMATION fileInfo = new BY_HANDLE_FILE_INFORMATION();
  135. if (GetFileInformationByHandle(handle, out fileInfo))
  136. result = (int)fileInfo.NumberOfLinks;
  137. CloseHandle(handle);
  138. return result;
  139. }
  140. //public static string[] GetFileSiblingHardLinks(string filepath)
  141. //{
  142. // List<string> result = new List<string>();
  143. // uint stringLength = 256;
  144. // StringBuilder sb = new StringBuilder(1024);
  145. // GetVolumePathName(filepath, sb, stringLength);
  146. // string volume = sb.ToString();
  147. // sb.Length = 0; stringLength = 1024;
  148. // IntPtr findHandle = FindFirstFileNameW(filepath, 0, ref stringLength, sb);
  149. // if (findHandle.ToInt64() != -1)
  150. // {
  151. // do
  152. // {
  153. // StringBuilder pathSb = new StringBuilder(volume, 1024);
  154. // PathAppend(pathSb, sb.ToString());
  155. // result.Add(pathSb.ToString());
  156. // sb.Length = 0; stringLength = 1024;
  157. // } while (FindNextFileNameW(findHandle, ref stringLength, sb));
  158. // FindClose(findHandle);
  159. // return result.ToArray();
  160. // }
  161. // return null;
  162. //}
  163. ///// <summary>
  164. ///// 获取指定文件的所有硬链接路径
  165. ///// </summary>
  166. ///// <param name="filePath">目标文件的路径</param>
  167. ///// <returns>文件的所有硬链接路径集合</returns>
  168. //public static IEnumerable<string> GetHardLinks(string filePath)
  169. //{
  170. // var hardLinks = new List<string>();
  171. // try
  172. // {
  173. // // 获取文件信息
  174. // using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read))
  175. // {
  176. // BY_HANDLE_FILE_INFORMATION fileInfo;
  177. // if (GetFileInformationByHandle(fileStream.SafeFileHandle, out fileInfo))
  178. // {
  179. // Console.WriteLine($"硬链接数量: {fileInfo.NumberOfLinks}");
  180. // if (fileInfo.NumberOfLinks > 1)
  181. // {
  182. // uint bufferSize = 1024;
  183. // StringBuilder linkName = new StringBuilder((int)bufferSize);
  184. // IntPtr handle = FindFirstFileNameW(filePath, 0, ref bufferSize, linkName);
  185. // if (handle != IntPtr.Zero)
  186. // {
  187. // do
  188. // {
  189. // hardLinks.Add(linkName.ToString());
  190. // bufferSize = 1024;
  191. // linkName.Clear();
  192. // } while (FindNextFileNameW(handle, ref bufferSize, linkName));
  193. // FindClose(handle);
  194. // }
  195. // }
  196. // }
  197. // else
  198. // {
  199. // Console.WriteLine("无法获取文件信息.");
  200. // }
  201. // }
  202. // }
  203. // catch (Exception ex)
  204. // {
  205. // Console.WriteLine($"错误: {ex.Message}");
  206. // }
  207. // return hardLinks;
  208. //}
  209. }