using System; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using UnityEngine; namespace FlowScope.Save { public interface ISaveStorage { Task WriteAsync(string key, byte[] data, CancellationToken cancellationToken); Task ReadAsync(string key, CancellationToken cancellationToken); void Delete(string key); bool Exists(string key); } public sealed class FileSaveStorage : ISaveStorage { private readonly string _rootDirectory; public FileSaveStorage(string rootDirectory = null) { _rootDirectory = string.IsNullOrWhiteSpace(rootDirectory) ? Path.Combine(Application.persistentDataPath, "FlowScopeSaves") : rootDirectory; } public async Task WriteAsync(string key, byte[] data, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (data == null) { throw new ArgumentNullException(nameof(data)); } Directory.CreateDirectory(_rootDirectory); var path = GetPath(key); using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); await stream.WriteAsync(data, 0, data.Length, cancellationToken); } public async Task ReadAsync(string key, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var path = GetPath(key); if (!File.Exists(path)) { return null; } using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); var data = new byte[stream.Length]; var read = 0; while (read < data.Length) { var count = await stream.ReadAsync(data, read, data.Length - read, cancellationToken); if (count == 0) { break; } read += count; } return data; } public void Delete(string key) { var path = GetPath(key); if (File.Exists(path)) { File.Delete(path); } } public bool Exists(string key) { return File.Exists(GetPath(key)); } private string GetPath(string key) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("Save key is required.", nameof(key)); } return Path.Combine(_rootDirectory, Convert.ToBase64String(Encoding.UTF8.GetBytes(key)) + ".json"); } } }