diff --git a/MatrixRoomUtils.Desktop/App.axaml.cs b/MatrixRoomUtils.Desktop/App.axaml.cs
index 3dfcdee..3fe8862 100644
--- a/MatrixRoomUtils.Desktop/App.axaml.cs
+++ b/MatrixRoomUtils.Desktop/App.axaml.cs
@@ -4,6 +4,7 @@ using Avalonia.Markup.Xaml;
using MatrixRoomUtils.Core.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Sentry;
namespace MatrixRoomUtils.Desktop;
@@ -16,20 +17,24 @@ public partial class App : Application {
public override void OnFrameworkInitializationCompleted() {
host = Host.CreateDefaultBuilder().ConfigureServices((ctx, services) => {
- services.AddScoped<MRUDesktopConfiguration>();
- services.AddScoped<TieredStorageService>(x =>
+ services.AddSingleton<MRUDesktopConfiguration>();
+ services.AddSingleton<SentryService>();
+ services.AddSingleton<TieredStorageService>(x =>
new(
cacheStorageProvider: new FileStorageProvider(x.GetService<MRUDesktopConfiguration>().CacheStoragePath),
- dataStorageProvider: new FileStorageProvider(x.GetService<MRUDesktopConfiguration>().CacheStoragePath)
+ dataStorageProvider: new FileStorageProvider(x.GetService<MRUDesktopConfiguration>().DataStoragePath)
)
);
+ services.AddSingleton(new RoryLibMatrixConfiguration() {
+ AppName = "MatrixRoomUtils.Desktop"
+ });
services.AddRoryLibMatrixServices();
// foreach (var commandClass in new ClassCollector<ICommand>().ResolveFromAllAccessibleAssemblies()) {
// Console.WriteLine($"Adding command {commandClass.Name}");
// services.AddScoped(typeof(ICommand), commandClass);
// }
- services.AddScoped<MRUStorageWrapper>();
- services.AddScoped<MainWindow>();
+ services.AddSingleton<MRUStorageWrapper>();
+ services.AddSingleton<MainWindow>();
services.AddSingleton(this);
}).UseConsoleLifetime().Build();
diff --git a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml
new file mode 100644
index 0000000..e0812ec
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml
@@ -0,0 +1,12 @@
+<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"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="MatrixRoomUtils.Desktop.NavigationStack">
+ <DockPanel x:Name="dock">
+ <StackPanel x:Name="navPanel"></StackPanel>
+ <UserControl x:Name="content"></UserControl>
+ </DockPanel>
+
+</UserControl>
diff --git a/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs
new file mode 100644
index 0000000..f4e0fed
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/Components/NavigationStack.axaml.cs
@@ -0,0 +1,57 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class NavigationStack : UserControl {
+ public NavigationStack() {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent() {
+ AvaloniaXamlLoader.Load(this);
+ buildView();
+ }
+
+ private void buildView() {
+ navPanel.Children.Clear();
+ foreach (var item in _stack) {
+ Button btn = new() {
+ Content = item.Name
+ };
+ btn.Click += (sender, args) => {
+ PopTo(_stack.IndexOf(item));
+ buildView();
+ };
+ navPanel.Children.Add(btn);
+ }
+ content = Current?.View ?? new UserControl();
+ }
+
+
+ public class NavigationStackItem {
+ public string Name { get; set; }
+ public string Description { get; set; } = "";
+ public UserControl View { get; set; }
+ }
+
+ private List<NavigationStackItem> _stack = new();
+
+ public NavigationStackItem? Current => _stack.LastOrDefault();
+
+ public void Push(string name, UserControl view) {
+ _stack.Add(new NavigationStackItem() {
+ Name = name,
+ View = view
+ });
+ }
+
+ public void Pop() {
+ _stack.RemoveAt(_stack.Count - 1);
+ }
+
+ public void PopTo(int index) {
+ _stack.RemoveRange(index, _stack.Count - index);
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/RoomListEntry.axaml b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml
index c80ef2f..c80ef2f 100644
--- a/MatrixRoomUtils.Desktop/RoomListEntry.axaml
+++ b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml
diff --git a/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs
new file mode 100644
index 0000000..a447701
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/Components/RoomListEntry.axaml.cs
@@ -0,0 +1,69 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media.Imaging;
+using MatrixRoomUtils.Core;
+using MatrixRoomUtils.Core.Helpers;
+using MatrixRoomUtils.Core.Services;
+using MatrixRoomUtils.Core.StateEventTypes.Spec;
+using MatrixRoomUtils.Web.Classes;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MatrixRoomUtils.Desktop;
+
+public partial class RoomListEntry : UserControl {
+ private readonly IServiceScopeFactory _serviceScopeFactory;
+ private readonly RoomInfo _roomInfo;
+
+ public RoomListEntry(IServiceScopeFactory serviceScopeFactory, RoomInfo roomInfo) {
+ _serviceScopeFactory = serviceScopeFactory;
+ _roomInfo = roomInfo;
+ InitializeComponent();
+ }
+
+ protected override void OnLoaded(RoutedEventArgs e) {
+ base.OnLoaded(e);
+ RoomName.Content = _roomInfo.Room.RoomId;
+ Task.WhenAll(GetRoomName(), GetRoomIcon());
+ }
+
+ private async Task GetRoomName() {
+ try {
+ var nameEvent = await _roomInfo.GetStateEvent("m.room.name");
+ if (nameEvent is not null && nameEvent.TypedContent is RoomNameEventData nameData)
+ RoomName.Content = nameData.Name;
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode != "M_NOT_FOUND")
+ throw;
+ }
+ }
+
+ private async Task GetRoomIcon() {
+ try {
+ var avatarEvent = await _roomInfo.GetStateEvent("m.room.avatar");
+ if (avatarEvent is not null && avatarEvent.TypedContent is RoomAvatarEventData avatarData) {
+ var mxcUrl = avatarData.Url;
+ await using var svc = _serviceScopeFactory.CreateAsyncScope();
+ var hs = await svc.ServiceProvider.GetService<MRUStorageWrapper>().GetCurrentSessionOrPrompt();
+ var storage = svc.ServiceProvider.GetService<TieredStorageService>().CacheStorageProvider;
+ var resolvedUrl = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, mxcUrl);
+ var storageKey = $"media/{mxcUrl.Replace("mxc://", "").Replace("/", ".")}";
+ try {
+ if (!await storage.ObjectExistsAsync(storageKey))
+ await storage.SaveStreamAsync(storageKey, await hs._httpClient.GetStreamAsync(resolvedUrl));
+
+ RoomIcon.Source = new Bitmap(await storage.LoadStreamAsync(storageKey) ?? throw new NullReferenceException());
+ }
+ catch (IOException) { }
+ catch (MatrixException e) {
+ if (e.ErrorCode != "M_UNKNOWN")
+ throw;
+ }
+ }
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode != "M_NOT_FOUND")
+ throw;
+ }
+ }
+}
diff --git a/MatrixRoomUtils.Desktop/FileStorageProvider.cs b/MatrixRoomUtils.Desktop/FileStorageProvider.cs
index 36025eb..6c44fd2 100644
--- a/MatrixRoomUtils.Desktop/FileStorageProvider.cs
+++ b/MatrixRoomUtils.Desktop/FileStorageProvider.cs
@@ -3,7 +3,7 @@ using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces.Services;
using Microsoft.Extensions.Logging;
-namespace MatrixRoomUtils.Desktop;
+namespace MatrixRoomUtils.Desktop;
public class FileStorageProvider : IStorageProvider {
private readonly ILogger<FileStorageProvider> _logger;
@@ -32,4 +32,11 @@ public class FileStorageProvider : IStorageProvider {
public async Task<List<string>> GetAllKeysAsync() => Directory.GetFiles(TargetPath).Select(Path.GetFileName).ToList();
public async Task DeleteObjectAsync(string key) => File.Delete(Path.Join(TargetPath, key));
-}
\ No newline at end of file
+ public async Task SaveStreamAsync(string key, Stream stream) {
+ Directory.CreateDirectory(Path.GetDirectoryName(Path.Join(TargetPath, key)) ?? throw new InvalidOperationException());
+ await using var fileStream = File.Create(Path.Join(TargetPath, key));
+ await stream.CopyToAsync(fileStream);
+ }
+
+ public async Task<Stream?> LoadStreamAsync(string key) => File.Exists(Path.Join(TargetPath, key)) ? File.OpenRead(Path.Join(TargetPath, key)) : null;
+}
diff --git a/MatrixRoomUtils.Desktop/LoginWindow.axaml b/MatrixRoomUtils.Desktop/LoginWindow.axaml
index d61bfd3..a4600d5 100644
--- a/MatrixRoomUtils.Desktop/LoginWindow.axaml
+++ b/MatrixRoomUtils.Desktop/LoginWindow.axaml
@@ -6,10 +6,12 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MatrixRoomUtils.Desktop.LoginWindow"
Title="LoginWindow"
- x:DataType="desktop:LoginWindow">
+ x:DataType="desktop:LoginWindow"
+ DataContext="{Binding $self}"
+ >
<StackPanel>
<TextBox Text="{Binding Username, Mode=TwoWay}" />
- <MaskedTextBox Text="{Binding Password, Mode=TwoWay}" />
+ <MaskedTextBox PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" />
<Button Click="Login">Login</Button>
</StackPanel>
</Window>
diff --git a/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs b/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs
index d321591..39c42cf 100644
--- a/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs
+++ b/MatrixRoomUtils.Desktop/MRUDesktopConfiguration.cs
@@ -19,6 +19,7 @@ public class MRUDesktopConfiguration {
public string DataStoragePath { get; set; } = "";
public string CacheStoragePath { get; set; } = "";
+ public string? SentryDsn { get; set; }
private static string ExpandPath(string path, bool retry = true) {
_logger.LogInformation($"Expanding path `{path}`");
diff --git a/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs
index 27403dc..a5494ad 100644
--- a/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs
+++ b/MatrixRoomUtils.Desktop/MRUStorageWrapper.cs
@@ -54,6 +54,8 @@ public class MRUStorageWrapper {
tokens.Add(loginResponse);
await _storageService.DataStorageProvider.SaveObjectAsync("mru.tokens", tokens);
+ if(await GetCurrentToken() is null)
+ await SetCurrentToken(loginResponse);
}
private async Task<AuthenticatedHomeServer?> GetCurrentSession() {
@@ -85,7 +87,7 @@ public class MRUStorageWrapper {
if (session is null) {
// _navigationManager.NavigateTo("/Login");
var wnd = new LoginWindow(this);
- wnd.Show();
+ wnd.ShowDialog(MainWindow.Instance);
while (wnd.IsVisible) await Task.Delay(100);
session = await GetCurrentSession();
}
diff --git a/MatrixRoomUtils.Desktop/MainWindow.axaml b/MatrixRoomUtils.Desktop/MainWindow.axaml
index bc01bee..464fc77 100644
--- a/MatrixRoomUtils.Desktop/MainWindow.axaml
+++ b/MatrixRoomUtils.Desktop/MainWindow.axaml
@@ -2,6 +2,7 @@
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:desktop="clr-namespace:MatrixRoomUtils.Desktop"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="MatrixRoomUtils.Desktop.MainWindow"
Title="Rory&::MatrixRoomUtils">
@@ -10,8 +11,5 @@
<!-- <InvokeCommandAction Command="{Binding LoadedCommand}"></InvokeCommandAction> -->
<!-- </EventTriggerBehavior> -->
<!-- </Interaction.Behaviors> -->
- <StackPanel>
- <Label FontSize="24">Rooms</Label>
- <StackPanel x:Name="roomList" Orientation="Vertical"/>
- </StackPanel>
+ <desktop:NavigationStack x:Name="windowContent"/>
</Window>
diff --git a/MatrixRoomUtils.Desktop/MainWindow.axaml.cs b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs
index 41e0888..f89bbfa 100644
--- a/MatrixRoomUtils.Desktop/MainWindow.axaml.cs
+++ b/MatrixRoomUtils.Desktop/MainWindow.axaml.cs
@@ -1,5 +1,7 @@
using Avalonia.Controls;
+using Avalonia.Data;
using Avalonia.Interactivity;
+using MatrixRoomUtils.Web.Classes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -7,13 +9,18 @@ namespace MatrixRoomUtils.Desktop;
public partial class MainWindow : Window {
private readonly ILogger<MainWindow> _logger;
+ private readonly IServiceScopeFactory _scopeFactory;
private readonly MRUStorageWrapper _storageWrapper;
private readonly MRUDesktopConfiguration _configuration;
+ public static MainWindow Instance { get; private set; } = null!;
- public MainWindow(ILogger<MainWindow> logger, IServiceScopeFactory scopeFactory) {
+ public MainWindow(ILogger<MainWindow> logger, IServiceScopeFactory scopeFactory, SentryService _) {
+ Instance = this;
_logger = logger;
+ _scopeFactory = scopeFactory;
_configuration = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUDesktopConfiguration>();
_storageWrapper = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUStorageWrapper>();
+
_logger.LogInformation("Initialising MainWindow");
InitializeComponent();
@@ -22,15 +29,19 @@ public partial class MainWindow : Window {
_logger.LogInformation("Data location: " + _configuration.DataStoragePath);
- for (int i = 0; i < 100; i++) {
- roomList.Children.Add(new RoomListEntry());
- }
+ // for (int i = 0; i < 100; i++) {
+ // roomList.Children.Add(new RoomListEntry());
+ // }
}
// ReSharper disable once AsyncVoidMethod
protected override async void OnLoaded(RoutedEventArgs e) {
_logger.LogInformation("async onloaded override");
var hs = await _storageWrapper.GetCurrentSessionOrPrompt();
+ var rooms = await hs.GetJoinedRooms();
+ foreach (var room in rooms) {
+ roomList.Children.Add(new RoomListEntry(_scopeFactory, new RoomInfo(room)));
+ }
base.OnLoaded(e);
}
diff --git a/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj
index 5b6d3f6..6f3e256 100644
--- a/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj
+++ b/MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj
@@ -26,6 +26,7 @@
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.0" />
<!--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.0" />
+ <PackageReference Include="Sentry" Version="3.34.0" />
</ItemGroup>
@@ -46,4 +47,17 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
+ <ItemGroup>
+ <Folder Include="Classes\" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Update="Components\NavigationStack.axaml.cs">
+ <DependentUpon>NavigationStack.axaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Update="Components\RoomListEntry.axaml.cs">
+ <DependentUpon>RoomListEntry.axaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
</Project>
diff --git a/MatrixRoomUtils.Desktop/Program.cs b/MatrixRoomUtils.Desktop/Program.cs
index 74ab579..f692e45 100644
--- a/MatrixRoomUtils.Desktop/Program.cs
+++ b/MatrixRoomUtils.Desktop/Program.cs
@@ -9,7 +9,7 @@ internal class Program {
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
- [STAThread]
+ // [STAThread]
public static async Task Main(string[] args) {
try {
BuildAvaloniaApp()
diff --git a/MatrixRoomUtils.Desktop/RoomInfo.cs b/MatrixRoomUtils.Desktop/RoomInfo.cs
new file mode 100644
index 0000000..a31d67a
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/RoomInfo.cs
@@ -0,0 +1,37 @@
+using MatrixRoomUtils.Core;
+using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Responses;
+using MatrixRoomUtils.Core.RoomTypes;
+
+namespace MatrixRoomUtils.Web.Classes;
+
+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,
+ };
+ try {
+ @event.TypedContent = await Room.GetStateAsync<object>(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/RoomListEntry.axaml.cs b/MatrixRoomUtils.Desktop/RoomListEntry.axaml.cs
deleted file mode 100644
index 490316d..0000000
--- a/MatrixRoomUtils.Desktop/RoomListEntry.axaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using Avalonia.Media.Imaging;
-
-namespace MatrixRoomUtils.Desktop;
-
-public partial class RoomListEntry : UserControl {
- public RoomListEntry() {
- InitializeComponent();
- }
-
- protected override void OnLoaded(RoutedEventArgs e) {
- base.OnLoaded(e);
- RoomName.Content = "asdf";
- RoomIcon.Source = new Bitmap("/home/root@Rory/giphy.gif");
- }
-}
diff --git a/MatrixRoomUtils.Desktop/SentryService.cs b/MatrixRoomUtils.Desktop/SentryService.cs
new file mode 100644
index 0000000..ed96697
--- /dev/null
+++ b/MatrixRoomUtils.Desktop/SentryService.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Sentry;
+
+namespace MatrixRoomUtils.Desktop;
+
+public class SentryService : IDisposable {
+ private IDisposable? _sentrySdkDisposable;
+ public SentryService(IServiceScopeFactory scopeFactory, ILogger<SentryService> logger) {
+ MRUDesktopConfiguration config = scopeFactory.CreateScope().ServiceProvider.GetRequiredService<MRUDesktopConfiguration>();
+ if (config.SentryDsn is null) {
+ logger.LogWarning("Sentry DSN is not set, skipping Sentry initialisation");
+ return;
+ }
+ _sentrySdkDisposable = SentrySdk.Init(o => {
+ o.Dsn = config.SentryDsn;
+ // When configuring for the first time, to see what the SDK is doing:
+ o.Debug = true;
+ // Set traces_sample_rate to 1.0 to capture 100% of transactions for performance monitoring.
+ // We recommend adjusting this value in production.
+ o.TracesSampleRate = 1.0;
+ // Enable Global Mode if running in a client app
+ o.IsGlobalModeEnabled = true;
+ });
+ }
+
+ /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+ public void Dispose() => _sentrySdkDisposable?.Dispose();
+}
diff --git a/MatrixRoomUtils.Desktop/appsettings.Development.json b/MatrixRoomUtils.Desktop/appsettings.Development.json
index 20b09a7..a00f349 100644
--- a/MatrixRoomUtils.Desktop/appsettings.Development.json
+++ b/MatrixRoomUtils.Desktop/appsettings.Development.json
@@ -8,6 +8,7 @@
},
"MRUDesktop": {
"DataStoragePath": "mru-desktop/data",
- "CacheStoragePath": "mru-desktop/cache"
+ "CacheStoragePath": "mru-desktop/cache",
+ "SentryDsn": "https://a41e99dd2fdd45f699c432b21ebce632@sentry.thearcanebrony.net/15"
}
}
|