using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AibisDream.Utility;
using Newtonsoft.Json;
using UnityEngine;
namespace AibisDream.SaveSystem
{
///
/// 测试存档模式:每次自动存档写入 下的独立子目录。
/// 开启后不写入 slot_0,存多少次就保留多少份,与正式 demo_saves/ 完全隔离。
///
public static class TestSaveArchive
{
public const string EditorPrefsEnabledKey = "AibisDream.SaveSystem.TestArchiveEnabled";
/// 测试存档模式是否开启(Editor 下由 EditorPrefs 控制,构建产物恒为 false)。
public static bool IsEnabled
{
get
{
#if UNITY_EDITOR
return UnityEditor.EditorPrefs.GetBool(EditorPrefsEnabledKey, true);
#else
return false;
#endif
}
}
private static readonly object ArchiveWriteLock = new object();
/// 将快照写入 testsavs 新条目,返回文件夹名。每次调用均新建,不覆盖已有条目。
public static string Archive(SaveSnapshot snapshot, byte[] thumbnailPng)
{
if (snapshot == null)
{
return null;
}
var entryId = BuildUniqueEntryId(snapshot);
var entryDir = Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId);
Directory.CreateDirectory(entryDir);
var json = SnapshotPersistence.Serialize(snapshot);
var meta = BuildMeta(entryId, snapshot);
lock (ArchiveWriteLock)
{
SlotAtomicWriter.WriteText(json, Path.Combine(entryDir, $"{ConstRef.SaveSnapshotFileName}.json"));
SlotAtomicWriter.WriteJson(meta, Path.Combine(entryDir, $"{ConstRef.SaveMetaFileName}.json"));
if (thumbnailPng != null)
{
SlotAtomicWriter.WriteBytes(
thumbnailPng,
Path.Combine(entryDir, $"{ConstRef.SaveThumbnailFileName}.png"));
}
}
Debug.Log($"[TestSaveArchive] 已归档测试存档: {entryId}");
return entryId;
}
public static List ListEntries()
{
var result = new List();
if (!Directory.Exists(ConstRef.TestAutoSaveArchivePath))
{
return result;
}
foreach (var dir in Directory.GetDirectories(ConstRef.TestAutoSaveArchivePath))
{
var entryId = Path.GetFileName(dir);
var snapshotPath = Path.Combine(dir, $"{ConstRef.SaveSnapshotFileName}.json");
if (!File.Exists(snapshotPath))
{
continue;
}
var metaPath = Path.Combine(dir, $"{ConstRef.SaveMetaFileName}.json");
var thumbPath = Path.Combine(dir, $"{ConstRef.SaveThumbnailFileName}.png");
SlotMeta meta = null;
if (File.Exists(metaPath))
{
try
{
meta = JsonConvert.DeserializeObject(File.ReadAllText(metaPath));
}
catch (Exception ex)
{
Debug.LogWarning($"[TestSaveArchive] 读取 meta 失败 ({entryId}): {ex.Message}");
}
}
result.Add(new TestSaveArchiveEntry(
entryId,
dir,
snapshotPath,
File.Exists(thumbPath) ? thumbPath : null,
meta));
}
return result
.OrderByDescending(e => e.Meta?.savedAt ?? string.Empty)
.ThenByDescending(e => e.EntryId, StringComparer.Ordinal)
.ToList();
}
public static void DeleteEntry(string entryId)
{
if (string.IsNullOrEmpty(entryId))
{
return;
}
var dir = Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId);
if (Directory.Exists(dir))
{
Directory.Delete(dir, recursive: true);
}
}
public static void ClearAll()
{
if (!Directory.Exists(ConstRef.TestAutoSaveArchivePath))
{
return;
}
foreach (var dir in Directory.GetDirectories(ConstRef.TestAutoSaveArchivePath))
{
Directory.Delete(dir, recursive: true);
}
}
private static string BuildUniqueEntryId(SaveSnapshot snapshot)
{
var baseId = BuildEntryId(snapshot);
var entryId = baseId;
var suffix = 2;
while (Directory.Exists(Path.Combine(ConstRef.TestAutoSaveArchivePath, entryId)))
{
entryId = $"{baseId}_{suffix}";
suffix++;
}
return entryId;
}
private static string BuildEntryId(SaveSnapshot snapshot)
{
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss_fff");
var nodeName = snapshot.anchor?.nodeName;
var safeNode = string.IsNullOrEmpty(nodeName) ? "no_node" : SanitizeEntryName(nodeName);
return $"{timestamp}_{safeNode}";
}
private static SlotMeta BuildMeta(string entryId, SaveSnapshot snapshot)
{
var summary = SaveSnapshotSummary.From(snapshot);
return new SlotMeta
{
slotIndex = -1,
savedAt = snapshot.savedAt,
sceneName = summary.SceneName,
sceneSoName = summary.SceneSoName,
yarnProjectId = summary.YarnProjectId,
nodeName = snapshot.anchor?.nodeName,
schemaVersion = snapshot.schemaVersion,
gameVersion = snapshot.gameVersion,
thumbnailFile = $"{ConstRef.SaveThumbnailFileName}.png"
};
}
private static string SanitizeEntryName(string name)
{
foreach (var c in Path.GetInvalidFileNameChars())
{
name = name.Replace(c, '_');
}
if (name.Length > 48)
{
name = name.Substring(0, 48);
}
return string.IsNullOrWhiteSpace(name) ? "no_node" : name;
}
}
public readonly struct TestSaveArchiveEntry
{
public readonly string EntryId;
public readonly string DirectoryPath;
public readonly string SnapshotPath;
public readonly string ThumbnailPath;
public readonly SlotMeta Meta;
public TestSaveArchiveEntry(
string entryId,
string directoryPath,
string snapshotPath,
string thumbnailPath,
SlotMeta meta)
{
EntryId = entryId;
DirectoryPath = directoryPath;
SnapshotPath = snapshotPath;
ThumbnailPath = thumbnailPath;
Meta = meta;
}
}
}