From 740471d105d4b7495ead2c60271d37ac50827ada Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 7 Feb 2025 17:00:29 -0500 Subject: [PATCH 01/13] rememberer. but it doesn't remember. FUCK this is what I get for saying I like entity framework. *adds thing* *hits save* "(unrelated shit) is already here, why are you trying to add it again you dumbass?" FUCK IF I KNOW, you're supposed to be straightening this shit out! --- Behaver.cs | 44 ++----- Behavior/LinkMe.cs | 2 +- ConsoleService.cs | 1 - .../DiscordInterface/DiscordInterface.cs | 112 +++++++++--------- Rememberer.cs | 79 ++++++++++++ WebInterface/Controllers/HomeController.cs | 1 - 6 files changed, 145 insertions(+), 94 deletions(-) create mode 100644 Rememberer.cs diff --git a/Behaver.cs b/Behaver.cs index c225422..e255e80 100644 --- a/Behaver.cs +++ b/Behaver.cs @@ -72,55 +72,31 @@ public class Behaver public void MarkSelf(Account selfAccount) { - var db = new ChattingContext(); if(SelfUser == null) { SelfUser = selfAccount.IsUser; } else if (SelfUser != selfAccount.IsUser) { - CollapseUsers(SelfUser, selfAccount.IsUser, db); + CollapseUsers(SelfUser, selfAccount.IsUser); } - SelfAccounts = db.Accounts.Where(a => a.IsUser == SelfUser).ToList(); - db.SaveChanges(); + SelfAccounts = Rememberer.SearchAccounts(a => a.IsUser == SelfUser); + Rememberer.RememberAccount(selfAccount); } - public bool CollapseUsers(User primary, User secondary, ChattingContext db) + public bool CollapseUsers(User primary, User secondary) { - Console.WriteLine($"{secondary.Id} is being consumed into {primary.Id}"); - primary.Accounts.AddRange(secondary.Accounts); + if(primary.Accounts == null) + primary.Accounts = new List(); + if(secondary.Accounts != null) + primary.Accounts.AddRange(secondary.Accounts); foreach(var a in secondary.Accounts) { a.IsUser = primary; } secondary.Accounts.Clear(); - Console.WriteLine("accounts transferred"); - try - { - db.SaveChangesAsync().Wait(); - } - catch(Exception e) - { - Console.WriteLine("First save exception."); - Console.Error.WriteLine(e); - return false; - } - Console.WriteLine("saved"); - - - db.Users.Remove(secondary); - Console.WriteLine("old account cleaned up"); - try - { - db.SaveChangesAsync().Wait(); - } - catch(Exception e) - { - Console.WriteLine("Second save exception."); - Console.Error.WriteLine(e); - return false; - } - Console.WriteLine("saved, again, separately"); + Rememberer.ForgetUser(secondary); + Rememberer.RememberUser(primary); return true; } } diff --git a/Behavior/LinkMe.cs b/Behavior/LinkMe.cs index 841e6b6..fcd039b 100644 --- a/Behavior/LinkMe.cs +++ b/Behavior/LinkMe.cs @@ -72,7 +72,7 @@ public class LinkClose : Behavior return true; } - if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary, new ChattingContext())) + if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary)) { await message.Channel.SendMessage("done :)"); } diff --git a/ConsoleService.cs b/ConsoleService.cs index 937f136..c01c663 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -39,7 +39,6 @@ namespace vassago await t.Init(tc); ProtocolInterfaces.ProtocolList.twitchs.Add(t); } - Console.WriteLine("survived initting"); } public Task StopAsync(CancellationToken cancellationToken) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 4a9cacd..5bb23ea 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -23,7 +23,6 @@ public class DiscordInterface private static SemaphoreSlim discordChannelSetup = new SemaphoreSlim(1, 1); private Channel protocolAsChannel; - public async Task Init(string token) { await SetupDiscordChannel(); @@ -47,8 +46,7 @@ public class DiscordInterface try { - var db = new ChattingContext(); - protocolAsChannel = db.Channels.FirstOrDefault(c => c.ParentChannel == null && c.Protocol == PROTOCOL); + protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL); if (protocolAsChannel == null) { protocolAsChannel = new Channel() @@ -64,11 +62,15 @@ public class DiscordInterface Protocol = PROTOCOL, SubChannels = new List() }; - protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"discord itself cannot accept text"); }; - protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"discord itself cannot send file"); }; - db.Channels.Add(protocolAsChannel); - db.SaveChanges(); } + else + { + Console.WriteLine($"discord, channel with id {protocolAsChannel.Id}, already exists"); + } + protocolAsChannel.DisplayName = "discord (itself)"; + protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); }; + protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); }; + Rememberer.RememberChannel(protocolAsChannel); } finally { @@ -110,11 +112,8 @@ public class DiscordInterface private async Task SelfConnected() { - var db = new ChattingContext(); var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel); selfAccount.DisplayName = client.CurrentUser.Username; - await db.SaveChangesAsync(); - Behaver.Instance.MarkSelf(selfAccount); } @@ -137,7 +136,6 @@ public class DiscordInterface } await Behaver.Instance.ActOn(m); m.ActedOn = true; // for its own ruposess it might act on it later, but either way, fuck it, we checked. - } private void UserJoined(SocketGuildUser arg) @@ -185,31 +183,26 @@ public class DiscordInterface } internal vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment) { - - var db = new ChattingContext(); - var a = db.Attachments.FirstOrDefault(ai => ai.ExternalId == dAttachment.Id); + var a = Rememberer.SearchAttachment(ai => ai.ExternalId == dAttachment.Id); if (a == null) { a = new vassago.Models.Attachment(); - db.Attachments.Add(a); } a.ContentType = dAttachment.ContentType; a.Description = dAttachment.Description; a.Filename = dAttachment.Filename; a.Size = dAttachment.Size; a.Source = new Uri(dAttachment.Url); - db.SaveChanges(); + Rememberer.RememberAttachment(a); return a; } internal Message UpsertMessage(IUserMessage dMessage) { - var db = new ChattingContext(); - var m = db.Messages.FirstOrDefault(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL); + var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL); if (m == null) { m = new Message(); m.Protocol = PROTOCOL; - db.Messages.Add(m); } m.Attachments = m.Attachments ?? new List(); if (dMessage.Attachments?.Any() == true) @@ -225,7 +218,7 @@ public class DiscordInterface m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt; m.Channel = UpsertChannel(dMessage.Channel); m.Author = UpsertAccount(dMessage.Author, m.Channel); - if(dMessage.Channel is IGuildChannel) + if (dMessage.Channel is IGuildChannel) { m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work. } @@ -234,18 +227,16 @@ public class DiscordInterface m.Reply = (t) => { return dMessage.ReplyAsync(t); }; m.React = (e) => { return attemptReact(dMessage, e); }; - db.SaveChanges(); + Rememberer.RememberChannel(m.Channel); return m; } internal Channel UpsertChannel(IMessageChannel channel) { - - var db = new ChattingContext(); - Channel c = db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); + Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); if (c == null) { c = new Channel(); - db.Channels.Add(c); + Console.WriteLine($"adding channel {channel.Name}"); } c.DisplayName = channel.Name; @@ -255,8 +246,7 @@ public class DiscordInterface c.Protocol = PROTOCOL; if (channel is IGuildChannel) { - c.ParentChannel = UpsertChannel((channel as IGuildChannel).Guild, db); - c.ParentChannel.SubChannels.Add(c); + UpsertChannel((channel as IGuildChannel).Guild); } else if (channel is IPrivateChannel) { @@ -267,39 +257,51 @@ public class DiscordInterface c.ParentChannel = protocolAsChannel; Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } - c.SubChannels = c.SubChannels ?? new List(); - c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; - c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; - switch(c.ChannelType) + switch (c.ChannelType) { case vassago.Models.Enumerations.ChannelType.DM: c.DisplayName = "DM: " + (channel as IPrivateChannel).Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username; break; } - db.SaveChanges(); - return c; - } - internal Channel UpsertChannel(IGuild channel, ChattingContext db = null) - { - db = db ?? new ChattingContext(); - Console.WriteLine($"upserting *guild*: {channel.Id}"); - Channel c = db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); - if (c == null) + Rememberer.RememberChannel(c); + + Channel parentChannel = null; + if (channel is IGuildChannel) { - Console.WriteLine($"don't have one already. Creating."); - c = new Channel(); - db.Channels.Add(c); - Console.WriteLine($"upserting channel {channel.Name} from discord, have to create a new one in the DB"); + parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); + + } + else if (channel is IPrivateChannel) + { + parentChannel = protocolAsChannel; } else { - Console.WriteLine($"found one."); + parentChannel = protocolAsChannel; + Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); + } + if (parentChannel.SubChannels == null) + parentChannel.SubChannels = new List(); + parentChannel.SubChannels.Add(c); + Rememberer.RememberChannel(parentChannel); + + c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; + c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; + return c; + } + internal Channel UpsertChannel(IGuild channel) + { + Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); + if (c == null) + { + c = new Channel(); + Rememberer.RememberChannel(c); } c.DisplayName = channel.Name; c.ExternalId = channel.Id.ToString(); - c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal; + c.ChannelType = vassago.Models.Enumerations.ChannelType.OU; c.Messages = c.Messages ?? new List(); c.Protocol = protocolAsChannel.Protocol; c.ParentChannel = protocolAsChannel; @@ -308,17 +310,15 @@ public class DiscordInterface c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); }; - db.SaveChanges(); + Rememberer.RememberChannel(c); return c; } internal Account UpsertAccount(IUser user, Channel inChannel) { - var db = new ChattingContext(); - var acc = db.Accounts.FirstOrDefault(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); + var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); if (acc == null) { acc = new Account(); - db.Accounts.Add(acc); } acc.Username = user.Username; acc.ExternalId = user.Id.ToString(); @@ -326,21 +326,19 @@ public class DiscordInterface acc.Protocol = PROTOCOL; acc.SeenInChannel = inChannel; - acc.IsUser = db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - if(acc.IsUser == null) + acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); + //db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); + if (acc.IsUser == null) { acc.IsUser = new User() { Accounts = new List() { acc } }; - db.Users.Add(acc.IsUser); } - db.SaveChanges(); + Rememberer.RememberAccount(acc); return acc; } private Task attemptReact(IUserMessage msg, string e) { - - var db = new ChattingContext(); - var c = db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString()); + var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString()); //var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides var preferredEmote = e; if (Emoji.TryParse(preferredEmote, out Emoji emoji)) diff --git a/Rememberer.cs b/Rememberer.cs new file mode 100644 index 0000000..80f3f07 --- /dev/null +++ b/Rememberer.cs @@ -0,0 +1,79 @@ +namespace vassago; + +using System.Linq.Expressions; +using vassago.Models; + +public static class Rememberer +{ + public static Account SearchAccount(Expression> predicate) + { + return (new ChattingContext()).Accounts.FirstOrDefault(predicate); + } + public static List SearchAccounts(Expression> predicate) + { + return (new ChattingContext()).Accounts.Where(predicate).ToList(); + } + public static Attachment SearchAttachment(Expression> predicate) + { + return (new ChattingContext()).Attachments.FirstOrDefault(predicate); + } + public static Channel SearchChannel(Expression> predicate) + { + return (new ChattingContext()).Channels.FirstOrDefault(predicate); + } + public static Message SearchMessage(Expression> predicate) + { + return (new ChattingContext()).Messages.FirstOrDefault(predicate); + } + public static User SearchUser(Expression> predicate) + { + return (new ChattingContext()).Users.FirstOrDefault(predicate); + } + public static void RememberAccount(Account toRemember) + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + db.Accounts.Add(toRemember); + + db.SaveChanges(); + } + public static void RememberAttachment(Attachment toRemember) + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + db.Attachments.Add(toRemember); + + db.SaveChanges(); + } + public static void RememberChannel(Channel toRemember) + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + db.Channels.Add(toRemember); + + db.SaveChanges(); + } + public static void RememberMessage(Message toRemember) + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + db.Messages.Add(toRemember); + } + db.SaveChanges(); + } + public static void RememberUser(User toRemember) + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + db.Users.Add(toRemember); + + db.SaveChanges(); + } + public static void ForgetUser(User toForget) + { + var db = new ChattingContext(); + db.Users.Remove(toForget); + db.SaveChanges(); + } +} \ No newline at end of file diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index b790795..e048ac4 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -166,7 +166,6 @@ public class HomeController : Controller } private void serializeUser(ref StringBuilder sb, ref List allAccounts, User currentUser) { - Console.WriteLine(currentUser); sb.Append($"{{\"text\": \""); sb.Append(currentUser.DisplayName); sb.Append("\", "); From 736e3cb76340e81aac328ee6dec4dcf31e8658b1 Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 22 Feb 2025 22:14:16 -0500 Subject: [PATCH 02/13] it's always something --- .vscode/launch.json | 2 +- vassago.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1955653..0610636 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/net8.0/vassago.dll", + "program": "${workspaceFolder}/bin/Debug/net9.0/vassago.dll", "args": [], "cwd": "${workspaceFolder}", "stopAtEntry": false, diff --git a/vassago.csproj b/vassago.csproj index dae7d44..3c3c5ba 100644 --- a/vassago.csproj +++ b/vassago.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable $(NoWarn);CA2254 From 3ed37959ad400720e649964b1e0e1b45034cfd12 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 25 Feb 2025 23:45:30 -0500 Subject: [PATCH 03/13] fixed most compiler complaints in discord interface --- ConsoleService.cs | 3 +- .../DiscordInterface/DiscordInterface.cs | 88 ++++++++----------- .../DiscordInterface/SlashCommandsHelper.cs | 2 +- .../Controllers/ChannelsController.cs | 26 +++--- WebInterface/Controllers/HomeController.cs | 1 + WebInterface/Controllers/UsersController.cs | 26 +++--- .../ErrorPageViewModel.cs | 4 +- WebInterface/Views/Shared/Error.cshtml | 2 +- 8 files changed, 64 insertions(+), 88 deletions(-) rename WebInterface/{Controllers => Models}/ErrorPageViewModel.cs (54%) diff --git a/ConsoleService.cs b/ConsoleService.cs index c01c663..a54c815 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -4,6 +4,7 @@ namespace vassago using vassago; using vassago.Models; using vassago.TwitchInterface; + using vassago.ProtocolInterfaces.DiscordInterface; internal class ConsoleService : IHostedService { @@ -27,7 +28,7 @@ namespace vassago if (DiscordTokens?.Any() ?? false) foreach (var dt in DiscordTokens) { - var d = new DiscordInterface.DiscordInterface(); + var d = new DiscordInterface(); await d.Init(dt); ProtocolInterfaces.ProtocolList.discords.Add(d); } diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 5bb23ea..875b286 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -13,14 +13,14 @@ using Microsoft.EntityFrameworkCore; using System.Threading; using System.Reactive.Linq; -namespace vassago.DiscordInterface; +namespace vassago.ProtocolInterfaces.DiscordInterface; public class DiscordInterface { internal const string PROTOCOL = "discord"; internal DiscordSocketClient client; private bool eventsSignedUp = false; - private static SemaphoreSlim discordChannelSetup = new SemaphoreSlim(1, 1); + private static readonly SemaphoreSlim discordChannelSetup = new(1, 1); private Channel protocolAsChannel; public async Task Init(string token) @@ -33,8 +33,8 @@ public class DiscordInterface Console.WriteLine(msg.ToString()); return Task.CompletedTask; }; - client.Connected += SelfConnected; - client.Ready += ClientReady; + client.Connected += () => Task.Run(SelfConnected); + client.Ready += () => Task.Run(ClientReady); await client.LoginAsync(TokenType.Bot, token); await client.StartAsync(); @@ -60,7 +60,7 @@ public class DiscordInterface ReactionsPossible = true, ExternalId = null, Protocol = PROTOCOL, - SubChannels = new List() + SubChannels = [] }; } else @@ -87,7 +87,7 @@ public class DiscordInterface client.MessageReceived += MessageReceived; // _client.MessageUpdated += - //client.UserJoined += UserJoined; + client.UserJoined += UserJoined; client.SlashCommandExecuted += SlashCommandHandler; //client.ChannelCreated += // _client.ChannelDestroyed += @@ -110,7 +110,7 @@ public class DiscordInterface } } - private async Task SelfConnected() + private void SelfConnected() { var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel); selfAccount.DisplayName = client.CurrentUser.Username; @@ -119,12 +119,13 @@ public class DiscordInterface private async Task MessageReceived(SocketMessage messageParam) { - var suMessage = messageParam as SocketUserMessage; - if (suMessage == null) + if(messageParam is not SocketUserMessage) { Console.WriteLine($"{messageParam.Content}, but not a user message"); return; } + var suMessage = messageParam as SocketUserMessage; + Console.WriteLine($"#{suMessage.Channel}[{DateTime.Now}][{suMessage.Author.Username} [id={suMessage.Author.Id}]][msg id: {suMessage.Id}] {suMessage.Content}"); var m = UpsertMessage(suMessage); @@ -138,22 +139,14 @@ public class DiscordInterface m.ActedOn = true; // for its own ruposess it might act on it later, but either way, fuck it, we checked. } - private void UserJoined(SocketGuildUser arg) + private Task UserJoined(SocketGuildUser arg) { var guild = UpsertChannel(arg.Guild); var defaultChannel = UpsertChannel(arg.Guild.DefaultChannel); defaultChannel.ParentChannel = guild; var u = UpsertAccount(arg, guild); u.DisplayName = arg.DisplayName; - } - private async Task ButtonHandler(SocketMessageComponent component) - { - switch (component.Data.CustomId) - { - case "custom-id": - await component.RespondAsync($"{component.User.Mention}, it's been here the whole time!"); - break; - } + return null; } internal static async Task SlashCommandHandler(SocketSlashCommand command) { @@ -181,13 +174,11 @@ public class DiscordInterface break; } } - internal vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment) + internal static vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment) { - var a = Rememberer.SearchAttachment(ai => ai.ExternalId == dAttachment.Id); - if (a == null) - { - a = new vassago.Models.Attachment(); - } + var a = Rememberer.SearchAttachment(ai => ai.ExternalId == dAttachment.Id) + ?? new vassago.Models.Attachment(); + a.ContentType = dAttachment.ContentType; a.Description = dAttachment.Description; a.Filename = dAttachment.Filename; @@ -198,16 +189,16 @@ public class DiscordInterface } internal Message UpsertMessage(IUserMessage dMessage) { - var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL); - if (m == null) + var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL) + ?? new() + { + Protocol = PROTOCOL + }; + + + if (dMessage.Attachments?.Count > 0) { - m = new Message(); - m.Protocol = PROTOCOL; - } - m.Attachments = m.Attachments ?? new List(); - if (dMessage.Attachments?.Any() == true) - { - m.Attachments = new List(); + m.Attachments = []; foreach (var da in dMessage.Attachments) { m.Attachments.Add(UpsertAttachment(da)); @@ -226,7 +217,7 @@ public class DiscordInterface && (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0)); m.Reply = (t) => { return dMessage.ReplyAsync(t); }; - m.React = (e) => { return attemptReact(dMessage, e); }; + m.React = (e) => { return AttemptReact(dMessage, e); }; Rememberer.RememberChannel(m.Channel); return m; } @@ -242,7 +233,7 @@ public class DiscordInterface c.DisplayName = channel.Name; c.ExternalId = channel.Id.ToString(); c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal; - c.Messages = c.Messages ?? new List(); + c.Messages ??= []; c.Protocol = PROTOCOL; if (channel is IGuildChannel) { @@ -270,7 +261,6 @@ public class DiscordInterface if (channel is IGuildChannel) { parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); - } else if (channel is IPrivateChannel) { @@ -281,8 +271,7 @@ public class DiscordInterface parentChannel = protocolAsChannel; Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } - if (parentChannel.SubChannels == null) - parentChannel.SubChannels = new List(); + parentChannel.SubChannels ??= []; parentChannel.SubChannels.Add(c); Rememberer.RememberChannel(parentChannel); @@ -302,10 +291,10 @@ public class DiscordInterface c.DisplayName = channel.Name; c.ExternalId = channel.Id.ToString(); c.ChannelType = vassago.Models.Enumerations.ChannelType.OU; - c.Messages = c.Messages ?? new List(); + c.Messages ??= []; c.Protocol = protocolAsChannel.Protocol; c.ParentChannel = protocolAsChannel; - c.SubChannels = c.SubChannels ?? new List(); + c.SubChannels ??= []; c.MaxAttachmentBytes = channel.MaxUploadLimit; c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; @@ -313,13 +302,11 @@ public class DiscordInterface Rememberer.RememberChannel(c); return c; } - internal Account UpsertAccount(IUser user, Channel inChannel) + internal static Account UpsertAccount(IUser user, Channel inChannel) { var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); - if (acc == null) - { - acc = new Account(); - } + acc ??= new Account(); + acc.Username = user.Username; acc.ExternalId = user.Id.ToString(); acc.IsBot = user.IsBot || user.IsWebhook; @@ -328,15 +315,14 @@ public class DiscordInterface acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); //db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - if (acc.IsUser == null) - { - acc.IsUser = new User() { Accounts = new List() { acc } }; - } + + acc.IsUser ??= new User() { Accounts = [ acc ] }; + Rememberer.RememberAccount(acc); return acc; } - private Task attemptReact(IUserMessage msg, string e) + private static Task AttemptReact(IUserMessage msg, string e) { var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString()); //var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides diff --git a/ProtocolInterfaces/DiscordInterface/SlashCommandsHelper.cs b/ProtocolInterfaces/DiscordInterface/SlashCommandsHelper.cs index 95ad279..a1cba8f 100644 --- a/ProtocolInterfaces/DiscordInterface/SlashCommandsHelper.cs +++ b/ProtocolInterfaces/DiscordInterface/SlashCommandsHelper.cs @@ -7,7 +7,7 @@ using Discord.WebSocket; using Discord; using Discord.Net; -namespace vassago.DiscordInterface +namespace vassago.ProtocolInterfaces.DiscordInterface { public static class SlashCommandsHelper { diff --git a/WebInterface/Controllers/ChannelsController.cs b/WebInterface/Controllers/ChannelsController.cs index 93b4b75..8e45a82 100644 --- a/WebInterface/Controllers/ChannelsController.cs +++ b/WebInterface/Controllers/ChannelsController.cs @@ -4,35 +4,29 @@ using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using vassago.Models; +using vassago.WebInterface.Models; -namespace vassago.Controllers; +namespace vassago.WebInterface.Controllers; -public class ChannelsController : Controller +public class ChannelsController(ChattingContext db) : Controller { - private readonly ILogger _logger; - private readonly ChattingContext _db; + private ChattingContext Database => db; - public ChannelsController(ILogger logger, ChattingContext db) + public IActionResult Index() { - _logger = logger; - _db = db; - } - - public async Task Index(string searchString) - { - return _db.Channels != null ? - View(_db.Channels.Include(u => u.ParentChannel).ToList().OrderBy(c => c.LineageSummary)) : + return Database.Channels != null ? + View(Database.Channels.Include(u => u.ParentChannel).ToList().OrderBy(c => c.LineageSummary)) : Problem("Entity set '_db.Channels' is null."); } public async Task Details(Guid id) { - if(_db.Channels == null) + if(Database.Channels == null) return Problem("Entity set '_db.Channels' is null."); //"but adam", says the strawman, "why load *every* channel and walk your way up? surely there's a .Load command that works or something." //eh. I checked. Not really. You could make an SQL view that recurses its way up, meh idk how. You could just eagerly load *every* related object... //but that would take in all the messages. //realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel. - var AllChannels = await _db.Channels + var AllChannels = await Database.Channels .Include(u => u.SubChannels) .Include(u => u.Users) .Include(u => u.ParentChannel) @@ -46,7 +40,7 @@ public class ChannelsController : Controller walker = walker.ParentChannel; } var sb = new StringBuilder(); - sb.Append("["); + sb.Append('['); sb.Append($"{{text: \"{channel.SubChannels?.Count}\", nodes: ["); var first=true; foreach(var subChannel in channel.SubChannels) diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index e048ac4..6515e07 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments; using vassago.Models; +using vassago.WebInterface.Models; namespace vassago.Controllers; diff --git a/WebInterface/Controllers/UsersController.cs b/WebInterface/Controllers/UsersController.cs index ae37360..f18ba0a 100644 --- a/WebInterface/Controllers/UsersController.cs +++ b/WebInterface/Controllers/UsersController.cs @@ -2,37 +2,31 @@ using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using vassago.Models; +using vassago.WebInterface.Models; -namespace vassago.Controllers; +namespace vassago.WebInterface.Controllers; -public class UsersController : Controller +public class UsersController(ChattingContext db) : Controller { - private readonly ILogger _logger; - private readonly ChattingContext _db; + private ChattingContext Database => db; - public UsersController(ILogger logger, ChattingContext db) + public async Task Index() { - _logger = logger; - _db = db; - } - - public async Task Index(string searchString) - { - return _db.Users != null ? - View(await _db.Users.Include(u => u.Accounts).ToListAsync()) : + return Database.Users != null ? + View(await Database.Users.Include(u => u.Accounts).ToListAsync()) : Problem("Entity set '_db.Users' is null."); } public async Task Details(Guid id) { - var user = await _db.Users + var user = await Database.Users .Include(u => u.Accounts) .FirstAsync(u => u.Id == id); - var allTheChannels = await _db.Channels.ToListAsync(); + var allTheChannels = await Database.Channels.ToListAsync(); foreach(var acc in user.Accounts) { acc.SeenInChannel = allTheChannels.FirstOrDefault(c => c.Id == acc.SeenInChannel.Id); } - return _db.Users != null ? + return Database.Users != null ? View(user) : Problem("Entity set '_db.Users' is null."); } diff --git a/WebInterface/Controllers/ErrorPageViewModel.cs b/WebInterface/Models/ErrorPageViewModel.cs similarity index 54% rename from WebInterface/Controllers/ErrorPageViewModel.cs rename to WebInterface/Models/ErrorPageViewModel.cs index def8bca..a013dc1 100644 --- a/WebInterface/Controllers/ErrorPageViewModel.cs +++ b/WebInterface/Models/ErrorPageViewModel.cs @@ -1,8 +1,8 @@ -namespace vassago.Models; +namespace vassago.WebInterface.Models; public class ErrorPageViewModel { - public string? RequestId { get; set; } + public string RequestId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); } diff --git a/WebInterface/Views/Shared/Error.cshtml b/WebInterface/Views/Shared/Error.cshtml index 941bdaa..8b6606d 100644 --- a/WebInterface/Views/Shared/Error.cshtml +++ b/WebInterface/Views/Shared/Error.cshtml @@ -1,4 +1,4 @@ -@model ErrorPageViewModel +@model vassago.WebInterface.Models.ErrorPageViewModel @{ ViewData["Title"] = "Error"; } From c971add13727c3246b1ddbd4cc0a4ebb71f2e2f1 Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 27 Feb 2025 16:17:26 -0500 Subject: [PATCH 04/13] a lot of my problems are who owns what --- ConsoleService.cs | 11 +- .../DiscordInterface/DiscordInterface.cs | 48 ++++-- Rememberer.cs | 160 +++++++++++++++--- devuitls.sh | 2 +- 4 files changed, 172 insertions(+), 49 deletions(-) diff --git a/ConsoleService.cs b/ConsoleService.cs index a54c815..1a568a3 100644 --- a/ConsoleService.cs +++ b/ConsoleService.cs @@ -5,10 +5,10 @@ namespace vassago using vassago.Models; using vassago.TwitchInterface; using vassago.ProtocolInterfaces.DiscordInterface; + using System.Runtime.CompilerServices; internal class ConsoleService : IHostedService { - public ConsoleService(IConfiguration aspConfig) { Shared.DBConnectionString = aspConfig["DBConnectionString"]; @@ -22,14 +22,15 @@ namespace vassago public async Task StartAsync(CancellationToken cancellationToken) { + var initTasks = new List(); var dbc = new ChattingContext(); - await dbc.Database.MigrateAsync(); + await dbc.Database.MigrateAsync(cancellationToken); if (DiscordTokens?.Any() ?? false) foreach (var dt in DiscordTokens) { var d = new DiscordInterface(); - await d.Init(dt); + initTasks.Add(d.Init(dt)); ProtocolInterfaces.ProtocolList.discords.Add(d); } @@ -37,9 +38,11 @@ namespace vassago foreach (var tc in TwitchConfigs) { var t = new TwitchInterface.TwitchInterface(); - await t.Init(tc); + initTasks.Add(t.Init(tc)); ProtocolInterfaces.ProtocolList.twitchs.Add(t); } + + Task.WaitAll(initTasks, cancellationToken); } public Task StopAsync(CancellationToken cancellationToken) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 875b286..efaff3f 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -70,7 +70,8 @@ public class DiscordInterface protocolAsChannel.DisplayName = "discord (itself)"; protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); }; protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); }; - Rememberer.RememberChannel(protocolAsChannel); + protocolAsChannel= Rememberer.RememberChannel(protocolAsChannel); + Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}"); } finally { @@ -110,11 +111,20 @@ public class DiscordInterface } } - private void SelfConnected() + private async Task SelfConnected() { - var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel); - selfAccount.DisplayName = client.CurrentUser.Username; - Behaver.Instance.MarkSelf(selfAccount); + await discordChannelSetup.WaitAsync(); + + try + { + var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel); + selfAccount.DisplayName = client.CurrentUser.Username; + Behaver.Instance.MarkSelf(selfAccount); + } + finally + { + discordChannelSetup.Release(); + } } private async Task MessageReceived(SocketMessage messageParam) @@ -195,7 +205,6 @@ public class DiscordInterface Protocol = PROTOCOL }; - if (dMessage.Attachments?.Count > 0) { m.Attachments = []; @@ -209,6 +218,7 @@ public class DiscordInterface m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt; m.Channel = UpsertChannel(dMessage.Channel); m.Author = UpsertAccount(dMessage.Author, m.Channel); + Console.WriteLine($"received message; author: {m.Author.DisplayName}, {m.Author.Id}"); if (dMessage.Channel is IGuildChannel) { m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work. @@ -218,7 +228,7 @@ public class DiscordInterface m.Reply = (t) => { return dMessage.ReplyAsync(t); }; m.React = (e) => { return AttemptReact(dMessage, e); }; - Rememberer.RememberChannel(m.Channel); + Rememberer.RememberMessage(m); return m; } internal Channel UpsertChannel(IMessageChannel channel) @@ -237,7 +247,8 @@ public class DiscordInterface c.Protocol = PROTOCOL; if (channel is IGuildChannel) { - UpsertChannel((channel as IGuildChannel).Guild); + Console.WriteLine($"{channel.Name} is a guild channel. So i'm going to upsert the guild, {(channel as IGuildChannel).Guild}"); + c.ParentChannel = UpsertChannel((channel as IGuildChannel).Guild); } else if (channel is IPrivateChannel) { @@ -255,12 +266,15 @@ public class DiscordInterface c.DisplayName = "DM: " + (channel as IPrivateChannel).Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username; break; } - Rememberer.RememberChannel(c); Channel parentChannel = null; if (channel is IGuildChannel) { parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); + if(parentChannel is null) + { + Console.Error.WriteLine("why am I still null?"); + } } else if (channel is IPrivateChannel) { @@ -273,11 +287,11 @@ public class DiscordInterface } parentChannel.SubChannels ??= []; parentChannel.SubChannels.Add(c); - Rememberer.RememberChannel(parentChannel); c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; - return c; + + return Rememberer.RememberChannel(c); } internal Channel UpsertChannel(IGuild channel) { @@ -285,7 +299,6 @@ public class DiscordInterface if (c == null) { c = new Channel(); - Rememberer.RememberChannel(c); } c.DisplayName = channel.Name; @@ -299,8 +312,8 @@ public class DiscordInterface c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); }; - Rememberer.RememberChannel(c); - return c; + + return Rememberer.RememberChannel(c); } internal static Account UpsertAccount(IUser user, Channel inChannel) { @@ -314,10 +327,9 @@ public class DiscordInterface acc.SeenInChannel = inChannel; acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - //db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - - acc.IsUser ??= new User() { Accounts = [ acc ] }; - + acc.IsUser ??= new User() { Accounts = [ acc ] }; + inChannel.Users ??= []; + inChannel.Users.Add(acc); Rememberer.RememberAccount(acc); return acc; } diff --git a/Rememberer.cs b/Rememberer.cs index 80f3f07..920e8ac 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -5,6 +5,7 @@ using vassago.Models; public static class Rememberer { + private static readonly SemaphoreSlim dbAccessSemaphore = new(1, 1); public static Account SearchAccount(Expression> predicate) { return (new ChattingContext()).Accounts.FirstOrDefault(predicate); @@ -31,49 +32,156 @@ public static class Rememberer } public static void RememberAccount(Account toRemember) { - var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - db.Accounts.Add(toRemember); + dbAccessSemaphore.Wait(); + try + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + var parentChannel = toRemember.SeenInChannel; + var isUser = toRemember.IsUser; + toRemember.SeenInChannel = null; + toRemember.IsUser = null; + db.Accounts.Add(toRemember); + db.SaveChanges(); - db.SaveChanges(); + toRemember.SeenInChannel = parentChannel; + toRemember.IsUser = isUser; + db.SaveChanges(); + } + else + { + db.SaveChanges(); + } + } + finally + { + dbAccessSemaphore.Release(); + } } public static void RememberAttachment(Attachment toRemember) { - var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - db.Attachments.Add(toRemember); - - db.SaveChanges(); + dbAccessSemaphore.Wait(); + try + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + var msg = toRemember.Message; + toRemember.Message = null; + db.Attachments.Add(toRemember); + db.SaveChanges(); + toRemember.Message = msg; + db.SaveChanges(); + } + else + { + db.SaveChanges(); + } + } + finally + { + dbAccessSemaphore.Release(); + } } - public static void RememberChannel(Channel toRemember) + public static Channel RememberChannel(Channel toRemember) { - var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - db.Channels.Add(toRemember); + dbAccessSemaphore.Wait(); + try + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + var parent = toRemember.ParentChannel; + var subChannesl = toRemember.SubChannels; + var msgs = toRemember.Messages; + var accounts = toRemember.Users; + toRemember.ParentChannel = null; + toRemember.SubChannels = null; + toRemember.Messages = null; + toRemember.Users = null; + db.Channels.Add(toRemember); + db.SaveChanges(); + toRemember.ParentChannel = parent; + toRemember.SubChannels = subChannesl; + toRemember.Messages = msgs; + toRemember.Users = accounts; + db.SaveChanges(); + } - db.SaveChanges(); + db.SaveChanges(); + } + finally + { + dbAccessSemaphore.Release(); + } + return toRemember; } public static void RememberMessage(Message toRemember) { - var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) + dbAccessSemaphore.Wait(); + try { - db.Messages.Add(toRemember); + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + var author = toRemember.Author; + var channel = toRemember.Channel; + var attachments = toRemember.Attachments; + toRemember.Author = null; + toRemember.Channel = null; + toRemember.Attachments = null; + db.Messages.Add(toRemember); + db.SaveChanges(); + toRemember.Author = author; + toRemember.Channel = channel; + toRemember.Attachments = attachments; + db.SaveChanges(); + } + db.SaveChanges(); + } + finally + { + dbAccessSemaphore.Release(); } - db.SaveChanges(); } public static void RememberUser(User toRemember) { - var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - db.Users.Add(toRemember); - - db.SaveChanges(); + dbAccessSemaphore.Wait(); + try + { + var db = new ChattingContext(); + if (toRemember.Id == Guid.Empty) + { + var accs = toRemember.Accounts; + toRemember.Accounts = null; + db.Users.Add(toRemember); + db.SaveChanges(); + toRemember.Accounts = accs; + db.SaveChanges(); + } + else + { + db.SaveChanges(); + } + } + finally + { + dbAccessSemaphore.Release(); + } } public static void ForgetUser(User toForget) { - var db = new ChattingContext(); - db.Users.Remove(toForget); - db.SaveChanges(); + dbAccessSemaphore.Wait(); + try + { + var db = new ChattingContext(); + db.Users.Remove(toForget); + db.SaveChanges(); + } + finally + { + dbAccessSemaphore.Release(); + } } } \ No newline at end of file diff --git a/devuitls.sh b/devuitls.sh index 5c603de..d0f1748 100755 --- a/devuitls.sh +++ b/devuitls.sh @@ -26,7 +26,7 @@ case "$1" in "db-fullreset") sudo -u postgres psql -c "drop database ${servicename}_dev;" - sudo -u postgres psql -c "delete user $servicename" + sudo -u postgres psql -c "drop user $servicename" $0 "initial" ;; *) From d006367ecc8872fc8ded8d0ee067bec1386b176a Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 28 Feb 2025 22:53:10 -0500 Subject: [PATCH 05/13] you know why its change is being tracked? because you have another db context. --- .../DiscordInterface/DiscordInterface.cs | 3 +- Rememberer.cs | 91 +++---------------- 2 files changed, 16 insertions(+), 78 deletions(-) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index efaff3f..05434f2 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -236,8 +236,8 @@ public class DiscordInterface Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); if (c == null) { + Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); c = new Channel(); - Console.WriteLine($"adding channel {channel.Name}"); } c.DisplayName = channel.Name; @@ -298,6 +298,7 @@ public class DiscordInterface Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); if (c == null) { + Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); c = new Channel(); } diff --git a/Rememberer.cs b/Rememberer.cs index 920e8ac..64afd1d 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -36,23 +36,9 @@ public static class Rememberer try { var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - { - var parentChannel = toRemember.SeenInChannel; - var isUser = toRemember.IsUser; - toRemember.SeenInChannel = null; - toRemember.IsUser = null; - db.Accounts.Add(toRemember); - db.SaveChanges(); - - toRemember.SeenInChannel = parentChannel; - toRemember.IsUser = isUser; - db.SaveChanges(); - } - else - { - db.SaveChanges(); - } + db.Update(toRemember); + db.ChangeTracker.Clear(); + db.SaveChanges(); } finally { @@ -65,19 +51,9 @@ public static class Rememberer try { var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - { - var msg = toRemember.Message; - toRemember.Message = null; - db.Attachments.Add(toRemember); - db.SaveChanges(); - toRemember.Message = msg; - db.SaveChanges(); - } - else - { - db.SaveChanges(); - } + db.Update(toRemember); + db.ChangeTracker.Clear(); + db.SaveChanges(); } finally { @@ -90,25 +66,8 @@ public static class Rememberer try { var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - { - var parent = toRemember.ParentChannel; - var subChannesl = toRemember.SubChannels; - var msgs = toRemember.Messages; - var accounts = toRemember.Users; - toRemember.ParentChannel = null; - toRemember.SubChannels = null; - toRemember.Messages = null; - toRemember.Users = null; - db.Channels.Add(toRemember); - db.SaveChanges(); - toRemember.ParentChannel = parent; - toRemember.SubChannels = subChannesl; - toRemember.Messages = msgs; - toRemember.Users = accounts; - db.SaveChanges(); - } - + db.Update(toRemember); + db.ChangeTracker.Clear(); db.SaveChanges(); } finally @@ -123,21 +82,8 @@ public static class Rememberer try { var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - { - var author = toRemember.Author; - var channel = toRemember.Channel; - var attachments = toRemember.Attachments; - toRemember.Author = null; - toRemember.Channel = null; - toRemember.Attachments = null; - db.Messages.Add(toRemember); - db.SaveChanges(); - toRemember.Author = author; - toRemember.Channel = channel; - toRemember.Attachments = attachments; - db.SaveChanges(); - } + db.Update(toRemember); + db.ChangeTracker.Clear(); db.SaveChanges(); } finally @@ -151,19 +97,9 @@ public static class Rememberer try { var db = new ChattingContext(); - if (toRemember.Id == Guid.Empty) - { - var accs = toRemember.Accounts; - toRemember.Accounts = null; - db.Users.Add(toRemember); - db.SaveChanges(); - toRemember.Accounts = accs; - db.SaveChanges(); - } - else - { - db.SaveChanges(); - } + db.Users.Update(toRemember); + db.ChangeTracker.Clear(); + db.SaveChanges(); } finally { @@ -177,6 +113,7 @@ public static class Rememberer { var db = new ChattingContext(); db.Users.Remove(toForget); + db.ChangeTracker.Clear(); db.SaveChanges(); } finally From 18e8f0f36e9bce34f5e88ae29746e02b5cb73f45 Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 5 Mar 2025 23:11:58 -0500 Subject: [PATCH 06/13] issue cracked. now, apply rememberer to webinterface. --- Rememberer.cs | 91 ++++++++++----------------------------------------- 1 file changed, 18 insertions(+), 73 deletions(-) diff --git a/Rememberer.cs b/Rememberer.cs index 64afd1d..45c2a93 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -5,7 +5,7 @@ using vassago.Models; public static class Rememberer { - private static readonly SemaphoreSlim dbAccessSemaphore = new(1, 1); + private static readonly ChattingContext db = new(); public static Account SearchAccount(Expression> predicate) { return (new ChattingContext()).Accounts.FirstOrDefault(predicate); @@ -32,93 +32,38 @@ public static class Rememberer } public static void RememberAccount(Account toRemember) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Update(toRemember); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Update(toRemember); + db.SaveChanges(); } public static void RememberAttachment(Attachment toRemember) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Update(toRemember); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Update(toRemember); + + db.SaveChanges(); } public static Channel RememberChannel(Channel toRemember) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Update(toRemember); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Update(toRemember); + + db.SaveChanges(); return toRemember; } public static void RememberMessage(Message toRemember) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Update(toRemember); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Update(toRemember); + + db.SaveChanges(); } public static void RememberUser(User toRemember) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Users.Update(toRemember); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Users.Update(toRemember); + + db.SaveChanges(); } public static void ForgetUser(User toForget) { - dbAccessSemaphore.Wait(); - try - { - var db = new ChattingContext(); - db.Users.Remove(toForget); - db.ChangeTracker.Clear(); - db.SaveChanges(); - } - finally - { - dbAccessSemaphore.Release(); - } + db.Users.Remove(toForget); + + db.SaveChanges(); } } \ No newline at end of file From 0d3a56c8db75bf36e76cc3d0fc77cb3f7a45d03a Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 6 Mar 2025 17:02:33 -0500 Subject: [PATCH 07/13] double-adding of Accounts is sovled - but now it's double-adding Users. whyyyy --- Behaver.cs | 1 + .../DiscordInterface/DiscordInterface.cs | 36 +++++++++++++------ README.md | 10 ++++++ Rememberer.cs | 36 ++++++++++++++----- .../Controllers/ChannelsController.cs | 20 +++++------ WebInterface/Controllers/HomeController.cs | 17 +++++---- .../Controllers/api/ChannelsControler.cs | 1 + 7 files changed, 81 insertions(+), 40 deletions(-) diff --git a/Behaver.cs b/Behaver.cs index e255e80..859f6f7 100644 --- a/Behaver.cs +++ b/Behaver.cs @@ -8,6 +8,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Collections.Generic; +using vassago.ProtocolInterfaces.DiscordInterface; public class Behaver { diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 05434f2..2487c20 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -70,7 +70,7 @@ public class DiscordInterface protocolAsChannel.DisplayName = "discord (itself)"; protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); }; protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); }; - protocolAsChannel= Rememberer.RememberChannel(protocolAsChannel); + protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel); Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}"); } finally @@ -115,7 +115,7 @@ public class DiscordInterface { await discordChannelSetup.WaitAsync(); - try + try { var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel); selfAccount.DisplayName = client.CurrentUser.Username; @@ -129,7 +129,7 @@ public class DiscordInterface private async Task MessageReceived(SocketMessage messageParam) { - if(messageParam is not SocketUserMessage) + if (messageParam is not SocketUserMessage) { Console.WriteLine($"{messageParam.Content}, but not a user message"); return; @@ -271,7 +271,7 @@ public class DiscordInterface if (channel is IGuildChannel) { parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); - if(parentChannel is null) + if (parentChannel is null) { Console.Error.WriteLine("why am I still null?"); } @@ -290,7 +290,7 @@ public class DiscordInterface c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; - + return Rememberer.RememberChannel(c); } internal Channel UpsertChannel(IGuild channel) @@ -313,14 +313,19 @@ public class DiscordInterface c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); }; c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); }; - + return Rememberer.RememberChannel(c); } internal static Account UpsertAccount(IUser user, Channel inChannel) { var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); - acc ??= new Account(); - + Console.WriteLine($"upserting account, retrieved {acc?.Id}."); + if (acc != null) + { + Console.WriteLine($"acc's user: {acc.IsUser?.Id}"); + } + acc ??= new Account() { IsUser = new User() }; + acc.Username = user.Username; acc.ExternalId = user.Id.ToString(); acc.IsBot = user.IsBot || user.IsWebhook; @@ -328,9 +333,18 @@ public class DiscordInterface acc.SeenInChannel = inChannel; acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - acc.IsUser ??= new User() { Accounts = [ acc ] }; - inChannel.Users ??= []; - inChannel.Users.Add(acc); + + Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); + if (acc.IsUser != null) + { + Console.WriteLine($"user has record of {acc.IsUser.Accounts?.Count ?? 0} accounts"); + } + acc.IsUser ??= new User() { Accounts = [acc] }; + if (inChannel.Users?.Count > 0) + { + Console.WriteLine($"channel has {inChannel.Users.Count} accounts"); + } + inChannel.Users ??= [acc]; Rememberer.RememberAccount(acc); return acc; } diff --git a/README.md b/README.md index ca9e08f..a2e53b5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,16 @@ that's read messages/view channels, send messages, send messages in threads, and ## Data Types +database diagram. is a fancy term. + +message 1:n attachment +user 1:n account +channel 1:n account +channel 1:n message +account 1:n message + +featurepermission n:n ? + ### Accounts a `User` can have multiple `Account`s. e.g., @adam:greyn.club? that's an "account". I, however, am a `User`. An `Account` has references to the `Channels` its seen in - as in, leaf-level. If you're in a subchannel, you'll have an appropriate listing there - i.e., you will never have an account in "discord (itself)", you'll have one in the guild text-channels diff --git a/Rememberer.cs b/Rememberer.cs index 45c2a93..3371cc8 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -2,13 +2,14 @@ namespace vassago; using System.Linq.Expressions; using vassago.Models; +using Microsoft.EntityFrameworkCore; public static class Rememberer { private static readonly ChattingContext db = new(); public static Account SearchAccount(Expression> predicate) { - return (new ChattingContext()).Accounts.FirstOrDefault(predicate); + return (new ChattingContext()).Accounts.Include(a => a.IsUser).FirstOrDefault(predicate); } public static List SearchAccounts(Expression> predicate) { @@ -28,30 +29,30 @@ public static class Rememberer } public static User SearchUser(Expression> predicate) { - return (new ChattingContext()).Users.FirstOrDefault(predicate); + return (new ChattingContext()).Users.Include(u => u.Accounts).FirstOrDefault(predicate); } public static void RememberAccount(Account toRemember) { - db.Update(toRemember); + toRemember.IsUser ??= new User{ Accounts = [toRemember]}; + db.Update(toRemember.IsUser); db.SaveChanges(); } public static void RememberAttachment(Attachment toRemember) { - db.Update(toRemember); - + toRemember.Message ??= new Message() { Attachments = [toRemember]}; + db.Update(toRemember.Message); db.SaveChanges(); } public static Channel RememberChannel(Channel toRemember) { db.Update(toRemember); - db.SaveChanges(); return toRemember; } public static void RememberMessage(Message toRemember) { - db.Update(toRemember); - + toRemember.Channel ??= new (){ Messages = [toRemember] }; + db.Update(toRemember.Channel); db.SaveChanges(); } public static void RememberUser(User toRemember) @@ -66,4 +67,23 @@ public static class Rememberer db.SaveChanges(); } + public static List AccountsOverview() + { + return db.Accounts.ToList(); + } + public static List ChannelsOverview() + { + return db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel).ToList(); + } + public static Channel ChannelDetail(Guid Id) + { + return db.Channels.Find(Id); + // .Include(u => u.SubChannels) + // .Include(u => u.Users) + // .Include(u => u.ParentChannel); + } + public static List UsersOverview() + { + return db.Users.ToList(); + } } \ No newline at end of file diff --git a/WebInterface/Controllers/ChannelsController.cs b/WebInterface/Controllers/ChannelsController.cs index 8e45a82..164208d 100644 --- a/WebInterface/Controllers/ChannelsController.cs +++ b/WebInterface/Controllers/ChannelsController.cs @@ -8,30 +8,26 @@ using vassago.WebInterface.Models; namespace vassago.WebInterface.Controllers; -public class ChannelsController(ChattingContext db) : Controller +public class ChannelsController() : Controller { - private ChattingContext Database => db; - public IActionResult Index() { - return Database.Channels != null ? - View(Database.Channels.Include(u => u.ParentChannel).ToList().OrderBy(c => c.LineageSummary)) : + var channels = Rememberer.ChannelsOverview(); + return channels != null ? + View(channels.OrderBy(c => c.LineageSummary)) : Problem("Entity set '_db.Channels' is null."); } public async Task Details(Guid id) { - if(Database.Channels == null) + var allChannels = Rememberer.ChannelsOverview(); + if(allChannels == null) return Problem("Entity set '_db.Channels' is null."); //"but adam", says the strawman, "why load *every* channel and walk your way up? surely there's a .Load command that works or something." //eh. I checked. Not really. You could make an SQL view that recurses its way up, meh idk how. You could just eagerly load *every* related object... //but that would take in all the messages. //realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel. - var AllChannels = await Database.Channels - .Include(u => u.SubChannels) - .Include(u => u.Users) - .Include(u => u.ParentChannel) - .ToListAsync(); - var channel = AllChannels.First(u => u.Id == id); + + var channel = allChannels.First(u => u.Id == id); var walker = channel; while(walker != null) { diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index 6515e07..b28e16e 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -12,24 +12,23 @@ namespace vassago.Controllers; public class HomeController : Controller { private readonly ILogger _logger; - private readonly ChattingContext _db; - public HomeController(ILogger logger, ChattingContext db) + public HomeController(ILogger logger) { _logger = logger; - _db = db; } public IActionResult Index() { - var allAccounts = _db.Accounts.ToList(); - var allChannels = _db.Channels.Include(c => c.Users).ToList(); + var allAccounts = Rememberer.AccountsOverview(); + var allChannels = Rememberer.ChannelsOverview(); + Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}"); var sb = new StringBuilder(); - sb.Append("["); + sb.Append('['); sb.Append("{text: \"channels\", nodes: ["); var first = true; - var topLevelChannels = _db.Channels.Where(x => x.ParentChannel == null); + var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null); foreach (var topLevelChannel in topLevelChannels) { if (first) @@ -85,13 +84,13 @@ public class HomeController : Controller } sb.Append("]}"); } - var users = _db.Users.ToList(); + var users = Rememberer.UsersOverview();// _db.Users.ToList(); if(users.Any()) { sb.Append(",{text: \"users\", nodes: ["); first=true; //refresh list; we'll be knocking them out again in serializeUser - allAccounts = _db.Accounts.ToList(); + allAccounts = Rememberer.AccountsOverview(); foreach(var user in users) { if (first) diff --git a/WebInterface/Controllers/api/ChannelsControler.cs b/WebInterface/Controllers/api/ChannelsControler.cs index 8487e64..4a6db9f 100644 --- a/WebInterface/Controllers/api/ChannelsControler.cs +++ b/WebInterface/Controllers/api/ChannelsControler.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using vassago.Models; +using vassago.ProtocolInterfaces.DiscordInterface; namespace vassago.Controllers.api; From 50ecfc5867363e858837513b37c11e49bb55b255 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 11 Mar 2025 13:33:00 -0400 Subject: [PATCH 08/13] double add solved. also, i was relinking... I think that's supposed to have been only if needed. --- .../DiscordInterface/DiscordInterface.cs | 7 ++++--- Rememberer.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 2487c20..013647b 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -324,7 +324,10 @@ public class DiscordInterface { Console.WriteLine($"acc's user: {acc.IsUser?.Id}"); } - acc ??= new Account() { IsUser = new User() }; + acc ??= new Account() { + IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)) + ?? new User() + }; acc.Username = user.Username; acc.ExternalId = user.Id.ToString(); @@ -332,8 +335,6 @@ public class DiscordInterface acc.Protocol = PROTOCOL; acc.SeenInChannel = inChannel; - acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)); - Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); if (acc.IsUser != null) { diff --git a/Rememberer.cs b/Rememberer.cs index 3371cc8..24d532e 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -9,27 +9,27 @@ public static class Rememberer private static readonly ChattingContext db = new(); public static Account SearchAccount(Expression> predicate) { - return (new ChattingContext()).Accounts.Include(a => a.IsUser).FirstOrDefault(predicate); + return db.Accounts.Include(a => a.IsUser).FirstOrDefault(predicate); } public static List SearchAccounts(Expression> predicate) { - return (new ChattingContext()).Accounts.Where(predicate).ToList(); + return db.Accounts.Where(predicate).ToList(); } public static Attachment SearchAttachment(Expression> predicate) { - return (new ChattingContext()).Attachments.FirstOrDefault(predicate); + return db.Attachments.FirstOrDefault(predicate); } public static Channel SearchChannel(Expression> predicate) { - return (new ChattingContext()).Channels.FirstOrDefault(predicate); + return db.Channels.FirstOrDefault(predicate); } public static Message SearchMessage(Expression> predicate) { - return (new ChattingContext()).Messages.FirstOrDefault(predicate); + return db.Messages.FirstOrDefault(predicate); } public static User SearchUser(Expression> predicate) { - return (new ChattingContext()).Users.Include(u => u.Accounts).FirstOrDefault(predicate); + return db.Users.Include(u => u.Accounts).FirstOrDefault(predicate); } public static void RememberAccount(Account toRemember) { From 53753374f0dffe47dc770b354499aa9d4fc58e41 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 11 Mar 2025 22:20:09 -0400 Subject: [PATCH 09/13] runs and listens without exploding --- .../DiscordInterface/DiscordInterface.cs | 46 ++++++++++++++----- Rememberer.cs | 25 +++++++++- WebInterface/Controllers/HomeController.cs | 15 +++--- .../Controllers/api/ChannelsControler.cs | 24 ++++------ 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index 013647b..dfe19b3 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -237,10 +237,12 @@ public class DiscordInterface if (c == null) { Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); - c = new Channel(); + c = new Channel() + { + Users = [] + }; } - c.DisplayName = channel.Name; c.ExternalId = channel.Id.ToString(); c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal; c.Messages ??= []; @@ -263,7 +265,11 @@ public class DiscordInterface switch (c.ChannelType) { case vassago.Models.Enumerations.ChannelType.DM: - c.DisplayName = "DM: " + (channel as IPrivateChannel).Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username; + var asPriv =(channel as IPrivateChannel); + c.DisplayName = "DM: " + asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username; + break; + default: + c.DisplayName = channel.Name; break; } @@ -286,12 +292,23 @@ public class DiscordInterface Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } parentChannel.SubChannels ??= []; - parentChannel.SubChannels.Add(c); + if(!parentChannel.SubChannels.Contains(c)) + { + parentChannel.SubChannels.Add(c); + } c.SendMessage = (t) => { return channel.SendMessageAsync(t); }; c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); }; - return Rememberer.RememberChannel(c); + c = Rememberer.RememberChannel(c); + + var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString()); + if(selfAccountInChannel == null) + { + selfAccountInChannel = UpsertAccount(client.CurrentUser, c); + } + + return c; } internal Channel UpsertChannel(IGuild channel) { @@ -316,22 +333,22 @@ public class DiscordInterface return Rememberer.RememberChannel(c); } - internal static Account UpsertAccount(IUser user, Channel inChannel) + internal static Account UpsertAccount(IUser discordUser, Channel inChannel) { - var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); + var acc = Rememberer.SearchAccount(ui => ui.ExternalId == discordUser.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id); Console.WriteLine($"upserting account, retrieved {acc?.Id}."); if (acc != null) { Console.WriteLine($"acc's user: {acc.IsUser?.Id}"); } acc ??= new Account() { - IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol)) + IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == PROTOCOL)) ?? new User() }; - acc.Username = user.Username; - acc.ExternalId = user.Id.ToString(); - acc.IsBot = user.IsBot || user.IsWebhook; + acc.Username = discordUser.Username; + acc.ExternalId = discordUser.Id.ToString(); + acc.IsBot = discordUser.IsBot || discordUser.IsWebhook; acc.Protocol = PROTOCOL; acc.SeenInChannel = inChannel; @@ -345,8 +362,13 @@ public class DiscordInterface { Console.WriteLine($"channel has {inChannel.Users.Count} accounts"); } - inChannel.Users ??= [acc]; Rememberer.RememberAccount(acc); + inChannel.Users ??= []; + if(!inChannel.Users.Contains(acc)) + { + inChannel.Users.Add(acc); + Rememberer.RememberChannel(inChannel); + } return acc; } diff --git a/Rememberer.cs b/Rememberer.cs index 24d532e..8d8d830 100644 --- a/Rememberer.cs +++ b/Rememberer.cs @@ -61,6 +61,27 @@ public static class Rememberer db.SaveChanges(); } + public static void ForgetAccount(Account toForget) + { + var user = toForget.IsUser; + var usersOnlyAccount = user.Accounts?.Count == 1; + + if(usersOnlyAccount) + { + Rememberer.ForgetUser(user); + } + else + { + db.Accounts.Remove(toForget); + db.SaveChanges(); + } + } + public static void ForgetChannel(Channel toForget) + { + db.Channels.Remove(toForget); + + db.SaveChanges(); + } public static void ForgetUser(User toForget) { db.Users.Remove(toForget); @@ -69,11 +90,11 @@ public static class Rememberer } public static List AccountsOverview() { - return db.Accounts.ToList(); + return [..db.Accounts]; } public static List ChannelsOverview() { - return db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel).ToList(); + return [..db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel)]; } public static Channel ChannelDetail(Guid Id) { diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index b28e16e..769adcf 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -25,7 +25,7 @@ public class HomeController : Controller Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}"); var sb = new StringBuilder(); sb.Append('['); - sb.Append("{text: \"channels\", nodes: ["); + sb.Append("{text: \"channels\", expanded:true, nodes: ["); var first = true; var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null); @@ -46,7 +46,7 @@ public class HomeController : Controller if (allChannels.Any()) { - sb.Append(",{text: \"orphaned channels\", nodes: ["); + sb.Append(",{text: \"orphaned channels\", expanded:true, nodes: ["); first = true; while (true) { @@ -68,7 +68,7 @@ public class HomeController : Controller } if (allAccounts.Any()) { - sb.Append(",{text: \"channelless accounts\", nodes: ["); + sb.Append(",{text: \"channelless accounts\", expanded:true, nodes: ["); first = true; foreach (var acc in allAccounts) { @@ -87,7 +87,7 @@ public class HomeController : Controller var users = Rememberer.UsersOverview();// _db.Users.ToList(); if(users.Any()) { - sb.Append(",{text: \"users\", nodes: ["); + sb.Append(",{text: \"users\", expanded:true, nodes: ["); first=true; //refresh list; we'll be knocking them out again in serializeUser allAccounts = Rememberer.AccountsOverview(); @@ -105,7 +105,7 @@ public class HomeController : Controller } sb.Append("]}"); } - sb.Append("]"); + sb.Append(']'); ViewData.Add("treeString", sb.ToString()); return View("Index"); } @@ -114,6 +114,7 @@ public class HomeController : Controller allChannels.Remove(currentChannel); //"but adam", you say, "there's an href attribute, why make a link?" because that makes the entire bar a link, and trying to expand the node will probably click the link sb.Append($"{{\"text\": \"{currentChannel.DisplayName}\""); + sb.Append(", expanded:true "); var theseAccounts = allAccounts.Where(a => a.SeenInChannel?.Id == currentChannel.Id).ToList(); allAccounts.RemoveAll(a => a.SeenInChannel?.Id == currentChannel.Id); var first = true; @@ -123,7 +124,7 @@ public class HomeController : Controller } if (currentChannel.SubChannels != null) { - foreach (var subChannel in currentChannel.SubChannels ?? new List()) + foreach (var subChannel in currentChannel.SubChannels) { if (first) { @@ -135,7 +136,7 @@ public class HomeController : Controller } serializeChannel(ref sb, ref allChannels, ref allAccounts, subChannel); } - if (theseAccounts != null) + if (theseAccounts != null && !first) //"first" here tells us that we have at least one subchannel { sb.Append(','); } diff --git a/WebInterface/Controllers/api/ChannelsControler.cs b/WebInterface/Controllers/api/ChannelsControler.cs index 4a6db9f..7081646 100644 --- a/WebInterface/Controllers/api/ChannelsControler.cs +++ b/WebInterface/Controllers/api/ChannelsControler.cs @@ -11,26 +11,24 @@ namespace vassago.Controllers.api; public class ChannelsController : ControllerBase { private readonly ILogger _logger; - private readonly ChattingContext _db; - public ChannelsController(ILogger logger, ChattingContext db) + public ChannelsController(ILogger logger) { _logger = logger; - _db = db; } [HttpGet("{id}")] [Produces("application/json")] public Channel Get(Guid id) { - return _db.Find(id); + return Rememberer.ChannelDetail(id); } [HttpPatch] [Produces("application/json")] public IActionResult Patch([FromBody] Channel channel) { - var fromDb = _db.Channels.Find(channel.Id); + var fromDb = Rememberer.ChannelDetail(channel.Id); if (fromDb == null) { _logger.LogError($"attempt to update channel {channel.Id}, not found"); @@ -39,21 +37,20 @@ public class ChannelsController : ControllerBase //settable values: lewdness filter level, meanness filter level. maybe i could decorate them... fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel; fromDb.MeannessFilterLevel = channel.MeannessFilterLevel; - _db.SaveChanges(); + Rememberer.RememberChannel(fromDb); return Ok(fromDb); } [HttpDelete] [Produces("application/json")] public IActionResult Delete([FromBody] Channel channel) { - var fromDb = _db.Channels.Find(channel.Id); + var fromDb = Rememberer.ChannelDetail(channel.Id); if (fromDb == null) { _logger.LogError($"attempt to delete channel {channel.Id}, not found"); return NotFound(); } deleteChannel(fromDb); - _db.SaveChanges(); return Ok(); } private void deleteChannel(Channel channel) @@ -74,21 +71,16 @@ public class ChannelsController : ControllerBase } } - if(channel.Messages?.Count > 0) - { - _db.Remove(channel.Messages); - } - - _db.Remove(channel); + Rememberer.ForgetChannel(channel); } private void deleteAccount(Account account) { var user = account.IsUser; var usersOnlyAccount = user.Accounts?.Count == 1; - _db.Remove(account); + Rememberer.ForgetAccount(account); if(usersOnlyAccount) - _db.Users.Remove(user); + Rememberer.ForgetUser(user); } } From 6881816c948887ef93d07559ab0812da72e86511 Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 12 Mar 2025 16:05:22 -0400 Subject: [PATCH 10/13] self referencing serialization ignored --- Models/Account.cs | 2 ++ Models/Channel.cs | 19 +++++++++++++++++++ Program.cs | 5 ++++- .../Controllers/api/ChannelsControler.cs | 4 ++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Models/Account.cs b/Models/Account.cs index bb3eaf3..7ca9aba 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; +using System.Text.Json.Serialization; using System.Threading.Tasks; public class Account @@ -27,5 +28,6 @@ public class Account public bool IsBot { get; set; } //webhook counts public Channel SeenInChannel { get; set; } public string Protocol { get; set; } + [JsonIgnore] public User IsUser {get; set;} } \ No newline at end of file diff --git a/Models/Channel.cs b/Models/Channel.cs index f9c820d..0026965 100644 --- a/Models/Channel.cs +++ b/Models/Channel.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Web; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using static vassago.Models.Enumerations; public class Channel @@ -17,6 +18,7 @@ public class Channel public string DisplayName { get; set; } [DeleteBehavior(DeleteBehavior.Cascade)] public List SubChannels { get; set; } + [JsonIgnore] public Channel ParentChannel { get; set; } public string Protocol { get; set; } [DeleteBehavior(DeleteBehavior.Cascade)] @@ -82,6 +84,23 @@ public class Channel } } } + + /// + ///break self-referencing loops for library-agnostic serialization + /// + public Channel AsSerializable() + { + var toReturn = this.MemberwiseClone() as Channel; + toReturn.ParentChannel = null; + if(toReturn.Users?.Count > 0) + { + foreach (var account in toReturn.Users) + { + account.SeenInChannel = null; + } + } + return toReturn; + } } public class DefinitePermissionSettings diff --git a/Program.cs b/Program.cs index 7b5c9f0..6e8287e 100644 --- a/Program.cs +++ b/Program.cs @@ -11,7 +11,10 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddSingleton(); builder.Services.AddDbContext(); -builder.Services.AddControllers().AddNewtonsoftJson(); +builder.Services.AddControllers().AddNewtonsoftJson(options => { + options.SerializerSettings.ReferenceLoopHandling = + Newtonsoft.Json.ReferenceLoopHandling.Ignore; + }); builder.Services.AddProblemDetails(); builder.Services.Configure(o => { o.ViewLocationFormats.Clear(); diff --git a/WebInterface/Controllers/api/ChannelsControler.cs b/WebInterface/Controllers/api/ChannelsControler.cs index 7081646..3001123 100644 --- a/WebInterface/Controllers/api/ChannelsControler.cs +++ b/WebInterface/Controllers/api/ChannelsControler.cs @@ -33,6 +33,10 @@ public class ChannelsController : ControllerBase { _logger.LogError($"attempt to update channel {channel.Id}, not found"); return NotFound(); + } + else + { + _logger.LogDebug($"patching {channel.DisplayName} (id: {channel.Id})"); } //settable values: lewdness filter level, meanness filter level. maybe i could decorate them... fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel; From d22faae2f66593341cfb4b4e1c5f61ba1e05e71f Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 12 Mar 2025 18:34:39 -0400 Subject: [PATCH 11/13] vassago is back to 0! sort of! --- .../DiscordInterface/DiscordInterface.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs index dfe19b3..5dc6e9e 100644 --- a/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs +++ b/ProtocolInterfaces/DiscordInterface/DiscordInterface.cs @@ -255,6 +255,7 @@ public class DiscordInterface else if (channel is IPrivateChannel) { c.ParentChannel = protocolAsChannel; + Console.WriteLine("i'm a private channel so I'm setting my parent channel to the protocol as channel"); } else { @@ -262,11 +263,20 @@ public class DiscordInterface Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); } + Console.WriteLine($"upsertion of channel {c.DisplayName}, it's type {c.ChannelType}"); switch (c.ChannelType) { case vassago.Models.Enumerations.ChannelType.DM: var asPriv =(channel as IPrivateChannel); - c.DisplayName = "DM: " + asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username; + var sender = asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id); // why yes, there's a list of recipients, and it's the sender. + if(sender != null) + { + c.DisplayName = "DM: " + sender.Username; + } + else + { + //I sent it, so I don't know the recipient's name. + } break; default: c.DisplayName = channel.Name; From 488a89614a9509459f8c27cc695cb50ecf2fff9b Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 17 Mar 2025 23:38:16 -0400 Subject: [PATCH 12/13] account details view lineage summary doesn't work --- .../Controllers/AccountsController.cs | 35 ++++++ WebInterface/Controllers/HomeController.cs | 2 +- .../Controllers/api/UsersController.cs | 40 +++++++ WebInterface/Views/Accounts/Details.cshtml | 66 +++++++++++ WebInterface/Views/Channels/Details.cshtml | 38 ++++++- WebInterface/Views/Users/Details.cshtml | 106 +++++++++--------- 6 files changed, 230 insertions(+), 57 deletions(-) create mode 100644 WebInterface/Controllers/AccountsController.cs create mode 100644 WebInterface/Controllers/api/UsersController.cs create mode 100644 WebInterface/Views/Accounts/Details.cshtml diff --git a/WebInterface/Controllers/AccountsController.cs b/WebInterface/Controllers/AccountsController.cs new file mode 100644 index 0000000..09e26e6 --- /dev/null +++ b/WebInterface/Controllers/AccountsController.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago.Models; +using vassago.WebInterface.Models; + +namespace vassago.WebInterface.Controllers; + +public class AccountsController(ChattingContext db) : Controller +{ + private ChattingContext Database => db; + + public async Task Index() + { + return Database.Accounts != null ? + View(await Database.Accounts.ToListAsync()) : + Problem("Entity set '_db.Accounts' is null."); + } + public async Task Details(Guid id) + { + var account = await Database.Accounts + .Include(a => a.IsUser) + .Include(a => a.SeenInChannel) + .FirstAsync(a => a.Id == id); + return Database.Accounts != null ? + View(account) : + Problem("Entity set '_db.Accounts' is null."); + } + + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public IActionResult Error() + { + return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); + } +} \ No newline at end of file diff --git a/WebInterface/Controllers/HomeController.cs b/WebInterface/Controllers/HomeController.cs index 769adcf..f0d9b00 100644 --- a/WebInterface/Controllers/HomeController.cs +++ b/WebInterface/Controllers/HomeController.cs @@ -163,7 +163,7 @@ public class HomeController : Controller } private void serializeAccount(ref StringBuilder sb, Account currentAccount) { - sb.Append($"{{\"text\": \"{currentAccount.DisplayName}\"}}"); + sb.Append($"{{\"text\": \"{currentAccount.DisplayName}\"}}"); } private void serializeUser(ref StringBuilder sb, ref List allAccounts, User currentUser) { diff --git a/WebInterface/Controllers/api/UsersController.cs b/WebInterface/Controllers/api/UsersController.cs new file mode 100644 index 0000000..c522461 --- /dev/null +++ b/WebInterface/Controllers/api/UsersController.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using vassago.Models; +using vassago.ProtocolInterfaces.DiscordInterface; + +namespace vassago.Controllers.api; + +[Route("api/[controller]")] +[ApiController] +public class UsersController : ControllerBase +{ + private readonly ILogger _logger; + + public UsersController(ILogger logger) + { + _logger = logger; + } + + [HttpPatch] + [Produces("application/json")] + public IActionResult Patch([FromBody] User user) + { + var fromDb = Rememberer.SearchUser(u => u.Id == user.Id); + if (fromDb == null) + { + _logger.LogError($"attempt to update user {user.Id}, not found"); + return NotFound(); + } + else + { + _logger.LogDebug($"patching {user.DisplayName} (id: {user.Id})"); + } + + //TODO: settable values: display name + //fromDb.DisplayName = user.DisplayName; + Rememberer.RememberUser(fromDb); + return Ok(fromDb); + } +} \ No newline at end of file diff --git a/WebInterface/Views/Accounts/Details.cshtml b/WebInterface/Views/Accounts/Details.cshtml new file mode 100644 index 0000000..ea6a847 --- /dev/null +++ b/WebInterface/Views/Accounts/Details.cshtml @@ -0,0 +1,66 @@ +@model Account +@using Newtonsoft.Json +@using System.Text +@{ + ViewData["Title"] = "Account details"; +} + + + + + + + + + + + + + + + + +
belongs to user@Model.IsUser.DisplayName
Seen in channel
 
