using System; using System.IO; using System.Linq; using System.Threading.Tasks; using UnityEngine; namespace Jovian.SaveSystem { /// /// File-system backed storage. Base path is typically Application.persistentDataPath. /// All paths passed to methods are relative to the constructed base path. /// public sealed class FileSystemSaveStorage : ISaveStorage { private readonly string basePath; public FileSystemSaveStorage(string rootPath, string saveDirectoryName) { basePath = Path.Combine(rootPath, saveDirectoryName); } private string ResolvePath(string relativePath) { return Path.Combine(basePath, relativePath); } public void CreateDirectory(string path) { string fullPath = ResolvePath(path); if(!Directory.Exists(fullPath)) { Directory.CreateDirectory(fullPath); } } public void Write(string path, byte[] data) { string fullPath = ResolvePath(path); EnsureDirectory(fullPath); File.WriteAllBytes(fullPath, data); } public byte[] Read(string path) { string fullPath = ResolvePath(path); if(!File.Exists(fullPath)) { throw new FileNotFoundException($"Save file not found: {fullPath}"); } return File.ReadAllBytes(fullPath); } public bool Exists(string path) { return File.Exists(ResolvePath(path)); } public void Delete(string path) { string fullPath = ResolvePath(path); if(File.Exists(fullPath)) { File.Delete(fullPath); } } public string[] List(string directoryPath) { string fullPath = ResolvePath(directoryPath); if(!Directory.Exists(fullPath)) { return Array.Empty(); } return Directory.GetFiles(fullPath) .Select(f => Path.GetFileName(f)) .ToArray(); } public async Task WriteAsync(string path, byte[] data) { string fullPath = ResolvePath(path); EnsureDirectory(fullPath); using(FileStream stream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) { await stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); } } public async Task ReadAsync(string path) { string fullPath = ResolvePath(path); if(!File.Exists(fullPath)) { throw new FileNotFoundException($"Save file not found: {fullPath}"); } using(FileStream stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true)) { byte[] buffer = new byte[stream.Length]; await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); return buffer; } } public Task ExistsAsync(string path) { return Task.FromResult(Exists(path)); } public Task DeleteAsync(string path) { Delete(path); return Task.CompletedTask; } public Task ListAsync(string directoryPath) { return Task.FromResult(List(directoryPath)); } private static void EnsureDirectory(string filePath) { string directory = Path.GetDirectoryName(filePath); if(!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } } }