1
0

add comments

This commit is contained in:
Rokas Puzonas 2024-09-28 17:34:51 +03:00
parent bacde2dbbe
commit 5bf5699a51
4 changed files with 199 additions and 8 deletions

View File

@ -1,21 +1,38 @@
using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using NLog;
using NLog;
namespace ChatRoom; namespace ChatRoom;
/// <summary>
/// Chat room service logic
/// </summary>
public class ChatRoomLogic public class ChatRoomLogic
{ {
/// <summary>
/// Background thread of deleting approved or rejected messages
/// </summary>
private Thread thread; private Thread thread;
/// <summary>
/// Chat Room state
/// </summary>
private ChatRoomState state = new ChatRoomState(); private ChatRoomState state = new ChatRoomState();
/// <summary>
/// Logger
/// </summary>
private Logger log = LogManager.GetCurrentClassLogger(); private Logger log = LogManager.GetCurrentClassLogger();
/// <summary>
/// Chat room logic constructor
/// </summary>
public ChatRoomLogic() public ChatRoomLogic()
{ {
thread = new Thread(BackgroundTask); thread = new Thread(BackgroundTask);
thread.Start(); thread.Start();
} }
/// <summary>
/// Generate the next incrementing ID
/// </summary>
/// <returns>Unique ID</returns>
int NextId() int NextId()
{ {
int id = state.lastUniqueId; int id = state.lastUniqueId;
@ -23,6 +40,11 @@ public class ChatRoomLogic
return id; return id;
} }
/// <summary>
/// Register a client
/// </summary>
/// <param name="name">Client name</param>
/// <returns>Client ID</returns>
public int RegisterClient(string name) public int RegisterClient(string name)
{ {
lock (state.accessLock) lock (state.accessLock)
@ -38,6 +60,11 @@ public class ChatRoomLogic
} }
} }
/// <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) Client? FindClientById(int clientId)
{ {
foreach (var client in state.clients) foreach (var client in state.clients)
@ -50,6 +77,11 @@ public class ChatRoomLogic
return null; 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) Message? FindMessageById(int messageId)
{ {
foreach (var message in state.messages) foreach (var message in state.messages)
@ -62,6 +94,11 @@ public class ChatRoomLogic
return null; 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) bool GetAndUpdateBlockedState(Client client)
{ {
if (client.blockedUntil != null && DateTime.UtcNow >= client.blockedUntil) if (client.blockedUntil != null && DateTime.UtcNow >= client.blockedUntil)
@ -72,6 +109,13 @@ public class ChatRoomLogic
return 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) public bool SendMessage(int clientId, string contents, bool needsToBeCensored)
{ {
lock (state.accessLock) lock (state.accessLock)
@ -93,7 +137,8 @@ public class ChatRoomLogic
clientId = clientId, clientId = clientId,
contents = contents, contents = contents,
needsToBeCensored = needsToBeCensored, needsToBeCensored = needsToBeCensored,
status = MessageStatus.WaitingForModerator status = MessageStatus.WaitingForModerator,
sentAt = DateTime.UtcNow
}; };
state.messages.Add(message); state.messages.Add(message);
log.Info($"Client '{client.name}' ({client.id}) sent message '{contents}' ({message.id}). Needs to censored: {needsToBeCensored}"); log.Info($"Client '{client.name}' ({client.id}) sent message '{contents}' ({message.id}). Needs to censored: {needsToBeCensored}");
@ -102,6 +147,10 @@ public class ChatRoomLogic
return true; return true;
} }
/// <summary>
/// Get next message which isin't approved or rejected
/// </summary>
/// <returns>Optional message object</returns>
public ChatRoomContract.Message? GetNewMessage() public ChatRoomContract.Message? GetNewMessage()
{ {
lock (state.accessLock) lock (state.accessLock)
@ -123,6 +172,10 @@ public class ChatRoomLogic
return null; return null;
} }
/// <summary>
/// Approve a message
/// </summary>
/// <param name="messageId">Message ID</param>
public void ApproveMessage(int messageId) public void ApproveMessage(int messageId)
{ {
lock (state.accessLock) lock (state.accessLock)
@ -143,6 +196,10 @@ public class ChatRoomLogic
} }
} }
/// <summary>
/// Reject a message
/// </summary>
/// <param name="messageId">Message ID</param>
public void RejectMessage(int messageId) public void RejectMessage(int messageId)
{ {
lock (state.accessLock) lock (state.accessLock)
@ -177,6 +234,11 @@ public class ChatRoomLogic
} }
} }
/// <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) public int GetStrikes(int clientId)
{ {
lock (state.accessLock) lock (state.accessLock)
@ -191,13 +253,39 @@ public class ChatRoomLogic
} }
} }
/// <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() public void BackgroundTask()
{ {
while (true) while (true)
{ {
lock (state.accessLock) lock (state.accessLock)
{ {
int count = state.messages.RemoveAll(msg => msg.status == MessageStatus.Approved || msg.status == MessageStatus.Rejected); 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"); log.Info($"Running periodic cleanup, removed {count} messages");
} }

View File

@ -1,4 +1,5 @@
namespace ChatRoom; 
namespace ChatRoom;
public class ChatRoomService : ChatRoomContract.IChatRoomService public class ChatRoomService : ChatRoomContract.IChatRoomService
{ {
@ -34,4 +35,9 @@ public class ChatRoomService : ChatRoomContract.IChatRoomService
{ {
return logic.SendMessage(clientId, contents, needsToBeCensored); return logic.SendMessage(clientId, contents, needsToBeCensored);
} }
public DateTime? GetBlockedUntil(int clientId)
{
return logic.GetBlockedUntil(clientId);
}
} }

View File

@ -1,14 +1,31 @@
namespace ChatRoom; namespace ChatRoom;
/// <summary>
/// Client information
/// </summary>
public class Client public class Client
{ {
/// <summary>
/// Client ID
/// </summary>
public int id; public int id;
/// <summary>
/// Client name, can be the same between multiple clients
/// </summary>
public string name; public string name;
/// <summary>
/// Number of strikes
/// </summary>
public int strikes = 0; public int strikes = 0;
/// <summary>
/// Until when is this client blocked from sending messages
/// </summary>
public DateTime? blockedUntil = null; public DateTime? blockedUntil = null;
} }
/// <summary>
/// Describes the messages status/stage
/// </summary>
public enum MessageStatus public enum MessageStatus
{ {
WaitingForModerator, WaitingForModerator,
@ -17,13 +34,36 @@ public enum MessageStatus
Rejected Rejected
} }
/// <summary>
/// Message information
/// </summary>
public class Message public class Message
{ {
/// <summary>
/// Message ID
/// </summary>
public int id; public int id;
/// <summary>
/// Client ID
/// </summary>
public int clientId; public int clientId;
/// <summary>
/// Message contents
/// </summary>
public string contents; public string contents;
/// <summary>
/// Does this message need to be censored?
/// </summary>
public bool needsToBeCensored; public bool needsToBeCensored;
/// <summary>
/// Message status/stage
/// </summary>
public MessageStatus status = MessageStatus.WaitingForModerator; public MessageStatus status = MessageStatus.WaitingForModerator;
/// <summary>
/// When was this message sent
/// </summary>
public DateTime sentAt;
} }
public class ChatRoomState public class ChatRoomState
@ -38,7 +78,13 @@ public class ChatRoomState
/// </summary> /// </summary>
public int lastUniqueId; public int lastUniqueId;
/// <summary>
/// List of all registered clients
/// </summary>
public List<Client> clients = new List<Client>(); public List<Client> clients = new List<Client>();
/// <summary>
/// List of messages
/// </summary>
public List<Message> messages = new List<Message>(); public List<Message> messages = new List<Message>();
} }

View File

@ -1,23 +1,74 @@
namespace ChatRoomContract; namespace ChatRoomContract;
/// <summary>
/// Minimal message description
/// </summary>
public class Message public class Message
{ {
/// <summary>
/// Message ID
/// </summary>
public int id; public int id;
/// <summary>
/// Message contents
/// </summary>
public string contents; public string contents;
/// <summary>
/// Does this message need to be censored?
/// </summary>
public bool needsToBeCensored; public bool needsToBeCensored;
} }
/// <summary>
/// Chat room service contract
/// </summary>
public interface IChatRoomService 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); 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); 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); 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(); Message? GetNewMessage();
/// <summary>
/// Reject a message
/// </summary>
/// <param name="messageId">Message ID</param>
void RejectMessage(int messageId); void RejectMessage(int messageId);
/// <summary>
/// Approve a message
/// </summary>
/// <param name="messageId">Message ID</param>
void ApproveMessage(int messageId); void ApproveMessage(int messageId);
} }