Minimum viable
This commit is contained in:
1 parent
476aff202f
commit
07229424a4
14 files changed
+564
No files matched your search
@@ -0,0 +1,141 @@
|
||||
using CommunityMapLocation.Config;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using UnityModManagerNet;
|
||||
|
||||
namespace CommunityMapLocation.Api
|
||||
{
|
||||
public class ApiServer
|
||||
{
|
||||
public ApiServer(Settings settings, UnityModManager.ModEntry.ModLogger logger)
|
||||
{
|
||||
listener = new HttpListener();
|
||||
|
||||
serializerOptions = new JsonSerializerOptions()
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||
IncludeFields = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
this.settings = settings;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private readonly Settings settings;
|
||||
private readonly UnityModManager.ModEntry.ModLogger logger;
|
||||
|
||||
private readonly HttpListener listener;
|
||||
|
||||
private readonly JsonSerializerOptions serializerOptions;
|
||||
|
||||
private readonly List<IEndpoint> endpoints = new List<IEndpoint>();
|
||||
|
||||
public void AutoRegister(Assembly assembly = null)
|
||||
{
|
||||
if (assembly == null)
|
||||
{
|
||||
assembly = Assembly.GetExecutingAssembly();
|
||||
}
|
||||
|
||||
foreach (var endpointType in assembly.ExportedTypes.Where(t => !t.IsInterface && !t.IsAbstract && typeof(IEndpoint).IsAssignableFrom(t)))
|
||||
{
|
||||
try
|
||||
{
|
||||
endpoints.Add((IEndpoint)Activator.CreateInstance(endpointType));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warning($"Could not register endpoint {endpointType.Name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterEndpoint(IEndpoint endpoint)
|
||||
{
|
||||
endpoints.Add(endpoint);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.Prefixes.Add($"http://localhost:{settings.Port}/");
|
||||
listener.Start();
|
||||
listener.BeginGetContext(HandleConnection, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warning(ex.ToString());
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnection(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = listener.EndGetContext(ar);
|
||||
|
||||
listener.BeginGetContext(HandleConnection, listener);
|
||||
|
||||
var request = context.Request;
|
||||
var response = context.Response;
|
||||
|
||||
var matchedEndpoints = endpoints.Where(e => e.Matches(request.Url) &&
|
||||
(request.HttpMethod == "OPTIONS" || request.HttpMethod.Equals(e.HttpMethod.ToString(), StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
if (!endpoints.Any())
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
var methods = matchedEndpoints.Select(e => e.HttpMethod).Distinct();
|
||||
|
||||
response.AppendHeader("Access-Control-Allow-Origin", settings.RequestSourceType == RequestSourceType.All ? "*" : settings.RequestSource);
|
||||
|
||||
if (request.HttpMethod == "OPTIONS")
|
||||
{
|
||||
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
|
||||
response.AddHeader("Access-Control-Allow-Methods", String.Join(", ", methods.Select(m => m.ToString().ToUpper())));
|
||||
response.AddHeader("Access-Control-Max-Age", "1728000");
|
||||
}
|
||||
else
|
||||
{
|
||||
var responseBody = matchedEndpoints.First().Handle(logger, request);
|
||||
|
||||
var buffer = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(responseBody, serializerOptions));
|
||||
|
||||
response.ContentLength64 = buffer.Length;
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
response.OutputStream.Close();
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
// HttpListener is stopping, swallow exception
|
||||
}
|
||||
}
|
||||
|
||||
internal void Reload()
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
listener.Stop();
|
||||
listener.Prefixes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using UnityModManagerNet;
|
||||
|
||||
namespace CommunityMapLocation.Api
|
||||
{
|
||||
public abstract class Endpoint<T> : IEndpoint
|
||||
{
|
||||
public abstract HttpMethod HttpMethod { get; }
|
||||
public abstract string RoutePattern { get; }
|
||||
public bool Matches(Uri uri)
|
||||
{
|
||||
return String.Concat(uri.Segments).Equals(RoutePattern, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public abstract T Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request);
|
||||
|
||||
object IEndpoint.Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request)
|
||||
{
|
||||
return Handle(logger, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using UnityModManagerNet;
|
||||
|
||||
namespace CommunityMapLocation.Api
|
||||
{
|
||||
public interface IEndpoint
|
||||
{
|
||||
HttpMethod HttpMethod { get; }
|
||||
string RoutePattern { get; }
|
||||
bool Matches(Uri uri);
|
||||
object Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using DV.HUD;
|
||||
using DV.OriginShift;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using UnityEngine;
|
||||
using UnityModManagerNet;
|
||||
|
||||
namespace CommunityMapLocation.Api.Location
|
||||
{
|
||||
public class LocationEndpoint : Endpoint<LocationResponse>
|
||||
{
|
||||
public override HttpMethod HttpMethod => HttpMethod.Get;
|
||||
|
||||
public override string RoutePattern => "/Location";
|
||||
|
||||
public override LocationResponse Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request)
|
||||
{
|
||||
float angle;
|
||||
|
||||
// If the player is in a loco, we want to get the heading of the loco instead of the play
|
||||
if (PlayerManager.Car?.IsLoco == true)
|
||||
{
|
||||
angle = PlayerManager.Car.transform.rotation.eulerAngles.y;
|
||||
|
||||
// If the loco is in reverse, rotate the direction 180 degrees to match the direction of travel
|
||||
if (PlayerManager.Car.interior.GetComponentInChildren<InteriorControlsManager>().TryGetControl(InteriorControlsManager.ControlType.Reverser, out var control) &&
|
||||
control.controlImplBase.Value <= 0.25)
|
||||
{
|
||||
angle += 180;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = PlayerManager.PlayerCamera.transform.rotation.eulerAngles.y;
|
||||
}
|
||||
|
||||
return new LocationResponse(PlayerManager.PlayerTransform.AbsolutePosition(), angle * Mathf.PI / 180);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace CommunityMapLocation.Api.Location
|
||||
{
|
||||
public class LocationResponse
|
||||
{
|
||||
public LocationResponse(Vector3 position, float rotation)
|
||||
{
|
||||
X = position.x;
|
||||
Y = position.y;
|
||||
Z = position.z;
|
||||
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public float X { get; }
|
||||
public float Y { get; }
|
||||
public float Z { get; }
|
||||
public float Rotation { get; }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user