SevenZipCompressor.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. using Masuit.Tools.Systems;
  2. using SharpCompress.Archives;
  3. using SharpCompress.Common;
  4. using SharpCompress.Writers;
  5. using System;
  6. using System.Collections.Concurrent;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Net.Http;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using System.Web;
  14. namespace Masuit.Tools.Files;
  15. /// <summary>
  16. /// 7z压缩
  17. /// </summary>
  18. public class SevenZipCompressor : ISevenZipCompressor
  19. {
  20. private readonly HttpClient _httpClient;
  21. /// <summary>
  22. ///
  23. /// </summary>
  24. /// <param name="httpClient"></param>
  25. public SevenZipCompressor(HttpClient httpClient)
  26. {
  27. _httpClient = httpClient;
  28. }
  29. /// <summary>
  30. /// 将多个文件压缩到一个文件流中,可保存为zip文件,方便于web方式下载
  31. /// </summary>
  32. /// <param name="files">多个文件路径,文件或文件夹,或网络路径http/https</param>
  33. /// <param name="rootdir"></param>
  34. /// <param name="archiveType"></param>
  35. /// <returns>文件流</returns>
  36. public PooledMemoryStream ZipStream(IEnumerable<string> files, string rootdir = "", ArchiveType archiveType = ArchiveType.Zip)
  37. {
  38. using var archive = CreateZipArchive(files, rootdir, archiveType);
  39. var ms = new PooledMemoryStream();
  40. archive.SaveTo(ms, new WriterOptions(CompressionType.LZMA)
  41. {
  42. LeaveStreamOpen = true,
  43. ArchiveEncoding = new ArchiveEncoding()
  44. {
  45. Default = Encoding.UTF8
  46. }
  47. });
  48. return ms;
  49. }
  50. /// <summary>
  51. /// 将文件夹压缩到一个文件流中,可保存为zip文件,方便于web方式下载
  52. /// </summary>
  53. /// <param name="dir">文件夹</param>
  54. /// <param name="rootdir"></param>
  55. /// <param name="archiveType"></param>
  56. /// <returns>文件流</returns>
  57. public PooledMemoryStream ZipStream(string dir, string rootdir = "", ArchiveType archiveType = ArchiveType.Zip)
  58. {
  59. return ZipStream(Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories), rootdir, archiveType);
  60. }
  61. /// <summary>
  62. /// 将多个文件压缩到一个文件流中,可保存为zip文件,方便于web方式下载
  63. /// </summary>
  64. /// <param name="streams">多个文件流</param>
  65. /// <param name="archiveType"></param>
  66. /// <param name="disposeAllStreams">是否需要释放所有流</param>
  67. /// <returns>文件流</returns>
  68. public PooledMemoryStream ZipStream(DisposableDictionary<string, Stream> streams, ArchiveType archiveType = ArchiveType.Zip, bool disposeAllStreams = false)
  69. {
  70. using var archive = ArchiveFactory.Create(archiveType);
  71. foreach (var pair in streams)
  72. {
  73. archive.AddEntry(pair.Key, pair.Value, true);
  74. }
  75. var ms = new PooledMemoryStream();
  76. archive.SaveTo(ms, new WriterOptions(CompressionType.LZMA)
  77. {
  78. LeaveStreamOpen = true,
  79. ArchiveEncoding = new ArchiveEncoding()
  80. {
  81. Default = Encoding.UTF8
  82. }
  83. });
  84. if (disposeAllStreams)
  85. {
  86. streams.Dispose();
  87. }
  88. return ms;
  89. }
  90. /// <summary>
  91. /// 压缩文件夹
  92. /// </summary>
  93. /// <param name="dir">文件夹</param>
  94. /// <param name="zipFile">压缩到...</param>
  95. /// <param name="rootdir">压缩包内部根文件夹</param>
  96. /// <param name="archiveType"></param>
  97. public void Zip(string dir, string zipFile, string rootdir = "", ArchiveType archiveType = ArchiveType.Zip)
  98. {
  99. Zip(Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories), zipFile, rootdir, archiveType);
  100. }
  101. /// <summary>
  102. /// 压缩多个文件
  103. /// </summary>
  104. /// <param name="files">多个文件路径,文件或文件夹</param>
  105. /// <param name="zipFile">压缩到...</param>
  106. /// <param name="rootdir">压缩包内部根文件夹</param>
  107. /// <param name="archiveType"></param>
  108. public void Zip(IEnumerable<string> files, string zipFile, string rootdir = "", ArchiveType archiveType = ArchiveType.Zip)
  109. {
  110. using var archive = CreateZipArchive(files, rootdir, archiveType);
  111. archive.SaveTo(zipFile, new WriterOptions(CompressionType.LZMA)
  112. {
  113. LeaveStreamOpen = true,
  114. ArchiveEncoding = new ArchiveEncoding()
  115. {
  116. Default = Encoding.UTF8
  117. }
  118. });
  119. }
  120. /// <summary>
  121. /// 压缩多个文件
  122. /// </summary>
  123. /// <param name="streams">多个文件流</param>
  124. /// <param name="zipFile">压缩到...</param>
  125. /// <param name="archiveType"></param>
  126. /// <param name="disposeAllStreams">是否需要释放所有流</param>
  127. public void Zip(DisposableDictionary<string, Stream> streams, string zipFile, ArchiveType archiveType = ArchiveType.Zip, bool disposeAllStreams = false)
  128. {
  129. using var archive = ArchiveFactory.Create(archiveType);
  130. foreach (var pair in streams)
  131. {
  132. archive.AddEntry(pair.Key, pair.Value, true);
  133. }
  134. archive.SaveTo(zipFile, new WriterOptions(CompressionType.LZMA)
  135. {
  136. LeaveStreamOpen = true,
  137. ArchiveEncoding = new ArchiveEncoding()
  138. {
  139. Default = Encoding.UTF8
  140. }
  141. });
  142. if (disposeAllStreams)
  143. {
  144. streams.Dispose();
  145. }
  146. }
  147. /// <summary>
  148. /// 解压文件,自动检测压缩包类型
  149. /// </summary>
  150. /// <param name="compressedFile">rar文件</param>
  151. /// <param name="dir">解压到...</param>
  152. /// <param name="ignoreEmptyDir">忽略空文件夹</param>
  153. public void Decompress(string compressedFile, string dir, bool ignoreEmptyDir = true)
  154. {
  155. if (string.IsNullOrEmpty(dir))
  156. {
  157. dir = Path.GetDirectoryName(compressedFile);
  158. }
  159. ArchiveFactory.WriteToDirectory(compressedFile, Directory.CreateDirectory(dir).FullName, new ExtractionOptions()
  160. {
  161. ExtractFullPath = true,
  162. Overwrite = true
  163. });
  164. }
  165. /// <summary>
  166. /// 创建zip包
  167. /// </summary>
  168. /// <param name="files"></param>
  169. /// <param name="rootdir"></param>
  170. /// <param name="archiveType"></param>
  171. /// <returns></returns>
  172. private IWritableArchive CreateZipArchive(IEnumerable<string> files, string rootdir, ArchiveType archiveType)
  173. {
  174. var archive = ArchiveFactory.Create(archiveType);
  175. var dic = GetFileEntryMaps(files);
  176. var remoteUrls = files.Distinct().Where(s => s.StartsWith("http")).Select(s =>
  177. {
  178. try
  179. {
  180. return new Uri(s);
  181. }
  182. catch (UriFormatException)
  183. {
  184. return null;
  185. }
  186. }).Where(u => u != null).ToList();
  187. foreach (var pair in dic)
  188. {
  189. archive.AddEntry(Path.Combine(rootdir, pair.Value), pair.Key);
  190. }
  191. if (remoteUrls.Any())
  192. {
  193. var streams = new ConcurrentDictionary<string, Stream>();
  194. Parallel.ForEach(remoteUrls, url =>
  195. {
  196. _httpClient.GetAsync(url).ContinueWith(async t =>
  197. {
  198. if (t.IsCompleted)
  199. {
  200. var res = await t;
  201. if (res.IsSuccessStatusCode)
  202. {
  203. var stream = await res.Content.ReadAsStreamAsync();
  204. streams[Path.Combine(rootdir, Path.GetFileName(HttpUtility.UrlDecode(url.AbsolutePath)))] = stream;
  205. }
  206. }
  207. }).Wait();
  208. });
  209. foreach (var pair in streams)
  210. {
  211. archive.AddEntry(pair.Key, pair.Value, true);
  212. }
  213. }
  214. return archive;
  215. }
  216. /// <summary>
  217. /// 获取文件路径和zip-entry的映射
  218. /// </summary>
  219. /// <param name="files"></param>
  220. /// <returns></returns>
  221. private Dictionary<string, string> GetFileEntryMaps(IEnumerable<string> files)
  222. {
  223. var fileList = new List<string>();
  224. void GetFilesRecurs(string path)
  225. {
  226. //遍历目标文件夹的所有文件
  227. fileList.AddRange(Directory.GetFiles(path));
  228. //遍历目标文件夹的所有文件夹
  229. foreach (var directory in Directory.GetDirectories(path))
  230. {
  231. GetFilesRecurs(directory);
  232. }
  233. }
  234. files.Where(s => !s.StartsWith("http")).ForEach(s =>
  235. {
  236. if (Directory.Exists(s))
  237. {
  238. GetFilesRecurs(s);
  239. }
  240. else
  241. {
  242. fileList.Add(s);
  243. }
  244. });
  245. if (!fileList.Any())
  246. {
  247. return new Dictionary<string, string>();
  248. }
  249. var dirname = new string(fileList.First().Substring(0, fileList.Min(s => s.Length)).TakeWhile((c, i) => fileList.All(s => s[i] == c)).ToArray());
  250. if (!Directory.Exists(dirname))
  251. {
  252. dirname = Directory.GetParent(dirname).FullName;
  253. }
  254. return fileList.ToDictionary(s => s, s => s.Substring(dirname.Length));
  255. }
  256. }