vassago/Rememberer.cs
adam 56c71ee533
Some checks failed
gitea.arg.rip/vassago/pipeline/head There was a failure building this commit
frontend progress. I am *almost* returning to monke.... but my datatype uses strings to serialize some objects in order to cooperate with entity framework.
how hard do you want to not use javascript.. is knockout.js worse than a viewmodel?
2025-07-03 13:18:35 -04:00

365 lines
11 KiB
C#

namespace vassago;
using System.Linq.Expressions;
using vassago.Models;
using Microsoft.EntityFrameworkCore;
public class Rememberer
{
private readonly SemaphoreSlim dbAccessSemaphore = new(1, 1);
private readonly ChattingContext db = new();
private List<Channel> channels;
private bool channelCacheDirty = true;
private Rememberer() { }
private static Rememberer _instance = null;
public static Rememberer Instance
{
get
{
if (_instance == null)
{
lock (instantiationLock)
{
if (_instance == null)
{
_instance = new Rememberer();
}
}
}
return _instance;
}
}
private static readonly object instantiationLock = new();
private void cacheChannels()
{
dbAccessSemaphore.Wait();
channels = db.Channels.ToList();
foreach (Channel ch in channels)
{
if (ch.ParentChannelId != null)
{
ch.ParentChannel = channels.FirstOrDefault(c => c.Id == ch.ParentChannelId);
ch.ParentChannel.SubChannels ??= [];
if (!ch.ParentChannel.SubChannels.Contains(ch))
{
ch.ParentChannel.SubChannels.Add(ch);
}
}
ch.SubChannels ??= [];
}
channelCacheDirty = false;
dbAccessSemaphore.Release();
}
public Account SearchAccount(Expression<Func<Account, bool>> predicate)
{
Account toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Accounts?.Include(a => a.IsUser)?.FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
}
public List<Account> SearchAccounts(Expression<Func<Account, bool>> predicate)
{
List<Account> toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Accounts.Where(predicate).ToList();
dbAccessSemaphore.Release();
return toReturn;
}
public Attachment SearchAttachment(Expression<Func<Attachment, bool>> predicate)
{
Attachment toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Attachments.FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
}
public Channel SearchChannel(Func<Channel, bool> predicate)
{
if (channelCacheDirty)
Task.Run(() => cacheChannels()).Wait();
return channels.FirstOrDefault(predicate);
}
public Message SearchMessage(Expression<Func<Message, bool>> predicate)
{
Message toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Messages.FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
}
public List<Message> SearchMessages(Expression<Func<Message, bool>> predicate)
{
List<Message> toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Messages.Where(predicate).ToList();
dbAccessSemaphore.Release();
return toReturn;
}
public User SearchUser(Expression<Func<User, bool>> predicate)
{
User toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Users.Where(predicate).Include(u => u.Accounts).FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
}
public void RememberAccount(Account toRemember)
{
dbAccessSemaphore.Wait();
toRemember.IsUser ??= new User { Accounts = [toRemember] };
db.Update(toRemember.IsUser);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void RememberAttachment(Attachment toRemember)
{
dbAccessSemaphore.Wait();
toRemember.Message ??= new Message() { Attachments = [toRemember] };
db.Update(toRemember.Message);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public Channel RememberChannel(Channel toRemember)
{
if (channelCacheDirty)
Task.Run(() => cacheChannels()).Wait(); //so we always do 2 db trips?
dbAccessSemaphore.Wait();
db.Update(toRemember);
db.SaveChanges();
dbAccessSemaphore.Release();
channelCacheDirty = true;
cacheChannels();
return toRemember;
}
public void RememberMessage(Message toRemember)
{
dbAccessSemaphore.Wait();
toRemember.Channel ??= new();
toRemember.Channel.Messages ??= [];
if (!toRemember.Channel.Messages.Contains(toRemember))
{
toRemember.Channel.Messages.Add(toRemember);
db.Update(toRemember.Channel);
}
db.Update(toRemember);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void RememberUser(User toRemember)
{
dbAccessSemaphore.Wait();
db.Users.Update(toRemember);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void ForgetAccount(Account toForget)
{
var user = toForget.IsUser;
var usersOnlyAccount = user.Accounts?.Count == 1;
if (usersOnlyAccount)
{
ForgetUser(user);
}
else
{
dbAccessSemaphore.Wait();
db.Accounts.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
}
public void ForgetAttachment(Attachment toForget)
{
dbAccessSemaphore.Wait();
db.Attachments.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void ForgetChannel(Channel toForget)
{
if (toForget.SubChannels?.Count > 0)
{
foreach (var childChannel in toForget.SubChannels.ToList())
{
ForgetChannel(childChannel);
}
}
if (toForget.Users?.Count > 0)
{
foreach (var account in toForget.Users.ToList())
{
ForgetAccount(account);
}
}
dbAccessSemaphore.Wait();
db.Channels.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
channelCacheDirty = true;
cacheChannels();
}
public void ForgetMessage(Message toForget)
{
dbAccessSemaphore.Wait();
db.Messages.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void ForgetUAC(UAC toForget)
{
dbAccessSemaphore.Wait();
db.UACs.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public void ForgetUser(User toForget)
{
dbAccessSemaphore.Wait();
db.Users.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public List<Account> AccountsOverview()
{
List<Account> toReturn;
dbAccessSemaphore.Wait();
toReturn = [.. db.Accounts];
dbAccessSemaphore.Release();
return toReturn;
}
///<summary>
///intentionally does not include Users; to help search for orphaned accounts.
///</summary>
public List<Channel> ChannelsOverview()
{
if (channelCacheDirty)
Task.Run(() => cacheChannels()).Wait();
return channels.ToList();
}
public Account AccountDetail(Guid Id)
{
Account toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Accounts.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public Attachment AttachmentDetail(Guid Id)
{
Attachment toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Attachments.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public Channel ChannelDetail(Guid Id, bool accounts = true, bool messages = false)
{
if (channelCacheDirty)
Task.Run(() => cacheChannels()).Wait();
var ch = channels.Find(c => c.Id == Id);
if(accounts)
ch.Users = SearchAccounts(a => a.SeenInChannel == ch);
if (messages)
ch.Messages = SearchMessages(m => m.ChannelId == ch.Id);
return ch;
}
public Message MessageDetail(Guid Id)
{
Message toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Messages.Find(Id);
db.Entry(toReturn).Reference(m => m.Channel).Load();
dbAccessSemaphore.Release();
return toReturn;
}
public UAC UACDetail(Guid Id)
{
UAC toReturn;
dbAccessSemaphore.Wait();
toReturn = db.UACs.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public User UserDetail(Guid Id)
{
User toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Users.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public List<User> UsersOverview()
{
List<User> toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Users.ToList();
dbAccessSemaphore.Release();
return toReturn;
}
public List<UAC> UACsOverview()
{
List<UAC> toReturn;
dbAccessSemaphore.Wait();
toReturn = db.UACs.Include(uac => uac.Users).Include(uac => uac.Channels).Include(uac => uac.AccountInChannels).ToList();
dbAccessSemaphore.Release();
return toReturn;
}
public UAC SearchUAC(Expression<Func<UAC, bool>> predicate)
{
UAC toReturn;
dbAccessSemaphore.Wait();
toReturn = db.UACs.Include(uac => uac.Users).Include(uac => uac.Channels).Include(uac => uac.AccountInChannels)
.FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
}
public List<UAC> MatchUACs(Message message)
{
var msgId = message.Id;
var accId = message.Author.Id;
var usrId = message.Author.IsUser.Id;
var chId = message.Channel.Id;
return SearchUACs(uac => uac.AccountInChannels.FirstOrDefault(aic => aic.Id == accId) != null
|| uac.Users.FirstOrDefault(usr => usr.Id == usrId) != null
|| uac.Channels.FirstOrDefault(ch => ch.Id == chId) != null);
}
public List<UAC> SearchUACs(Expression<Func<UAC, bool>> predicate)
{
List<UAC> toReturn;
dbAccessSemaphore.Wait();
toReturn = db.UACs.Include(uac => uac.Users).Include(uac => uac.Channels).Include(uac => uac.AccountInChannels)
.Where(predicate).ToList();
dbAccessSemaphore.Release();
return toReturn;
}
public void RememberUAC(UAC toRemember)
{
dbAccessSemaphore.Wait();
db.Update(toRemember);
db.SaveChanges();
dbAccessSemaphore.Release();
if (toRemember.Channels?.Count() > 0)
cacheChannels();
}
public Configuration Configuration()
{
dbAccessSemaphore.Wait();
var toReturn = db.Configurations.FirstOrDefault();
dbAccessSemaphore.Release();
return toReturn;
}
public void RememberConfiguration(Configuration conf)
{
dbAccessSemaphore.Wait();
db.Update(conf);
db.SaveChanges();
dbAccessSemaphore.Release();
}
}