using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
internal static class DockedMaintenance
{
internal static async Task RunAsync(int pid,string directory,CancellationToken externalToken=default)
{
directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
using var lease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||!initial.Docked||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||!initial.StationServicesKnown
||initial.MarketId is not (3228792064 or 4210924803)||string.IsNullOrWhiteSpace(initial.CommanderId))
throw new InvalidOperationException("Expected the empty, docked CG ship at its verified route station.");
await using var protection=await FlightTestProtectionScope.ArmAsync(pid);
var channel=new NativeCommerceChannel(pid);using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));
NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
async Task Check(CancellationToken token)
{
var player=Player();token.ThrowIfCancellationRequested();
if(!player.InGame||!player.Docked||player.MarketId!=initial.MarketId||player.CommanderId!=initial.CommanderId||player.CurrentShipId!=initial.CurrentShipId
||player.LoadGameAt!=initial.LoadGameAt||player.CargoCount!=0||protection.Stopped||NavigationSafety.HasExternalThreat(player))
throw new InvalidOperationException("Docked maintenance ship, cargo or protection changed.");
var h=(await channel.CallAsync(new{op="health.read"},token)).GetProperty("shipHealth");
if(!h.GetProperty("verified").GetBoolean()||h.GetProperty("hullFraction").GetDouble()<.5)throw new InvalidOperationException("Fresh original ship health required.");
}
var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),(_,_,_)=>throw new InvalidOperationException("No travel in docked maintenance."),Check);
driver.WalletObserved+=value=>wallet=value;driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(3));
using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop")))stop.Cancel();},null,100,100);
string? failure=null;long? opening=null,closing=null;
try
{
await driver.PreflightAsync(stop.Token);var path=Path.Combine(directory,"opening-wallet.json");
if(File.Exists(path)){using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(path,stop.Token));opening=saved.RootElement.GetProperty("credits").GetInt64();}
else
{
opening=wallet!.Credits;await using var file=new FileStream(path,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{credits=opening},cancellationToken:stop.Token);
}
await driver.ServiceDockedAsync(new(initial.MarketId!.Value,192),Check,stop.Token);
await Check(stop.Token);var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closing=inventory.GetProperty("credits").GetInt64();
if(inventory.GetProperty("count").GetInt32()!=0||opening+ledger.Snapshot.CashChange!=closing)
throw new InvalidDataException("Docked maintenance wallet does not reconcile with confirmed service debits.");
}
catch(Exception error){failure=error.ToString();}
finally
{
await driver.ReleaseAllAsync();
await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),JsonSerializer.Serialize(new{passed=failure is null,pid,finished=DateTimeOffset.UtcNow,
initial.MarketId,opening,closing,profit=ledger.Snapshot,failure,cargoCount=0,tradeOrdersSent=0,screenUse=false},ToolboxSettings.JsonOptions));
}
if(failure is not null)throw new InvalidOperationException("Docked maintenance requires reconciliation: "+failure);
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,opening,closing,profit=ledger.Snapshot}));
}
}
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
internal static class DockedMaintenance
{
internal static async Task RunAsync(int pid,string directory,CancellationToken externalToken=default)
{
directory=Path.GetFullPath(directory);Directory.CreateDirectory(directory);
using var lease=new FileStream(Path.Combine(directory,"runner.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||!initial.Docked||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||!initial.StationServicesKnown
||initial.MarketId is not (3228792064 or 4210924803)||string.IsNullOrWhiteSpace(initial.CommanderId))
throw new InvalidOperationException("Expected the empty, docked CG ship at its verified route station.");
await using var protection=await FlightTestProtectionScope.ArmAsync(pid);
var channel=new NativeCommerceChannel(pid);using var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"));
NativeWalletObservation? wallet=null;PlayerState Player()=>wallet?.Project(journal.State)??journal.State;
async Task Check(CancellationToken token)
{
var player=Player();token.ThrowIfCancellationRequested();
if(!player.InGame||!player.Docked||player.MarketId!=initial.MarketId||player.CommanderId!=initial.CommanderId||player.CurrentShipId!=initial.CurrentShipId
||player.LoadGameAt!=initial.LoadGameAt||player.CargoCount!=0||protection.Stopped||NavigationSafety.HasExternalThreat(player))
throw new InvalidOperationException("Docked maintenance ship, cargo or protection changed.");
var h=(await channel.CallAsync(new{op="health.read"},token)).GetProperty("shipHealth");
if(!h.GetProperty("verified").GetBoolean()||h.GetProperty("hullFraction").GetDouble()<.5)throw new InvalidOperationException("Fresh original ship health required.");
}
var driver=new NativeTradeCommandDriver(channel,Player,Path.Combine(directory,"operations"),(_,_,_)=>throw new InvalidOperationException("No travel in docked maintenance."),Check);
driver.WalletObserved+=value=>wallet=value;driver.ServiceConfirmed+=receipt=>ledger.Record(receipt.ToCashEntry());driver.Status+=Console.WriteLine;
using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(3));
using var sentinel=new Timer(_=>{if(File.Exists(Path.Combine(directory,"stop")))stop.Cancel();},null,100,100);
string? failure=null;long? opening=null,closing=null;
try
{
await driver.PreflightAsync(stop.Token);var path=Path.Combine(directory,"opening-wallet.json");
if(File.Exists(path)){using var saved=JsonDocument.Parse(await File.ReadAllTextAsync(path,stop.Token));opening=saved.RootElement.GetProperty("credits").GetInt64();}
else
{
opening=wallet!.Credits;await using var file=new FileStream(path,FileMode.CreateNew,FileAccess.Write,FileShare.Read);
await JsonSerializer.SerializeAsync(file,new{credits=opening},cancellationToken:stop.Token);
}
await driver.ServiceDockedAsync(new(initial.MarketId!.Value,192),Check,stop.Token);
await Check(stop.Token);var inventory=await channel.CallAsync(new{op="commerce.inventory"},stop.Token);closing=inventory.GetProperty("credits").GetInt64();
if(inventory.GetProperty("count").GetInt32()!=0||opening+ledger.Snapshot.CashChange!=closing)
throw new InvalidDataException("Docked maintenance wallet does not reconcile with confirmed service debits.");
}
catch(Exception error){failure=error.ToString();}
finally
{
await driver.ReleaseAllAsync();
await File.WriteAllTextAsync(Path.Combine(directory,"result.json"),JsonSerializer.Serialize(new{passed=failure is null,pid,finished=DateTimeOffset.UtcNow,
initial.MarketId,opening,closing,profit=ledger.Snapshot,failure,cargoCount=0,tradeOrdersSent=0,screenUse=false},ToolboxSettings.JsonOptions));
}
if(failure is not null)throw new InvalidOperationException("Docked maintenance requires reconciliation: "+failure);
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,opening,closing,profit=ledger.Snapshot}));
}
}