diff --git a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml
index ae7a314..c468b91 100644
--- a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml
+++ b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml
@@ -4,8 +4,11 @@
<option name="projectPerEditor">
<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/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" />
+ <entry key="MatrixRoomUtils.Desktop/NavigationStack.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
<entry key="MatrixRoomUtils.Desktop/RoomListEntry.axaml" value="MatrixRoomUtils.Desktop/MatrixRoomUtils.Desktop.csproj" />
</map>
</option>
diff --git a/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml b/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml
index 9c54b37..4520708 100644
--- a/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml
+++ b/.idea/.idea.MatrixRoomUtils/.idea/indexLayout.xml
@@ -3,6 +3,7 @@
<component name="UserContentModel">
<attachedFolders>
<Path>MatrixRoomUtils.Bot/bot_data</Path>
+ <Path>MatrixRoomUtils.Desktop/bin/Debug/net7.0/mru-desktop</Path>
</attachedFolders>
<explicitIncludes />
<explicitExcludes />
diff --git a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
index c51baec..78bbdfa 100644
--- a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
@@ -10,4 +10,24 @@ public static class DictionaryExtensions {
dict[newKey] = value; // or dict.Add(newKey, value) depending on ur comfort
return true;
}
-}
\ No newline at end of file
+
+ public static Y GetOrCreate<X, Y>(this IDictionary<X, Y> dict, X key) where Y : new() {
+ if (dict.TryGetValue(key, out var value)) {
+ return value;
+ }
+
+ value = new Y();
+ dict.Add(key, value);
+ return value;
+ }
+
+ public static Y GetOrCreate<X, Y>(this IDictionary<X, Y> dict, X key, Func<X, Y> valueFactory) {
+ if (dict.TryGetValue(key, out var value)) {
+ return value;
+ }
+
+ value = valueFactory(key);
+ dict.Add(key, value);
+ return value;
+ }
+}
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
index 9ac9c6b..009338a 100644
--- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -38,7 +38,7 @@ public class MatrixHttpClient : HttpClient {
if (content.StartsWith('{')) {
var ex = JsonSerializer.Deserialize<MatrixException>(content);
ex.RawContent = content;
- Console.WriteLine($"Failed to send request: {ex}");
+ // Console.WriteLine($"Failed to send request: {ex}");
if (ex?.RetryAfterMs is not null) {
await Task.Delay(ex.RetryAfterMs.Value, cancellationToken);
typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)
@@ -64,4 +64,13 @@ public class MatrixHttpClient : HttpClient {
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
return await JsonSerializer.DeserializeAsync<T>(responseStream, cancellationToken: cancellationToken);
}
+
+ // GetStreamAsync
+ public async Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken = default) {
+ var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ var response = await SendAsync(request, cancellationToken);
+ response.EnsureSuccessStatusCode();
+ return await response.Content.ReadAsStreamAsync(cancellationToken);
+ }
}
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index d41a6cd..3521a51 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -11,7 +11,7 @@ public class IHomeServer {
public string HomeServerDomain { get; set; }
public string FullHomeServerDomain { get; set; }
- protected internal MatrixHttpClient _httpClient { get; set; } = new();
+ public MatrixHttpClient _httpClient { get; set; } = new();
public async Task<ProfileResponseEventData> GetProfile(string mxid) {
if(mxid is null) throw new ArgumentNullException(nameof(mxid));
@@ -20,12 +20,12 @@ public class IHomeServer {
if (_profileCache[mxid] is ProfileResponseEventData p) return p;
}
_profileCache[mxid] = new SemaphoreSlim(1);
-
+
var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}");
var data = await resp.Content.ReadFromJsonAsync<ProfileResponseEventData>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
_profileCache[mxid] = data;
-
+
return data;
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs b/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
index eefb79c..1de9885 100644
--- a/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
+++ b/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
@@ -1,4 +1,4 @@
-namespace MatrixRoomUtils.Core.Interfaces.Services;
+namespace MatrixRoomUtils.Core.Interfaces.Services;
public interface IStorageProvider {
// save all children of a type with reflection
@@ -6,7 +6,7 @@ public interface IStorageProvider {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveAllChildren<T>(key, value)!");
throw new NotImplementedException();
}
-
+
// load all children of a type with reflection
public Task<T?> LoadAllChildrenAsync<T>(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement LoadAllChildren<T>(key)!");
@@ -18,29 +18,41 @@ public interface IStorageProvider {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveObject<T>(key, value)!");
throw new NotImplementedException();
}
-
+
// load
public Task<T?> LoadObjectAsync<T>(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement LoadObject<T>(key)!");
throw new NotImplementedException();
}
-
+
// check if exists
public Task<bool> ObjectExistsAsync(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement ObjectExists(key)!");
throw new NotImplementedException();
}
-
+
// get all keys
public Task<List<string>> GetAllKeysAsync() {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement GetAllKeys()!");
throw new NotImplementedException();
}
-
+
// delete
public Task DeleteObjectAsync(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement DeleteObject(key)!");
throw new NotImplementedException();
}
-}
\ No newline at end of file
+
+ // save stream
+ public Task SaveStreamAsync(string key, Stream stream) {
+ Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveStream(key, stream)!");
+ throw new NotImplementedException();
+ }
+
+ // load stream
+ public Task<Stream?> LoadStreamAsync(string key) {
+ Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement LoadStream(key)!");
+ throw new NotImplementedException();
+ }
+}
diff --git a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
index b2ea987..4bc785a 100644
--- a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
+++ b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
@@ -23,8 +23,18 @@ public class HomeserverProviderService {
$"New HomeserverProviderService created with TieredStorageService<{string.Join(", ", tieredStorageService.GetType().GetProperties().Select(x => x.Name))}>!");
}
+ private static Dictionary<string, SemaphoreSlim> _authenticatedHomeserverSemaphore = new();
+ private static Dictionary<string, AuthenticatedHomeServer> _authenticatedHomeServerCache = new();
+
public async Task<AuthenticatedHomeServer> GetAuthenticatedWithToken(string homeserver, string accessToken,
string? overrideFullDomain = null) {
+ SemaphoreSlim sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver+accessToken, _ => new SemaphoreSlim(1, 1));
+ await sem.WaitAsync();
+ if (_authenticatedHomeServerCache.ContainsKey(homeserver+accessToken)) {
+ sem.Release();
+ return _authenticatedHomeServerCache[homeserver+accessToken];
+ }
+
var hs = new AuthenticatedHomeServer(_tieredStorageService, homeserver, accessToken);
hs.FullHomeServerDomain = overrideFullDomain ??
await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
@@ -34,6 +44,10 @@ public class HomeserverProviderService {
hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
+
+ _authenticatedHomeServerCache[homeserver+accessToken] = hs;
+ sem.Release();
+
return hs;
}
@@ -80,4 +94,4 @@ public class HomeserverProviderService {
public string User { get; set; } = "";
}
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs b/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
index e4d8063..5856000 100644
--- a/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
+++ b/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
@@ -3,14 +3,15 @@ using System.Text.Json;
using MatrixRoomUtils.Core.Extensions;
using Microsoft.Extensions.Logging;
-namespace MatrixRoomUtils.Core.Services;
+namespace MatrixRoomUtils.Core.Services;
public class HomeserverResolverService {
private readonly MatrixHttpClient _httpClient = new();
private readonly ILogger<HomeserverResolverService> _logger;
- private static Dictionary<string, object> _wellKnownCache = new();
-
+ private static readonly Dictionary<string, string> _wellKnownCache = new();
+ private static readonly Dictionary<string, SemaphoreSlim> _wellKnownSemaphores = new();
+
public HomeserverResolverService(ILogger<HomeserverResolverService> logger) {
_logger = logger;
}
@@ -23,31 +24,30 @@ public class HomeserverResolverService {
}
private async Task<string> _resolveHomeserverFromWellKnown(string homeserver) {
- if(homeserver is null) throw new ArgumentNullException(nameof(homeserver));
+ if (homeserver is null) throw new ArgumentNullException(nameof(homeserver));
+ SemaphoreSlim sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1));
+ await sem.WaitAsync();
if (_wellKnownCache.ContainsKey(homeserver)) {
- if (_wellKnownCache[homeserver] is SemaphoreSlim s) await s.WaitAsync();
- if (_wellKnownCache[homeserver] is string p) return p;
+ sem.Release();
+ return _wellKnownCache[homeserver];
}
- _wellKnownCache[homeserver] = new SemaphoreSlim(1);
+
string? result = null;
_logger.LogInformation($"Attempting to resolve homeserver: {homeserver}");
- if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
result ??= await _tryResolveFromClientWellknown(homeserver);
result ??= await _tryResolveFromServerWellknown(homeserver);
result ??= await _tryCheckIfDomainHasHomeserver(homeserver);
- // if(!homeserver.Contains("http")) homeserver = "https://" + homeserver;
- // result ??= await _tryCheckIfSubDomainHasHomeserver(homeserver, "matrix");
- // result ??= await _tryCheckIfSubDomainHasHomeserver(homeserver, "chat");
- if(result is not null) {
+ if (result is not null) {
_logger.LogInformation($"Resolved homeserver: {homeserver} -> {result}");
- _wellKnownCache.TryAdd(homeserver, result);
+ _wellKnownCache[homeserver] = result;
+ sem.Release();
return result;
}
-
+
throw new InvalidDataException($"Failed to resolve homeserver for {homeserver}! Is it online and configured correctly?");
}
-
+
private async Task<string?> _tryResolveFromClientWellknown(string homeserver) {
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) {
@@ -55,9 +55,11 @@ public class HomeserverResolverService {
var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
return hs;
}
+
_logger.LogInformation("No client well-known...");
return null;
}
+
private async Task<string?> _tryResolveFromServerWellknown(string homeserver) {
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) {
@@ -65,6 +67,7 @@ public class HomeserverResolverService {
var hs = resp.GetProperty("m.server").GetString();
return hs;
}
+
_logger.LogInformation("No server well-known...");
return null;
}
@@ -81,4 +84,4 @@ public class HomeserverResolverService {
homeserver = homeserver.Replace("https://", $"https://{subdomain}.");
return await _tryCheckIfDomainHasHomeserver(homeserver);
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
index ef9238d..bd5b1bd 100644
--- a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
+++ b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
@@ -1,9 +1,10 @@
+using MatrixRoomUtils.Core.Extensions;
using Microsoft.Extensions.DependencyInjection;
-namespace MatrixRoomUtils.Core.Services;
+namespace MatrixRoomUtils.Core.Services;
public static class ServiceInstaller {
-
+
public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) {
//Check required services
if (!services.Any(x => x.ServiceType == typeof(TieredStorageService)))
@@ -15,15 +16,15 @@ public static class ServiceInstaller {
services.AddSingleton(new RoryLibMatrixConfiguration());
}
//Add services
- services.AddScoped<HomeserverProviderService>();
+ services.AddSingleton<HomeserverProviderService>();
services.AddSingleton<HomeserverResolverService>();
- services.AddScoped<HttpClient>();
+ // services.AddScoped<MatrixHttpClient>();
return services;
}
-
-
+
+
}
public class RoryLibMatrixConfiguration {
public string AppName { get; set; } = "Rory&::LibMatrix";
-}
\ No newline at end of file
+}
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"
}
}
|