diff --git a/Lab1/ChatRoom/ChatRoomLogic.cs b/Lab1/ChatRoom/ChatRoomLogic.cs
index edf352f..6481d92 100644
--- a/Lab1/ChatRoom/ChatRoomLogic.cs
+++ b/Lab1/ChatRoom/ChatRoomLogic.cs
@@ -1,21 +1,38 @@
-using Castle.DynamicProxy.Generators.Emitters.SimpleAST;
-using NLog;
+using NLog;
namespace ChatRoom;
+///
+/// Chat room service logic
+///
public class ChatRoomLogic
{
+ ///
+ /// Background thread of deleting approved or rejected messages
+ ///
private Thread thread;
+ ///
+ /// Chat Room state
+ ///
private ChatRoomState state = new ChatRoomState();
+ ///
+ /// Logger
+ ///
private Logger log = LogManager.GetCurrentClassLogger();
-
+ ///
+ /// Chat room logic constructor
+ ///
public ChatRoomLogic()
{
thread = new Thread(BackgroundTask);
thread.Start();
}
+ ///
+ /// Generate the next incrementing ID
+ ///
+ /// Unique ID
int NextId()
{
int id = state.lastUniqueId;
@@ -23,6 +40,11 @@ public class ChatRoomLogic
return id;
}
+ ///
+ /// Register a client
+ ///
+ /// Client name
+ /// Client ID
public int RegisterClient(string name)
{
lock (state.accessLock)
@@ -38,6 +60,11 @@ public class ChatRoomLogic
}
}
+ ///
+ /// Find a client by ID, can return NULL if not found
+ ///
+ /// Client ID
+ /// Optional client object
Client? FindClientById(int clientId)
{
foreach (var client in state.clients)
@@ -50,6 +77,11 @@ public class ChatRoomLogic
return null;
}
+ ///
+ /// Find message by ID, can return NULL if not found
+ ///
+ /// Message ID
+ /// Optional message object
Message? FindMessageById(int messageId)
{
foreach (var message in state.messages)
@@ -61,7 +93,12 @@ public class ChatRoomLogic
}
return null;
}
-
+
+ ///
+ /// Check if client is still blocked, will clear `blockedUntil` if client became unblocked
+ ///
+ /// Client object
+ /// Is client blocked?
bool GetAndUpdateBlockedState(Client client)
{
if (client.blockedUntil != null && DateTime.UtcNow >= client.blockedUntil)
@@ -72,6 +109,13 @@ public class ChatRoomLogic
return client.blockedUntil != null;
}
+ ///
+ /// Send a message
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Was sending the message successful, can fail if client is blocked
public bool SendMessage(int clientId, string contents, bool needsToBeCensored)
{
lock (state.accessLock)
@@ -93,7 +137,8 @@ public class ChatRoomLogic
clientId = clientId,
contents = contents,
needsToBeCensored = needsToBeCensored,
- status = MessageStatus.WaitingForModerator
+ 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}");
@@ -102,6 +147,10 @@ public class ChatRoomLogic
return true;
}
+ ///
+ /// Get next message which isin't approved or rejected
+ ///
+ /// Optional message object
public ChatRoomContract.Message? GetNewMessage()
{
lock (state.accessLock)
@@ -123,6 +172,10 @@ public class ChatRoomLogic
return null;
}
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
public void ApproveMessage(int messageId)
{
lock (state.accessLock)
@@ -143,6 +196,10 @@ public class ChatRoomLogic
}
}
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
public void RejectMessage(int messageId)
{
lock (state.accessLock)
@@ -177,6 +234,11 @@ public class ChatRoomLogic
}
}
+ ///
+ /// Get number of strikes a client has
+ ///
+ /// Client ID
+ /// Number of strikes
public int GetStrikes(int clientId)
{
lock (state.accessLock)
@@ -191,13 +253,39 @@ public class ChatRoomLogic
}
}
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Optional datetime object
+ public DateTime? GetBlockedUntil(int clientId)
+ {
+ lock (state.accessLock)
+ {
+ var client = FindClientById(clientId);
+ if (client == null)
+ {
+ return null;
+ }
+
+ return client.blockedUntil;
+ }
+ }
+
+ ///
+ /// Main loop for background thread. Used for deleting approved or rejected messages.
+ ///
public void BackgroundTask()
{
while (true)
{
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");
}
diff --git a/Lab1/ChatRoom/ChatRoomService.cs b/Lab1/ChatRoom/ChatRoomService.cs
index 0fca3e1..938c303 100644
--- a/Lab1/ChatRoom/ChatRoomService.cs
+++ b/Lab1/ChatRoom/ChatRoomService.cs
@@ -1,4 +1,5 @@
-namespace ChatRoom;
+
+namespace ChatRoom;
public class ChatRoomService : ChatRoomContract.IChatRoomService
{
@@ -34,4 +35,9 @@ public class ChatRoomService : ChatRoomContract.IChatRoomService
{
return logic.SendMessage(clientId, contents, needsToBeCensored);
}
+
+ public DateTime? GetBlockedUntil(int clientId)
+ {
+ return logic.GetBlockedUntil(clientId);
+ }
}
diff --git a/Lab1/ChatRoom/ChatRoomState.cs b/Lab1/ChatRoom/ChatRoomState.cs
index fb4d6c6..4e6f3c2 100644
--- a/Lab1/ChatRoom/ChatRoomState.cs
+++ b/Lab1/ChatRoom/ChatRoomState.cs
@@ -1,14 +1,31 @@
namespace ChatRoom;
+///
+/// Client information
+///
public class Client
{
+ ///
+ /// Client ID
+ ///
public int id;
+ ///
+ /// Client name, can be the same between multiple clients
+ ///
public string name;
+ ///
+ /// Number of strikes
+ ///
public int strikes = 0;
-
+ ///
+ /// Until when is this client blocked from sending messages
+ ///
public DateTime? blockedUntil = null;
}
+///
+/// Describes the messages status/stage
+///
public enum MessageStatus
{
WaitingForModerator,
@@ -17,13 +34,36 @@ public enum MessageStatus
Rejected
}
+///
+/// Message information
+///
public class Message
{
+ ///
+ /// Message ID
+ ///
public int id;
+ ///
+ /// Client ID
+ ///
public int clientId;
+ ///
+ /// Message contents
+ ///
public string contents;
+ ///
+ /// Does this message need to be censored?
+ ///
public bool needsToBeCensored;
+ ///
+ /// Message status/stage
+ ///
public MessageStatus status = MessageStatus.WaitingForModerator;
+
+ ///
+ /// When was this message sent
+ ///
+ public DateTime sentAt;
}
public class ChatRoomState
@@ -38,7 +78,13 @@ public class ChatRoomState
///
public int lastUniqueId;
+ ///
+ /// List of all registered clients
+ ///
public List clients = new List();
+ ///
+ /// List of messages
+ ///
public List messages = new List();
}
diff --git a/Lab1/ChatRoomContract/IChatRoomService.cs b/Lab1/ChatRoomContract/IChatRoomService.cs
index 4eeca52..1e57a5b 100644
--- a/Lab1/ChatRoomContract/IChatRoomService.cs
+++ b/Lab1/ChatRoomContract/IChatRoomService.cs
@@ -1,23 +1,74 @@
namespace ChatRoomContract;
+///
+/// Minimal message description
+///
public class Message
{
+ ///
+ /// Message ID
+ ///
public int id;
+ ///
+ /// Message contents
+ ///
public string contents;
+ ///
+ /// Does this message need to be censored?
+ ///
public bool needsToBeCensored;
}
+///
+/// Chat room service contract
+///
public interface IChatRoomService
{
+ ///
+ /// Register client with a name
+ ///
+ /// Name of client, can be duplicate between clients
+ /// Client ID
int RegisterClient(string name);
+ ///
+ /// Get number of strikes a participant has
+ ///
+ /// Client ID
+ /// Number of strikes
int GetStrikes(int clientId);
+ ///
+ /// Get timestamp until when the client is blocked
+ ///
+ /// Client ID
+ /// Optional datetime object
+ DateTime? GetBlockedUntil(int clientId);
+
+ ///
+ /// Send a message, will be given to a moderator to be approved
+ ///
+ /// Client ID
+ /// Message contents
+ /// Does this message need to be censored?
+ /// Was sending successful, can fail if user is blocked
bool SendMessage(int clientId, string contents, bool needsToBeCensored);
+ ///
+ /// Get the next message which hasn't been approved or rejected
+ ///
+ /// Message object. Returns null if there is no message
Message? GetNewMessage();
+ ///
+ /// Reject a message
+ ///
+ /// Message ID
void RejectMessage(int messageId);
+ ///
+ /// Approve a message
+ ///
+ /// Message ID
void ApproveMessage(int messageId);
}