FilePackageTool.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. //************************************************************************
  2. // https://github.com/yuzhengyang
  3. // author: yuzhengyang
  4. // date: 2017.6.10 - 2017.6.15
  5. // desc: 文件打包工具
  6. // Copyright (c) yuzhengyang. All rights reserved.
  7. //************************************************************************
  8. using System;
  9. using System.Collections;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Resources;
  14. using System.Runtime.Serialization.Formatters.Binary;
  15. using System.Text;
  16. using Y.Utils.DataUtils.Collections;
  17. using Y.Utils.IOUtils.PathUtils;
  18. namespace Y.Utils.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. #region 类型单一,文件处理复杂,加载占用超大内存(这都是辣鸡)
  29. /// <summary>
  30. /// 批量打包任意对象到资源文件
  31. /// </summary>
  32. /// <param name="objCollection">被打包对象的列表。键值对中键为其在资源文件中的唯一标示名。</param>
  33. /// <param name="targetFilePath">目标资源文件。默认参数为当前目录下的"MyRes.pck"文件。</param>
  34. /// <param name="overwrite">是否覆盖已存在的目标文件。默认=True</param>
  35. public static void ResourcePackage(IDictionary<string, object> objCollection, string targetFilePath, bool overwrite = true)
  36. {
  37. if (overwrite) File.Delete(targetFilePath);
  38. using (ResourceWriter rw = new ResourceWriter(targetFilePath))
  39. {
  40. foreach (KeyValuePair<string, object> pair in objCollection)
  41. //为了防传进来的资源名有数字开头,资源名都加了前缀_
  42. rw.AddResource("_" + pair.Key, pair.Value);
  43. rw.Generate();
  44. rw.Close();
  45. }
  46. }
  47. /// <summary>
  48. /// 解包资源文件,返回所有资源及其资源名
  49. /// </summary>
  50. /// <param name="targetFilePath">要解包的资源文件。默认为当前目录下的"MyRes.pck"</param>
  51. /// <returns>资源字典,键值为资源唯一标示名。若无资源返回空集合。</returns>
  52. public static Dictionary<string, object> ResourceUnpack(string targetFilePath)
  53. {
  54. Dictionary<string, object> rtn = new Dictionary<string, object>();
  55. using (ResourceReader rr = new ResourceReader(targetFilePath))
  56. {
  57. foreach (DictionaryEntry entry in rr)
  58. rtn.Add(((string)entry.Key).Substring(1), entry.Value);
  59. }
  60. return rtn;
  61. }
  62. /// <summary>
  63. /// 根据资源名在指定的资源文件中检索资源
  64. /// </summary>
  65. /// <param name="resName">资源名</param>
  66. /// <param name="targetFilePath">要在其中检索的资源文件名,默认为"MyRes.pck"</param>
  67. /// <returns>资源名对应的资源</returns>
  68. public static object ResourceSearch(string resName, string targetFilePath)
  69. {
  70. object rtn = null;
  71. using (ResourceReader rr = new ResourceReader(targetFilePath))
  72. {
  73. foreach (DictionaryEntry entry in rr)
  74. if ((string)entry.Key == '_' + resName)
  75. {
  76. rtn = entry.Value;
  77. break;
  78. }
  79. }
  80. return rtn;
  81. }
  82. /// <summary>
  83. /// 将对象序列化
  84. /// </summary>
  85. /// <param name="FilePath">文件(支持绝大多数数据类型)</param>
  86. /// <param name="obj">要序列化的对象(如哈希表,数组等等)</param>
  87. public static void FileSerialize(string FilePath, object obj)
  88. {
  89. if (File.Exists(FilePath))
  90. {
  91. try
  92. {
  93. FileStream fs = new FileStream(FilePath, FileMode.Create);
  94. BinaryFormatter sl = new BinaryFormatter();
  95. sl.Serialize(fs, obj);
  96. fs.Close();
  97. }
  98. catch
  99. {
  100. //序列化存储失败!
  101. }
  102. }
  103. else
  104. {
  105. //您读取的文件对象不存在
  106. }
  107. }
  108. /// <summary>
  109. /// 将文件反序列化
  110. /// </summary>
  111. /// <param name="FilePath">文件路径(必须是经过当前序列化后的文件)</param>
  112. /// <returns>返回 null 表示序列反解失败或者目标文件不存在</returns>
  113. public static object FileDeSerialize(string FilePath)
  114. {
  115. if (System.IO.File.Exists(FilePath))
  116. {
  117. try
  118. {
  119. FileStream fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
  120. BinaryFormatter sl = new BinaryFormatter();
  121. object obg = sl.Deserialize(fs);
  122. fs.Close();
  123. return obg;
  124. }
  125. catch
  126. {
  127. return null;
  128. }
  129. }
  130. else
  131. {
  132. return null;
  133. }
  134. }
  135. #endregion
  136. /// <summary>
  137. /// 打包
  138. /// </summary>
  139. /// <returns>
  140. /// -11;//要打包的路径不存在
  141. /// -12;//打包后的目标文件已存在
  142. /// -13;//要打包的路径中没有文件
  143. /// -404;//未知错误,操作失败
  144. /// </returns>
  145. public static int Pack(string srcPath, string dstFile, bool overwrite = true)
  146. {
  147. DateTime beginTime = DateTime.Now;
  148. if (!Directory.Exists(srcPath)) return -11;//要打包的路径不存在
  149. if (File.Exists(dstFile) && !overwrite) return -12;//打包后的目标文件已存在
  150. List<string> tempfiles = FileTool.GetAllFile(srcPath);
  151. List<FilePackageModel> files = CreateFilePackageModel(tempfiles, srcPath);
  152. if (ListTool.HasElements(files))
  153. {
  154. long allfilesize = files.Sum(x => x.Size);
  155. using (FileStream fsWrite = new FileStream(dstFile, FileMode.Create))
  156. {
  157. //写入文件类型标识和版本号
  158. byte[] filetypeandversion = Encoding.Default.GetBytes(FileType + FileVersion);
  159. fsWrite.Write(filetypeandversion, 0, filetypeandversion.Length);
  160. //写入头部总长度
  161. int headl = files.Sum(x => x.AllByteLength);
  162. byte[] headlength = BitConverter.GetBytes(headl);
  163. fsWrite.Write(headlength, 0, headlength.Length);
  164. //循环写入文件信息
  165. files.ForEach(x =>
  166. {
  167. fsWrite.Write(x.NameLengthByte, 0, x.NameLengthByte.Length);
  168. fsWrite.Write(x.NameByte, 0, x.NameByte.Length);
  169. fsWrite.Write(x.PathLengthByte, 0, x.PathLengthByte.Length);
  170. fsWrite.Write(x.PathByte, 0, x.PathByte.Length);
  171. fsWrite.Write(x.SizeLengthByte, 0, x.SizeLengthByte.Length);
  172. fsWrite.Write(x.SizeByte, 0, x.SizeByte.Length);
  173. fsWrite.Write(x.MD5LengthByte, 0, x.MD5LengthByte.Length);
  174. fsWrite.Write(x.MD5Byte, 0, x.MD5Byte.Length);
  175. });
  176. //循环写入文件
  177. files.ForEach(x =>
  178. {
  179. using (FileStream fsRead = new FileStream(DirTool.Combine(srcPath, x.Path, x.Name), FileMode.Open))
  180. {
  181. int readCount = 0;
  182. byte[] buffer = new byte[FileBuffer];
  183. while ((readCount = fsRead.Read(buffer, 0, buffer.Length)) > 0)
  184. {
  185. fsWrite.Write(buffer, 0, readCount);
  186. allfilesize -= readCount;
  187. }
  188. fsRead.Close();
  189. }
  190. });
  191. fsWrite.Close();
  192. }
  193. if (allfilesize == 0)
  194. {
  195. return (int)Math.Ceiling((DateTime.Now - beginTime).TotalSeconds);//操作成功
  196. }
  197. }
  198. else
  199. {
  200. return -13;//要打包的路径中没有文件
  201. }
  202. //打包失败后,删除打包后的文件
  203. try { File.Delete(dstFile); } catch (Exception e) { }
  204. return -404;//未知错误,操作失败
  205. }
  206. /// <summary>
  207. /// 解包
  208. /// </summary>
  209. /// <returns></returns>
  210. public static int Unpack(string srcFile, string dstPath, bool overwrite = true)
  211. {
  212. DateTime beginTime = DateTime.Now;
  213. if (!File.Exists(srcFile)) return -11; //要解包的文件不存在
  214. if (Directory.Exists(dstPath) && !overwrite) return -12;//要解包的目标文件夹已存在
  215. using (FileStream fsRead = new FileStream(srcFile, FileMode.Open))
  216. {
  217. string version = GetFileVersion(fsRead);
  218. if (version == null) return -20;// 文件类型不匹配
  219. //读取头部总长度
  220. byte[] headl = new byte[4];
  221. int headlength = 0;
  222. fsRead.Read(headl, 0, headl.Length);
  223. headlength = BitConverter.ToInt32(headl, 0);
  224. if (headlength > 0)
  225. {
  226. //读取文件列表信息
  227. byte[] headdata = new byte[headlength];
  228. fsRead.Read(headdata, 0, headlength);
  229. List<FilePackageModel> files = GetFilePackageModel(headdata);
  230. if (ListTool.HasElements(files))
  231. {
  232. files.ForEach(x =>
  233. {
  234. if (DirTool.Create(DirTool.Combine(dstPath, x.Path)))
  235. {
  236. using (FileStream fsWrite = new FileStream(DirTool.Combine(dstPath, x.Path, x.Name), FileMode.Create))
  237. {
  238. long size = x.Size;
  239. int readCount = 0;
  240. byte[] buffer = new byte[FileBuffer];
  241. while (size > FileBuffer)
  242. {
  243. readCount = fsRead.Read(buffer, 0, buffer.Length);
  244. fsWrite.Write(buffer, 0, readCount);
  245. size -= readCount;
  246. }
  247. if (size <= FileBuffer)
  248. {
  249. readCount = fsRead.Read(buffer, 0, (int)size);
  250. fsWrite.Write(buffer, 0, readCount);
  251. }
  252. fsWrite.Close();
  253. }
  254. }
  255. });
  256. }
  257. int a = 111;
  258. }
  259. fsRead.Close();
  260. }
  261. return (int)Math.Ceiling((DateTime.Now - beginTime).TotalSeconds);//操作成功
  262. }
  263. /// <summary>
  264. /// 获取文件类型的类型版本
  265. /// </summary>
  266. /// <param name="fs"></param>
  267. /// <returns>
  268. /// 如果文件类型不匹配,则返回null
  269. /// </returns>
  270. private static string GetFileVersion(FileStream fs)
  271. {
  272. string result = null;
  273. //读取文件类型标识和版本号
  274. byte[] filetype = Encoding.Default.GetBytes(FileType);
  275. fs.Read(filetype, 0, filetype.Length);
  276. string filetypestr = Encoding.Default.GetString(filetype);
  277. byte[] fileversion = Encoding.Default.GetBytes(FileVersion);
  278. fs.Read(fileversion, 0, fileversion.Length);
  279. string fileversionstr = Encoding.Default.GetString(fileversion);
  280. //如果文件类型匹配,则返回版本号
  281. if (filetypestr == FileType) result = fileversionstr;
  282. return result;
  283. }
  284. /// <summary>
  285. /// 解析打包文件文件列表
  286. /// </summary>
  287. /// <param name="headdata"></param>
  288. /// <returns></returns>
  289. private static List<FilePackageModel> GetFilePackageModel(byte[] headdata)
  290. {
  291. List<FilePackageModel> files = new List<FilePackageModel>();
  292. int index = 0;
  293. try
  294. {
  295. while (index < headdata.Length)
  296. {
  297. #region 读取文件名长度和内容
  298. //文件名长度
  299. byte[] namelengthbyte = new byte[4];
  300. Buffer.BlockCopy(headdata, index, namelengthbyte, 0, namelengthbyte.Length);
  301. int namelength = BitConverter.ToInt32(namelengthbyte, 0);
  302. index += namelengthbyte.Length;
  303. //文件名内容
  304. byte[] namebyte = new byte[namelength];
  305. Buffer.BlockCopy(headdata, index, namebyte, 0, namelength);
  306. string name = Encoding.Default.GetString(namebyte);
  307. index += namebyte.Length;
  308. #endregion
  309. #region 读取路径长度和内容
  310. //路径长度
  311. byte[] pathlengthbyte = new byte[4];
  312. Buffer.BlockCopy(headdata, index, pathlengthbyte, 0, pathlengthbyte.Length);
  313. int pathlength = BitConverter.ToInt32(pathlengthbyte, 0);
  314. index += pathlengthbyte.Length;
  315. //路径内容
  316. byte[] pathbyte = new byte[pathlength];
  317. Buffer.BlockCopy(headdata, index, pathbyte, 0, pathlength);
  318. string path = Encoding.Default.GetString(pathbyte);
  319. index += pathbyte.Length;
  320. #endregion
  321. #region 读取文件大小长度和内容
  322. //文件大小长度
  323. byte[] sizelengthbyte = new byte[4];
  324. Buffer.BlockCopy(headdata, index, sizelengthbyte, 0, sizelengthbyte.Length);
  325. int sizelength = BitConverter.ToInt32(sizelengthbyte, 0);
  326. index += sizelengthbyte.Length;
  327. //文件大小
  328. byte[] sizebyte = new byte[sizelength];
  329. Buffer.BlockCopy(headdata, index, sizebyte, 0, sizelength);
  330. long size = BitConverter.ToInt64(sizebyte, 0);
  331. index += sizebyte.Length;
  332. #endregion
  333. #region 读取文件MD5码长度和内容
  334. //文件大小长度
  335. byte[] md5lengthbyte = new byte[4];
  336. Buffer.BlockCopy(headdata, index, md5lengthbyte, 0, md5lengthbyte.Length);
  337. int md5length = BitConverter.ToInt32(md5lengthbyte, 0);
  338. index += md5lengthbyte.Length;
  339. //文件大小
  340. byte[] md5byte = new byte[md5length];
  341. Buffer.BlockCopy(headdata, index, md5byte, 0, md5length);
  342. string md5 = Encoding.Default.GetString(md5byte);
  343. index += md5byte.Length;
  344. #endregion
  345. files.Add(new FilePackageModel()
  346. {
  347. Name = name,
  348. Path = path,
  349. Size = size,
  350. MD5 = md5,
  351. });
  352. }
  353. return files;
  354. }
  355. catch (Exception e) { return null; }
  356. }
  357. /// <summary>
  358. /// 创建打包文件列表信息
  359. /// </summary>
  360. /// <param name="files"></param>
  361. /// <param name="srcPath"></param>
  362. /// <returns></returns>
  363. private static List<FilePackageModel> CreateFilePackageModel(List<string> files, string srcPath)
  364. {
  365. if (ListTool.IsNullOrEmpty(files)) return null;
  366. List<FilePackageModel> result = new List<FilePackageModel>();
  367. //汇总所有文件
  368. files.ForEach(x =>
  369. {
  370. result.Add(new FilePackageModel()
  371. {
  372. Name = Path.GetFileName(x),
  373. Path = DirTool.GetFilePath(x).Substring(srcPath.Count()),
  374. Size = FileTool.Size(x),
  375. MD5 = FileTool.GetMD5(x),
  376. });
  377. });
  378. return result;
  379. }
  380. }
  381. }