diff options
author | Rory& <root@rory.gay> | 2024-01-08 13:55:15 +0100 |
---|---|---|
committer | Rory& <root@rory.gay> | 2024-01-08 13:56:32 +0100 |
commit | ede3857084bc7c6e65b7d36cbf913b09596e2787 (patch) | |
tree | b94694c307fb831ea5e63fabde0dbb5f56f02941 | |
parent | Small changes (diff) | |
download | MatrixUtils-ede3857084bc7c6e65b7d36cbf913b09596e2787.tar.xz |
Internal changes to policy list viewer (extensibility), fix duplicating change handler for room list page (performance), use /state in room list page before sync
41 files changed, 611 insertions, 428 deletions
diff --git a/.editorconfig b/.editorconfig index e8dd2db..3bc1adf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -371,7 +371,7 @@ dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:error -file_header_template = # ReSharper properties +file_header_template = # ReSharper properties resharper_alignment_tab_fill_style = use_spaces resharper_align_first_arg_by_paren = false @@ -606,8 +606,8 @@ resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = expression_body -resharper_macro_block_begin = -resharper_macro_block_end = +resharper_macro_block_begin = +resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 10000 resharper_max_attribute_length_for_same_line = 38 resharper_max_enum_members_on_line = 3 @@ -622,7 +622,7 @@ resharper_new_line_before_catch = true resharper_new_line_before_else = true resharper_new_line_before_enumerators = true resharper_normalize_tag_names = false -resharper_no_indent_inside_elements = +resharper_no_indent_inside_elements = resharper_no_indent_inside_if_element_longer_than = 2000000 resharper_null_checking_pattern_style = not_null_pattern resharper_object_creation_when_type_evident = target_typed @@ -675,7 +675,7 @@ resharper_requires_expression_braces = next_line resharper_resx_allow_far_alignment = false resharper_resx_attribute_indent = single_indent resharper_resx_insert_final_newline = false -resharper_resx_linebreak_before_elements = +resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_max_line_length = 2147483647 resharper_resx_pi_attribute_style = do_not_touch @@ -902,7 +902,7 @@ resharper_xmldoc_wrap_text = true resharper_xml_allow_far_alignment = false resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_insert_final_newline = false -resharper_xml_linebreak_before_elements = +resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_max_line_length = 180 resharper_xml_pi_attribute_style = do_not_touch @@ -1792,3 +1792,6 @@ resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_ resharper_xaml_x_key_attribute_disallowed_highlighting = error resharper_xunit_xunit_test_with_console_output_highlighting = warning resharper_zero_index_from_end_highlighting = warning + +# ReSharper properties +resharper_csharp_int_align_comments = true diff --git a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml index c468b91..f74ab1c 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml +++ b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml @@ -5,6 +5,8 @@ <map> <entry key="MatrixRoomUtils.Desktop/App.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> <entry key="MatrixRoomUtils.Desktop/Components/NavigationStack.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> + <entry key="MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> + <entry key="MatrixRoomUtils.Desktop/Components/RoomList.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> <entry key="MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> <entry key="MatrixRoomUtils.Desktop/LoginWindow.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> <entry key="MatrixRoomUtils.Desktop/MainWindow.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" /> diff --git a/LibMatrix b/LibMatrix -Subproject 5affd9f061e75f6575a2fe6715f9e8757cfe87e +Subproject 0f9f9e9201bbbed5981135d67e1265fd0f31aef diff --git a/MatrixRoomUtils.Desktop/FileStorageProvider.cs b/MatrixRoomUtils.Abstractions/FileStorageProvider.cs index 0429d1a..73d5604 100644 --- a/MatrixRoomUtils.Desktop/FileStorageProvider.cs +++ b/MatrixRoomUtils.Abstractions/FileStorageProvider.cs @@ -5,7 +5,7 @@ using LibMatrix.Extensions; using LibMatrix.Interfaces.Services; using Microsoft.Extensions.Logging; -namespace MatrixRoomUtils.Desktop; +namespace MatrixRoomUtils.Abstractions; public class FileStorageProvider : IStorageProvider { private readonly ILogger<FileStorageProvider> _logger; @@ -17,7 +17,7 @@ public class FileStorageProvider : IStorageProvider { /// </summary> /// <param name="targetPath"></param> public FileStorageProvider(string targetPath) { - new Logger<FileStorageProvider>(new LoggerFactory()).LogInformation("test"); + // new Logger<FileStorageProvider>(new LoggerFactory()).LogInformation("test"); Console.WriteLine($"Initialised FileStorageProvider with path {targetPath}"); TargetPath = targetPath; if (!Directory.Exists(targetPath)) { diff --git a/MatrixRoomUtils.Abstractions/MatrixRoomUtils.Abstractions.csproj b/MatrixRoomUtils.Abstractions/MatrixRoomUtils.Abstractions.csproj new file mode 100644 index 0000000..1665ff0 --- /dev/null +++ b/MatrixRoomUtils.Abstractions/MatrixRoomUtils.Abstractions.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> + </ItemGroup> +</Project> diff --git a/MatrixRoomUtils.Web/Classes/RoomInfo.cs b/MatrixRoomUtils.Abstractions/RoomInfo.cs index 9d0cd59..6db3447 100644 --- a/MatrixRoomUtils.Web/Classes/RoomInfo.cs +++ b/MatrixRoomUtils.Abstractions/RoomInfo.cs @@ -4,10 +4,9 @@ using ArcaneLibs; using LibMatrix; using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; -using LibMatrix.Interfaces; using LibMatrix.RoomTypes; -namespace MatrixRoomUtils.Web.Classes; +namespace MatrixRoomUtils.Abstractions; public class RoomInfo : NotifyPropertyChanged { public required GenericRoom Room { get; set; } @@ -44,7 +43,9 @@ public class RoomInfo : NotifyPropertyChanged { else @event.RawContent = default!; } - else throw; + else { + throw; + } } StateEvents.Add(@event); @@ -84,12 +85,12 @@ public class RoomInfo : NotifyPropertyChanged { public RoomInfo() { StateEvents.CollectionChanged += (_, args) => { if (args.NewItems is { Count: > 0 }) - foreach (StateEventResponse newState in args.NewItems) { - if (newState.GetType == typeof(RoomNameEventContent) && newState.TypedContent is RoomNameEventContent roomNameContent) + foreach (StateEventResponse newState in args.NewItems) { // TODO: switch statement benchmark? + if (newState.Type == RoomNameEventContent.EventId && newState.TypedContent is RoomNameEventContent roomNameContent) RoomName = roomNameContent.Name; - else if (newState.GetType == typeof(RoomAvatarEventContent) && newState.TypedContent is RoomAvatarEventContent roomAvatarContent) + else if (newState is { Type: RoomAvatarEventContent.EventId, TypedContent: RoomAvatarEventContent roomAvatarContent }) RoomIcon = roomAvatarContent.Url; - else if (newState.GetType == typeof(RoomCreateEventContent) && newState.TypedContent is RoomCreateEventContent roomCreateContent) { + else if (newState is { Type: RoomCreateEventContent.EventId, TypedContent: RoomCreateEventContent roomCreateContent }) { CreationEventContent = roomCreateContent; RoomCreator = newState.Sender; } diff --git a/MatrixRoomUtils.Desktop/App.axaml.cs b/MatrixRoomUtils.Desktop/App.axaml.cs index 3963be6..aeb154c 100644 --- a/MatrixRoomUtils.Desktop/App.axaml.cs +++ b/MatrixRoomUtils.Desktop/App.axaml.cs @@ -1,7 +1,9 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using Avalonia.Styling; using LibMatrix.Services; +using MatrixRoomUtils.Abstractions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -10,10 +12,6 @@ namespace MatrixRoomUtils.Desktop; public partial class App : Application { public IHost host { get; set; } - public override void Initialize() { - AvaloniaXamlLoader.Load(this); - } - public override void OnFrameworkInitializationCompleted() { host = Host.CreateDefaultBuilder().ConfigureServices((ctx, services) => { services.AddSingleton<MRUDesktopConfiguration>(); @@ -42,6 +40,10 @@ public partial class App : Application { var scope = scopeFac.CreateScope(); desktop.MainWindow = scope.ServiceProvider.GetRequiredService<MainWindow>(); } + + if(Environment.GetEnvironmentVariable("AVALONIA_THEME")?.Equals("dark", StringComparison.OrdinalIgnoreCase) ?? false) + RequestedThemeVariant = ThemeVariant.Dark; + base.OnFrameworkInitializationCompleted(); } -} +} \ No newline at end of file diff --git a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml index c773b8d..bc6b75d 100644 --- a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml +++ b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="MatrixRoomUtils.Desktop.Components.NavigationStack"> - <DockPanel x:Name="dock"> - <StackPanel x:Name="navPanel"></StackPanel> - <UserControl x:Name="content"></UserControl> - </DockPanel> - -</UserControl> + <StackPanel x:Name="dock"> + <Label>NagivationStack</Label> + <StackPanel x:Name="navPanel" Orientation="Horizontal"></StackPanel> + <ContentControl x:Name="content"></ContentControl> + </StackPanel> +</UserControl> \ No newline at end of file diff --git a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs index d6343e2..92c617b 100644 --- a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs +++ b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; namespace MatrixRoomUtils.Desktop.Components; @@ -8,12 +9,24 @@ public partial class NavigationStack : UserControl { InitializeComponent(); } - private void InitializeComponent() { - AvaloniaXamlLoader.Load(this); + // private void InitializeComponent() { + // AvaloniaXamlLoader.Load(this); + // buildView(); + // } + + protected override void OnLoaded(RoutedEventArgs e) { + base.OnLoaded(e); buildView(); } - + private void buildView() { + if (navPanel is null) { + Console.WriteLine("NavigationStack buildView called while navpanel is null!"); + // await Task.Delay(100); + // if (navPanel is null) + // await buildView(); + // else Console.WriteLine("navpanel is not null!"); + } navPanel.Children.Clear(); foreach (var item in _stack) { Button btn = new() { @@ -25,7 +38,7 @@ public partial class NavigationStack : UserControl { }; navPanel.Children.Add(btn); } - content = Current?.View ?? new UserControl(); + content.Content = Current?.View ?? new UserControl(); } @@ -44,13 +57,16 @@ public partial class NavigationStack : UserControl { Name = name, View = view }); + buildView(); } public void Pop() { _stack.RemoveAt(_stack.Count - 1); + buildView(); } public void PopTo(int index) { _stack.RemoveRange(index, _stack.Count - index); + buildView(); } } diff --git a/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml b/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml new file mode 100644 index 0000000..0e43d99 --- /dev/null +++ b/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml @@ -0,0 +1,21 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:components="clr-namespace:MatrixRoomUtils.Desktop.Components.Pages" + xmlns:components1="clr-namespace:MatrixRoomUtils.Desktop.Components" + xmlns:abstractions="clr-namespace:MatrixRoomUtils.Abstractions;assembly=MatrixRoomUtils.Abstractions" + + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="MatrixRoomUtils.Desktop.Components.Pages.RoomList" + x:DataType="components:RoomList" + DataContext="{Binding $self}" + > + <ListBox ItemsSource="{Binding Rooms}"> + <ListBox.ItemTemplate> + <DataTemplate DataType="abstractions:RoomInfo"> + <components1:RoomListEntry Room="{Binding Path=.}"/> + </DataTemplate> + </ListBox.ItemTemplate> + </ListBox> +</UserControl> diff --git a/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml.cs b/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml.cs new file mode 100644 index 0000000..53c3063 --- /dev/null +++ b/MatrixRoomUtils.Desktop/Components/Pages/RoomList.axaml.cs @@ -0,0 +1,15 @@ +using System.Collections.ObjectModel; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using MatrixRoomUtils.Abstractions; + +namespace MatrixRoomUtils.Desktop.Components.Pages; + +public partial class RoomList : UserControl { + private ObservableCollection<RoomInfo> Rooms { get; set; } = new(); + + public RoomList() { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml index 09fe52b..db22ccc 100644 --- a/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml +++ b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml @@ -2,10 +2,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:components="clr-namespace:MatrixRoomUtils.Desktop.Components" mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="32" - x:Class="MatrixRoomUtils.Desktop.Components.RoomListEntry"> + x:Class="MatrixRoomUtils.Desktop.Components.RoomListEntry" + + x:DataType="components:RoomListEntry" + DataContext="{Binding $self}" + > <StackPanel Orientation="Horizontal"> <Image MaxWidth="64" x:Name="RoomIcon"></Image> - <Label x:Name="RoomName"></Label> + <Label x:Name="RoomName" Content="{Binding Room.RoomName}"></Label> </StackPanel> </UserControl> diff --git a/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs index 69458aa..73115a2 100644 --- a/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs +++ b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs @@ -7,29 +7,28 @@ using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Helpers; using LibMatrix.Interfaces.Services; using LibMatrix.Services; +using MatrixRoomUtils.Abstractions; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace MatrixRoomUtils.Desktop.Components; public partial class RoomListEntry : UserControl { - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly RoomInfo _roomInfo; + public RoomInfo Room { get; set; } - public RoomListEntry(IServiceScopeFactory serviceScopeFactory, RoomInfo roomInfo) { - _serviceScopeFactory = serviceScopeFactory; - _roomInfo = roomInfo; + public RoomListEntry() { InitializeComponent(); } protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); - RoomName.Content = _roomInfo.Room.RoomId; + RoomName.Content = Room.Room.RoomId; Task.WhenAll(GetRoomName(), GetRoomIcon()); } private async Task GetRoomName() { try { - var nameEvent = await _roomInfo.GetStateEvent("m.room.name"); + var nameEvent = await Room.GetStateEvent("m.room.name"); if (nameEvent?.TypedContent is RoomNameEventContent nameData) RoomName.Content = nameData.Name; } @@ -41,18 +40,22 @@ public partial class RoomListEntry : UserControl { private async Task GetRoomIcon() { try { - var avatarEvent = await _roomInfo.GetStateEvent("m.room.avatar"); + using var hc = new HttpClient(); + var avatarEvent = await Room.GetStateEvent("m.room.avatar"); if (avatarEvent?.TypedContent is RoomAvatarEventContent avatarData) { var mxcUrl = avatarData.Url; - await using var svc = _serviceScopeFactory.CreateAsyncScope(); - var hs = await svc.ServiceProvider.GetService<MRUStorageWrapper>()?.GetCurrentSessionOrPrompt()!; - var hsResolver = svc.ServiceProvider.GetService<HomeserverResolverService>(); - var storage = svc.ServiceProvider.GetService<TieredStorageService>()?.CacheStorageProvider; - var resolvedUrl = await hsResolver.ResolveMediaUri(hs.ServerName, mxcUrl); + var resolvedUrl = await Room.Room.GetResolvedRoomAvatarUrlAsync(); + + // await using var svc = _serviceScopeFactory.CreateAsyncScope(); + // var hs = await svc.ServiceProvider.GetService<MRUStorageWrapper>()?.GetCurrentSessionOrPrompt()!; + // var hsResolver = svc.ServiceProvider.GetService<HomeserverResolverService>(); + // var storage = svc.ServiceProvider.GetService<TieredStorageService>()?.CacheStorageProvider; + // var resolvedUrl = await hsResolver.ResolveMediaUri(hs.ServerName, mxcUrl); + var storage = new FileStorageProvider("cache"); var storageKey = $"media/{mxcUrl.Replace("mxc://", "").Replace("/", ".")}"; try { if (!await storage.ObjectExistsAsync(storageKey)) - await storage.SaveStreamAsync(storageKey, await hs.ClientHttpClient.GetStreamAsync(resolvedUrl)); + await storage.SaveStreamAsync(storageKey, await hc.GetStreamAsync(resolvedUrl)); RoomIcon.Source = new Bitmap(await storage.LoadStreamAsync(storageKey) ?? throw new NullReferenceException()); } diff --git a/MatrixRoomUtils.Desktop/LoginWindow.axaml b/MatrixRoomUtils.Desktop/LoginWindow.axaml index a4600d5..fc0ee6f 100644 --- a/MatrixRoomUtils.Desktop/LoginWindow.axaml +++ b/MatrixRoomUtils.Desktop/LoginWindow.axaml @@ -4,14 +4,22 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:desktop="clr-namespace:MatrixRoomUtils.Desktop" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="MatrixRoomUtils.Desktop.LoginWindow" Title="LoginWindow" + x:Class="MatrixRoomUtils.Desktop.LoginWindow" x:DataType="desktop:LoginWindow" DataContext="{Binding $self}" - > + SizeToContent="WidthAndHeight" CanResize="False" + MinWidth="250"> <StackPanel> - <TextBox Text="{Binding Username, Mode=TwoWay}" /> - <MaskedTextBox PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" /> + <Label>Log in</Label> + <StackPanel Orientation="Horizontal"> + <Label Width="100">User ID</Label> + <TextBox MinWidth="250" Text="{Binding Username, Mode=TwoWay}" /> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <Label Width="100">Password</Label> + <MaskedTextBox MinWidth="250" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" /> + </StackPanel> <Button Click="Login">Login</Button> </StackPanel> -</Window> +</Window> \ No newline at end of file diff --git a/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs b/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs index 1f31b05..183c46b 100644 --- a/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs +++ b/MatrixRoomUtils.Desktop/LoginWindow.axaml.cs @@ -2,6 +2,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; namespace MatrixRoomUtils.Desktop; diff --git a/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs index 8a44518..b69c50d 100644 --- a/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs +++ b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs @@ -1,3 +1,4 @@ +using Avalonia; using LibMatrix; using LibMatrix.Homeservers; using LibMatrix.Responses; @@ -74,8 +75,11 @@ public class MRUStorageWrapper(TieredStorageService storageService, HomeserverPr if (session is null) { // _navigationManager.NavigateTo("/Login"); var wnd = new LoginWindow(this); + wnd.Position = MainWindow.Instance.Position + new PixelPoint(50, 50); await wnd.ShowDialog(MainWindow.Instance); - while (wnd.IsVisible) await Task.Delay(100); + while (wnd.IsVisible) { + await Task.Delay(100); + } session = await GetCurrentSession(); } diff --git a/MatrixRoomUtils.Desktop/MainWindow.axaml.cs b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs index 9db59c5..6ef573e 100644 --- a/MatrixRoomUtils.Desktop/MainWindow.axaml.cs +++ b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs @@ -1,5 +1,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; +using MatrixRoomUtils.Abstractions; +using MatrixRoomUtils.Desktop.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -26,7 +28,6 @@ public partial class MainWindow : Window { _logger.LogInformation("Cache location: {}", _configuration.CacheStoragePath); _logger.LogInformation("Data location: {}", _configuration.DataStoragePath); - // for (int i = 0; i < 100; i++) { // roomList.Children.Add(new RoomListEntry()); // } @@ -39,12 +40,18 @@ public partial class MainWindow : Window { var rooms = await hs.GetJoinedRooms(); foreach (var room in rooms) { // roomList.Children.Add(new RoomListEntry(_scopeFactory, new RoomInfo(room))); + + windowContent.Push("home", new RoomListEntry() { + Room = new RoomInfo() { + Room = room + } + }); + base.OnLoaded(e); } - base.OnLoaded(e); } // public Command // protected void LoadedCommand() { // _logger.LogInformation("async command"); // } -} +} \ No newline at end of file diff --git a/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj index 6d9fc0e..94bf245 100644 --- a/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj +++ b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj @@ -10,29 +10,27 @@ <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <InvariantGlobalization>true</InvariantGlobalization> - <PublishTrimmed>true</PublishTrimmed> - <PublishReadyToRun>true</PublishReadyToRun> - <PublishSingleFile>true</PublishSingleFile> - <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings> - <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison> - <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings> +<!-- <PublishTrimmed>true</PublishTrimmed>--> +<!-- <PublishReadyToRun>true</PublishReadyToRun>--> +<!-- <PublishSingleFile>true</PublishSingleFile>--> +<!-- <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings>--> +<!-- <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison>--> +<!-- <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings>--> </PropertyGroup> <ItemGroup> - <PackageReference Include="Avalonia" Version="11.0.5" /> - <PackageReference Include="Avalonia.Desktop" Version="11.0.5" /> - <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" /> - <PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.5" /> + <PackageReference Include="Avalonia" Version="11.0.6" /> + <PackageReference Include="Avalonia.Desktop" Version="11.0.6" /> + <PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" /> + <PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" /> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> - <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.5" /> + <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" /> <PackageReference Include="Sentry" Version="3.36.0" /> </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> - </ItemGroup> + <ItemGroup> <PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.0.5" /> @@ -46,4 +44,7 @@ <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\MatrixRoomUtils.Abstractions\MatrixRoomUtils.Abstractions.csproj" /> + </ItemGroup> </Project> diff --git a/MatrixRoomUtils.Desktop/Properties/launchSettings.json b/MatrixRoomUtils.Desktop/Properties/launchSettings.json index 997e294..36405e8 100644 --- a/MatrixRoomUtils.Desktop/Properties/launchSettings.json +++ b/MatrixRoomUtils.Desktop/Properties/launchSettings.json @@ -12,7 +12,8 @@ "commandName": "Project", "dotnetRunMessages": true, "environmentVariables": { - "DOTNET_ENVIRONMENT": "Development" + "DOTNET_ENVIRONMENT": "Development", + "AVALONIA_THEME": "Dark" } }, "Local config": { diff --git a/MatrixRoomUtils.Desktop/RoomInfo.cs b/MatrixRoomUtils.Desktop/RoomInfo.cs deleted file mode 100644 index a562086..0000000 --- a/MatrixRoomUtils.Desktop/RoomInfo.cs +++ /dev/null @@ -1,40 +0,0 @@ -using LibMatrix; -using LibMatrix.EventTypes; -using LibMatrix.Interfaces; -using LibMatrix.Responses; -using LibMatrix.RoomTypes; - -namespace MatrixRoomUtils.Desktop; - -public class RoomInfo { - public RoomInfo() { } - - public RoomInfo(GenericRoom room) { - Room = room; - } - - public GenericRoom Room { get; set; } - public List<StateEventResponse?> StateEvents { get; init; } = new(); - - public async Task<StateEventResponse?> GetStateEvent(string type, string stateKey = "") { - var @event = StateEvents.FirstOrDefault(x => x.Type == type && x.StateKey == stateKey); - if (@event is not null) return @event; - @event = new StateEventResponse { - RoomId = Room.RoomId, - Type = type, - StateKey = stateKey, - Sender = null, //TODO: implement - EventId = null - }; - try { - @event.TypedContent = await Room.GetStateAsync<EventContent>(type, stateKey); - } - catch (MatrixException e) { - if (e is { ErrorCode: "M_NOT_FOUND" }) @event.TypedContent = default!; - else throw; - } - - StateEvents.Add(@event); - return @event; - } -} diff --git a/MatrixRoomUtils.Desktop/SentryService.cs b/MatrixRoomUtils.Desktop/SentryService.cs index 648946c..26212fa 100644 --- a/MatrixRoomUtils.Desktop/SentryService.cs +++ b/MatrixRoomUtils.Desktop/SentryService.cs @@ -6,7 +6,7 @@ namespace MatrixRoomUtils.Desktop; public class SentryService : IDisposable { private IDisposable? _sentrySdkDisposable; - public SentryService(IServiceScopeFactory scopeFactory, ILogger logger) { + public SentryService(IServiceScopeFactory scopeFactory, ILogger<SentryService> logger) { var config = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUDesktopConfiguration>(); if (config.SentryDsn is null) { logger.LogWarning("Sentry DSN is not set, skipping Sentry initialisation"); diff --git a/MatrixRoomUtils.LibDMSpace/DMSpaceRoom.cs b/MatrixRoomUtils.LibDMSpace/DMSpaceRoom.cs index cbe2303..1cf7064 100644 --- a/MatrixRoomUtils.LibDMSpace/DMSpaceRoom.cs +++ b/MatrixRoomUtils.LibDMSpace/DMSpaceRoom.cs @@ -24,7 +24,7 @@ public class DMSpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomI } public async Task<EventIdResponse> AddChildAsync(GenericRoom room) { - var members = room.GetMembersAsync(true); + var members = room.GetMembersEnumerableAsync(true); Dictionary<string, int> memberCountByHs = new(); await foreach (var member in members) { var server = member.StateKey.Split(':')[1]; @@ -61,12 +61,12 @@ public class DMSpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomI } }; // Add all DM room members - var members = homeserver.GetRoom(roomid).GetMembersAsync(); + var members = homeserver.GetRoom(roomid).GetMembersEnumerableAsync(); await foreach (var member in members) if (member.StateKey != userId) dri.RemoteUsers.Add(member.StateKey); // Remove members of DM space - members = GetMembersAsync(); + members = GetMembersEnumerableAsync(); await foreach (var member in members) if (dri.RemoteUsers.Contains(member.StateKey)) dri.RemoteUsers.Remove(member.StateKey); diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj index c6678ce..ed62751 100644 --- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj +++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj @@ -23,6 +23,7 @@ <ProjectReference Condition="Exists('..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj')" Include="..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj" /> <PackageReference Condition="!Exists('..\LibMatrix\ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj')" Include="ArcaneLibs.Blazor.Components" Version="*-preview*" /> <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" /> + <ProjectReference Include="..\MatrixRoomUtils.Abstractions\MatrixRoomUtils.Abstractions.csproj" /> <ProjectReference Include="..\MatrixRoomUtils.LibDMSpace\MatrixRoomUtils.LibDMSpace.csproj" /> </ItemGroup> @@ -35,39 +36,4 @@ </Content> </ItemGroup> - <ItemGroup> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-BoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-ExtraBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-ExtraBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-ExtraLight.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-ExtraLightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-LightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Medium.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-MediumItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-SemiBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-SemiBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-Thin.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMono-ThinItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Bold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-BoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-ExtraBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-ExtraBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-ExtraLight.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-ExtraLightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Italic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Light.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-LightItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Medium.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-MediumItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Regular.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-SemiBold.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-SemiBoldItalic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-Thin.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\jetbrains-mono\ttf\JetBrainsMonoNL-ThinItalic.ttf" /> - </ItemGroup> - </Project> diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor index 94c51b2..27fe35e 100644 --- a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor @@ -3,6 +3,7 @@ @using ArcaneLibs.Extensions @using LibMatrix.Extensions @using LibMatrix.Homeservers +@using MatrixRoomUtils.Abstractions @inject ILocalStorageService LocalStorage @inject NavigationManager NavigationManager <h3>Debug Tools</h3> diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor index 2d1d6c0..ebb0ebb 100644 --- a/MatrixRoomUtils.Web/Pages/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Index.razor @@ -16,30 +16,29 @@ Small collection of tools to do not-so-everyday things. <hr/> <form> <table> - @foreach (var __auth in _auth.OrderByDescending(x => x.UserInfo.RoomCount)) { - var _auth = __auth.UserAuth; + @foreach (var session in _sessions.OrderByDescending(x => x.UserInfo.RoomCount)) { + var _auth = session.UserAuth; <tr class="user-entry"> <td> - <img class="avatar" src="@__auth.UserInfo.AvatarUrl"/> + <img class="avatar" src="@session.UserInfo.AvatarUrl"/> </td> <td class="user-info"> - @* <div class="user-info"> *@ <p> <input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(() => SwitchSession(_auth))" style="text-decoration-line: unset;"/> - <b>@__auth.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/> + <b>@session.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/> </p> - <span style="display: inline-block; width: 128px;">@__auth.UserInfo.RoomCount rooms</span> - <a style="color: #888888" href="@("/ServerInfo/"+__auth.Homeserver.ServerName+"/")">@__auth.ServerVersion.Server.Name @__auth.ServerVersion.Server.Version</a> + <span style="display: inline-block; width: 128px;">@session.UserInfo.RoomCount rooms</span> + <a style="color: #888888" href="@("/ServerInfo/" + session.Homeserver.ServerName + "/")">@session.ServerVersion.Server.Name @session.ServerVersion.Server.Version</a> @if (_auth.Proxy != null) { <span class="badge badge-info"> (proxied via @_auth.Proxy)</span> } else { <p>Not proxied</p> } - @if (DEBUG) { - <p>T=@__auth.Homeserver.GetType().FullName</p> - <p>D=@__auth.Homeserver.WhoAmI.DeviceId</p> - <p>U=@__auth.Homeserver.WhoAmI.UserId</p> + @if (_debug) { + <p>T=@session.Homeserver.GetType().FullName</p> + <p>D=@session.Homeserver.WhoAmI.DeviceId</p> + <p>U=@session.Homeserver.WhoAmI.UserId</p> } </td> <td> @@ -49,18 +48,46 @@ Small collection of tools to do not-so-everyday things. <LinkButton OnClick="@(() => RemoveUser(_auth, true))">Log out</LinkButton> </p> </td> - @* </div> *@ </tr> } </table> </form> +@if (_offlineSessions.Count > 0) { + <br/> + <br/> + <h5>Sessions on unreachable servers</h5> + <hr/> + <form> + <table> + @foreach (var session in _offlineSessions) { + <tr class="user-entry"> + <td> + <p> + @{ + string[] parts = session.UserId.Split(':'); + } + <span>@parts[0][1..]</span> on <span>@parts[1]</span> + @if (!string.IsNullOrWhiteSpace(session.Proxy)) { + <span class="badge badge-info"> (proxied via @session.Proxy)</span> + } + </p> + </td> + <td> + <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton> + </td> + </tr> + } + </table> + </form> +} + @code { #if DEBUG - bool DEBUG = true; + private const bool _debug = true; #else - bool DEBUG = false; + private const bool _debug = false; #endif private class AuthInfo { @@ -71,35 +98,42 @@ Small collection of tools to do not-so-everyday things. } // private Dictionary<UserAuth, UserInfo> _users = new(); - private List<AuthInfo> _auth = new(); + private readonly List<AuthInfo> _sessions = []; + private readonly List<UserAuth> _offlineSessions = []; + private LoginResponse? _currentSession; protected override async Task OnInitializedAsync() { + Console.WriteLine("Index.OnInitializedAsync"); _currentSession = await MRUStorage.GetCurrentToken(); - // _users.Clear(); - _auth.Clear(); + _sessions.Clear(); + _offlineSessions.Clear(); var tokens = await MRUStorage.GetAllTokens(); var profileTasks = tokens.Select(async token => { UserInfo userInfo = new(); AuthenticatedHomeserverGeneric hs; + Console.WriteLine($"Getting hs for {token.ToJson()}"); try { hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); } catch (MatrixException e) { - if (e.ErrorCode == "M_UNKNOWN_TOKEN") { - NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); - return; - } - throw; + if (e.ErrorCode != "M_UNKNOWN_TOKEN") throw; + NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); + return; + } catch (HttpRequestException e) { logger.LogError(e, $"Failed to instantiate AuthenticatedHomeserver for {token.ToJson()}, homeserver may be offline?", token.UserId); + _offlineSessions.Add(token); return; } + + Console.WriteLine($"Got hs for {token.ToJson()}"); + var roomCountTask = hs.GetJoinedRooms(); var profile = await hs.GetProfileAsync(hs.WhoAmI.UserId); userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId; Console.WriteLine(profile.ToJson()); - _auth.Add(new() { + _sessions.Add(new() { UserInfo = new() { AvatarUrl = string.IsNullOrWhiteSpace(profile.AvatarUrl) ? "https://api.dicebear.com/6.x/identicon/svg?seed=" + hs.WhoAmI.UserId : hs.ResolveMediaUri(profile.AvatarUrl), RoomCount = (await roomCountTask).Count, @@ -110,7 +144,9 @@ Small collection of tools to do not-so-everyday things. Homeserver = hs }); }); + Console.WriteLine("Waiting for profile tasks"); await Task.WhenAll(profileTasks); + Console.WriteLine("Done waiting for profile tasks"); await base.OnInitializedAsync(); } @@ -127,19 +163,20 @@ Small collection of tools to do not-so-everyday things. } } catch (Exception e) { - if (e is MatrixException {ErrorCode: "M_UNKNOWN_TOKEN" }) { - //todo: handle this + if (e is MatrixException { ErrorCode: "M_UNKNOWN_TOKEN" }) { + //todo: handle this return; } + Console.WriteLine(e); } + await MRUStorage.RemoveToken(auth); if ((await MRUStorage.GetCurrentToken())?.AccessToken == auth.AccessToken) await MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens() ?? throw new InvalidOperationException()).FirstOrDefault()); await OnInitializedAsync(); } - private LoginResponse _currentSession; private async Task SwitchSession(UserAuth auth) { Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}"); diff --git a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor index d33756b..506a8a1 100644 --- a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor +++ b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor @@ -4,6 +4,7 @@ @using LibMatrix.EventTypes.Spec.State @using LibMatrix.RoomTypes @using ArcaneLibs.Extensions +@using MatrixRoomUtils.Abstractions <h3>UserRoomHistory</h3> <span>Enter mxid: </span> diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor index 2ac4bcb..6cabe82 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor @@ -1,25 +1,21 @@ @page "/Rooms" @using LibMatrix.Filters @using LibMatrix.Helpers -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix -@using LibMatrix.Homeservers -@using ArcaneLibs.Extensions @using LibMatrix.Extensions @using LibMatrix.Responses @using System.Collections.ObjectModel @using System.Diagnostics +@using ArcaneLibs.Extensions +@using MatrixRoomUtils.Abstractions @inject ILogger<Index> logger <h3>Room list</h3> <p>@Status</p> <p>@Status2</p> -@* @if (RenderContents) { *@ + +<LinkButton href="/Rooms/Create">Create new room</LinkButton> + <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList> -@* } *@ -@* else { *@ -@* <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" StillFetching="true"></RoomList> *@ -@* } *@ @code { private ObservableCollection<RoomInfo> Rooms { get; } = new(); @@ -47,9 +43,9 @@ }, State = new SyncFilter.RoomFilter.StateFilter { Types = new List<string> { + "m.room.create", "m.room.name", "m.room.avatar", - "m.room.create", "org.matrix.mjolnir.shortcode", "m.room.power_levels", } @@ -92,49 +88,72 @@ // } // }; + private SyncHelper syncHelper; + + // SyncHelper profileSyncHelper; + protected override async Task OnInitializedAsync() { Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; var rooms = await Homeserver.GetJoinedRooms(); - foreach (var room in rooms) { - Rooms.Add(new(){Room = room}); + // SemaphoreSlim _semaphore = new(160, 160); + + var roomTasks = rooms.Select(async room => { + RoomInfo ri; + // await _semaphore.WaitAsync(); + ri = new() { Room = room }; + await Task.WhenAll((filter.Room?.State?.Types ?? []).Select(x => ri.GetStateEvent(x))); + return ri; + }).ToAsyncEnumerable(); + + await foreach (var room in roomTasks) { + Rooms.Add(room); + StateHasChanged(); + // await Task.Delay(50); + // _semaphore.Release(); } - - GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); - var syncHelper = new SyncHelper(Homeserver, logger) { - Timeout = 10000, + if (rooms.Count >= 150) RenderContents = true; + + GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); + syncHelper = new SyncHelper(Homeserver, logger) { + Timeout = 30000, Filter = filter, MinimumDelay = TimeSpan.FromMilliseconds(5000) }; - // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); - // var profileSyncHelper = new SyncHelper(Homeserver, logger) { + // profileSyncHelper = new SyncHelper(Homeserver, logger) { // Timeout = 10000, // Filter = profileUpdateFilter, // MinimumDelay = TimeSpan.FromMilliseconds(5000) - // }; + // }; + // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); + RunSyncLoop(syncHelper); // RunSyncLoop(profileSyncHelper); RunQueueProcessor(); + await base.OnInitializedAsync(); } - + private async Task RunQueueProcessor() { var renderTimeSw = Stopwatch.StartNew(); + var isInitialSync = true; while (true) { try { - if (queue.Count == 0) { - while (queue.Count == 0) { - Console.WriteLine("Queue is empty, waiting..."); - await Task.Delay(2500); - } - Console.WriteLine("Queue no longer empty!"); + while (queue.Count == 0) { + Console.WriteLine("Queue is empty, waiting..."); + await Task.Delay(isInitialSync ? 100 : 2500); } - while (queue.TryDequeue(out var queueEntry)) { + + Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!"); + + int maxUpdates = 10; + isInitialSync = false; + while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { var (roomId, roomData) = queueEntry; Console.WriteLine($"Dequeued room {roomId}"); RoomInfo room; - + if (Rooms.Any(x => x.Room.RoomId == roomId)) { room = Rooms.First(x => x.Room.RoomId == roomId); Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events"); @@ -146,26 +165,23 @@ }; Rooms.Add(room); } - + if (room.StateEvents is null) { Console.WriteLine($"QueueWorker: {roomId} does not have state events on record?"); throw new InvalidDataException("Somehow this is null???"); } - if (roomData.State?.Events is {Count: >0 }) + + if (roomData.State?.Events is { Count: > 0 }) room.StateEvents.MergeStateEventLists(roomData.State.Events); else { Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!"); } - if (Random.Shared.Next(101) < 20 || true) { - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; - } - RenderContents |= queue.Count == 0; - if (queue.Count > 10) RenderContents = false; - await Task.Delay(RenderContents ? 25 : 6); } - // else { - // Console.WriteLine("Failed to dequeue item"); - // } + Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); + Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; + + RenderContents |= queue.Count == 0; + await Task.Delay(Rooms.Count); } catch (Exception e) { Console.WriteLine("QueueWorker exception: " + e); @@ -214,11 +230,15 @@ // We can't trust servers to give us what we ask for, and this ruins performance // Thanks, Conduit. joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.Types?.Contains(x.Type) == false); - if(filter.Room?.State?.NotSenders?.Any() ?? false) + if (filter.Room?.State?.NotSenders?.Any() ?? false) joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.NotSenders?.Contains(x.Sender) ?? false); - + queue.Enqueue(joinedRoom); } + if (sync.Rooms.Leave is {Count: > 0}) + foreach (var leftRoom in sync.Rooms.Leave) + if (Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) + Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key)); Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue... " + $"{sync?.Rooms?.Join?.Count ?? 0} new updates!"; diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor index 846d1cb..dbe0648 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor @@ -4,184 +4,199 @@ @using ArcaneLibs.Extensions @using LibMatrix.EventTypes.Spec.State @using LibMatrix.EventTypes.Spec.State.Policy +@using System.Diagnostics +@using System.Diagnostics.CodeAnalysis +@using LibMatrix.Extensions +@using LibMatrix.Responses <h3>Policy list editor - Editing @RoomId</h3> <hr/> <p> - This policy list contains @PolicyEvents.Count(x => x.Type == "m.policy.rule.server") server bans, - @PolicyEvents.Count(x => x.Type == "m.policy.rule.room") room bans and - @PolicyEvents.Count(x => x.Type == "m.policy.rule.user") user bans. + This policy list contains @GetPolicyCount(typeof(ServerPolicyRuleEventContent)) server bans, + @GetPolicyCount(typeof(RoomPolicyRuleEventContent)) room bans and + @GetPolicyCount(typeof(UserPolicyRuleEventContent)) user bans. + @foreach (var (key, value) in PolicyEventsByType) { + <p>@key.Name: @value.Count</p> + } </p> -<InputCheckbox @bind-Value="_enableAvatars" @oninput="GetAllAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> - +<InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> -@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.server")) { +<h3>Server policies</h3> +<hr/> +@if (!GetPolicyEventsByType(typeof(ServerPolicyRuleEventContent)).Any()) { <p>No server policies</p> } else { - <h3>Server policies</h3> - <hr/> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - <th scope="col" style="max-width: 50vw;">Server</th> - <th scope="col">Reason</th> - <th scope="col">Expires</th> - <th scope="col">Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; <tr> - <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button> - @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@ - </td> + <th style="max-width: 50vw;">Server</th> + <th>Reason</th> + <th>Expires</th> + <th>Actions</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { + var policyData = policyEvent.TypedContent as PolicyRuleEventContent; + <tr> + <td> + <span>Entity: @policyData.Entity</span> + <span><br/>State: @policyEvent.StateKey</span> + </td> + <td>@policyData.Reason</td> + <td> + @policyData.ExpiryDateTime + </td> + <td> + <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button> + @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@ + </td> + </tr> + } </tbody> </table> <details> - <summary>Redacted events</summary> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <summary>Redacted or invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - <th scope="col" style="max-width: 50vw;">State key</th> - <th scope="col">Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent.ToJson(false, true)</td> + <th style="max-width: 50vw;">State key</th> + <th>Serialised Contents</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { + <tr> + <td>@policyEvent.StateKey</td> + <td>@policyEvent.RawContent.ToJson(false, true)</td> + </tr> + } </tbody> </table> </details> } -@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.room")) { +<h3>Room policies</h3> +<hr/> +@if (!GetPolicyEventsByType(typeof(RoomPolicyRuleEventContent)).Any()) { <p>No room policies</p> } else { - <h3>Room policies</h3> - <hr/> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - <th scope="col" style="max-width: 50vw;">Room</th> - <th scope="col">Reason</th> - <th scope="col">Expires</th> - <th scope="col">Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; <tr> - <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> - </td> + <th style="max-width: 50vw;">Room</th> + <th>Reason</th> + <th>Expires</th> + <th>Actions</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { + var policyData = policyEvent.TypedContent as PolicyRuleEventContent; + <tr> + <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td> + <td>@policyData.Reason</td> + <td> + @policyData.ExpiryDateTime + </td> + <td> + <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> + </td> + </tr> + } </tbody> </table> <details> - <summary>Redacted events</summary> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <summary>Redacted or invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - <th scope="col" style="max-width: 50vw;">State key</th> - <th scope="col">Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) { <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent!.ToJson(false, true)</td> + <th style="max-width: 50vw;">State key</th> + <th>Serialised Contents</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { + <tr> + <td>@policyEvent.StateKey</td> + <td>@policyEvent.RawContent!.ToJson(false, true)</td> + </tr> + } </tbody> </table> </details> } -@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.user")) { +<h3>User policies</h3> +<hr/> +@if (!GetPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Any()) { <p>No user policies</p> } else { - <h3>User policies</h3> - <hr/> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - @if (_enableAvatars) { - <th scope="col"></th> - } - <th scope="col" style="max-width: 0.2vw; word-wrap: anywhere;">User</th> - <th scope="col">Reason</th> - <th scope="col">Expires</th> - <th scope="col">Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; <tr> - @if (_enableAvatars) { - <td scope="col"> - <img style="width: 48px; height: 48px; aspect-ratio: unset; border-radius: 50%;" src="@(avatars.ContainsKey(policyData.Entity) ? avatars[policyData.Entity] : "")"/> - </td> + @if (EnableAvatars) { + <th></th> } - <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> - </td> + <th style="max-width: 0.2vw; word-wrap: anywhere;">User</th> + <th>Reason</th> + <th>Expires</th> + <th>Actions</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { + var policyData = policyEvent.TypedContent as PolicyRuleEventContent; + <tr> + @if (EnableAvatars) { + <td> + @if (Avatars.ContainsKey(policyData.Entity)) { + <img class="avatar48" src="@Avatars[policyData.Entity]"/> + } + </td> + } + <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td> + <td>@policyData.Reason</td> + <td> + @policyData.ExpiryDateTime + </td> + <td> + <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> + </td> + </tr> + } </tbody> </table> <details> - <summary>Redacted events</summary> - <table class="table table-striped table-hover" style="width: fit-Content;"> + <summary>Redacted or invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> <thead> - <tr> - <th scope="col">State key</th> - <th scope="col">Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) { <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent.ToJson(false, true)</td> + <th>State key</th> + <th>Serialised Contents</th> </tr> - } + </thead> + <tbody> + @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { + <tr> + <td>@policyEvent.StateKey</td> + <td>@policyEvent.RawContent.ToJson(false, true)</td> + </tr> + } </tbody> </table> </details> } -<LogView></LogView> - @code { + +#if DEBUG + private const bool Debug = true; +#else + private const bool Debug = false; +#endif + //get room list // - sync withroom list filter // Type = support.feline.msc3784 @@ -192,18 +207,28 @@ else { private bool _enableAvatars; - static readonly Dictionary<string, string?> avatars = new(); - static readonly Dictionary<string, RemoteHomeserver> servers = new(); + static readonly Dictionary<string, string?> Avatars = new(); + // static readonly Dictionary<string, RemoteHomeserver> Servers = new(); - public static List<StateEventResponse> PolicyEvents { get; set; } = new(); + // private static List<StateEventResponse> PolicyEvents { get; set; } = new(); + private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); + + public bool EnableAvatars { + get => _enableAvatars; + set { + _enableAvatars = value; + if (value) GetAllAvatars(); + } + } protected override async Task OnInitializedAsync() { + var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); var hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; RoomId = RoomId.Replace('~', '.'); await LoadStatesAsync(); - Console.WriteLine("Policy list editor initialized!"); + Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); } private async Task LoadStatesAsync() { @@ -214,39 +239,52 @@ else { var states = room.GetFullStateAsync(); await foreach (var state in states) { - if (!state.Type.StartsWith("m.policy.rule")) continue; - PolicyEvents.Add(state); + if (state is null) continue; + if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new()); + PolicyEventsByType[state.MappedType].Add(state); } - - // var stateEventsQuery = await room.GetStateAsync(""); - // var stateEvents = stateEventsQuery.Value.Deserialize<List<StateEventResponse>>(); - // PolicyEvents = stateEvents.Where(x => x.Type.StartsWith("m.policy.rule")) - // .Select(x => JsonSerializer.Deserialize<StateEventResponse>(JsonSerializer.Serialize(x))).ToList(); StateHasChanged(); } - private async Task GetAvatar(string userId) { - try { - if (avatars.ContainsKey(userId)) return; - var hs = userId.Split(':')[1]; - var server = servers.ContainsKey(hs) ? servers[hs] : new RemoteHomeserver(userId.Split(':')[1]); - if (!servers.ContainsKey(hs)) servers.Add(hs, server); - var profile = await server.GetProfileAsync(userId); - avatars.Add(userId, await hsResolver.ResolveMediaUri(server.BaseUrl, profile.AvatarUrl)); - servers.Add(userId, server); + private async Task GetAllAvatars() { + // if (!_enableAvatars) return; + Console.WriteLine("Getting avatars..."); + var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue<string>()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); + Console.WriteLine($"Got {users.Count} users!"); + var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); + Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); + var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); + await foreach (var server in homeserverTasks) { + if (server is null) continue; + var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); + await Task.WhenAll(profileTasks); + profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); + foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { + // if (profile is null) continue; + if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { + var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); + Avatars.TryAdd(profile.Key, url); + } + else Avatars.TryAdd(profile.Key, null); + } StateHasChanged(); } - catch { - // ignored - } } - private async Task GetAllAvatars() { - foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) { - await GetAvatar((policyEvent.TypedContent as PolicyRuleEventContent).Entity); + private async Task<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { + try { + return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid)); + } + catch { + return null; } - StateHasChanged(); } -} + private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; + private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); + private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); + private int GetPolicyCount(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type].Count : 0; + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor index 1b28516..a327793 100644 --- a/MatrixRoomUtils.Web/Pages/User/DMManager.razor +++ b/MatrixRoomUtils.Web/Pages/User/DMManager.razor @@ -1,7 +1,7 @@ @page "/User/DirectMessages" -@using LibMatrix.Homeservers @using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses +@using MatrixRoomUtils.Abstractions <h3>Direct Messages</h3> <hr/> diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor index 553f46d..60c68ac 100644 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor @@ -7,6 +7,7 @@ @using MatrixRoomUtils.LibDMSpace.StateEvents @using ArcaneLibs.Extensions @using System.Text.Json.Serialization +@using MatrixRoomUtils.Abstractions <b> <u>DM Space setup tool - stage 2: Fix DM room attribution</u> </b> @@ -185,9 +186,9 @@ else { } catch { } - var membersEnum = room.GetMembersAsync(); + var membersEnum = room.GetMembersEnumerableAsync(true); await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join") + if (member.TypedContent is RoomMemberEventContent memberEvent) roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor index 854b09c..42573e6 100644 --- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor +++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor @@ -7,6 +7,8 @@ @using MatrixRoomUtils.LibDMSpace.StateEvents @using ArcaneLibs.Extensions @using System.Text.Json.Serialization +@using MatrixRoomUtils.Abstractions + <b> <u>DM Space setup tool - stage 3: Preview space layout</u> </b> @@ -154,9 +156,9 @@ else { } catch { } - var membersEnum = room.GetMembersAsync(); + var membersEnum = room.GetMembersEnumerableAsync(true); await foreach (var member in membersEnum) - if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join") + if (member.TypedContent is RoomMemberEventContent memberEvent) roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { diff --git a/MatrixRoomUtils.Web/Pages/User/Profile.razor b/MatrixRoomUtils.Web/Pages/User/Profile.razor index ae3fb76..73d7c6e 100644 --- a/MatrixRoomUtils.Web/Pages/User/Profile.razor +++ b/MatrixRoomUtils.Web/Pages/User/Profile.razor @@ -9,40 +9,43 @@ @if (NewProfile is not null) { <h4>Profile</h4> <hr/> - - <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> - <div style="display: inline-block; vertical-align: middle;"> - <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/> - <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox> - <InputFile OnChange="@AvatarChanged"></InputFile><br/> - <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton> - <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton> + <div> + <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <div style="display: inline-block; vertical-align: middle;"> + <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/> + <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox> + <InputFile OnChange="@AvatarChanged"></InputFile><br/> + <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton> + <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton> + </div> </div> @if (!string.IsNullOrWhiteSpace(Status)) { <p>@Status</p> } - <details> - <summary style="font-size: 1.5rem;">Room profiles<hr></summary> - - @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x=>RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { - <details class="details-compact"> - <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> - <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> - <div style="display: inline-block; vertical-align: middle;"> - <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/> - <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox> - <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/> - <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton> - </div> - <br/> - @if (!string.IsNullOrWhiteSpace(Status)) { - <p>@Status</p> - } - </details> + <br/> + + @* <details> *@ + <h4>Room profiles<hr></h4> + + @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) { + <details class="details-compact"> + <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary> + <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/> + <div style="display: inline-block; vertical-align: middle;"> + <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/> + <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox> + <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/> + <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton> + </div> <br/> - } - </details> + @if (!string.IsNullOrWhiteSpace(Status)) { + <p>@Status</p> + } + </details> + <br/> + } + // </details> } @code { @@ -54,7 +57,10 @@ private string? Status { get => _status; - set { _status = value; StateHasChanged(); } + set { + _status = value; + StateHasChanged(); + } } private Dictionary<string, RoomMemberEventContent> RoomProfiles { get; set; } = new(); @@ -65,14 +71,15 @@ if (Homeserver is null) return; Status = "Loading global profile..."; if (Homeserver.WhoAmI?.UserId is null) return; - NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)).DeepClone(); - OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)).DeepClone(); + NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); + OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone(); Status = "Loading room profiles..."; var roomProfiles = Homeserver.GetRoomProfilesAsync(); await foreach (var (roomId, roomProfile) in roomProfiles) { // Status = $"Got profile for {roomId}..."; - RoomProfiles[roomId] = roomProfile.DeepClone(); + RoomProfiles[roomId] = roomProfile; //.DeepClone(); } + StateHasChanged(); Status = "Room profiles loaded, loading room names..."; @@ -80,6 +87,7 @@ var name = await x.GetNameOrFallbackAsync(); return new KeyValuePair<string, string?>(x.RoomId, name); }).ToAsyncEnumerable(); + await foreach (var (roomId, roomName) in roomNameTasks) { // Status = $"Got room name for {roomId}: {roomName}"; RoomNames[roomId] = roomName; @@ -106,13 +114,14 @@ StateHasChanged(); await OnInitializedAsync(); } + private async Task RoomAvatarChanged(InputFileChangeEventArgs arg, string roomId) { var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType); Console.WriteLine(res); RoomProfiles[roomId].AvatarUrl = res; StateHasChanged(); } - + private async Task UpdateRoomProfile(string roomId) { Status = "Busy processing room profile update, please do not leave this page..."; StateHasChanged(); @@ -122,5 +131,4 @@ StateHasChanged(); } -} - +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/RoomList.razor b/MatrixRoomUtils.Web/Shared/RoomList.razor index f78c7f7..31f0430 100644 --- a/MatrixRoomUtils.Web/Shared/RoomList.razor +++ b/MatrixRoomUtils.Web/Shared/RoomList.razor @@ -5,6 +5,7 @@ @using LibMatrix.EventTypes.Spec.State @using System.Collections.ObjectModel @using LibMatrix.Responses +@using MatrixRoomUtils.Abstractions @using _Imports = MatrixRoomUtils.Web._Imports @if (!StillFetching) { <p>Fetching room details... @RoomsWithTypes.Sum(x => x.Value.Count) out of @Rooms.Count done!</p> @@ -34,18 +35,23 @@ else { private Dictionary<string, List<RoomInfo>> RoomsWithTypes => Rooms is null ? new() : Rooms.GroupBy(x => GetRoomTypeName(x.CreationEventContent?.Type)).ToDictionary(x => x.Key, x => x.ToList()); + private bool hooked; protected override async Task OnParametersSetAsync() { var hs = await MRUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - Rooms.CollectionChanged += (_, args) => { - foreach (RoomInfo item in args.NewItems) { - item.PropertyChanged += (_, args2) => { - Console.WriteLine(args2); - if(args2.PropertyName == nameof(item.CreationEventContent)) - StateHasChanged(); - }; - } - }; + if (!hooked) { + Rooms.CollectionChanged += (_, args) => { + foreach (RoomInfo item in args.NewItems) { + item.PropertyChanged += (_, args2) => { + // Console.WriteLine(args2); + + if (args2.PropertyName == nameof(item.CreationEventContent)) + StateHasChanged(); + }; + } + }; + hooked = true; + } // GlobalProfile ??= await hs.GetProfileAsync(hs.WhoAmI.UserId); @@ -53,10 +59,10 @@ else { } private string GetRoomTypeName(string? roomType) => roomType switch { + null => "Room", "m.space" => "Space", "msc3588.stories.stories-room" => "Story room", "support.feline.policy.lists.msc.v1" => "MSC3784 Policy list (v1)", - null => "Room", _ => roomType }; @@ -88,5 +94,4 @@ else { // _semaphoreSlim.Release(); // } -} - +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor index 55ffc1e..4db25e1 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor @@ -3,6 +3,7 @@ @using LibMatrix.EventTypes.Spec.State @using LibMatrix.Homeservers @using LibMatrix.Responses +@using MatrixRoomUtils.Abstractions <details> <summary>@RoomType (@Rooms.Count)</summary> @foreach (var room in Rooms) { @@ -42,17 +43,19 @@ private List<RoomInfo> Rooms => Category.Value; private int RoomVersionDangerLevel(RoomInfo room) { - var roomVersion = room.StateEvents.FirstOrDefault(x => x.Type == "m.room.create"); - if (roomVersion is null) return 0; - return roomVersion.TypedContent is not RoomCreateEventContent roomVersionContent ? 0 + var creationEvent = room.StateEvents.FirstOrDefault(x => x?.Type == "m.room.create"); + if (creationEvent is null) return 0; + return creationEvent.TypedContent is not RoomCreateEventContent roomVersionContent ? 0 : RoomConstants.DangerousRoomVersions.Contains(roomVersionContent.RoomVersion) ? 2 : roomVersionContent.RoomVersion != RoomConstants.RecommendedRoomVersion ? 1 : 0; } public static string GetRoomTypeName(string roomType) { return roomType switch { - "Room" => "Rooms", - "org.matrix.mjolnir.policy" => "Policies", + null => "Room", + "m.space" => "Space", + "org.matrix.mjolnir.policy" => "Policy room", + _ => roomType }; } diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor index e08f98d..a6c006b 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor @@ -1,4 +1,5 @@ @using System.Collections.ObjectModel +@using MatrixRoomUtils.Abstractions <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{Space.Room.RoomId}/Space")">Manage space</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton> <br/> diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor index 3aa28e6..07f0756 100644 --- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor +++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor @@ -5,6 +5,7 @@ @using LibMatrix.Homeservers @using LibMatrix.Responses @using LibMatrix.RoomTypes +@using MatrixRoomUtils.Abstractions @using MatrixRoomUtils.Web.Classes.Constants @if (RoomInfo is not null) { <div class="roomListItem @(HasDangerousRoomVersion ? "dangerousRoomVersion" : HasOldRoomVersion ? "oldRoomVersion" : "")" id="@RoomInfo.Room.RoomId"> @@ -70,12 +71,16 @@ else { private bool _loadData = false; private static AuthenticatedHomeserverGeneric? hs { get; set; } + private bool _hooked; protected override async Task OnParametersSetAsync() { if (RoomInfo != null) { - RoomInfo.PropertyChanged += (_, a) => { - Console.WriteLine(a.PropertyName); - StateHasChanged(); - }; + if (!_hooked) { + _hooked = true; + RoomInfo.PropertyChanged += (_, a) => { + Console.WriteLine(a.PropertyName); + StateHasChanged(); + }; + } if (LoadData) { try { diff --git a/MatrixRoomUtils.Web/wwwroot/css/app.css b/MatrixRoomUtils.Web/wwwroot/css/app.css index 3baddbb..3fac9ca 100644 --- a/MatrixRoomUtils.Web/wwwroot/css/app.css +++ b/MatrixRoomUtils.Web/wwwroot/css/app.css @@ -1,8 +1,16 @@ @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); @import url('jetbrains-mono/jetbrains-mono.css'); +.avatar48 { + width: 48px; + height: 48px; + aspect-ratio: unset; + border-radius: 50%; +} + .details-compact > summary { - line-height: 0px; + line-height: 0; + white-space: nowrap; } .details-compact[open] > summary { diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln index 58379b3..a26bbaa 100644 --- a/MatrixRoomUtils.sln +++ b/MatrixRoomUtils.sln @@ -48,6 +48,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.LibDMSpace" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{1CAA2B6D-0365-4C8B-96EE-26026514FEE2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixRoomUtils.Abstractions", "MatrixRoomUtils.Abstractions\MatrixRoomUtils.Abstractions.csproj", "{FE20ED20-0D55-4D74-822B-E2AC7A54C487}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -126,6 +128,10 @@ Global {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CAA2B6D-0365-4C8B-96EE-26026514FEE2}.Release|Any CPU.Build.0 = Release|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE20ED20-0D55-4D74-822B-E2AC7A54C487}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F4E241C3-0300-4B87-8707-BCBDEF1F0185} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} diff --git a/MatrixRoomUtils.sln.DotSettings.user b/MatrixRoomUtils.sln.DotSettings.user index 6daed5b..7142e45 100644 --- a/MatrixRoomUtils.sln.DotSettings.user +++ b/MatrixRoomUtils.sln.DotSettings.user @@ -1,2 +1,9 @@ <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> - <s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">DoNotShowAndRun</s:String></wpf:ResourceDictionary> \ No newline at end of file + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/SourceGeneratedFilesToSkip/=MatrixRoomUtils_002EDesktop_005CAvalonia_002EGenerators_002FAvalonia_002EGenerators_002ENameGenerator_002EAvaloniaNameSourceGenerator_002FMatrixRoomUtils_002EDesktop_002EComponents_002ENavigationStack_002Eg_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String> + <s:String x:Key="/Default/CodeInspection/ExcludedFiles/SourceGeneratedFilesToSkip/=MatrixRoomUtils_002EDesktop_005CAvalonia_002EGenerators_002FAvalonia_002EGenerators_002ENameGenerator_002EAvaloniaNameSourceGenerator_002FMatrixRoomUtils_002EDesktop_002EComponents_002ERoomListEntry_002Eg_002Ecs/@EntryIndexedValue">ExplicitlyExcluded</s:String> + <s:String x:Key="/Default/CodeInspection/Highlighting/SweaWarningsMode/@EntryValue">DoNotShowAndRun</s:String> + <s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=eac3956f_002D7f23_002D4a95_002Dbcf9_002Dc3bb0c1010f5/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Solution /> +</SessionState></s:String> + <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=27c08a4f_002D5af0_002D4c2c_002Dafcb_002D050e3388c116_0023MatrixRoomUtils_002EDesktop/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=f997f26f_002D2ec1_002D4d18_002Db3dd_002Dc46fb2ad65c0_0023MatrixRoomUtils_002EWeb_002EServer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file diff --git a/MxApiExtensions b/MxApiExtensions -Subproject 0b662d36de30c4bdd3d9be97d08ace8d4d7be58 +Subproject 3c0c7b2e56a24bda06b8c567e5608546898c99d diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..d9402a4 --- /dev/null +++ b/nuget.config @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> + +<configuration> + <packageSources> + <!-- optional --> + <clear /> + <!-- optional --> + <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" /> + <!-- nightly feed --> + <add key="avalonia-nightly" value="https://nuget-feed-nightly.avaloniaui.net/v3/index.json" protocolVersion="3" /> + </packageSources> +</configuration> |