1
0
tinklu-paslaugos/Lab4/ChatRoom/ChatRoomLogic.cs
2024-12-07 17:21:09 +02:00

297 lines
8.1 KiB
C#

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);
}
}
}