using System.Text;
using GFramework.Core.Abstractions.storage;
using GFramework.Game.Abstractions.serializer;
namespace GFramework.Game.storage;
///
/// 基于文件系统的存储实现,实现了IStorage接口
///
public sealed class FileStorage : IStorage
{
private readonly string _extension;
private readonly string _rootPath;
private readonly ISerializer _serializer;
///
/// 初始化FileStorage实例
///
/// 存储根目录路径
/// 序列化器实例
/// 存储文件的扩展名
public FileStorage(string rootPath, ISerializer serializer, string extension = ".dat")
{
_rootPath = rootPath;
_serializer = serializer;
_extension = extension;
Directory.CreateDirectory(_rootPath);
}
#region Delete
///
/// 删除指定键的存储项
///
/// 存储键
public void Delete(string key)
{
var path = ToPath(key);
if (File.Exists(path))
File.Delete(path);
}
#endregion
///
/// 清理文件段字符串,将其中的无效文件名字符替换为下划线
///
/// 需要清理的文件段字符串
/// 清理后的字符串,其中所有无效文件名字符都被替换为下划线
private static string SanitizeSegment(string segment)
{
return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_'));
}
#region Helpers
///
/// 将存储键转换为文件路径
///
/// 存储键
/// 对应的文件路径
private string ToPath(string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Storage key cannot be empty", nameof(key));
// 统一分隔符
key = key.Replace('\\', '/');
// 防止路径逃逸
if (key.Contains(".."))
throw new ArgumentException("Storage key cannot contain '..'", nameof(key));
var segments = key
.Split('/', StringSplitOptions.RemoveEmptyEntries)
.Select(SanitizeSegment)
.ToArray();
if (segments.Length == 0)
throw new ArgumentException("Invalid storage key", nameof(key));
// 目录部分
var dirSegments = segments[..^1];
var fileName = segments[^1] + _extension;
var dirPath = dirSegments.Length == 0
? _rootPath
: Path.Combine(_rootPath, Path.Combine(dirSegments));
Directory.CreateDirectory(dirPath);
return Path.Combine(dirPath, fileName);
}
#endregion
#region Exists
///
/// 检查指定键的存储项是否存在
///
/// 存储键
/// 存在返回true,否则返回false
public bool Exists(string key)
=> File.Exists(ToPath(key));
///
/// 异步检查指定键的存储项是否存在
///
/// 存储键
/// 存在返回true,否则返回false
public Task ExistsAsync(string key)
=> Task.FromResult(Exists(key));
#endregion
#region Read
///
/// 读取指定键的存储项
///
/// 要反序列化的类型
/// 存储键
/// 反序列化后的对象
/// 当指定键不存在时抛出
public T Read(string key)
{
var path = ToPath(key);
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
var content = File.ReadAllText(path, Encoding.UTF8);
return _serializer.Deserialize(content);
}
///
/// 读取指定键的存储项,如果不存在则返回默认值
///
/// 要反序列化的类型
/// 存储键
/// 默认值
/// 存在则返回反序列化后的对象,否则返回默认值
public T Read(string key, T defaultValue)
{
var path = ToPath(key);
if (!File.Exists(path))
return defaultValue;
var content = File.ReadAllText(path, Encoding.UTF8);
return _serializer.Deserialize(content);
}
///
/// 异步读取指定键的存储项
///
/// 要反序列化的类型
/// 存储键
/// 反序列化后的对象
/// 当指定键不存在时抛出
public async Task ReadAsync(string key)
{
var path = ToPath(key);
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
var content = await File.ReadAllTextAsync(path, Encoding.UTF8);
return _serializer.Deserialize(content);
}
#endregion
#region Write
///
/// 写入指定键的存储项
///
/// 要序列化的类型
/// 存储键
/// 要存储的值
public void Write(string key, T value)
{
var path = ToPath(key);
var content = _serializer.Serialize(value);
File.WriteAllText(path, content, Encoding.UTF8);
}
///
/// 异步写入指定键的存储项
///
/// 要序列化的类型
/// 存储键
/// 要存储的值
public async Task WriteAsync(string key, T value)
{
var path = ToPath(key);
var content = _serializer.Serialize(value);
await File.WriteAllTextAsync(path, content, Encoding.UTF8);
}
#endregion
}