@Model.SeenInChannel.LineageSummary@Model.SeenInChannel.DisplayName
Permission Tags +
+
+ +@section Scripts{ + + +} diff --git a/WebInterface/Views/Channels/Details.cshtml b/WebInterface/Views/Channels/Details.cshtml index 8d9d549..b342938 100644 --- a/WebInterface/Views/Channels/Details.cshtml +++ b/WebInterface/Views/Channels/Details.cshtml @@ -1,5 +1,6 @@ @using System.ComponentModel @using Newtonsoft.Json +@using System.Text; @model Tuple @{ var ThisChannel = Model.Item1; @@ -91,7 +92,16 @@ Accounts - @(ThisChannel.Users?.Count ?? 0) + + @if((ThisChannel.Users?.Count ?? 0) > 0) + { + @Html.Raw("
"); + } + else + { + @Html.Raw("none") + } + @@ -105,9 +115,9 @@ } \ No newline at end of file diff --git a/WebInterface/Views/Users/Details.cshtml b/WebInterface/Views/Users/Details.cshtml index d127af4..ffa17f0 100644 --- a/WebInterface/Views/Users/Details.cshtml +++ b/WebInterface/Views/Users/Details.cshtml @@ -5,35 +5,37 @@ ViewData["Title"] = "User details"; } - - - - - - - - - - - + + + + + + + + + + + + + +
-
-
-
-
Display Name (here)
Accounts +
+
Permission Tags +
+
-placeholderlink - @section Scripts{ - + } From b6f74f580cec9ce3b1ea07185ce35adb6d870f69 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 18 Mar 2025 20:14:52 -0400 Subject: [PATCH 13/13] pages all work --- WebInterface/Controllers/ChannelsController.cs | 7 ------- WebInterface/Views/Accounts/Details.cshtml | 6 ++++-- WebInterface/Views/Channels/Details.cshtml | 2 +- WebInterface/Views/Home/Index.cshtml | 3 +-- WebInterface/Views/Users/Details.cshtml | 4 +++- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/WebInterface/Controllers/ChannelsController.cs b/WebInterface/Controllers/ChannelsController.cs index 164208d..a5c4c95 100644 --- a/WebInterface/Controllers/ChannelsController.cs +++ b/WebInterface/Controllers/ChannelsController.cs @@ -10,13 +10,6 @@ namespace vassago.WebInterface.Controllers; public class ChannelsController() : Controller { - public IActionResult Index() - { - var channels = Rememberer.ChannelsOverview(); - return channels != null ? - View(channels.OrderBy(c => c.LineageSummary)) : - Problem("Entity set '_db.Channels' is null."); - } public async Task Details(Guid id) { var allChannels = Rememberer.ChannelsOverview(); diff --git a/WebInterface/Views/Accounts/Details.cshtml b/WebInterface/Views/Accounts/Details.cshtml index ea6a847..4a18080 100644 --- a/WebInterface/Views/Accounts/Details.cshtml +++ b/WebInterface/Views/Accounts/Details.cshtml @@ -4,16 +4,18 @@ @{ ViewData["Title"] = "Account details"; } + +home/@Html.Raw(ViewData["breadcrumbs"]) - + - + diff --git a/WebInterface/Views/Channels/Details.cshtml b/WebInterface/Views/Channels/Details.cshtml index b342938..4a40142 100644 --- a/WebInterface/Views/Channels/Details.cshtml +++ b/WebInterface/Views/Channels/Details.cshtml @@ -152,7 +152,7 @@ foreach (var acc in ThisChannel.Users.OrderBy(a => a.SeenInChannel.LineageSummary)) { if(!first) - sb.Append(','); + sb.Append(','); sb.Append($"{{text: \"
 
{acc.SeenInChannel.LineageSummary}/{acc.DisplayName}\"}}"); first=false; } diff --git a/WebInterface/Views/Home/Index.cshtml b/WebInterface/Views/Home/Index.cshtml index 446233a..4f5544e 100644 --- a/WebInterface/Views/Home/Index.cshtml +++ b/WebInterface/Views/Home/Index.cshtml @@ -1,8 +1,7 @@ @{ ViewData["Title"] = "Home Page"; } -
-tree above. +
tree here
@section Scripts{
belongs to user @Model.IsUser.DisplayName
Seen in channel
 
@Model.SeenInChannel.LineageSummary@Model.SeenInChannel.DisplayName
Permission Tags