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 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> predicate) { Account toReturn; dbAccessSemaphore.Wait(); toReturn = db.Accounts?.Include(a => a.IsUser)?.FirstOrDefault(predicate); dbAccessSemaphore.Release(); return toReturn; } public List SearchAccounts(Expression> predicate) { List toReturn; dbAccessSemaphore.Wait(); toReturn = db.Accounts.Where(predicate).ToList(); dbAccessSemaphore.Release(); return toReturn; } public Attachment SearchAttachment(Expression> predicate) { Attachment toReturn; dbAccessSemaphore.Wait(); toReturn = db.Attachments.FirstOrDefault(predicate); dbAccessSemaphore.Release(); return toReturn; } public Channel SearchChannel(Func predicate) { if (channelCacheDirty) Task.Run(() => cacheChannels()).Wait(); return channels.FirstOrDefault(predicate); } public Message SearchMessage(Expression> predicate) { Message toReturn; dbAccessSemaphore.Wait(); toReturn = db.Messages.FirstOrDefault(predicate); dbAccessSemaphore.Release(); return toReturn; } public List SearchMessages(Expression> predicate) { List toReturn; dbAccessSemaphore.Wait(); toReturn = db.Messages.Where(predicate).ToList(); dbAccessSemaphore.Release(); return toReturn; } public User SearchUser(Expression> 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 AccountsOverview() { List toReturn; dbAccessSemaphore.Wait(); toReturn = [.. db.Accounts]; dbAccessSemaphore.Release(); return toReturn; } /// ///intentionally does not include Users; to help search for orphaned accounts. /// public List 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 UsersOverview() { List toReturn; dbAccessSemaphore.Wait(); toReturn = db.Users.ToList(); dbAccessSemaphore.Release(); return toReturn; } public List UACsOverview() { List 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> 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 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 SearchUACs(Expression> predicate) { List 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(); } }