using System.Collections.Concurrent; using System.IO; using System.Text; using GFramework.Core.Abstractions.serializer; using GFramework.Core.Abstractions.storage; using GFramework.Godot.extensions; using Godot; using Error = Godot.Error; using FileAccess = Godot.FileAccess; namespace GFramework.Godot.storage; /// /// Godot 特化的文件存储实现,支持 res://、user:// 和普通文件路径 /// 支持按 key 细粒度锁保证线程安全 /// public sealed class GodotFileStorage : IStorage { /// /// 每个 key 对应的锁对象 /// private readonly ConcurrentDictionary _keyLocks = new(); private readonly ISerializer _serializer; /// /// 初始化 Godot 文件存储 /// /// 序列化器实例 public GodotFileStorage(ISerializer serializer) { _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } #region Delete /// /// 删除指定键对应的文件 /// /// 存储键 public void Delete(string key) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); lock (keyLock) { // 处理Godot文件系统路径的删除操作 if (path.IsGodotPath()) { if (FileAccess.FileExists(path)) { var err = DirAccess.RemoveAbsolute(path); if (err != Error.Ok) throw new IOException($"Failed to delete Godot file: {path}, error: {err}"); } } // 处理标准文件系统路径的删除操作 else { if (File.Exists(path)) File.Delete(path); } } // 删除完成后尝试移除锁,防止锁字典无限增长 _keyLocks.TryRemove(path, out _); } /// /// 异步删除指定键对应的文件 /// /// 存储键 /// 异步任务 public async Task DeleteAsync(string key) { await Task.Run(() => Delete(key)); } #endregion #region Helpers /// /// 清理路径段中的无效字符,将无效文件名字符替换为下划线 /// /// 要清理的路径段 /// 清理后的路径段 private static string SanitizeSegment(string segment) { return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_')); } /// /// 将存储键转换为绝对路径,处理 Godot 虚拟路径和普通文件系统路径 /// /// 存储键 /// 绝对路径字符串 private static string ToAbsolutePath(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)); // Godot 虚拟路径直接使用 FileAccess 支持 if (key.IsGodotPath()) return key; // 普通文件系统路径 var segments = key.Split('/', StringSplitOptions.RemoveEmptyEntries) .Select(SanitizeSegment) .ToArray(); if (segments.Length == 0) throw new ArgumentException("Invalid storage key", nameof(key)); var dir = Path.Combine(segments[..^1]); var fileName = segments[^1]; if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); return Path.Combine(dir, fileName); } /// /// 获取指定路径对应的锁对象,如果不存在则创建新的锁对象 /// /// 文件路径 /// 对应路径的锁对象 private object GetLock(string path) { return _keyLocks.GetOrAdd(path, _ => new object()); } #endregion #region Exists /// /// 检查指定键对应的文件是否存在 /// /// 存储键 /// 文件存在返回 true,否则返回 false public bool Exists(string key) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); lock (keyLock) { if (!path.IsGodotPath()) return File.Exists(path); using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); return file != null; } } /// /// 异步检查指定键对应的文件是否存在 /// /// 存储键 /// 表示异步操作的任务,结果为布尔值表示文件是否存在 public Task ExistsAsync(string key) { return Task.FromResult(Exists(key)); } #endregion #region Read /// /// 读取指定键对应的序列化数据并反序列化为指定类型 /// /// 要反序列化的类型 /// 存储键 /// 反序列化后的对象实例 /// 当指定键对应的文件不存在时抛出 public T Read(string key) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); lock (keyLock) { string content; if (path.IsGodotPath()) { using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); if (file == null) throw new FileNotFoundException($"Storage key not found: {key}", path); content = file.GetAsText(); } else { if (!File.Exists(path)) throw new FileNotFoundException($"Storage key not found: {key}", path); content = File.ReadAllText(path, Encoding.UTF8); } return _serializer.Deserialize(content); } } /// /// 读取指定键对应的序列化数据,如果文件不存在则返回默认值 /// /// 要反序列化的类型 /// 存储键 /// 当文件不存在时返回的默认值 /// 反序列化后的对象实例或默认值 public T Read(string key, T defaultValue) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); lock (keyLock) { if ((path.IsGodotPath() && !FileAccess.FileExists(path)) || (!path.IsGodotPath() && !File.Exists(path))) return defaultValue; return Read(key); } } /// /// 异步读取指定键对应的序列化数据并反序列化为指定类型 /// /// 要反序列化的类型 /// 存储键 /// 表示异步操作的任务,结果为反序列化后的对象实例 public async Task ReadAsync(string key) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); return await Task.Run(() => { lock (keyLock) { string content; if (path.IsGodotPath()) { using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read); if (file == null) throw new FileNotFoundException($"Storage key not found: {key}", path); content = file.GetAsText(); } else { if (!File.Exists(path)) throw new FileNotFoundException($"Storage key not found: {key}", path); content = File.ReadAllText(path, Encoding.UTF8); } return _serializer.Deserialize(content); } }); } #endregion #region Write /// /// 将指定对象序列化并写入到指定键对应的文件中 /// /// 要序列化的对象类型 /// 存储键 /// 要写入的对象实例 public void Write(string key, T value) { var path = ToAbsolutePath(key); var keyLock = GetLock(path); lock (keyLock) { var content = _serializer.Serialize(value); if (path.IsGodotPath()) { using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write); if (file == null) throw new IOException($"Cannot write file: {path}"); file.StoreString(content); } else { Directory.CreateDirectory(Path.GetDirectoryName(path)!); File.WriteAllText(path, content, Encoding.UTF8); } } } /// /// 异步将指定对象序列化并写入到指定键对应的文件中 /// /// 要序列化的对象类型 /// 存储键 /// 要写入的对象实例 /// 表示异步操作的任务 public async Task WriteAsync(string key, T value) { await Task.Run(() => Write(key, value)); } #endregion }