Kavita/API/SignalR/Presence/PresenceTracker.cs
Joseph Milazzo bbe8f800f6
.NET 6 Coding Patterns + Unit Tests (#823)
* Refactored all files to have Interfaces within the same file. Started moving over to file-scoped namespaces.

* Refactored common methods for getting underlying file's cover, pages, and extracting into 1 interface.

* More refactoring around removing dependence on explicit filetype testing for getting information.

* Code is buildable, tests are broken. Huge refactor (not completed) which makes most of DirectoryService testable with a mock filesystem (and thus the services that utilize it).

* Finished porting DirectoryService to use mocked filesystem implementation.

* Added a null check

* Added a null check

* Finished all unit tests for DirectoryService.

* Some misc cleanup on the code

* Fixed up some bugs from refactoring scan loop.

* Implemented CleanupService testing and refactored more of DirectoryService to be non-static.

Fixed a bug where cover file cleanup wasn't properly finding files due to a regex bug.

* Fixed an issue in CleanupBackup() where we weren't properly selecting database files older than 30 days. Finished CleanupService Tests.

* Refactored Flatten and RemoveNonImages to directory service to allow CacheService to be testable.

* Finally have CacheService tested. Rewrote GetCachedPagePath() to be much more straightforward & performant.

* Updated DefaultParserTests.cs to contain all existing tests and follow new test layout format.

* All tests fixed up
2021-12-05 08:58:53 -08:00

103 lines
3.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
namespace API.SignalR.Presence
{
public interface IPresenceTracker
{
Task UserConnected(string username, string connectionId);
Task UserDisconnected(string username, string connectionId);
Task<string[]> GetOnlineAdmins();
Task<List<string>> GetConnectionsForUser(string username);
}
/// <summary>
/// This is a singleton service for tracking what users have a SignalR connection and their difference connectionIds
/// </summary>
public class PresenceTracker : IPresenceTracker
{
private readonly IUnitOfWork _unitOfWork;
private static readonly Dictionary<string, List<string>> OnlineUsers = new Dictionary<string, List<string>>();
public PresenceTracker(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task UserConnected(string username, string connectionId)
{
lock (OnlineUsers)
{
if (OnlineUsers.ContainsKey(username))
{
OnlineUsers[username].Add(connectionId);
}
else
{
OnlineUsers.Add(username, new List<string>() { connectionId });
}
}
// Update the last active for the user
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
user.LastActive = DateTime.Now;
await _unitOfWork.CommitAsync();
}
public Task UserDisconnected(string username, string connectionId)
{
lock (OnlineUsers)
{
if (!OnlineUsers.ContainsKey(username)) return Task.CompletedTask;
OnlineUsers[username].Remove(connectionId);
if (OnlineUsers[username].Count == 0)
{
OnlineUsers.Remove(username);
}
}
return Task.CompletedTask;
}
public static Task<string[]> GetOnlineUsers()
{
string[] onlineUsers;
lock (OnlineUsers)
{
onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray();
}
return Task.FromResult(onlineUsers);
}
public async Task<string[]> GetOnlineAdmins()
{
string[] onlineUsers;
lock (OnlineUsers)
{
onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray();
}
var admins = await _unitOfWork.UserRepository.GetAdminUsersAsync();
var result = admins.Select(a => a.UserName).Intersect(onlineUsers).ToArray();
return result;
}
public Task<List<string>> GetConnectionsForUser(string username)
{
List<string> connectionIds;
lock (OnlineUsers)
{
connectionIds = OnlineUsers.GetValueOrDefault(username);
}
return Task.FromResult(connectionIds);
}
}
}