complete lab2 grpc
This commit is contained in:
parent
4d2e40282d
commit
bc374aceba
@ -54,6 +54,14 @@ internal class Participant
|
|||||||
log.Info("Sent message");
|
log.Info("Sent message");
|
||||||
} else {
|
} else {
|
||||||
log.Info("Failed to send message, blocked");
|
log.Info("Failed to send message, blocked");
|
||||||
|
var blockedUntil = chatRoom.GetBlockedUntil(clientId);
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (blockedUntil != null && blockedUntil > now)
|
||||||
|
{
|
||||||
|
var delta = blockedUntil.Value - now;
|
||||||
|
log.Info($"Waiting {delta.TotalSeconds:F3}s until block expires");
|
||||||
|
Thread.Sleep((int)delta.TotalMilliseconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(2 * 1000);
|
Thread.Sleep(2 * 1000);
|
||||||
|
38
Lab2-grpc/.vscode/launch.json
vendored
Normal file
38
Lab2-grpc/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "ChatRoom",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build-ChatRoom",
|
||||||
|
"program": "${workspaceFolder}/ChatRoom/bin/Debug/net6.0/ChatRoom.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/ChatRoom",
|
||||||
|
"console": "externalTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Moderator",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build-Moderator",
|
||||||
|
"program": "${workspaceFolder}/Moderator/bin/Debug/net6.0/Moderator.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/Moderator",
|
||||||
|
"console": "externalTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Participant",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build-Participant",
|
||||||
|
"program": "${workspaceFolder}/Participant/bin/Debug/net6.0/Participant.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/Participant",
|
||||||
|
"console": "externalTerminal",
|
||||||
|
"stopAtEntry": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
41
Lab2-grpc/.vscode/tasks.json
vendored
Normal file
41
Lab2-grpc/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build-ChatRoom",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/ChatRoom/ChatRoom.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build-Moderator",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/Moderator/Moderator.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "build-Participant",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/Participant/Participant.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
18
Lab2-grpc/ChatRoom/ChatRoom.csproj
Normal file
18
Lab2-grpc/ChatRoom/ChatRoom.csproj
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.66.0" />
|
||||||
|
<PackageReference Include="NLog" Version="5.3.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ChatRoomContract\ChatRoomContract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
295
Lab2-grpc/ChatRoom/ChatRoomLogic.cs
Normal file
295
Lab2-grpc/ChatRoom/ChatRoomLogic.cs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
using NLog;
|
||||||
|
|
||||||
|
namespace ChatRoom;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat room service logic
|
||||||
|
/// </summary>
|
||||||
|
public class ChatRoomLogic
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Background thread of deleting approved or rejected messages
|
||||||
|
/// </summary>
|
||||||
|
private Thread thread;
|
||||||
|
/// <summary>
|
||||||
|
/// Chat Room state
|
||||||
|
/// </summary>
|
||||||
|
private ChatRoomState state = new ChatRoomState();
|
||||||
|
/// <summary>
|
||||||
|
/// Logger
|
||||||
|
/// </summary>
|
||||||
|
private Logger log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat room logic constructor
|
||||||
|
/// </summary>
|
||||||
|
public ChatRoomLogic()
|
||||||
|
{
|
||||||
|
thread = new Thread(BackgroundTask);
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate the next incrementing ID
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Unique ID</returns>
|
||||||
|
int NextId()
|
||||||
|
{
|
||||||
|
int id = state.lastUniqueId;
|
||||||
|
state.lastUniqueId++;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Client name</param>
|
||||||
|
/// <returns>Client ID</returns>
|
||||||
|
public int RegisterClient(string name)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
int clientId = NextId();
|
||||||
|
state.clients.Add(new Client
|
||||||
|
{
|
||||||
|
id = clientId,
|
||||||
|
name = name
|
||||||
|
});
|
||||||
|
log.Info($"Registered with client '{name}' with id {clientId}");
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find a client by ID, can return NULL if not found
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Optional client object</returns>
|
||||||
|
Client? FindClientById(int clientId)
|
||||||
|
{
|
||||||
|
foreach (var client in state.clients)
|
||||||
|
{
|
||||||
|
if (client.id == clientId)
|
||||||
|
{
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find message by ID, can return NULL if not found
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
/// <returns>Optional message object</returns>
|
||||||
|
Message? FindMessageById(int messageId)
|
||||||
|
{
|
||||||
|
foreach (var message in state.messages)
|
||||||
|
{
|
||||||
|
if (message.id == messageId)
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if client is still blocked, will clear `blockedUntil` if client became unblocked
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">Client object</param>
|
||||||
|
/// <returns>Is client blocked?</returns>
|
||||||
|
bool GetAndUpdateBlockedState(Client client)
|
||||||
|
{
|
||||||
|
if (client.blockedUntil != null && DateTime.UtcNow >= client.blockedUntil)
|
||||||
|
{
|
||||||
|
client.blockedUntil = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.blockedUntil != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <param name="contents">Message contents</param>
|
||||||
|
/// <param name="needsToBeCensored">Does this message need to be censored?</param>
|
||||||
|
/// <returns>Was sending the message successful, can fail if client is blocked</returns>
|
||||||
|
public bool SendMessage(int clientId, string contents, bool needsToBeCensored)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var client = FindClientById(clientId);
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAndUpdateBlockedState(client))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new Message
|
||||||
|
{
|
||||||
|
id = NextId(),
|
||||||
|
clientId = clientId,
|
||||||
|
contents = contents,
|
||||||
|
needsToBeCensored = needsToBeCensored,
|
||||||
|
status = MessageStatus.WaitingForModerator,
|
||||||
|
sentAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
state.messages.Add(message);
|
||||||
|
log.Info($"Client '{client.name}' ({client.id}) sent message '{contents}' ({message.id}). Needs to censored: {needsToBeCensored}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get next message which isin't approved or rejected
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Optional message object</returns>
|
||||||
|
public ChatRoomContract.Message? GetNewMessage()
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
foreach (var message in state.messages)
|
||||||
|
{
|
||||||
|
if (message.status != MessageStatus.WaitingForModerator) continue;
|
||||||
|
|
||||||
|
log.Info($"Message '{message.id}' given to moderator");
|
||||||
|
message.status = MessageStatus.GivenToModerator;
|
||||||
|
return new ChatRoomContract.Message{
|
||||||
|
id = message.id,
|
||||||
|
contents = message.contents,
|
||||||
|
needsToBeCensored = message.needsToBeCensored
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Approve a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
public void ApproveMessage(int messageId)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var message = FindMessageById(messageId);
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.status != MessageStatus.GivenToModerator)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.status = MessageStatus.Approved;
|
||||||
|
log.Info($"Message {message.id} was approved");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reject a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
public void RejectMessage(int messageId)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var message = FindMessageById(messageId);
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.status != MessageStatus.GivenToModerator)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.status = MessageStatus.Rejected;
|
||||||
|
log.Info($"Message {message.id} was rejected");
|
||||||
|
|
||||||
|
var client = FindClientById(message.clientId);
|
||||||
|
if (client != null && !GetAndUpdateBlockedState(client))
|
||||||
|
{
|
||||||
|
client.strikes++;
|
||||||
|
|
||||||
|
var rnd = new Random();
|
||||||
|
if (client.strikes > rnd.Next(0, 10))
|
||||||
|
{
|
||||||
|
log.Info($"Client '{client.name}' ({client.id}) was blocked for {client.strikes}s");
|
||||||
|
client.blockedUntil = DateTime.UtcNow.AddSeconds(client.strikes);
|
||||||
|
client.strikes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get number of strikes a client has
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Number of strikes</returns>
|
||||||
|
public int GetStrikes(int clientId)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var client = FindClientById(clientId);
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.strikes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get timestamp until when the client is blocked
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Optional datetime object</returns>
|
||||||
|
public DateTime? GetBlockedUntil(int clientId)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var client = FindClientById(clientId);
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.blockedUntil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main loop for background thread. Used for deleting approved or rejected messages.
|
||||||
|
/// </summary>
|
||||||
|
public void BackgroundTask()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lock (state.accessLock)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
int count = state.messages.RemoveAll(msg =>
|
||||||
|
(now - msg.sentAt).TotalSeconds > 5 &&
|
||||||
|
(msg.status == MessageStatus.Approved || msg.status == MessageStatus.Rejected)
|
||||||
|
);
|
||||||
|
log.Info($"Running periodic cleanup, removed {count} messages");
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(10 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
Lab2-grpc/ChatRoom/ChatRoomService.cs
Normal file
110
Lab2-grpc/ChatRoom/ChatRoomService.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using ChatRoomContract.Protocol;
|
||||||
|
|
||||||
|
namespace ChatRoom;
|
||||||
|
|
||||||
|
public class ChatRoomService : ChatRoomContract.Protocol.ChatRoom.ChatRoomBase
|
||||||
|
{
|
||||||
|
//NOTE: instance-per-request service would need logic to be static or injected from a singleton instance
|
||||||
|
private readonly ChatRoomLogic logic = new ChatRoomLogic();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register client with a name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Name of client.</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Client ID</returns>
|
||||||
|
public override Task<ClientId> RegisterClient(RegisterClientRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = new ClientId { Id = logic.RegisterClient(request.Name) };
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get number of strikes a participant has
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Client ID</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Number of strikes</returns>
|
||||||
|
public override Task<Srikes> GetStrikes(ClientId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = new Srikes { Strikes = logic.GetStrikes(request.Id) };
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get timestamp until when the client is blocked
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Client ID</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Optional datetime object</returns>
|
||||||
|
public override Task<BlockedUntil> GetBlockedUntil(ClientId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var timestamp = logic.GetBlockedUntil(request.Id);
|
||||||
|
var result = new BlockedUntil { HasTimestamp = false };
|
||||||
|
if (timestamp != null)
|
||||||
|
{
|
||||||
|
result.HasTimestamp = true;
|
||||||
|
result.Timestamp = Timestamp.FromDateTime(timestamp.Value);
|
||||||
|
}
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a message, will be given to a moderator to be approved
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Message details</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Was sending successful, can fail if user is blocked</returns>
|
||||||
|
public override Task<BoolResponse> SendMessage(UserMessageRequest request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var success = logic.SendMessage(request.ClientId, request.Contents, request.NeedsToBeCensored);
|
||||||
|
var result = new BoolResponse { Success = success };
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the next message which hasn't been approved or rejected
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Empty</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Message object. Returns null if there is no message</returns>
|
||||||
|
public override Task<NewUserMessage> GetNewMessage(Empty request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var message = logic.GetNewMessage();
|
||||||
|
var result = new NewUserMessage { HasMessage = false, Message = new UserMessage() };
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
result.HasMessage = true;
|
||||||
|
result.Message.Id = message.id;
|
||||||
|
result.Message.Contents = message.contents;
|
||||||
|
result.Message.NeedsToBeCensored = message.needsToBeCensored;
|
||||||
|
}
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reject a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Message ID</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Empty</returns>
|
||||||
|
public override Task<Empty> ApproveMessage(MessageId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
logic.ApproveMessage(request.Id);
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Approve a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Message ID</param>
|
||||||
|
/// <param name="context">Call context.</param>
|
||||||
|
/// <returns>Empty</returns>
|
||||||
|
public override Task<Empty> RejectMessage(MessageId request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
logic.RejectMessage(request.Id);
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
}
|
90
Lab2-grpc/ChatRoom/ChatRoomState.cs
Normal file
90
Lab2-grpc/ChatRoom/ChatRoomState.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
namespace ChatRoom;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client information
|
||||||
|
/// </summary>
|
||||||
|
public class Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client ID
|
||||||
|
/// </summary>
|
||||||
|
public int id;
|
||||||
|
/// <summary>
|
||||||
|
/// Client name, can be the same between multiple clients
|
||||||
|
/// </summary>
|
||||||
|
public string name;
|
||||||
|
/// <summary>
|
||||||
|
/// Number of strikes
|
||||||
|
/// </summary>
|
||||||
|
public int strikes = 0;
|
||||||
|
/// <summary>
|
||||||
|
/// Until when is this client blocked from sending messages
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? blockedUntil = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the messages status/stage
|
||||||
|
/// </summary>
|
||||||
|
public enum MessageStatus
|
||||||
|
{
|
||||||
|
WaitingForModerator,
|
||||||
|
GivenToModerator,
|
||||||
|
Approved,
|
||||||
|
Rejected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Message information
|
||||||
|
/// </summary>
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message ID
|
||||||
|
/// </summary>
|
||||||
|
public int id;
|
||||||
|
/// <summary>
|
||||||
|
/// Client ID
|
||||||
|
/// </summary>
|
||||||
|
public int clientId;
|
||||||
|
/// <summary>
|
||||||
|
/// Message contents
|
||||||
|
/// </summary>
|
||||||
|
public string contents;
|
||||||
|
/// <summary>
|
||||||
|
/// Does this message need to be censored?
|
||||||
|
/// </summary>
|
||||||
|
public bool needsToBeCensored;
|
||||||
|
/// <summary>
|
||||||
|
/// Message status/stage
|
||||||
|
/// </summary>
|
||||||
|
public MessageStatus status = MessageStatus.WaitingForModerator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When was this message sent
|
||||||
|
/// </summary>
|
||||||
|
public DateTime sentAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChatRoomState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Access lock.
|
||||||
|
/// </summary>
|
||||||
|
public readonly object accessLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Last unique ID value generated.
|
||||||
|
/// </summary>
|
||||||
|
public int lastUniqueId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of all registered clients
|
||||||
|
/// </summary>
|
||||||
|
public List<Client> clients = new List<Client>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of messages
|
||||||
|
/// </summary>
|
||||||
|
public List<Message> messages = new List<Message>();
|
||||||
|
}
|
90
Lab2-grpc/ChatRoom/Server.cs
Normal file
90
Lab2-grpc/ChatRoom/Server.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||||
|
using NLog;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace ChatRoom;
|
||||||
|
|
||||||
|
public class Server
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var self = new Server();
|
||||||
|
self.Run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logger for this class.
|
||||||
|
/// </summary>
|
||||||
|
Logger log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures logging subsystem.
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureLogging()
|
||||||
|
{
|
||||||
|
var config = new NLog.Config.LoggingConfiguration();
|
||||||
|
|
||||||
|
var console =
|
||||||
|
new NLog.Targets.ConsoleTarget("console")
|
||||||
|
{
|
||||||
|
Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
|
||||||
|
};
|
||||||
|
config.AddTarget(console);
|
||||||
|
config.AddRuleForAllLevels(console);
|
||||||
|
|
||||||
|
LogManager.Configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Program body.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Command line arguments.</param>
|
||||||
|
private void Run(string[] args)
|
||||||
|
{
|
||||||
|
//configure logging
|
||||||
|
ConfigureLogging();
|
||||||
|
|
||||||
|
//indicate server is about to start
|
||||||
|
log.Info("Server is about to start");
|
||||||
|
|
||||||
|
//start the server
|
||||||
|
StartServer(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts integrated server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Command line arguments.</param>
|
||||||
|
private void StartServer(string[] args)
|
||||||
|
{
|
||||||
|
//create web app builder
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
//configure integrated server
|
||||||
|
builder.WebHost.ConfigureKestrel(opts => {
|
||||||
|
opts.Listen(IPAddress.Loopback, 5000, opts =>
|
||||||
|
{
|
||||||
|
opts.Protocols = HttpProtocols.Http2;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//add support for GRPC services
|
||||||
|
builder.Services.AddGrpc();
|
||||||
|
|
||||||
|
//add the actual services
|
||||||
|
builder.Services.AddSingleton(new ChatRoomService());
|
||||||
|
|
||||||
|
//build the server
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
//turn on request routing
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
//configure routes
|
||||||
|
app.MapGrpcService<ChatRoomService>();
|
||||||
|
|
||||||
|
//run the server
|
||||||
|
app.Run();
|
||||||
|
// app.RunAsync(); //use this if you need to implement background processing in the main thread
|
||||||
|
}
|
||||||
|
}
|
11
Lab2-grpc/ChatRoom/appsettings.json
Normal file
11
Lab2-grpc/ChatRoom/appsettings.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information",
|
||||||
|
"Microsoft.AspNetCore.Hosting.Diagnostics": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
114
Lab2-grpc/ChatRoomContract/ChatRoomClient.cs
Normal file
114
Lab2-grpc/ChatRoomContract/ChatRoomClient.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
|
||||||
|
using ChatRoomContract.Protocol;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
|
||||||
|
namespace ChatRoomContract;
|
||||||
|
|
||||||
|
public class ChatRoomClient : IChatRoomService
|
||||||
|
{
|
||||||
|
GrpcChannel channel;
|
||||||
|
ChatRoom.ChatRoomClient client;
|
||||||
|
|
||||||
|
public ChatRoomClient(string address)
|
||||||
|
{
|
||||||
|
channel = GrpcChannel.ForAddress(address);
|
||||||
|
client = new ChatRoom.ChatRoomClient(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Approve a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
public void ApproveMessage(int messageId)
|
||||||
|
{
|
||||||
|
client.ApproveMessage(new MessageId { Id = messageId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get timestamp until when the client is blocked
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Optional datetime object</returns>
|
||||||
|
public DateTime? GetBlockedUntil(int clientId)
|
||||||
|
{
|
||||||
|
var result = client.GetBlockedUntil(new ClientId { Id = clientId });
|
||||||
|
if (result.HasTimestamp)
|
||||||
|
{
|
||||||
|
return result.Timestamp.ToDateTime();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the next message which hasn't been approved or rejected
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Message object. Returns null if there is no message</returns>
|
||||||
|
public Message? GetNewMessage()
|
||||||
|
{
|
||||||
|
var result = client.GetNewMessage(new Empty());
|
||||||
|
if (result.HasMessage)
|
||||||
|
{
|
||||||
|
return new Message
|
||||||
|
{
|
||||||
|
id = result.Message.Id,
|
||||||
|
contents = result.Message.Contents,
|
||||||
|
needsToBeCensored = result.Message.NeedsToBeCensored
|
||||||
|
};
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get number of strikes a participant has
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Number of strikes</returns>
|
||||||
|
public int GetStrikes(int clientId)
|
||||||
|
{
|
||||||
|
var result = client.GetStrikes(new ClientId { Id = clientId });
|
||||||
|
return result.Strikes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register client with a name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of client, can be duplicate between clients</param>
|
||||||
|
/// <returns>Client ID</returns>
|
||||||
|
public int RegisterClient(string name)
|
||||||
|
{
|
||||||
|
var result = client.RegisterClient(new RegisterClientRequest { Name = name });
|
||||||
|
return result.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reject a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
public void RejectMessage(int messageId)
|
||||||
|
{
|
||||||
|
client.RejectMessage(new MessageId { Id = messageId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a message, will be given to a moderator to be approved
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <param name="contents">Message contents</param>
|
||||||
|
/// <param name="needsToBeCensored">Does this message need to be censored?</param>
|
||||||
|
/// <returns>Was sending successful, can fail if user is blocked</returns>
|
||||||
|
public bool SendMessage(int clientId, string contents, bool needsToBeCensored)
|
||||||
|
{
|
||||||
|
var result = client.SendMessage(new UserMessageRequest
|
||||||
|
{
|
||||||
|
ClientId = clientId,
|
||||||
|
Contents = contents,
|
||||||
|
NeedsToBeCensored = needsToBeCensored
|
||||||
|
});
|
||||||
|
return result.Success;
|
||||||
|
}
|
||||||
|
}
|
23
Lab2-grpc/ChatRoomContract/ChatRoomContract.csproj
Normal file
23
Lab2-grpc/ChatRoomContract/ChatRoomContract.csproj
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Protos\service.proto" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Protos\service.proto" GrpcServices="Both" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Grpc.AspNetCore" Version="2.49.0" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.66.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
74
Lab2-grpc/ChatRoomContract/IChatRoomService.cs
Normal file
74
Lab2-grpc/ChatRoomContract/IChatRoomService.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
namespace ChatRoomContract;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimal message description
|
||||||
|
/// </summary>
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Message ID
|
||||||
|
/// </summary>
|
||||||
|
public int id;
|
||||||
|
/// <summary>
|
||||||
|
/// Message contents
|
||||||
|
/// </summary>
|
||||||
|
public string contents;
|
||||||
|
/// <summary>
|
||||||
|
/// Does this message need to be censored?
|
||||||
|
/// </summary>
|
||||||
|
public bool needsToBeCensored;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Chat room service contract
|
||||||
|
/// </summary>
|
||||||
|
public interface IChatRoomService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register client with a name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of client, can be duplicate between clients</param>
|
||||||
|
/// <returns>Client ID</returns>
|
||||||
|
int RegisterClient(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get number of strikes a participant has
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Number of strikes</returns>
|
||||||
|
int GetStrikes(int clientId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get timestamp until when the client is blocked
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <returns>Optional datetime object</returns>
|
||||||
|
DateTime? GetBlockedUntil(int clientId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send a message, will be given to a moderator to be approved
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientId">Client ID</param>
|
||||||
|
/// <param name="contents">Message contents</param>
|
||||||
|
/// <param name="needsToBeCensored">Does this message need to be censored?</param>
|
||||||
|
/// <returns>Was sending successful, can fail if user is blocked</returns>
|
||||||
|
bool SendMessage(int clientId, string contents, bool needsToBeCensored);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the next message which hasn't been approved or rejected
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Message object. Returns null if there is no message</returns>
|
||||||
|
Message? GetNewMessage();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reject a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
void RejectMessage(int messageId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Approve a message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageId">Message ID</param>
|
||||||
|
void ApproveMessage(int messageId);
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"ChatRoom": {
|
"ChatRoomContract": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:54058;http://localhost:54059"
|
"applicationUrl": "https://localhost:51617;http://localhost:51618"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
61
Lab2-grpc/ChatRoomContract/Protos/service.proto
Normal file
61
Lab2-grpc/ChatRoomContract/Protos/service.proto
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//set the language version
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
//this will translate into C# namespace
|
||||||
|
package ChatRoomContract.Protocol;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
service ChatRoom {
|
||||||
|
rpc RegisterClient(RegisterClientRequest) returns (ClientId);
|
||||||
|
rpc GetStrikes(ClientId) returns (Srikes);
|
||||||
|
rpc GetBlockedUntil(ClientId) returns (BlockedUntil);
|
||||||
|
rpc SendMessage(UserMessageRequest) returns (BoolResponse);
|
||||||
|
rpc GetNewMessage(google.protobuf.Empty) returns (NewUserMessage);
|
||||||
|
rpc RejectMessage(MessageId) returns (google.protobuf.Empty);
|
||||||
|
rpc ApproveMessage(MessageId) returns (google.protobuf.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
message BoolResponse {
|
||||||
|
bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisterClientRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Srikes {
|
||||||
|
int32 strikes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientId {
|
||||||
|
int32 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MessageId {
|
||||||
|
int32 id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BlockedUntil {
|
||||||
|
bool hasTimestamp = 1;
|
||||||
|
google.protobuf.Timestamp timestamp = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserMessageRequest {
|
||||||
|
int32 clientId = 1;
|
||||||
|
string contents = 2;
|
||||||
|
bool needsToBeCensored = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message UserMessage {
|
||||||
|
int32 id = 1;
|
||||||
|
string contents = 2;
|
||||||
|
bool needsToBeCensored = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NewUserMessage {
|
||||||
|
bool hasMessage = 1;
|
||||||
|
UserMessage message = 2;
|
||||||
|
}
|
42
Lab2-grpc/Lab2-grpc.sln
Normal file
42
Lab2-grpc/Lab2-grpc.sln
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.11.35208.52
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatRoom", "ChatRoom\ChatRoom.csproj", "{CC3F5D4A-0D02-49FC-86BC-2FB160BE9F19}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatRoomContract", "ChatRoomContract\ChatRoomContract.csproj", "{0DFEA55B-6A79-4874-A9A3-A08FA9FC30DE}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moderator", "Moderator\Moderator.csproj", "{1D689F6B-33EF-49F7-B7A7-2F9D0840BFAD}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Participant", "Participant\Participant.csproj", "{62E8635E-9105-4764-B54E-D4968EEC4EA0}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{CC3F5D4A-0D02-49FC-86BC-2FB160BE9F19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CC3F5D4A-0D02-49FC-86BC-2FB160BE9F19}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CC3F5D4A-0D02-49FC-86BC-2FB160BE9F19}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CC3F5D4A-0D02-49FC-86BC-2FB160BE9F19}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0DFEA55B-6A79-4874-A9A3-A08FA9FC30DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0DFEA55B-6A79-4874-A9A3-A08FA9FC30DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0DFEA55B-6A79-4874-A9A3-A08FA9FC30DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1D689F6B-33EF-49F7-B7A7-2F9D0840BFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D689F6B-33EF-49F7-B7A7-2F9D0840BFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1D689F6B-33EF-49F7-B7A7-2F9D0840BFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1D689F6B-33EF-49F7-B7A7-2F9D0840BFAD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{62E8635E-9105-4764-B54E-D4968EEC4EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{62E8635E-9105-4764-B54E-D4968EEC4EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{62E8635E-9105-4764-B54E-D4968EEC4EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{62E8635E-9105-4764-B54E-D4968EEC4EA0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {B8CFA0A9-EAF7-4147-8F61-FD8375D77828}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
92
Lab2-grpc/Moderator/Moderator.cs
Normal file
92
Lab2-grpc/Moderator/Moderator.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using ChatRoomContract;
|
||||||
|
using NLog;
|
||||||
|
using Bogus;
|
||||||
|
|
||||||
|
namespace Moderator;
|
||||||
|
|
||||||
|
internal class Moderator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logger for this class.
|
||||||
|
/// </summary>
|
||||||
|
Logger log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures logging subsystem.
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureLogging()
|
||||||
|
{
|
||||||
|
var config = new NLog.Config.LoggingConfiguration();
|
||||||
|
|
||||||
|
var console =
|
||||||
|
new NLog.Targets.ConsoleTarget("console")
|
||||||
|
{
|
||||||
|
Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
|
||||||
|
};
|
||||||
|
config.AddTarget(console);
|
||||||
|
config.AddRuleForAllLevels(console);
|
||||||
|
|
||||||
|
LogManager.Configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunConnection(IChatRoomService chatRoom)
|
||||||
|
{
|
||||||
|
var faker = new Faker("en");
|
||||||
|
|
||||||
|
var name = faker.Name.FullName();
|
||||||
|
int clientId = chatRoom.RegisterClient(name);
|
||||||
|
log.Info($"Registered with client id {clientId}");
|
||||||
|
|
||||||
|
Console.Title = $"Moderator | {name} | {clientId}";
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var message = chatRoom.GetNewMessage();
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
log.Info($"Checking message ({message.id}): {message.contents}");
|
||||||
|
Thread.Sleep(500);
|
||||||
|
|
||||||
|
if (message.needsToBeCensored)
|
||||||
|
{
|
||||||
|
chatRoom.RejectMessage(message.id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chatRoom.ApproveMessage(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(1 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Run()
|
||||||
|
{
|
||||||
|
ConfigureLogging();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var client = new ChatRoomClient("http://127.0.0.1:5000");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RunConnection(client);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//log whatever exception to console
|
||||||
|
log.Warn(e, "Unhandled exception caught. Will restart main loop.");
|
||||||
|
|
||||||
|
//prevent console spamming
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var self = new Moderator();
|
||||||
|
self.Run();
|
||||||
|
}
|
||||||
|
}
|
19
Lab2-grpc/Moderator/Moderator.csproj
Normal file
19
Lab2-grpc/Moderator/Moderator.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.6.1" />
|
||||||
|
<PackageReference Include="NLog" Version="5.3.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ChatRoomContract\ChatRoomContract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
94
Lab2-grpc/Participant/Participant.cs
Normal file
94
Lab2-grpc/Participant/Participant.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using ChatRoomContract;
|
||||||
|
using NLog;
|
||||||
|
using Bogus;
|
||||||
|
|
||||||
|
namespace Participant;
|
||||||
|
|
||||||
|
internal class Participant
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Logger for this class.
|
||||||
|
/// </summary>
|
||||||
|
Logger log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures logging subsystem.
|
||||||
|
/// </summary>
|
||||||
|
private void ConfigureLogging()
|
||||||
|
{
|
||||||
|
var config = new NLog.Config.LoggingConfiguration();
|
||||||
|
|
||||||
|
var console =
|
||||||
|
new NLog.Targets.ConsoleTarget("console")
|
||||||
|
{
|
||||||
|
Layout = @"${date:format=HH\:mm\:ss}|${level}| ${message} ${exception}"
|
||||||
|
};
|
||||||
|
config.AddTarget(console);
|
||||||
|
config.AddRuleForAllLevels(console);
|
||||||
|
|
||||||
|
LogManager.Configuration = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunConnection(IChatRoomService chatRoom)
|
||||||
|
{
|
||||||
|
var faker = new Faker("en");
|
||||||
|
var rnd = new Random();
|
||||||
|
|
||||||
|
var name = faker.Name.FullName();
|
||||||
|
int clientId = chatRoom.RegisterClient(name);
|
||||||
|
log.Info($"Registered with client id {clientId}");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int strikes = chatRoom.GetStrikes(clientId);
|
||||||
|
Console.Title = $"Participant | {name} | {clientId} | {strikes} Strikes";
|
||||||
|
|
||||||
|
var message = string.Join(" ", faker.Lorem.Words(5));
|
||||||
|
bool needsToBeCensored = rnd.Next(0, 100) > 50;
|
||||||
|
if (chatRoom.SendMessage(clientId, message, needsToBeCensored)) {
|
||||||
|
log.Info("Sent message");
|
||||||
|
} else {
|
||||||
|
log.Info("Failed to send message, blocked");
|
||||||
|
var blockedUntil = chatRoom.GetBlockedUntil(clientId);
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (blockedUntil != null && blockedUntil > now)
|
||||||
|
{
|
||||||
|
var delta = blockedUntil.Value - now;
|
||||||
|
log.Info($"Waiting {delta.TotalSeconds:F3}s until block expires");
|
||||||
|
Thread.Sleep((int)delta.TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(2 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Run()
|
||||||
|
{
|
||||||
|
ConfigureLogging();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var client = new ChatRoomClient("http://127.0.0.1:5000");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RunConnection(client);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//log whatever exception to console
|
||||||
|
log.Warn(e, "Unhandled exception caught. Will restart main loop.");
|
||||||
|
|
||||||
|
//prevent console spamming
|
||||||
|
Thread.Sleep(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var self = new Participant();
|
||||||
|
self.Run();
|
||||||
|
}
|
||||||
|
}
|
19
Lab2-grpc/Participant/Participant.csproj
Normal file
19
Lab2-grpc/Participant/Participant.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Bogus" Version="35.6.1" />
|
||||||
|
<PackageReference Include="NLog" Version="5.3.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ChatRoomContract\ChatRoomContract.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
|
||||||
"iisSettings": {
|
|
||||||
"windowsAuthentication": false,
|
|
||||||
"anonymousAuthentication": true,
|
|
||||||
"iisExpress": {
|
|
||||||
"applicationUrl": "http://localhost:64988",
|
|
||||||
"sslPort": 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"http": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"applicationUrl": "http://localhost:5125",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"IIS Express": {
|
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"launchUrl": "swagger",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -49,6 +49,14 @@ internal class Participant
|
|||||||
log.Info("Sent message");
|
log.Info("Sent message");
|
||||||
} else {
|
} else {
|
||||||
log.Info("Failed to send message, blocked");
|
log.Info("Failed to send message, blocked");
|
||||||
|
var blockedUntil = chatRoom.GetBlockedUntil(clientId);
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (blockedUntil != null && blockedUntil > now)
|
||||||
|
{
|
||||||
|
var delta = blockedUntil.Value - now;
|
||||||
|
log.Info($"Waiting {delta.TotalSeconds:F3}s until block expires");
|
||||||
|
Thread.Sleep((int)delta.TotalMilliseconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(2 * 1000);
|
Thread.Sleep(2 * 1000);
|
||||||
@ -62,7 +70,7 @@ internal class Participant
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var client = new ChatRoomClient("http://127.0.0.1:5000", new HttpClient());
|
var client = new ChatRoomClient("http://127.0.0.1:5000", new HttpClient());
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RunConnection(client);
|
RunConnection(client);
|
||||||
|
Loading…
Reference in New Issue
Block a user