using System.Text.Json;
namespace XFEEDToolBox.Core.Execution;
public sealed record TradeJourneyCheckpoint(string Commander,long ShipId,DateTimeOffset StartedAt,
ConfirmedTradeReceipt? Bought=null,ConfirmedTradeReceipt? Sold=null);
/// <summary>Durable journey state; reloading never changes a confirmed order's identity or session.</summary>
public static class TradeJourneyCheckpoints
{
private static readonly JsonSerializerOptions Json=new(JsonSerializerDefaults.Web){WriteIndented=true};
public static TradeJourneyCheckpoint Load(string path)
{
var value=JsonSerializer.Deserialize<TradeJourneyCheckpoint>(File.ReadAllText(path),Json)
??throw new InvalidDataException("跑商恢复记录为空。");
Validate(value);return value;
}
public static void Save(string path,TradeJourneyCheckpoint value)
{
Validate(value);path=Path.GetFullPath(path);Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var temporary=path+"."+Guid.NewGuid().ToString("N")+".tmp";
try
{
using(var file=new FileStream(temporary,FileMode.CreateNew,FileAccess.Write,FileShare.Read,4096,FileOptions.WriteThrough))
{JsonSerializer.Serialize(file,value,Json);file.Flush(true);}
File.Move(temporary,path,true);
}
finally{if(File.Exists(temporary))File.Delete(temporary);}
}
private static void Validate(TradeJourneyCheckpoint value)
{
if(string.IsNullOrWhiteSpace(value.Commander)||value.ShipId<0||value.StartedAt==default||value.Sold is not null&&value.Bought is null)
throw new InvalidDataException("跑商恢复记录缺少原始指挥官、飞船或买入回执。");
void Receipt(ConfirmedTradeReceipt receipt,bool buy)
{
if(!Guid.TryParseExact(receipt.OperationKey,"N",out _)||!receipt.ServerConfirmed||!receipt.ClientApplied
||receipt.CommanderId!=value.Commander||receipt.ShipId!=value.ShipId||receipt.MarketId<=0||string.IsNullOrWhiteSpace(receipt.Commodity)
||(buy?receipt.QuantityDelta<=0:receipt.QuantityDelta>=0)||receipt.UnitPrice<=0||receipt.CreditsBefore<0||receipt.CreditsAfter<0
||receipt.CargoBefore<0||receipt.CargoAfter<0||(long)receipt.CargoAfter-receipt.CargoBefore!=receipt.QuantityDelta
||(decimal)receipt.CreditsAfter-receipt.CreditsBefore!=-(decimal)receipt.UnitPrice*receipt.QuantityDelta
||receipt.SessionStartedAt==default||receipt.SessionStartedAt>receipt.ConfirmedAt||receipt.ConfirmedAt<value.StartedAt)
throw new InvalidDataException("跑商恢复记录中的原始交易回执不一致。");
}
if(value.Bought is {} purchase)Receipt(purchase,true);
if(value.Sold is {} sale)
{
Receipt(sale,false);var bought=value.Bought!;
if(sale.OperationKey==bought.OperationKey||sale.Commodity!=bought.Commodity||(long)sale.QuantityDelta!=-((long)bought.QuantityDelta)||sale.ConfirmedAt<bought.ConfirmedAt)
throw new InvalidDataException("跑商恢复记录中的买卖回执未对应同一批货物。");
}
}
}
using System.Text.Json;
namespace XFEEDToolBox.Core.Execution;
public sealed record TradeJourneyCheckpoint(string Commander,long ShipId,DateTimeOffset StartedAt,
ConfirmedTradeReceipt? Bought=null,ConfirmedTradeReceipt? Sold=null);
/// <summary>Durable journey state; reloading never changes a confirmed order's identity or session.</summary>
public static class TradeJourneyCheckpoints
{
private static readonly JsonSerializerOptions Json=new(JsonSerializerDefaults.Web){WriteIndented=true};
public static TradeJourneyCheckpoint Load(string path)
{
var value=JsonSerializer.Deserialize<TradeJourneyCheckpoint>(File.ReadAllText(path),Json)
??throw new InvalidDataException("跑商恢复记录为空。");
Validate(value);return value;
}
public static void Save(string path,TradeJourneyCheckpoint value)
{
Validate(value);path=Path.GetFullPath(path);Directory.CreateDirectory(Path.GetDirectoryName(path)!);
var temporary=path+"."+Guid.NewGuid().ToString("N")+".tmp";
try
{
using(var file=new FileStream(temporary,FileMode.CreateNew,FileAccess.Write,FileShare.Read,4096,FileOptions.WriteThrough))
{JsonSerializer.Serialize(file,value,Json);file.Flush(true);}
File.Move(temporary,path,true);
}
finally{if(File.Exists(temporary))File.Delete(temporary);}
}
private static void Validate(TradeJourneyCheckpoint value)
{
if(string.IsNullOrWhiteSpace(value.Commander)||value.ShipId<0||value.StartedAt==default||value.Sold is not null&&value.Bought is null)
throw new InvalidDataException("跑商恢复记录缺少原始指挥官、飞船或买入回执。");
void Receipt(ConfirmedTradeReceipt receipt,bool buy)
{
if(!Guid.TryParseExact(receipt.OperationKey,"N",out _)||!receipt.ServerConfirmed||!receipt.ClientApplied
||receipt.CommanderId!=value.Commander||receipt.ShipId!=value.ShipId||receipt.MarketId<=0||string.IsNullOrWhiteSpace(receipt.Commodity)
||(buy?receipt.QuantityDelta<=0:receipt.QuantityDelta>=0)||receipt.UnitPrice<=0||receipt.CreditsBefore<0||receipt.CreditsAfter<0
||receipt.CargoBefore<0||receipt.CargoAfter<0||(long)receipt.CargoAfter-receipt.CargoBefore!=receipt.QuantityDelta
||(decimal)receipt.CreditsAfter-receipt.CreditsBefore!=-(decimal)receipt.UnitPrice*receipt.QuantityDelta
||receipt.SessionStartedAt==default||receipt.SessionStartedAt>receipt.ConfirmedAt||receipt.ConfirmedAt<value.StartedAt)
throw new InvalidDataException("跑商恢复记录中的原始交易回执不一致。");
}
if(value.Bought is {} purchase)Receipt(purchase,true);
if(value.Sold is {} sale)
{
Receipt(sale,false);var bought=value.Bought!;
if(sale.OperationKey==bought.OperationKey||sale.Commodity!=bought.Commodity||(long)sale.QuantityDelta!=-((long)bought.QuantityDelta)||sale.ConfirmedAt<bought.ConfirmedAt)
throw new InvalidDataException("跑商恢复记录中的买卖回执未对应同一批货物。");
}
}
}