You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

461 lines
18 KiB

  1. using Apewer.Internals;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Reflection;
  6. using System.Security.Permissions;
  7. namespace Apewer
  8. {
  9. /// <summary>存储实用工具。</summary>
  10. public static class StorageUtility
  11. {
  12. private static Exception Try(Action action)
  13. {
  14. try { action?.Invoke(); return null; }
  15. catch (Exception ex) { return ex; }
  16. }
  17. #region delete
  18. /// <summary>删除文件。</summary>
  19. public static Exception DeleteFile(string path, bool useTemp = false)
  20. {
  21. if (useTemp)
  22. {
  23. try
  24. {
  25. if (string.IsNullOrEmpty(path)) return new ArgumentException();
  26. if (!File.Exists(path)) return new FileNotFoundException();
  27. Try(() => new FileInfo(path).Attributes = FileAttributes.Normal);
  28. var temp = Environment.GetEnvironmentVariable("TEMP");
  29. var name = Path.GetFileName(path);
  30. var dest = null as string;
  31. while (dest == null || File.Exists(dest))
  32. {
  33. var guid = Guid.NewGuid().ToString("n").Substring(0, 8);
  34. dest = $"trash_{guid}_{name}";
  35. }
  36. File.Move(path, dest);
  37. File.Delete(dest);
  38. return null;
  39. }
  40. catch { }
  41. }
  42. try
  43. {
  44. File.Delete(path);
  45. return null;
  46. }
  47. catch (Exception ex) { return ex; }
  48. }
  49. /// <summary>删除目录、子目录和子文件。</summary>
  50. public static Exception DeleteDirectory(string path, bool useTemp = false)
  51. {
  52. if (useTemp)
  53. {
  54. try
  55. {
  56. if (string.IsNullOrEmpty(path)) return new ArgumentException();
  57. if (!Directory.Exists(path)) return new DirectoryNotFoundException();
  58. var temp = Environment.GetEnvironmentVariable("TEMP");
  59. var name = Path.GetDirectoryName(path);
  60. var dest = null as string;
  61. while (dest == null || Directory.Exists(dest))
  62. {
  63. var guid = Guid.NewGuid().ToString("n").Substring(0, 8);
  64. dest = $"trash_{guid}_{name}";
  65. }
  66. Directory.Move(path, dest);
  67. Directory.Delete(dest, true);
  68. return null;
  69. }
  70. catch { }
  71. }
  72. try
  73. {
  74. Directory.Delete(path, true);
  75. return null;
  76. }
  77. catch (Exception ex) { return ex; }
  78. }
  79. #endregion
  80. /// <summary>向文件追加数据。文件不存在将创建,存在则覆盖。</summary>
  81. public static bool AppendFile(string path, params byte[] bytes)
  82. {
  83. return StorageHelper.AppendFile(path, bytes);
  84. }
  85. /// <summary>向文件写入数据,每写入 1 KB 数据后将执行回调。若写入失败则返回 -1 值。</summary>
  86. public static long AppendFile(string path, Stream stream, Action<Int64> callback = null)
  87. {
  88. return StorageHelper.AppendFile(path, stream, callback);
  89. }
  90. /// <summary>确保目录存在,若不存在则创建,返回目录的存在状态。</summary>
  91. public static bool AssureDirectory(string path)
  92. {
  93. return StorageHelper.AssureDirectory(path);
  94. }
  95. /// <summary>确保目标路径的上级目录存在,若不存在则创建,返回上级目录的存在状态。</summary>
  96. public static bool AssureParent(string path)
  97. {
  98. return StorageHelper.AssureParent(path);
  99. }
  100. /// <summary>获取文件或目录的上级目录完整路径,失败时返回 NULL 值。。</summary>
  101. public static string GetParentPath(string path)
  102. {
  103. try
  104. {
  105. if (File.Exists(path)) return Path.GetDirectoryName(path);
  106. if (Directory.Exists(path)) return Directory.GetParent(path).FullName;
  107. }
  108. catch { }
  109. return null;
  110. }
  111. /// <summary>复制文件。默认不替换目标路径的文件。</summary>
  112. public static bool CopyFile(string source, string destination, bool replace = false)
  113. {
  114. return StorageHelper.CopyFile(source, destination, replace);
  115. }
  116. /// <summary>获取目录的存在状态。</summary>
  117. public static bool DirectoryExists(string path)
  118. {
  119. return StorageHelper.DirectoryExists(path);
  120. }
  121. /// <summary>获取文件的存在状态。</summary>
  122. public static bool FileExists(string path)
  123. {
  124. return StorageHelper.FileExists(path);
  125. }
  126. /// <summary>修正文件名,去除不允许的字符。</summary>
  127. public static string FixFileName(string filename)
  128. {
  129. return StorageHelper.FixFileName(filename);
  130. }
  131. /// <summary>获取指定目录的子目录,可指定递归子目录。</summary>
  132. public static List<string> GetSubDirectories(string path, bool recursive = false)
  133. {
  134. return StorageHelper.GetSubDirectories(path, recursive, true);
  135. }
  136. /// <summary>获取指定目录的子目录,可指定递归子目录的深度。</summary>
  137. public static List<string> GetSubDirectories(string path, int depth)
  138. {
  139. return StorageHelper.GetSubDirectories(path, depth, true);
  140. }
  141. /// <summary>获取指定目录的子文件,可指定递归子目录。</summary>
  142. public static List<string> GetSubFiles(string path, bool recursive = false)
  143. {
  144. return StorageHelper.GetSubFiles(path, recursive, true);
  145. }
  146. /// <summary>获取指定目录的子文件,可指定递归子目录的深度。</summary>
  147. public static List<string> GetSubFiles(string path, int depth)
  148. {
  149. return StorageHelper.GetSubFiles(path, depth, true);
  150. }
  151. /// <summary>打开文件,并获取文件流。可选文件的锁定状态。</summary>
  152. public static FileStream OpenFile(string path, bool share = true)
  153. {
  154. return StorageHelper.OpenFile(path, share);
  155. }
  156. /// <summary>获取文件或目录的存在状态。</summary>
  157. public static bool PathExists(string path)
  158. {
  159. return StorageHelper.PathExists(path);
  160. }
  161. /// <summary>读取文件,获取文件内容。当文件为 UTF-8 文本文件时,可去除 BOM 头。</summary>
  162. public static byte[] ReadFile(string path, bool wipeBom = false)
  163. {
  164. return StorageHelper.ReadFile(path, wipeBom);
  165. }
  166. /// <summary>将数据写入新文件,若文件已存在则覆盖。返回写入的字节数,发生错误时返回 -1 值。</summary>
  167. public static long WriteFile(string path, Stream stream, Action<Int64> callback = null)
  168. {
  169. var created = CreateFile(path, 0, true);
  170. if (created && stream != null)
  171. {
  172. var result = 0L;
  173. try
  174. {
  175. var file = OpenFile(path);
  176. if (file != null)
  177. {
  178. result = StreamHelper.Read(stream, file, Constant.DefaultBufferCapacity, callback);
  179. }
  180. KernelUtility.Dispose(file);
  181. }
  182. catch { }
  183. return result;
  184. }
  185. return -1;
  186. }
  187. /// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
  188. public static bool WriteFile(string path, params byte[] bytes)
  189. {
  190. if (string.IsNullOrEmpty(path)) return false;
  191. if (!CreateFile(path, 0, true)) return false;
  192. try
  193. {
  194. if (bytes != null && bytes.LongLength > 0L)
  195. {
  196. File.WriteAllBytes(path, bytes);
  197. }
  198. }
  199. catch { return false; }
  200. return true;
  201. }
  202. /// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
  203. public static bool WriteFile(string path, bool bom, params byte[] bytes)
  204. {
  205. return WriteFile(path, bom ? BinaryUtility.AddTextBom(bytes) : bytes);
  206. }
  207. /// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
  208. public static bool WriteFile(string path, Json json, bool bom = false)
  209. {
  210. if (string.IsNullOrEmpty(path)) return false;
  211. if (json == null || json.IsNull || json.IsNone) return false;
  212. var text = json.ToString(true);
  213. var bytes = TextUtility.ToBinary(text);
  214. return StorageHelper.WriteFile(path, bom, bytes);
  215. }
  216. /// <summary>创建一个空文件且不保留句柄。</summary>
  217. /// <param name="path">文件路径,若已存在则返回失败。</param>
  218. /// <param name="length">文件长度(字节数)。</param>
  219. /// <param name="replace">替换现有文件。</param>
  220. /// <returns>创建成功。</returns>
  221. public static bool CreateFile(string path, long length = 0, bool replace = false)
  222. {
  223. return StorageHelper.CreateFile(path, length, replace);
  224. }
  225. /// <summary>获取文件的最后写入时间。</summary>
  226. public static DateTime GetFileLastWriteTime(string path, bool utc = false)
  227. {
  228. try
  229. {
  230. var info = new FileInfo(path);
  231. return utc ? info.LastWriteTimeUtc : info.LastWriteTime;
  232. }
  233. catch { }
  234. return new DateTime();
  235. }
  236. /// <summary>设置文件的最后写入时间。</summary>
  237. public static string SetFileLastWriteTime(string path, DateTime value)
  238. {
  239. try
  240. {
  241. var info = new FileInfo(path);
  242. info.LastWriteTime = value;
  243. return null;
  244. }
  245. catch (Exception ex)
  246. {
  247. return ex.Message;
  248. }
  249. }
  250. /// <summary>压缩文件到流。</summary>
  251. /// <param name="files">Key 为 ZIP 内的文件名,Value 为原始文件路径。</param>
  252. /// <param name="output">要输出的 ZIP 流。</param>
  253. public static Exception ToZip(Dictionary<string, string> files, Stream output)
  254. {
  255. if (files == null) return new ArgumentNullException("files");
  256. var streams = new List<Stream>();
  257. var ex = BinaryUtility.ToZip(files.Keys, output, (name) =>
  258. {
  259. if (name.IsEmpty()) return null;
  260. var path = files[name];
  261. if (path.IsEmpty()) return null;
  262. if (!FileExists(path)) return null;
  263. var input = OpenFile(path);
  264. streams.Add(input);
  265. return input;
  266. }, (name) => GetFileLastWriteTime(files[name]), true);
  267. KernelUtility.Dispose(streams);
  268. return ex;
  269. }
  270. /// <summary>压缩文件到流。</summary>
  271. /// <param name="files">Key 为 ZIP 内的文件名,Value 为原始文件路径。</param>
  272. /// <param name="output">要输出的 ZIP 文件路径,已存在的文件将被删除。</param>
  273. public static Exception ToZip(Dictionary<string, string> files, string output)
  274. {
  275. if (output.IsEmpty()) return new ArgumentException("output");
  276. if (FileExists(output)) DeleteFile(output);
  277. if (FileExists(output)) return new IOException();
  278. var stream = OpenFile(output);
  279. if (stream == null) return new IOException();
  280. var ex = ToZip(files, stream);
  281. KernelUtility.Dispose(stream, true);
  282. return ex;
  283. }
  284. /// <summary>压缩文件到流。</summary>
  285. /// <param name="directory">原始文件目录。</param>
  286. /// <param name="output">要输出的 ZIP 流。</param>
  287. public static Exception ToZip(string directory, Stream output)
  288. {
  289. if (!DirectoryExists(directory)) return new DirectoryNotFoundException();
  290. var dict = new Dictionary<string, string>();
  291. foreach (var path in GetSubFiles(directory, true))
  292. {
  293. var name = path.Substring(directory.Length);
  294. if (name.StartsWith("\\")) name = name.Substring(1);
  295. if (name.StartsWith("/")) name = name.Substring(1);
  296. dict.Add(name, path);
  297. Console.WriteLine(name);
  298. }
  299. return ToZip(dict, output);
  300. }
  301. /// <summary>压缩文件到流。</summary>
  302. /// <param name="directory">原始文件目录。</param>
  303. /// <param name="output">要输出的 ZIP 文件路径,已存在的旧文件将被删除。</param>
  304. public static Exception ToZip(string directory, string output)
  305. {
  306. if (output.IsEmpty()) return new ArgumentException("output");
  307. if (FileExists(output)) DeleteFile(output);
  308. if (FileExists(output)) return new IOException("无法删除现有文件。");
  309. var stream = OpenFile(output);
  310. if (stream == null) return new IOException("无法创建文件。");
  311. var ex = ToZip(directory, stream);
  312. KernelUtility.Dispose(stream, true);
  313. return ex;
  314. }
  315. /// <summary>解压 ZIP 文件到目标目录。已存在的旧文件将被删除。</summary>
  316. public static Exception FromZip(string zipPath, string outputDirectory = null)
  317. {
  318. if (!FileExists(zipPath)) return new FileNotFoundException("指定的 ZIP 文件路径无效。");
  319. var directory = outputDirectory;
  320. if (directory.IsEmpty())
  321. {
  322. if (zipPath.SafeLower().EndsWith(".zip"))
  323. {
  324. directory = zipPath.Substring(0, zipPath.Length - 4);
  325. //if (directory.EndsWith("/") || directory.EndsWith("\\"))
  326. //if (PathExists(directory)) directory = null;
  327. }
  328. else
  329. {
  330. directory = GetParentPath(zipPath);
  331. }
  332. }
  333. if (directory.IsEmpty()) return new ArgumentException("无法判断目录路径。");
  334. if (!AssureDirectory(directory)) return new DirectoryNotFoundException("无法创建目录。");
  335. var input = OpenFile(zipPath);
  336. if (input == null) return new IOException("无法打开 ZIP 文件。");
  337. var info = new Dictionary<string, DateTime>();
  338. var ex = BinaryUtility.FromZip(input, (name, size, modifield) =>
  339. {
  340. var path = Path.Combine(directory, name);
  341. if (FileExists(path)) DeleteFile(path);
  342. if (DirectoryExists(path)) DeleteDirectory(path, true);
  343. if (PathExists(path)) throw new IOException("无法删除旧文件或旧目录。");
  344. var output = OpenFile(path);
  345. if (output == null) throw new IOException("无法创建文件 " + path + "。");
  346. if (info.ContainsKey(path))
  347. {
  348. KernelUtility.Dispose(output);
  349. return null;
  350. }
  351. info.Add(path, modifield);
  352. return output;
  353. }, (name, modified) =>
  354. {
  355. var path = Path.Combine(directory, name);
  356. var exists = AssureDirectory(path);
  357. if (!exists) throw new IOException("无法创建 ZIP 中对应的目录。");
  358. }, true);
  359. // 恢复文件的修改时间。
  360. foreach (var i in info) SetFileLastWriteTime(i.Key, i.Value);
  361. return ex;
  362. }
  363. ///// <summary>解压 ZIP 文件到字典。失败且不允许异常时返回 NULL 值。</summary>
  364. //public static Dictionary<string, byte[]> FromZip(string zipPath, bool allowException = false)
  365. //{
  366. // if (FileExists(zipPath))
  367. // {
  368. // var ex = new FileNotFoundException("文件不存在。");
  369. // if (allowException) throw ex;
  370. // return null;
  371. // }
  372. // var input = OpenFile(zipPath);
  373. // if (input == null)
  374. // {
  375. // var ex = new IOException("无法打开 ZIP 文件。");
  376. // if (allowException) throw ex;
  377. // return null;
  378. // }
  379. // return BinaryUtility.FromZip(input, ;
  380. //}
  381. /// <summary>获取指定程序集的资源。</summary>
  382. public static Stream GetResource(string name, Assembly assembly = null)
  383. {
  384. try
  385. {
  386. if (assembly == null) assembly = Assembly.GetExecutingAssembly();
  387. var stream = assembly.GetManifestResourceStream(name);
  388. return stream;
  389. }
  390. catch { }
  391. return null;
  392. }
  393. /// <summary>获取当前进程程序集的资源。读取失败时返回 NULL 值。</summary>
  394. public static byte[] ReadResource(string name, Assembly assembly = null)
  395. {
  396. var stream = GetResource(name, assembly);
  397. if (stream == null) return null;
  398. var bytes = BinaryUtility.Read(stream, true);
  399. return bytes;
  400. }
  401. }
  402. }