XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace XFEEDToolBox.Core.Execution;

public sealed record OriginalCommunityGoalRead(ulong Generation,string Proof,string Body,IReadOnlyList<CommunityGoalInfo> Goals);

/// <summary>Official SDK membership; uncertain signup intents are never replayed.</summary>
public sealed class NativeCommunityGoalClient(IOriginalCommerceChannel channel,long marketId=CommunityGoalCatalogue.HomerMarket,ulong? goalId=null)
{
    private CommunityGoalInfo Target(IReadOnlyList<CommunityGoalInfo> goals,bool subscribed=false)
        =>CommunityGoalCatalogue.RequireDestinationTrade(goals,marketId,goalId,DateTimeOffset.UtcNow,subscribed);
    private async Task<JsonElement> WaitAsync(ulong generation,CancellationToken token)
    {
        using var timeout=CancellationTokenSource.CreateLinkedTokenSource(token);timeout.CancelAfter(TimeSpan.FromSeconds(35));
        while(true)
        {
            JsonElement result;
            try{result=await channel.CallAsync(new{op="cg.status",generation},timeout.Token);}
            catch(Exception error)when(!timeout.IsCancellationRequested&&error is IOException or OperationCanceledException)
            {await Task.Delay(80,timeout.Token);continue;}
            if(result.GetProperty("state").GetInt32()>=3)return result;
            await Task.Delay(80,timeout.Token);
        }
    }
    public async Task<OriginalCommunityGoalRead> ReadAsync(bool mine,CancellationToken token=default)
    {
        // Journal Docked can precede the original healthy docking snapshot.
        // Retry only this pre-queue refusal; an accepted generation is polled,
        // and signup intents retain their existing no-replay contract.
        var until=DateTimeOffset.UtcNow.AddSeconds(10);JsonElement queued;
        while(true)
        {
            token.ThrowIfCancellationRequested();
            try{queued=await channel.CallAsync(new{op=mine?"cg.read.mine":"cg.read",marketId},token);break;}
            catch(InvalidOperationException error)when(error.Message=="Fresh healthy docking at the requested CG market required"&&DateTimeOffset.UtcNow<until)
            {await Task.Delay(80,token);}
        }
        var generation=queued.GetProperty("generation").GetUInt64();var result=await WaitAsync(generation,token);
        if(result.GetProperty("state").GetInt32()!=3||result.GetProperty("kind").GetInt32()!=(mine?2:0)
            ||result.GetProperty("mutationsSent").GetInt32()!=0||!result.GetProperty("fresh").GetBoolean())
            throw new InvalidDataException("Original CG read did not return a fresh, read-only response.");
        var body=result.GetProperty("responseBody").GetString()!;var proof=result.GetProperty("proof").GetString()!;
        if(Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))!=proof)throw new InvalidDataException("CG response proof does not match.");
        return new(generation,proof,body,CommunityGoalCatalogue.Parse(body));
    }
    public async Task<CommunityGoalInfo> EnsureTradeJoinedAsync(string directory,Func<CancellationToken,Task> beforeInput,CancellationToken token=default)
    {
        Directory.CreateDirectory(directory);await beforeInput(token);
        var mine=await ReadAsync(true,token);
        await File.WriteAllTextAsync(Path.Combine(directory,"membership-latest.json"),JsonSerializer.Serialize(mine),token);
        var joined=mine.Goals.SingleOrDefault(goal=>goal.MarketId==marketId&&(!goalId.HasValue||goal.Id==goalId)&&goal.Active&&goal.Expires>DateTimeOffset.UtcNow);
        if(joined is not null)
        {
            joined=Target(mine.Goals,true);
            // A fresh original membership response also reconciles a prior
            // process loss after opt-in; no signup needs to be sent again.
            foreach(var pending in Directory.EnumerateFiles(directory,"*.pending.json"))
            {
                var confirmed=pending.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal);
                if(File.Exists(confirmed))continue;
                using var intent=JsonDocument.Parse(await File.ReadAllTextAsync(pending,token));
                var original=intent.RootElement.GetProperty("target");
                if(original.GetProperty("Id").GetUInt64()!=joined.Id||original.GetProperty("MarketId").GetInt64()!=joined.MarketId)
                    throw new InvalidDataException("A prior signup belongs to another CG and cannot be reconciled as this membership.");
                await File.WriteAllTextAsync(confirmed,JsonSerializer.Serialize(new{joined,reconciledFromOriginalMembership=true}),token);
            }
            return joined;
        }
        foreach(var pending in Directory.EnumerateFiles(directory,"*.pending.json"))
            if(!File.Exists(pending.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal)))
                throw new InvalidOperationException("A prior CG signup is unresolved; no signup will be replayed.");
        var all=await ReadAsync(false,token);var target=Target(all.Goals);
        await beforeInput(token);var operationKey=Guid.NewGuid().ToString("N");
        var pendingPath=Path.Combine(directory,operationKey+".pending.json");
        await using(var file=new FileStream(pendingPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read))
            await JsonSerializer.SerializeAsync(file,new{operationKey,target,channel=channel.Identity,all.Generation,all.Proof,at=DateTimeOffset.UtcNow},cancellationToken:token);
        // Once the original request is accepted, finish observing it even if
        // the caller cancels. Never substitute an unverified local flag.
        var queued=await channel.CallAsync(new{op="cg.join",marketId,goalId=target.Id,
            operationKey,generation=all.Generation,proof=all.Proof},token);
        using var reconcile=new CancellationTokenSource(TimeSpan.FromSeconds(50));
        var result=await WaitAsync(queued.GetProperty("generation").GetUInt64(),reconcile.Token);
        await File.WriteAllTextAsync(Path.Combine(directory,operationKey+".response.json"),JsonSerializer.Serialize(result),reconcile.Token);
        if(result.GetProperty("state").GetInt32()!=3)throw new InvalidOperationException("Original CG signup has not confirmed success; no replay is permitted.");
        mine=await ReadAsync(true,reconcile.Token);joined=Target(mine.Goals,true);
        if(joined.Id!=target.Id)throw new InvalidDataException("CG membership differs from the exact signup target.");
        await File.WriteAllTextAsync(Path.Combine(directory,"membership-latest.json"),JsonSerializer.Serialize(mine),reconcile.Token);
        await File.WriteAllTextAsync(Path.Combine(directory,operationKey+".confirmed.json"),JsonSerializer.Serialize(new{operationKey,joined,confirmedAt=DateTimeOffset.UtcNow}),reconcile.Token);
        return joined;
    }
}