First pass at members resource controller

This commit is contained in:
watsonb8 2020-01-20 16:55:12 -05:00
parent 4ec9db4a45
commit 320967be89
10 changed files with 630 additions and 7 deletions

View File

@ -81,7 +81,7 @@ message Party {
string description = 3;
string hostIp = 4;
Member hostMember = 5;
google.protobuf.Timestamp createdTime = 6;
google.protobuf.Timestamp createdOn = 6;
}
enum PartyJoinedStatusEnum {
@ -97,9 +97,8 @@ message Member {
//Resource name of the party member to be returned
string name = 1;
string userName = 2;
string id = 3;
string ipAddress = 4;
int32 port = 5;
string ipAddress = 3;
google.protobuf.Timestamp addedOn = 4;
}
message ListMembersRequest {
@ -122,8 +121,7 @@ message GetMemberRequest {
message CreateMemberRequest {
//Resource name of the parent collection of the member to be created (The party)
string parent = 1;
string memberId = 2;
Member member = 3;
Member member = 2;
}
message UpdateMemberRequest {
@ -217,7 +215,7 @@ message MemberCreatedEvent {
}
message MemberDeletedEvent {
Member member = 1;
string memberName = 1;
}
message EventSubscription {

View File

@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Aurora.Proto.PartyV2;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
public override Task GetEvents(GetEventsRequest request, Grpc.Core.IServerStreamWriter<BaseEvent> responseStream, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using Aurora.Proto.PartyV2;
using Aurora.Proto.General;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
public override Task<ListEventSubscriptionsResponse> ListEventSubscriptions(ListEventSubscriptionsRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<EventSubscription> CreateEventSubscription(CreateEventSubscriptionRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<Empty> DeleteEventSubscription(DeleteEventSubscriptionRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<Empty> DeleteAllEventSubscriptions(DeleteAllEventSubscriptionsRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Aurora.Proto.PartyV2;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
public override Task<ListMediaResponse> ListMedia(ListMediaRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<RemoteMedia> GetMedia(GetMediaRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task StreamMedia(StreamMediaRequest request, Grpc.Core.IServerStreamWriter<Proto.General.Chunk> responseStream, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task SyncMedia(SyncMediaRequest request, Grpc.Core.IServerStreamWriter<Sync> responseStream, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections;
using Aurora.Proto.PartyV2;
using Aurora.Proto.General;
using Aurora.Utils;
using Grpc.Core;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private SortedList<string, Member> _memberList;
public override Task<ListMembersResponse> ListMembers(ListMembersRequest request, Grpc.Core.ServerCallContext context)
{
//Ignoring parent field because there is only one instance of the party
ListMembersResponse resp = new ListMembersResponse();
//Determine start idx
int startIdx = 0;
if (!string.IsNullOrEmpty(request.PageToken))
{
startIdx = _memberList.IndexOfKey(request.PageToken) + 1;
}
//Gather page
List<Member> members = (List<Member>)_memberList.Values;
resp.Members.AddRange(members.GetRange(startIdx, request.PageSize));
//Set next page token
resp.NextPageToken = resp.Members[(startIdx + request.PageSize) - 1].Name;
return Task.FromResult(resp);
}
public override Task<Member> GetMember(GetMemberRequest request, Grpc.Core.ServerCallContext context)
{
_memberList.TryGetValue(request.Name, out Member member);
if (member == null)
{
throw new KeyNotFoundException();
}
return Task.FromResult(member);
}
public override Task<Member> UpdateMember(UpdateMemberRequest request, Grpc.Core.ServerCallContext context)
{
throw new NotImplementedException();
}
public override Task<Member> CreateMember(CreateMemberRequest request, Grpc.Core.ServerCallContext context)
{
//Generate Guid
string memberNameGuid = HashUtil.GetHashGuid(new string[] { context.Peer, request.Member.UserName }).ToString();
string resourceName = string.Format("{0}/members/{1}", request.Parent, memberNameGuid);
//Check if already added
if (_memberList.ContainsKey(resourceName))
{
throw new RpcException(new Status(StatusCode.AlreadyExists, "Member already exists"));
}
request.Member.Name = resourceName;
_memberList.Add(resourceName, request.Member);
BaseEvent @event = new BaseEvent
{
EventType = EventType.MemberCreated,
MemberCreatedEvent = new MemberCreatedEvent
{
Member = request.Member,
}
};
//Fire event manager event
this._eventManager.FireEvent(@event);
return Task.FromResult(request.Member);
}
public override Task<Empty> DeleteMember(DeleteMemberRequest request, Grpc.Core.ServerCallContext context)
{
string memberResourceName = request.Name;
//Check if member exists
if (!_memberList.ContainsKey(request.Name))
{
throw new RpcException(new Status(StatusCode.NotFound, "Member not found"));
}
_memberList.Remove(memberResourceName);
BaseEvent @event = new BaseEvent
{
EventType = EventType.MemberDeleted,
MemberDeletedEvent = new MemberDeletedEvent
{
MemberName = memberResourceName,
}
};
_eventManager.FireEvent(@event);
_eventManager.RemoveAllSubscriptions(memberResourceName);
_eventManager.CancelEventStream(memberResourceName);
return Task.FromResult(new Empty());
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Aurora.Proto.PartyV2;
using Google.Protobuf.WellKnownTypes;
namespace Aurora.Services.Server.Controllers
{
public partial class RemotePartyController : RemotePartyService.RemotePartyServiceBase
{
private string _displayName;
private string _description;
private Member _hostMember;
private DateTime _startDateTime;
private EventManager.EventManager _eventManager;
/// <summary>
/// Constructor for partial class
/// </summary>
public RemotePartyController(string partyName, string description)
{
this._startDateTime = DateTime.Now;
this._displayName = partyName;
this._description = description;
this._memberList = new SortedList<string, Member>();
string userName = SettingsService.Instance.Username;
this._eventManager = new EventManager.EventManager();
this._hostMember = new Member()
{
Name = userName,
UserName = userName,
IpAddress = ServerService.GetLocalIPAddress(),
};
}
public override Task<Party> GetParty(Proto.General.Empty request, Grpc.Core.ServerCallContext context)
{
Party party = new Party()
{
Name = "party/party1",
DisplayName = this._displayName,
Description = this._description,
HostIp = ServerService.GetLocalIPAddress(),
HostMember = this._hostMember,
CreatedOn = Timestamp.FromDateTime(_startDateTime)
};
return Task.FromResult(party);
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Aurora.Proto.PartyV2;
namespace Aurora.Services.Server.EventManager
{
public class EventAction
{
public EventAction(Action<BaseEvent> callback, Action cancel)
{
Callback = callback;
Cancel = cancel;
}
public Action<BaseEvent> Callback { get; set; }
public Action Cancel { get; set; }
}
}

View File

@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Aurora.Proto.PartyV2;
namespace Aurora.Services.Server.EventManager
{
public class EventManager
{
#region Fields
private Dictionary<string, List<EventType>> _subscriptionList;
private Dictionary<string, EventAction> _actionList;
#endregion Fields
public EventManager()
{
_subscriptionList = new Dictionary<string, List<EventType>>();
_actionList = new Dictionary<string, EventAction>();
}
#region Private Methods
#endregion Private Methods
#region Public Methods
/// <summary>
/// Get the list of event type subscriptions for a given sessionIdentifier id.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
public List<EventType> GetSubscriptionList(string sessionIdentifier)
{
List<EventType> eventList = new List<EventType>();
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
}
return eventList;
}
/// <summary>
/// Get the number of event subscriptions for a given sessionIdentifier
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <returns></returns>
public int GetSubscriptionCount(string sessionIdentifier)
{
List<EventType> eventList = new List<EventType>();
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.TryGetValue(sessionIdentifier, out eventList);
}
return eventList.Count();
}
/// <summary>
/// Add a new subscription
/// </summary>
/// <param name="sessionIdentifier"></param>
/// <param name="type"></param>
public bool AddSubscription(string sessionIdentifier, EventType type)
{
bool success = false;
lock (_subscriptionList)
{
if (!_subscriptionList.ContainsKey(sessionIdentifier))
{
//Add sessionIdentifier to subscription list
List<EventType> eventList = new List<EventType>();
eventList.Add(type);
_subscriptionList.Add(sessionIdentifier, eventList);
success = true;
}
else
{
_subscriptionList.TryGetValue(sessionIdentifier, out List<EventType> eventList);
if (eventList != null)
{
eventList.Add(type);
success = true;
}
}
}
return success;
}
/// <summary>
/// Add a list of subscriptions. This unsubscribes from unused events.
/// </summary>
/// <param name="sessionIdentifier">The browser sessionIdentifier id.</param>
/// <param name="types">The list of event types to subscribe to.</param>
public void AddSubscriptionList(string sessionIdentifier, List<EventType> types)
{
RemoveAllSubscriptions(sessionIdentifier);
foreach (EventType e in types)
{
AddSubscription(sessionIdentifier, e);
}
}
/// <summary>
/// Unsubscribe from a given event type.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
/// <param name="type">Event Type to be removed</param>
public void RemoveSubscription(string sessionIdentifier, EventType type)
{
lock (_subscriptionList)
{
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
List<EventType> eventTypeList;
_subscriptionList.TryGetValue(sessionIdentifier, out eventTypeList);
if (eventTypeList != null && eventTypeList.Contains(type))
{
eventTypeList.Remove(type);
//base.LogInformation(string.Format("Subscription removed for event type {0} subscription on sessionIdentifier {1}", type.ToString(), sessionIdentifier));
}
}
}
}
public void RemoveSubscriptionList(string sessionIdentifier, List<EventType> types)
{
foreach (EventType e in types)
{
RemoveSubscription(sessionIdentifier, e);
}
}
/// <summary>
/// Remove all subscriptons for a given sessionIdentifier.
/// </summary>
/// <param name="sessionIdentifier">sessionIdentifier Id</param>
public void RemoveAllSubscriptions(string sessionIdentifier)
{
if (_subscriptionList.ContainsKey(sessionIdentifier))
{
_subscriptionList.Remove(sessionIdentifier);
}
}
public void AddEventHandler(Action<BaseEvent> action, Action cancel, string sessionIdentifierId)
{
lock (_actionList)
{
_actionList.Add(sessionIdentifierId, new EventAction(action, cancel));
}
}
public void RemoveEventHandler(string sessionIdentifierId)
{
_actionList.Remove(sessionIdentifierId);
}
public void CancelEventStream(string sessionIdentifierId)
{
_actionList.TryGetValue(sessionIdentifierId, out EventAction value);
value.Cancel();
RemoveEventHandler(sessionIdentifierId);
}
public void FireEvent(BaseEvent bEvent)
{
Dictionary<string, EventAction> actionsCopy = new Dictionary<string, EventAction>();
//Copy actions list
lock (_actionList)
{
foreach (KeyValuePair<string, EventAction> pair in _actionList)
{
actionsCopy.Add(pair.Key, pair.Value);
}
}
lock (_subscriptionList)
{
foreach (KeyValuePair<string, List<EventType>> pair in _subscriptionList)
{
Task.Delay(1000);
//If action list contains an action for id, invoke
if (actionsCopy.ContainsKey(pair.Key))
{
actionsCopy.TryGetValue(pair.Key, out EventAction action);
Task executionTask = new Task(() => action.Callback(bEvent));
//Execute task with exception handler
executionTask.ContinueWith((Task task) =>
{
var exception = executionTask.Exception;
Console.WriteLine(string.Format("SERVER --- Exception occurred firing event"));
this._actionList.Remove(pair.Key);
},
TaskContinuationOptions.OnlyOnFaulted);
executionTask.Start();
}
}
}
}
#endregion Public Methods
}
}

View File

@ -0,0 +1,125 @@
using System;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using Grpc.Core;
using Aurora.Services.Server.Controllers;
using Aurora.Proto.PartyV2;
namespace Aurora.Services.Server
{
public class ServerService : BaseService<ServerService>
{
private int _port = SettingsService.Instance.DefaultPort;
private string _hostname;
private Grpc.Core.Server _server;
//Implementation class declarations
private RemotePartyController _remotePartyController;
/// <summary>
/// Constructor. Registers GRPC service implementations.
/// </summary>
public ServerService()
{
string host = GetLocalIPAddress();
if (string.IsNullOrWhiteSpace(host))
{
throw new Exception("This device must have a valid IP address");
}
_hostname = host;
_server = new Grpc.Core.Server
{
Ports = { new ServerPort(_hostname, _port, ServerCredentials.Insecure) }
};
}
public int Port
{
get { return _port; }
}
public string Hostname
{
get { return _hostname; }
}
public bool Initialized
{
get
{
return (_remotePartyController != null &&
_server != null);
}
}
/// <summary>
/// Start Server
/// </summary>
public void Start(string partyName, string description)
{
try
{
Console.WriteLine(string.Format("Starting gRPC server at hostname: {0}, port: {1}", _hostname, _port));
if (!Initialized)
{
//Construct implementations
_remotePartyController = new RemotePartyController(partyName, description);
// Register grpc RemoteService with singleton server service
RegisterService(RemotePartyService.BindService(_remotePartyController));
}
_server.Start();
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error starting gRPC server: {0}", ex.Message));
}
}
/// <summary>
/// Shutdown server async.
/// </summary>
/// <returns>Task</returns>
public async Task Stop()
{
await _server.ShutdownAsync();
}
public async Task Reset()
{
await Stop();
_server = new Grpc.Core.Server
{
Ports = { new ServerPort("localhost", _port, ServerCredentials.Insecure) }
};
}
private void RegisterService(ServerServiceDefinition definition)
{
_server.Services.Add(definition);
}
public static string GetLocalIPAddress()
{
string returnIp = "";
var host = Dns.GetHostEntry(Dns.GetHostName());
foreach (var ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
returnIp = ip.ToString();
}
}
return returnIp;
}
}
}

27
Aurora/Utils/HashUtil.cs Normal file
View File

@ -0,0 +1,27 @@
using System.Security.Cryptography;
using System.Text;
using System;
namespace Aurora.Utils
{
public class HashUtil
{
public static Guid GetHashGuid(string[] inputs)
{
string input = "";
foreach (string str in inputs)
{
input += str;
}
Guid result;
using (SHA256 sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(Encoding.Default.GetBytes(input));
result = new Guid(hash);
}
return result;
}
}
}