FilePackageTool.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. //************************************************************************
  2. // author: yuzhengyang
  3. // date: 2017.6.10 - 2017.6.15
  4. // desc: 文件打包工具
  5. // Copyright (c) yuzhengyang. All rights reserved.
  6. //************************************************************************
  7. using Azylee.Core.DataUtils.CollectionUtils;
  8. using Azylee.Core.DelegateUtils.ProcessDelegateUtils;
  9. using Azylee.Core.IOUtils.DirUtils;
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Linq;
  15. using System.Resources;
  16. using System.Runtime.Serialization.Formatters.Binary;
  17. using System.Text;
  18. namespace Azylee.Core.IOUtils.FileUtils
  19. {
  20. /// <summary>
  21. /// 文件打包工具
  22. /// </summary>
  23. public class FilePackageTool
  24. {
  25. const string FileType = "Y.Utils.FilePackage";//文件类型 禁止修改长度(19位)
  26. const string FileVersion = "100001";//类型的版本 禁止修改长度(6位)
  27. private static int FileBuffer = 1024 * 1024;
  28. /// <summary>
  29. /// 文件打包
  30. /// </summary>
  31. /// <param name="srcPath">要打包的路径</param>
  32. /// <param name="dstFile">打包后的文件</param>
  33. /// <param name="progress">回调进度</param>
  34. /// <param name="overwrite">覆盖打包后的文件(重复时)</param>
  35. /// <returns>
  36. /// -11;//要打包的路径不存在
  37. /// -12;//打包后的目标文件已存在
  38. /// -13;//要打包的路径中没有文件
  39. /// -14;//输出文件夹不存在
  40. /// -404;//未知错误,操作失败
  41. /// </returns>
  42. public static int Pack(string srcPath, string dstFile, ProgressDelegate.ProgressHandler progress = null, object sender = null, bool overwrite = true)
  43. {
  44. DateTime beginTime = DateTime.Now;
  45. if (!Directory.Exists(srcPath)) return -11;//要打包的路径不存在
  46. if (File.Exists(dstFile) && !overwrite) return -12;//打包后的目标文件已存在
  47. if (!DirTool.Create(DirTool.GetFilePath(dstFile))) return -14;//输出文件夹不存在
  48. List<string> tempfiles = FileTool.GetAllFile(srcPath);
  49. List<FilePackageModel> files = CreateFilePackageModel(tempfiles, srcPath);
  50. if (ListTool.HasElements(files))
  51. {
  52. long allfilesize = files.Sum(x => x.Size);//文件总大小
  53. long surplusfilesize = allfilesize;//剩余要写入的文件大小
  54. using (FileStream fsWrite = new FileStream(dstFile, FileMode.Create))
  55. {
  56. try
  57. {
  58. //写入文件类型标识和版本号
  59. byte[] filetypeandversion = Encoding.Default.GetBytes(FileType + FileVersion);
  60. fsWrite.Write(filetypeandversion, 0, filetypeandversion.Length);
  61. //写入头部总长度
  62. int headl = files.Sum(x => x.AllByteLength);
  63. byte[] headlength = BitConverter.GetBytes(headl);
  64. fsWrite.Write(headlength, 0, headlength.Length);
  65. //循环写入文件信息
  66. files.ForEach(x =>
  67. {
  68. fsWrite.Write(x.NameLengthByte, 0, x.NameLengthByte.Length);
  69. fsWrite.Write(x.NameByte, 0, x.NameByte.Length);
  70. fsWrite.Write(x.PathLengthByte, 0, x.PathLengthByte.Length);
  71. fsWrite.Write(x.PathByte, 0, x.PathByte.Length);
  72. fsWrite.Write(x.SizeLengthByte, 0, x.SizeLengthByte.Length);
  73. fsWrite.Write(x.SizeByte, 0, x.SizeByte.Length);
  74. fsWrite.Write(x.MD5LengthByte, 0, x.MD5LengthByte.Length);
  75. fsWrite.Write(x.MD5Byte, 0, x.MD5Byte.Length);
  76. });
  77. //循环写入文件
  78. files.ForEach(x =>
  79. {
  80. //读取文件(可访问被打开的exe文件)
  81. using (FileStream fsRead = new FileStream(DirTool.Combine(srcPath, x.Path, x.Name), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  82. {
  83. int readCount = 0;
  84. byte[] buffer = new byte[FileBuffer];
  85. while ((readCount = fsRead.Read(buffer, 0, buffer.Length)) > 0)
  86. {
  87. fsWrite.Write(buffer, 0, readCount);
  88. surplusfilesize -= readCount;
  89. progress?.Invoke(sender, new ProgressEventArgs(allfilesize - surplusfilesize, allfilesize));
  90. }
  91. }
  92. });
  93. }
  94. catch (Exception e) { }
  95. }
  96. if (surplusfilesize == 0)
  97. {
  98. return (int)Math.Ceiling((DateTime.Now - beginTime).TotalSeconds);//操作成功
  99. }
  100. }
  101. else
  102. {
  103. return -13;//要打包的路径中没有文件
  104. }
  105. //打包失败后,删除打包后的文件
  106. try { File.Delete(dstFile); } catch (Exception e) { }
  107. return -404;//未知错误,操作失败
  108. }
  109. /// <summary>
  110. /// 拆包
  111. /// </summary>
  112. /// <param name="srcFile">包文件路径</param>
  113. /// <param name="dstPath">拆包到的目录 </param>
  114. /// <param name="progress">回调进度</param>
  115. /// <param name="overwrite">覆盖拆包后的文件(重复时)</param>
  116. /// <returns>
  117. /// -11; //要解包的文件不存在
  118. /// -12;//要解包的目标文件夹已存在
  119. /// -20;// 文件类型不匹配
  120. /// -99;//未知错误,操作失败
  121. /// </returns>
  122. public static int Unpack(string srcFile, string dstPath, ProgressDelegate.ProgressHandler progress = null, object sender = null, bool overwrite = true)
  123. {
  124. DateTime beginTime = DateTime.Now;
  125. if (!File.Exists(srcFile)) return -11; //要解包的文件不存在
  126. if (Directory.Exists(dstPath) && !overwrite) return -12;//要解包的目标文件夹已存在
  127. using (FileStream fsRead = new FileStream(srcFile, FileMode.Open))
  128. {
  129. try
  130. {
  131. string version = GetFileVersion(fsRead);
  132. if (version == null) return -20;// 文件类型不匹配
  133. //读取头部总长度
  134. byte[] headl = new byte[4];
  135. int headlength = 0;
  136. fsRead.Read(headl, 0, headl.Length);
  137. headlength = BitConverter.ToInt32(headl, 0);
  138. if (headlength > 0)
  139. {
  140. //读取文件列表信息
  141. byte[] headdata = new byte[headlength];
  142. fsRead.Read(headdata, 0, headlength);
  143. List<FilePackageModel> files = GetFilePackageModel(headdata);
  144. if (ListTool.HasElements(files))
  145. {
  146. long allfilesize = files.Sum(x => x.Size);//文件总大小
  147. long current = 0;//当前进度
  148. //读取写出所有文件
  149. files.ForEach(x =>
  150. {
  151. if (DirTool.Create(DirTool.Combine(dstPath, x.Path)))
  152. {
  153. try
  154. {
  155. using (FileStream fsWrite = new FileStream(DirTool.Combine(dstPath, x.Path, x.Name), FileMode.Create))
  156. {
  157. long size = x.Size;
  158. int readCount = 0;
  159. byte[] buffer = new byte[FileBuffer];
  160. while (size > FileBuffer)
  161. {
  162. readCount = fsRead.Read(buffer, 0, buffer.Length);
  163. fsWrite.Write(buffer, 0, readCount);
  164. size -= readCount;
  165. current += readCount;
  166. progress?.Invoke(sender, new ProgressEventArgs(current, allfilesize));
  167. }
  168. if (size <= FileBuffer)
  169. {
  170. readCount = fsRead.Read(buffer, 0, (int)size);
  171. fsWrite.Write(buffer, 0, readCount);
  172. current += readCount;
  173. progress?.Invoke(sender, new ProgressEventArgs(current, allfilesize));
  174. }
  175. }
  176. }
  177. catch (Exception e)
  178. {
  179. fsRead.Seek(x.Size, SeekOrigin.Current);
  180. current += x.Size;
  181. progress?.Invoke(sender, new ProgressEventArgs(current, allfilesize));
  182. }
  183. }
  184. });
  185. //验证文件列表
  186. bool allCheck = true;
  187. foreach (var file in files)
  188. {
  189. string temp = DirTool.Combine(dstPath, file.Path, file.Name);
  190. string tempCk = FileTool.GetMD5(temp);
  191. if (tempCk != file.MD5)//验证文件(Size:速度会快一些,MD5在大文件的验证上非常耗时)
  192. {
  193. allCheck = false;
  194. break;
  195. }
  196. }
  197. if (allCheck) return (int)Math.Ceiling((DateTime.Now - beginTime).TotalSeconds);//操作成功
  198. }
  199. }
  200. }
  201. catch (Exception e) { }
  202. }
  203. return -99;//未知错误,操作失败
  204. }
  205. /// <summary>
  206. /// 获取文件类型的类型版本
  207. /// </summary>
  208. /// <param name="fs"></param>
  209. /// <returns>
  210. /// 如果文件类型不匹配,则返回null
  211. /// </returns>
  212. private static string GetFileVersion(FileStream fs)
  213. {
  214. string result = null;
  215. try
  216. {
  217. //读取文件类型标识和版本号
  218. byte[] filetype = Encoding.Default.GetBytes(FileType);
  219. fs.Read(filetype, 0, filetype.Length);
  220. string filetypestr = Encoding.Default.GetString(filetype);
  221. byte[] fileversion = Encoding.Default.GetBytes(FileVersion);
  222. fs.Read(fileversion, 0, fileversion.Length);
  223. string fileversionstr = Encoding.Default.GetString(fileversion);
  224. //如果文件类型匹配,则返回版本号
  225. if (filetypestr == FileType) result = fileversionstr;
  226. }
  227. catch (Exception e) { }
  228. return result;
  229. }
  230. /// <summary>
  231. /// 解析打包文件文件列表
  232. /// </summary>
  233. /// <param name="headdata"></param>
  234. /// <returns></returns>
  235. private static List<FilePackageModel> GetFilePackageModel(byte[] headdata)
  236. {
  237. List<FilePackageModel> files = new List<FilePackageModel>();
  238. int index = 0;
  239. try
  240. {
  241. while (index < headdata.Length)
  242. {
  243. #region 读取文件名长度和内容
  244. //文件名长度
  245. byte[] namelengthbyte = new byte[4];
  246. Buffer.BlockCopy(headdata, index, namelengthbyte, 0, namelengthbyte.Length);
  247. int namelength = BitConverter.ToInt32(namelengthbyte, 0);
  248. index += namelengthbyte.Length;
  249. //文件名内容
  250. byte[] namebyte = new byte[namelength];
  251. Buffer.BlockCopy(headdata, index, namebyte, 0, namelength);
  252. string name = Encoding.Default.GetString(namebyte);
  253. index += namebyte.Length;
  254. #endregion
  255. #region 读取路径长度和内容
  256. //路径长度
  257. byte[] pathlengthbyte = new byte[4];
  258. Buffer.BlockCopy(headdata, index, pathlengthbyte, 0, pathlengthbyte.Length);
  259. int pathlength = BitConverter.ToInt32(pathlengthbyte, 0);
  260. index += pathlengthbyte.Length;
  261. //路径内容
  262. byte[] pathbyte = new byte[pathlength];
  263. Buffer.BlockCopy(headdata, index, pathbyte, 0, pathlength);
  264. string path = Encoding.Default.GetString(pathbyte);
  265. index += pathbyte.Length;
  266. #endregion
  267. #region 读取文件大小长度和内容
  268. //文件大小长度
  269. byte[] sizelengthbyte = new byte[4];
  270. Buffer.BlockCopy(headdata, index, sizelengthbyte, 0, sizelengthbyte.Length);
  271. int sizelength = BitConverter.ToInt32(sizelengthbyte, 0);
  272. index += sizelengthbyte.Length;
  273. //文件大小
  274. byte[] sizebyte = new byte[sizelength];
  275. Buffer.BlockCopy(headdata, index, sizebyte, 0, sizelength);
  276. long size = BitConverter.ToInt64(sizebyte, 0);
  277. index += sizebyte.Length;
  278. #endregion
  279. #region 读取文件MD5码长度和内容
  280. //文件大小长度
  281. byte[] md5lengthbyte = new byte[4];
  282. Buffer.BlockCopy(headdata, index, md5lengthbyte, 0, md5lengthbyte.Length);
  283. int md5length = BitConverter.ToInt32(md5lengthbyte, 0);
  284. index += md5lengthbyte.Length;
  285. //文件大小
  286. byte[] md5byte = new byte[md5length];
  287. Buffer.BlockCopy(headdata, index, md5byte, 0, md5length);
  288. string md5 = Encoding.Default.GetString(md5byte);
  289. index += md5byte.Length;
  290. #endregion
  291. files.Add(new FilePackageModel()
  292. {
  293. Name = name,
  294. Path = path,
  295. Size = size,
  296. MD5 = md5,
  297. });
  298. }
  299. return files;
  300. }
  301. catch (Exception e) { return null; }
  302. }
  303. /// <summary>
  304. /// 创建打包文件列表信息
  305. /// </summary>
  306. /// <param name="files"></param>
  307. /// <param name="srcPath"></param>
  308. /// <returns></returns>
  309. private static List<FilePackageModel> CreateFilePackageModel(List<string> files, string srcPath)
  310. {
  311. if (ListTool.IsNullOrEmpty(files)) return null;
  312. List<FilePackageModel> result = new List<FilePackageModel>();
  313. //汇总所有文件
  314. files.ForEach(x =>
  315. {
  316. result.Add(new FilePackageModel()
  317. {
  318. Name = Path.GetFileName(x),
  319. Path = DirTool.GetFilePath(x).Substring(srcPath.Count()),
  320. Size = FileTool.Size(x),
  321. MD5 = FileTool.GetMD5(x),
  322. });
  323. });
  324. return result;
  325. }
  326. }
  327. }