163 lines
8.2 KiB
C#
163 lines
8.2 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using game.Account;
|
|
|
|
namespace game.PlayerDataStorage
|
|
{
|
|
// Prebuild distinct transports with one cookie jar, then transfer each owned handler once.
|
|
internal sealed class PlayerKVGameLoadCapability
|
|
{
|
|
internal Uri Api { get; }
|
|
internal string CacheRoot { get; }
|
|
internal int LoginObserveMs { get; }
|
|
internal int QuitObserveMs { get; }
|
|
internal bool RequireExistingArchive { get; }
|
|
internal Func<int,CancellationToken,Task> Delay { get; }
|
|
internal PlayerKVControlledLoadCapability LeaseCapability { get; private set; }
|
|
private readonly Func<CookieContainer, HttpMessageHandler> auth;
|
|
private readonly Func<CookieContainer, HttpMessageHandler> kv;
|
|
private readonly Func<CookieContainer, HttpMessageHandler> lease;
|
|
private readonly bool prepareOriginalComparison;
|
|
readonly object gate=new object();
|
|
CookieContainer cookies;
|
|
HttpMessageHandler pendingKV,pendingLease;
|
|
Exception cleanupFailure;
|
|
readonly System.Collections.Generic.List<HttpMessageHandler> failedHandlers=new System.Collections.Generic.List<HttpMessageHandler>();
|
|
bool claimed;
|
|
private PlayerKVGameLoadCapability(Uri api, string cacheRoot,
|
|
int loginMs, int quitMs, Func<int, CancellationToken, Task> delay,
|
|
Func<CookieContainer, HttpMessageHandler> auth, Func<CookieContainer, HttpMessageHandler> kv,
|
|
Func<CookieContainer, HttpMessageHandler> lease, bool requireExistingArchive = false, bool prepareOriginalComparison = false)
|
|
{
|
|
// Preserve the former wrappers' null-check order, diagnostic and configuration precedence.
|
|
this.auth = auth ?? throw new ArgumentNullException("leaseHandler");
|
|
this.kv = kv ?? throw new ArgumentNullException("leaseHandler");
|
|
this.lease = lease ?? throw new ArgumentNullException("leaseHandler");
|
|
if (api == null || !api.IsAbsoluteUri || (api.Scheme != "http" && api.Scheme != "https") || !string.IsNullOrEmpty(api.UserInfo) ||
|
|
!string.IsNullOrEmpty(api.Query) || !string.IsNullOrEmpty(api.Fragment) || string.IsNullOrEmpty(cacheRoot) ||
|
|
loginMs < 1000 || loginMs > 15000 || quitMs < 1000 || quitMs > 15000)
|
|
{
|
|
throw new ArgumentException("Controlled load configuration invalid.");
|
|
}
|
|
Api = new Uri(api.AbsoluteUri.TrimEnd('/') + "/");
|
|
CacheRoot = cacheRoot;
|
|
LoginObserveMs = loginMs;
|
|
QuitObserveMs = quitMs;
|
|
Delay = delay ?? Task.Delay;
|
|
RequireExistingArchive = requireExistingArchive;
|
|
this.prepareOriginalComparison = prepareOriginalComparison;
|
|
}
|
|
internal static PlayerKVGameLoadCapability CreateForGame(Uri api, string cacheRoot,
|
|
Func<CookieContainer, HttpMessageHandler> auth, Func<CookieContainer, HttpMessageHandler> kv,
|
|
Func<CookieContainer, HttpMessageHandler> lease, bool prepareOriginalComparison = false)
|
|
{
|
|
if (api == null || api.AbsoluteUri != "http://s101.local:18080/")
|
|
{
|
|
throw new ArgumentException("Formal KV endpoint mismatch.", nameof(api));
|
|
}
|
|
var capability = new PlayerKVGameLoadCapability(api, cacheRoot, 10000, 5000, null, auth, kv, lease, true, prepareOriginalComparison);
|
|
capability.LeaseCapability = PlayerKVControlledLoadCapability.CreateForGame(capability.TakeLease);
|
|
return capability;
|
|
}
|
|
#if UNITY_EDITOR
|
|
internal static PlayerKVGameLoadCapability CreateForValidation(Uri api, string cacheRoot,
|
|
Func<CookieContainer, HttpMessageHandler> auth, Func<CookieContainer, HttpMessageHandler> kv,
|
|
Func<CookieContainer, HttpMessageHandler> lease, int loginMs = 10000, int quitMs = 5000,
|
|
Func<int, CancellationToken, Task> delay = null)
|
|
{
|
|
var capability = new PlayerKVGameLoadCapability(api, cacheRoot, loginMs, quitMs, delay, auth, kv, lease);
|
|
capability.LeaseCapability = PlayerKVControlledLoadCapability.CreateForValidation(capability.TakeLease);
|
|
return capability;
|
|
}
|
|
#endif
|
|
internal PlayerKVLoginContext CreateContext(string id,long epoch,Func<long,bool> current)
|
|
{
|
|
lock(gate) { if(claimed) throw new InvalidOperationException("Controlled capability already used."); claimed=true; }
|
|
return new PlayerKVLoginContext(id,CacheRoot,epoch,current,Api,
|
|
()=>new S101AccountSession(Api,CreateAuthentication),()=>new PlayerKVGameSessionBridge(TakeKV, prepareOriginalComparison));
|
|
}
|
|
private HttpMessageHandler CreateAuthentication(CookieContainer jar)
|
|
{
|
|
lock (gate)
|
|
{
|
|
if (cookies != null)
|
|
{
|
|
throw new InvalidOperationException("Repeated authentication factory.");
|
|
}
|
|
cookies = jar;
|
|
HttpMessageHandler authentication = null;
|
|
try
|
|
{
|
|
// Validate all three returned handler graphs before the first authentication send.
|
|
authentication = PlayerKVControlledLoadCapability.CreateHandler(auth, jar, LeaseCapability.IsFormal);
|
|
pendingKV = PlayerKVControlledLoadCapability.CreateHandler(kv, jar, LeaseCapability.IsFormal);
|
|
pendingLease = PlayerKVControlledLoadCapability.CreateHandler(lease, jar, LeaseCapability.IsFormal);
|
|
if (ShareHandlerGraph(authentication, pendingKV, pendingLease))
|
|
{
|
|
throw new InvalidOperationException("Distinct transport ownership required.");
|
|
}
|
|
return authentication;
|
|
}
|
|
catch
|
|
{
|
|
try
|
|
{
|
|
authentication?.Dispose();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
cleanupFailure = ex;
|
|
failedHandlers.Add(authentication);
|
|
}
|
|
try
|
|
{
|
|
CloseUnused();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
static bool ShareHandlerGraph(params HttpMessageHandler[] roots)
|
|
{
|
|
var seen=new System.Collections.Generic.List<HttpMessageHandler>();
|
|
foreach(var root in roots)
|
|
for(var node=root;node!=null;node=(node as DelegatingHandler)?.InnerHandler)
|
|
{
|
|
if(seen.Exists(previous=>ReferenceEquals(previous,node))) return true;
|
|
seen.Add(node);
|
|
}
|
|
return false;
|
|
}
|
|
HttpMessageHandler TakeKV(CookieContainer jar)
|
|
{
|
|
lock(gate)
|
|
{ if(!ReferenceEquals(jar,cookies) || pendingKV==null) throw new InvalidOperationException("KV transport ownership invalid.");
|
|
var result=pendingKV; pendingKV=null; return result; }
|
|
}
|
|
HttpMessageHandler TakeLease(CookieContainer jar)
|
|
{
|
|
lock(gate)
|
|
{ if(!ReferenceEquals(jar,cookies) || pendingLease==null) throw new InvalidOperationException("Lease transport ownership invalid.");
|
|
var result=pendingLease; pendingLease=null; return result; }
|
|
}
|
|
internal void CloseUnused()
|
|
{
|
|
lock(gate)
|
|
{
|
|
// Attempt every owned cleanup once; retain a failure even if a later Dispose succeeds.
|
|
var ownedLease=pendingLease; pendingLease=null;
|
|
var ownedKV=pendingKV; pendingKV=null;
|
|
try { ownedLease?.Dispose(); } catch(Exception ex) { cleanupFailure=cleanupFailure ?? ex; failedHandlers.Add(ownedLease); }
|
|
try { ownedKV?.Dispose(); } catch(Exception ex) { cleanupFailure=cleanupFailure ?? ex; failedHandlers.Add(ownedKV); }
|
|
if(cleanupFailure!=null) throw new InvalidOperationException("Controlled transport cleanup failed.",cleanupFailure);
|
|
}
|
|
}
|
|
}
|
|
}
|