139 lines
4.0 KiB
C#
139 lines
4.0 KiB
C#
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using AibisDream.Utility;
|
||
using Newtonsoft.Json;
|
||
using UnityEngine;
|
||
|
||
namespace AibisDream.SaveSystem
|
||
{
|
||
/// <summary>
|
||
/// 存档槽位目录与文件路径工具。
|
||
/// </summary>
|
||
public static class SlotDirectory
|
||
{
|
||
public static string GetSlotPath(int slotIndex)
|
||
{
|
||
return Path.Combine(ConstRef.SaveFilePath, $"{ConstRef.SaveSlotDirPrefix}{slotIndex}");
|
||
}
|
||
|
||
public static string GetSnapshotPath(int slotIndex)
|
||
{
|
||
return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveSnapshotFileName}.json");
|
||
}
|
||
|
||
public static string GetMetaPath(int slotIndex)
|
||
{
|
||
return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveMetaFileName}.json");
|
||
}
|
||
|
||
public static string GetThumbnailPath(int slotIndex)
|
||
{
|
||
return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveThumbnailFileName}.png");
|
||
}
|
||
|
||
public static string GetLatestSlotIndexPath()
|
||
{
|
||
return Path.Combine(ConstRef.SaveFilePath, $"{ConstRef.LatestSlotFileName}.json");
|
||
}
|
||
|
||
public static bool Exists(int slotIndex)
|
||
{
|
||
return File.Exists(GetMetaPath(slotIndex));
|
||
}
|
||
|
||
public static IEnumerable<int> EnumerateExistingSlots()
|
||
{
|
||
if (!Directory.Exists(ConstRef.SaveFilePath))
|
||
{
|
||
yield break;
|
||
}
|
||
|
||
for (var i = SlotIndex.Auto; i <= SlotIndex.ManualEnd; i++)
|
||
{
|
||
if (Exists(i))
|
||
{
|
||
yield return i;
|
||
}
|
||
}
|
||
}
|
||
|
||
public static void EnsureSlotDirectory(int slotIndex)
|
||
{
|
||
Directory.CreateDirectory(GetSlotPath(slotIndex));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 槽位文件原子写工具。
|
||
/// <para>先写入临时文件,再重命名为最终文件,避免写入过程中崩溃导致存档损坏。</para>
|
||
/// </summary>
|
||
public static class SlotAtomicWriter
|
||
{
|
||
public static void WriteJson<T>(T obj, string finalPath)
|
||
{
|
||
var json = JsonConvert.SerializeObject(obj, Formatting.Indented);
|
||
WriteText(json, finalPath);
|
||
}
|
||
|
||
public static void WriteText(string text, string finalPath)
|
||
{
|
||
var tempPath = finalPath + ".tmp";
|
||
var dir = Path.GetDirectoryName(finalPath);
|
||
if (!string.IsNullOrEmpty(dir))
|
||
{
|
||
Directory.CreateDirectory(dir);
|
||
}
|
||
|
||
File.WriteAllText(tempPath, text);
|
||
CommitTempFile(tempPath, finalPath);
|
||
}
|
||
|
||
public static void WriteBytes(byte[] data, string finalPath)
|
||
{
|
||
if (data == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var tempPath = finalPath + ".tmp";
|
||
var dir = Path.GetDirectoryName(finalPath);
|
||
if (!string.IsNullOrEmpty(dir))
|
||
{
|
||
Directory.CreateDirectory(dir);
|
||
}
|
||
|
||
File.WriteAllBytes(tempPath, data);
|
||
CommitTempFile(tempPath, finalPath);
|
||
}
|
||
|
||
private static void CommitTempFile(string tempPath, string finalPath)
|
||
{
|
||
const int maxAttempts = 5;
|
||
|
||
for (var attempt = 0; attempt < maxAttempts; attempt++)
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(finalPath))
|
||
{
|
||
// Unity 使用的 .NET Standard 2.0 无 File.Move(..., overwrite);Replace 为原子覆盖。
|
||
File.Replace(tempPath, finalPath, null);
|
||
}
|
||
else
|
||
{
|
||
File.Move(tempPath, finalPath);
|
||
}
|
||
|
||
return;
|
||
}
|
||
catch (IOException) when (attempt < maxAttempts - 1)
|
||
{
|
||
Thread.Sleep(20 * (attempt + 1));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|