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
461 lines
18 KiB
using Apewer.Internals;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Security.Permissions;
|
|
|
|
namespace Apewer
|
|
{
|
|
|
|
/// <summary>存储实用工具。</summary>
|
|
public static class StorageUtility
|
|
{
|
|
|
|
private static Exception Try(Action action)
|
|
{
|
|
try { action?.Invoke(); return null; }
|
|
catch (Exception ex) { return ex; }
|
|
}
|
|
|
|
#region delete
|
|
|
|
/// <summary>删除文件。</summary>
|
|
public static Exception DeleteFile(string path, bool useTemp = false)
|
|
{
|
|
if (useTemp)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return new ArgumentException();
|
|
if (!File.Exists(path)) return new FileNotFoundException();
|
|
|
|
Try(() => new FileInfo(path).Attributes = FileAttributes.Normal);
|
|
|
|
var temp = Environment.GetEnvironmentVariable("TEMP");
|
|
var name = Path.GetFileName(path);
|
|
|
|
var dest = null as string;
|
|
while (dest == null || File.Exists(dest))
|
|
{
|
|
var guid = Guid.NewGuid().ToString("n").Substring(0, 8);
|
|
dest = $"trash_{guid}_{name}";
|
|
}
|
|
File.Move(path, dest);
|
|
File.Delete(dest);
|
|
return null;
|
|
}
|
|
catch { }
|
|
}
|
|
try
|
|
{
|
|
File.Delete(path);
|
|
return null;
|
|
}
|
|
catch (Exception ex) { return ex; }
|
|
}
|
|
|
|
/// <summary>删除目录、子目录和子文件。</summary>
|
|
public static Exception DeleteDirectory(string path, bool useTemp = false)
|
|
{
|
|
if (useTemp)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return new ArgumentException();
|
|
if (!Directory.Exists(path)) return new DirectoryNotFoundException();
|
|
|
|
var temp = Environment.GetEnvironmentVariable("TEMP");
|
|
var name = Path.GetDirectoryName(path);
|
|
|
|
var dest = null as string;
|
|
while (dest == null || Directory.Exists(dest))
|
|
{
|
|
var guid = Guid.NewGuid().ToString("n").Substring(0, 8);
|
|
dest = $"trash_{guid}_{name}";
|
|
}
|
|
Directory.Move(path, dest);
|
|
Directory.Delete(dest, true);
|
|
return null;
|
|
}
|
|
catch { }
|
|
}
|
|
try
|
|
{
|
|
Directory.Delete(path, true);
|
|
return null;
|
|
}
|
|
catch (Exception ex) { return ex; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>向文件追加数据。文件不存在将创建,存在则覆盖。</summary>
|
|
public static bool AppendFile(string path, params byte[] bytes)
|
|
{
|
|
return StorageHelper.AppendFile(path, bytes);
|
|
}
|
|
|
|
/// <summary>向文件写入数据,每写入 1 KB 数据后将执行回调。若写入失败则返回 -1 值。</summary>
|
|
public static long AppendFile(string path, Stream stream, Action<Int64> callback = null)
|
|
{
|
|
return StorageHelper.AppendFile(path, stream, callback);
|
|
}
|
|
|
|
/// <summary>确保目录存在,若不存在则创建,返回目录的存在状态。</summary>
|
|
public static bool AssureDirectory(string path)
|
|
{
|
|
return StorageHelper.AssureDirectory(path);
|
|
}
|
|
|
|
/// <summary>确保目标路径的上级目录存在,若不存在则创建,返回上级目录的存在状态。</summary>
|
|
public static bool AssureParent(string path)
|
|
{
|
|
return StorageHelper.AssureParent(path);
|
|
}
|
|
|
|
/// <summary>获取文件或目录的上级目录完整路径,失败时返回 NULL 值。。</summary>
|
|
public static string GetParentPath(string path)
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(path)) return Path.GetDirectoryName(path);
|
|
if (Directory.Exists(path)) return Directory.GetParent(path).FullName;
|
|
}
|
|
catch { }
|
|
return null;
|
|
}
|
|
|
|
/// <summary>复制文件。默认不替换目标路径的文件。</summary>
|
|
public static bool CopyFile(string source, string destination, bool replace = false)
|
|
{
|
|
return StorageHelper.CopyFile(source, destination, replace);
|
|
}
|
|
|
|
/// <summary>获取目录的存在状态。</summary>
|
|
public static bool DirectoryExists(string path)
|
|
{
|
|
return StorageHelper.DirectoryExists(path);
|
|
}
|
|
|
|
/// <summary>获取文件的存在状态。</summary>
|
|
public static bool FileExists(string path)
|
|
{
|
|
return StorageHelper.FileExists(path);
|
|
}
|
|
|
|
/// <summary>修正文件名,去除不允许的字符。</summary>
|
|
public static string FixFileName(string filename)
|
|
{
|
|
return StorageHelper.FixFileName(filename);
|
|
}
|
|
|
|
/// <summary>获取指定目录的子目录,可指定递归子目录。</summary>
|
|
public static List<string> GetSubDirectories(string path, bool recursive = false)
|
|
{
|
|
return StorageHelper.GetSubDirectories(path, recursive, true);
|
|
}
|
|
|
|
/// <summary>获取指定目录的子目录,可指定递归子目录的深度。</summary>
|
|
public static List<string> GetSubDirectories(string path, int depth)
|
|
{
|
|
return StorageHelper.GetSubDirectories(path, depth, true);
|
|
}
|
|
|
|
/// <summary>获取指定目录的子文件,可指定递归子目录。</summary>
|
|
public static List<string> GetSubFiles(string path, bool recursive = false)
|
|
{
|
|
return StorageHelper.GetSubFiles(path, recursive, true);
|
|
}
|
|
|
|
/// <summary>获取指定目录的子文件,可指定递归子目录的深度。</summary>
|
|
public static List<string> GetSubFiles(string path, int depth)
|
|
{
|
|
return StorageHelper.GetSubFiles(path, depth, true);
|
|
}
|
|
|
|
/// <summary>打开文件,并获取文件流。可选文件的锁定状态。</summary>
|
|
public static FileStream OpenFile(string path, bool share = true)
|
|
{
|
|
return StorageHelper.OpenFile(path, share);
|
|
}
|
|
|
|
/// <summary>获取文件或目录的存在状态。</summary>
|
|
public static bool PathExists(string path)
|
|
{
|
|
return StorageHelper.PathExists(path);
|
|
}
|
|
|
|
/// <summary>读取文件,获取文件内容。当文件为 UTF-8 文本文件时,可去除 BOM 头。</summary>
|
|
public static byte[] ReadFile(string path, bool wipeBom = false)
|
|
{
|
|
return StorageHelper.ReadFile(path, wipeBom);
|
|
}
|
|
|
|
/// <summary>将数据写入新文件,若文件已存在则覆盖。返回写入的字节数,发生错误时返回 -1 值。</summary>
|
|
public static long WriteFile(string path, Stream stream, Action<Int64> callback = null)
|
|
{
|
|
var created = CreateFile(path, 0, true);
|
|
if (created && stream != null)
|
|
{
|
|
var result = 0L;
|
|
try
|
|
{
|
|
var file = OpenFile(path);
|
|
if (file != null)
|
|
{
|
|
result = StreamHelper.Read(stream, file, Constant.DefaultBufferCapacity, callback);
|
|
}
|
|
KernelUtility.Dispose(file);
|
|
}
|
|
catch { }
|
|
return result;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
|
|
public static bool WriteFile(string path, params byte[] bytes)
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return false;
|
|
if (!CreateFile(path, 0, true)) return false;
|
|
|
|
try
|
|
{
|
|
if (bytes != null && bytes.LongLength > 0L)
|
|
{
|
|
File.WriteAllBytes(path, bytes);
|
|
}
|
|
}
|
|
catch { return false; }
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
|
|
public static bool WriteFile(string path, bool bom, params byte[] bytes)
|
|
{
|
|
return WriteFile(path, bom ? BinaryUtility.AddTextBom(bytes) : bytes);
|
|
}
|
|
|
|
/// <summary>将数据写入新文件,若文件已存在则覆盖。</summary>
|
|
public static bool WriteFile(string path, Json json, bool bom = false)
|
|
{
|
|
if (string.IsNullOrEmpty(path)) return false;
|
|
if (json == null || json.IsNull || json.IsNone) return false;
|
|
var text = json.ToString(true);
|
|
var bytes = TextUtility.ToBinary(text);
|
|
return StorageHelper.WriteFile(path, bom, bytes);
|
|
}
|
|
|
|
/// <summary>创建一个空文件且不保留句柄。</summary>
|
|
/// <param name="path">文件路径,若已存在则返回失败。</param>
|
|
/// <param name="length">文件长度(字节数)。</param>
|
|
/// <param name="replace">替换现有文件。</param>
|
|
/// <returns>创建成功。</returns>
|
|
public static bool CreateFile(string path, long length = 0, bool replace = false)
|
|
{
|
|
return StorageHelper.CreateFile(path, length, replace);
|
|
}
|
|
|
|
/// <summary>获取文件的最后写入时间。</summary>
|
|
public static DateTime GetFileLastWriteTime(string path, bool utc = false)
|
|
{
|
|
try
|
|
{
|
|
var info = new FileInfo(path);
|
|
return utc ? info.LastWriteTimeUtc : info.LastWriteTime;
|
|
}
|
|
catch { }
|
|
return new DateTime();
|
|
}
|
|
|
|
/// <summary>设置文件的最后写入时间。</summary>
|
|
public static string SetFileLastWriteTime(string path, DateTime value)
|
|
{
|
|
try
|
|
{
|
|
var info = new FileInfo(path);
|
|
info.LastWriteTime = value;
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ex.Message;
|
|
}
|
|
}
|
|
|
|
/// <summary>压缩文件到流。</summary>
|
|
/// <param name="files">Key 为 ZIP 内的文件名,Value 为原始文件路径。</param>
|
|
/// <param name="output">要输出的 ZIP 流。</param>
|
|
public static Exception ToZip(Dictionary<string, string> files, Stream output)
|
|
{
|
|
if (files == null) return new ArgumentNullException("files");
|
|
|
|
var streams = new List<Stream>();
|
|
var ex = BinaryUtility.ToZip(files.Keys, output, (name) =>
|
|
{
|
|
if (name.IsEmpty()) return null;
|
|
|
|
var path = files[name];
|
|
if (path.IsEmpty()) return null;
|
|
if (!FileExists(path)) return null;
|
|
|
|
var input = OpenFile(path);
|
|
streams.Add(input);
|
|
return input;
|
|
}, (name) => GetFileLastWriteTime(files[name]), true);
|
|
KernelUtility.Dispose(streams);
|
|
return ex;
|
|
}
|
|
|
|
/// <summary>压缩文件到流。</summary>
|
|
/// <param name="files">Key 为 ZIP 内的文件名,Value 为原始文件路径。</param>
|
|
/// <param name="output">要输出的 ZIP 文件路径,已存在的文件将被删除。</param>
|
|
public static Exception ToZip(Dictionary<string, string> files, string output)
|
|
{
|
|
if (output.IsEmpty()) return new ArgumentException("output");
|
|
if (FileExists(output)) DeleteFile(output);
|
|
if (FileExists(output)) return new IOException();
|
|
var stream = OpenFile(output);
|
|
if (stream == null) return new IOException();
|
|
var ex = ToZip(files, stream);
|
|
KernelUtility.Dispose(stream, true);
|
|
return ex;
|
|
}
|
|
|
|
/// <summary>压缩文件到流。</summary>
|
|
/// <param name="directory">原始文件目录。</param>
|
|
/// <param name="output">要输出的 ZIP 流。</param>
|
|
public static Exception ToZip(string directory, Stream output)
|
|
{
|
|
if (!DirectoryExists(directory)) return new DirectoryNotFoundException();
|
|
|
|
var dict = new Dictionary<string, string>();
|
|
foreach (var path in GetSubFiles(directory, true))
|
|
{
|
|
var name = path.Substring(directory.Length);
|
|
if (name.StartsWith("\\")) name = name.Substring(1);
|
|
if (name.StartsWith("/")) name = name.Substring(1);
|
|
dict.Add(name, path);
|
|
Console.WriteLine(name);
|
|
}
|
|
|
|
return ToZip(dict, output);
|
|
}
|
|
|
|
/// <summary>压缩文件到流。</summary>
|
|
/// <param name="directory">原始文件目录。</param>
|
|
/// <param name="output">要输出的 ZIP 文件路径,已存在的旧文件将被删除。</param>
|
|
public static Exception ToZip(string directory, string output)
|
|
{
|
|
if (output.IsEmpty()) return new ArgumentException("output");
|
|
if (FileExists(output)) DeleteFile(output);
|
|
if (FileExists(output)) return new IOException("无法删除现有文件。");
|
|
var stream = OpenFile(output);
|
|
if (stream == null) return new IOException("无法创建文件。");
|
|
var ex = ToZip(directory, stream);
|
|
KernelUtility.Dispose(stream, true);
|
|
return ex;
|
|
}
|
|
|
|
/// <summary>解压 ZIP 文件到目标目录。已存在的旧文件将被删除。</summary>
|
|
public static Exception FromZip(string zipPath, string outputDirectory = null)
|
|
{
|
|
if (!FileExists(zipPath)) return new FileNotFoundException("指定的 ZIP 文件路径无效。");
|
|
|
|
var directory = outputDirectory;
|
|
if (directory.IsEmpty())
|
|
{
|
|
if (zipPath.SafeLower().EndsWith(".zip"))
|
|
{
|
|
directory = zipPath.Substring(0, zipPath.Length - 4);
|
|
//if (directory.EndsWith("/") || directory.EndsWith("\\"))
|
|
//if (PathExists(directory)) directory = null;
|
|
}
|
|
else
|
|
{
|
|
directory = GetParentPath(zipPath);
|
|
}
|
|
}
|
|
if (directory.IsEmpty()) return new ArgumentException("无法判断目录路径。");
|
|
if (!AssureDirectory(directory)) return new DirectoryNotFoundException("无法创建目录。");
|
|
|
|
var input = OpenFile(zipPath);
|
|
if (input == null) return new IOException("无法打开 ZIP 文件。");
|
|
var info = new Dictionary<string, DateTime>();
|
|
var ex = BinaryUtility.FromZip(input, (name, size, modifield) =>
|
|
{
|
|
var path = Path.Combine(directory, name);
|
|
if (FileExists(path)) DeleteFile(path);
|
|
if (DirectoryExists(path)) DeleteDirectory(path, true);
|
|
if (PathExists(path)) throw new IOException("无法删除旧文件或旧目录。");
|
|
|
|
var output = OpenFile(path);
|
|
if (output == null) throw new IOException("无法创建文件 " + path + "。");
|
|
if (info.ContainsKey(path))
|
|
{
|
|
KernelUtility.Dispose(output);
|
|
return null;
|
|
}
|
|
info.Add(path, modifield);
|
|
return output;
|
|
}, (name, modified) =>
|
|
{
|
|
var path = Path.Combine(directory, name);
|
|
var exists = AssureDirectory(path);
|
|
if (!exists) throw new IOException("无法创建 ZIP 中对应的目录。");
|
|
}, true);
|
|
|
|
// 恢复文件的修改时间。
|
|
foreach (var i in info) SetFileLastWriteTime(i.Key, i.Value);
|
|
|
|
return ex;
|
|
}
|
|
|
|
///// <summary>解压 ZIP 文件到字典。失败且不允许异常时返回 NULL 值。</summary>
|
|
//public static Dictionary<string, byte[]> FromZip(string zipPath, bool allowException = false)
|
|
//{
|
|
// if (FileExists(zipPath))
|
|
// {
|
|
// var ex = new FileNotFoundException("文件不存在。");
|
|
// if (allowException) throw ex;
|
|
// return null;
|
|
// }
|
|
|
|
// var input = OpenFile(zipPath);
|
|
// if (input == null)
|
|
// {
|
|
// var ex = new IOException("无法打开 ZIP 文件。");
|
|
// if (allowException) throw ex;
|
|
// return null;
|
|
// }
|
|
|
|
// return BinaryUtility.FromZip(input, ;
|
|
//}
|
|
|
|
/// <summary>获取指定程序集的资源。</summary>
|
|
public static Stream GetResource(string name, Assembly assembly = null)
|
|
{
|
|
try
|
|
{
|
|
if (assembly == null) assembly = Assembly.GetExecutingAssembly();
|
|
var stream = assembly.GetManifestResourceStream(name);
|
|
return stream;
|
|
}
|
|
catch { }
|
|
return null;
|
|
}
|
|
|
|
/// <summary>获取当前进程程序集的资源。读取失败时返回 NULL 值。</summary>
|
|
public static byte[] ReadResource(string name, Assembly assembly = null)
|
|
{
|
|
var stream = GetResource(name, assembly);
|
|
if (stream == null) return null;
|
|
var bytes = BinaryUtility.Read(stream, true);
|
|
return bytes;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|