diff --git a/.gitignore b/.gitignore
index a6807bd..668274e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,12 @@
-# Dotnet build outputs
+# Regular dotnet things
**/bin/
**/obj/
+**/*.[Dd]ot[Ss]ettings.[Uu]ser
+**/BenchmarkDotNet.Artifacts/
-# User files
-*.DotSettings.user
-*.patch
+# Local files
/patches/
-
-# Local files:
appsettings.Local*.json
+appservice.yaml
+appservice.json
.run/local/*.run.xml
\ No newline at end of file
diff --git a/.idea/.idea.ModerationClient/.idea/avalonia.xml b/.idea/.idea.ModerationClient/.idea/avalonia.xml
index 6bc9c45..b045202 100644
--- a/.idea/.idea.ModerationClient/.idea/avalonia.xml
+++ b/.idea/.idea.ModerationClient/.idea/avalonia.xml
@@ -8,6 +8,9 @@
<entry key="ModerationClient/Views/LoginView.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/LoginWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/MainWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
+ <entry key="ModerationClient/Views/MainWindow/ClientView.axaml" value="ModerationClient/ModerationClient.csproj" />
+ <entry key="ModerationClient/Views/MainWindow/LoginView.axaml" value="ModerationClient/ModerationClient.csproj" />
+ <entry key="ModerationClient/Views/MainWindow/MainWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/MainWindow/NotificationPopupWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Views/UserManagementWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
<entry key="ModerationClient/Windows/LoginWindow.axaml" value="ModerationClient/ModerationClient.csproj" />
diff --git a/FilesystemBenchmark/Benchmarks.cs b/FilesystemBenchmark/Benchmarks.cs
new file mode 100644
index 0000000..9ab044a
--- /dev/null
+++ b/FilesystemBenchmark/Benchmarks.cs
@@ -0,0 +1,134 @@
+using System.Collections.Concurrent;
+using System.Security.Cryptography;
+using ArcaneLibs.Extensions;
+using BenchmarkDotNet.Attributes;
+
+namespace FilesystemBenchmark;
+
+public class Benchmarks {
+ private string testPath = "/home/Rory/.local/share/ModerationClient/default/syncCache";
+
+ private EnumerationOptions enumOpts = new EnumerationOptions() {
+ MatchType = MatchType.Simple,
+ AttributesToSkip = FileAttributes.None,
+ IgnoreInaccessible = false,
+ RecurseSubdirectories = true
+ };
+
+ [Benchmark]
+ public void GetFilesMatching() {
+ _ = Directory.GetFiles(testPath, "*.*", SearchOption.AllDirectories).Count();
+ }
+
+ [Benchmark]
+ public void EnumerateFilesMatching() {
+ _ = Directory.EnumerateFiles(testPath, "*.*", SearchOption.AllDirectories).Count();
+ }
+
+ [Benchmark]
+ public void GetFilesMatchingSingleStar() {
+ _ = Directory.GetFiles(testPath, "*", SearchOption.AllDirectories).Count();
+ }
+
+ [Benchmark]
+ public void EnumerateFilesMatchingSingleStar() {
+ _ = Directory.EnumerateFiles(testPath, "*", SearchOption.AllDirectories).Count();
+ }
+
+ [Benchmark]
+ public void GetFilesMatchingSingleStarSimple() {
+ _ = Directory.GetFiles(testPath, "*", new EnumerationOptions() {
+ MatchType = MatchType.Simple,
+ AttributesToSkip = FileAttributes.None,
+ IgnoreInaccessible = false,
+ RecurseSubdirectories = true
+ }).Count();
+ }
+
+ [Benchmark]
+ public void EnumerateFilesMatchingSingleStarSimple() {
+ _ = Directory.EnumerateFiles(testPath, "*", new EnumerationOptions() {
+ MatchType = MatchType.Simple,
+ AttributesToSkip = FileAttributes.None,
+ IgnoreInaccessible = false,
+ RecurseSubdirectories = true
+ }).Count();
+ }
+
+ [Benchmark]
+ public void GetFilesMatchingSingleStarSimpleCached() {
+ _ = Directory.GetFiles(testPath, "*", enumOpts).Count();
+ }
+
+ [Benchmark]
+ public void EnumerateFilesMatchingSingleStarSimpleCached() {
+ _ = Directory.EnumerateFiles(testPath, "*", enumOpts).Count();
+ }
+
+ // [Benchmark]
+ // public void GetFilesRecursiveFunc() {
+ // GetFilesRecursive(testPath);
+ // }
+ //
+ // [Benchmark]
+ // public void GetFilesRecursiveParallelFunc() {
+ // GetFilesRecursiveParallel(testPath);
+ // }
+ //
+ // [Benchmark]
+ // public void GetFilesRecursiveEntriesFunc() {
+ // GetFilesRecursiveEntries(testPath);
+ // }
+ //
+ // [Benchmark]
+ // public void GetFilesRecursiveAsyncFunc() {
+ // GetFilesRecursiveAsync(testPath).ToBlockingEnumerable();
+ // }
+
+
+ private List<string> GetFilesRecursive(string path) {
+ var result = new List<string>();
+ foreach (var dir in Directory.GetDirectories(path)) {
+ result.AddRange(GetFilesRecursive(dir));
+ }
+
+ result.AddRange(Directory.GetFiles(path));
+ return result;
+ }
+
+ private List<string> GetFilesRecursiveEntries(string path) {
+ var result = new List<string>();
+ foreach (var entry in Directory.EnumerateFileSystemEntries(path)) {
+ if (Directory.Exists(entry)) {
+ result.AddRange(GetFilesRecursiveEntries(entry));
+ }
+ else {
+ result.Add(entry);
+ }
+ }
+
+ return result;
+ }
+
+ private List<string> GetFilesRecursiveParallel(string path) {
+ var result = new ConcurrentBag<string>();
+ Directory.GetDirectories(path).AsParallel().ForAll(dir => {
+ GetFilesRecursiveParallel(dir).ForEach(result.Add);
+ });
+
+ Directory.GetFiles(path).AsParallel().ForAll(result.Add);
+ return result.ToList();
+ }
+
+ private async IAsyncEnumerable<string> GetFilesRecursiveAsync(string path) {
+ foreach (var dir in Directory.GetDirectories(path)) {
+ foreach (var file in GetFilesRecursiveAsync(dir).ToBlockingEnumerable()) {
+ yield return file;
+ }
+ }
+
+ foreach (var file in Directory.GetFiles(path)) {
+ yield return file;
+ }
+ }
+}
\ No newline at end of file
diff --git a/FilesystemBenchmark/FilesystemBenchmark.csproj b/FilesystemBenchmark/FilesystemBenchmark.csproj
new file mode 100644
index 0000000..bb0af83
--- /dev/null
+++ b/FilesystemBenchmark/FilesystemBenchmark.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net8.0</TargetFramework>
+ <LangVersion>preview</LangVersion>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\LibMatrix\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/FilesystemBenchmark/Program.cs b/FilesystemBenchmark/Program.cs
new file mode 100644
index 0000000..9454263
--- /dev/null
+++ b/FilesystemBenchmark/Program.cs
@@ -0,0 +1,8 @@
+// See https://aka.ms/new-console-template for more information
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Running;
+using FilesystemBenchmark;
+
+BenchmarkRunner.Run<Benchmarks>();
\ No newline at end of file
diff --git a/LibMatrix b/LibMatrix
-Subproject bba7333ee6581a92bbbc7479d72325e704fe7fa
+Subproject a8d20e9d57857296e4600f44807893f4dcad72d
diff --git a/ModerationClient.sln b/ModerationClient.sln
index c23825d..cff05e1 100644
--- a/ModerationClient.sln
+++ b/ModerationClient.sln
@@ -47,6 +47,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "Lib
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "LibMatrix\ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{046D35DF-E1F2-41DA-94D3-80CF960C100A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FilesystemBenchmark", "FilesystemBenchmark\FilesystemBenchmark.csproj", "{6F3FA65D-5661-4840-B421-BF64CD4DBBD2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -129,6 +133,14 @@ Global
{046D35DF-E1F2-41DA-94D3-80CF960C100A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{046D35DF-E1F2-41DA-94D3-80CF960C100A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{046D35DF-E1F2-41DA-94D3-80CF960C100A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC6C910C-79C7-42D3-ABD9-8B9ED9121AE5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F3FA65D-5661-4840-B421-BF64CD4DBBD2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{6FDD7CD7-4526-410B-858D-35FA8292317C} = {C5771E60-0DA8-42AC-B54B-94C91B5B719B}
@@ -154,7 +166,7 @@ Global
{046D35DF-E1F2-41DA-94D3-80CF960C100A} = {D7A5A7D3-4D9E-4B15-9C48-5E75A91483CF}
EndGlobalSection
GlobalSection(RiderSharedRunConfigurations) = postSolution
- File = .run\local\ModerationClient (Apothecary) test.run.xml
File = .run\local\ModerationClient (Apothecary).run.xml
+ File = .run\local\ModerationClient (matrix.org).run.xml
EndGlobalSection
EndGlobal
diff --git a/ModerationClient/App.axaml.cs b/ModerationClient/App.axaml.cs
index c44b5a2..b15c0fa 100644
--- a/ModerationClient/App.axaml.cs
+++ b/ModerationClient/App.axaml.cs
@@ -1,11 +1,11 @@
using System;
using System.IO;
+using System.Threading;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using LibMatrix.Services;
-using MatrixUtils.Abstractions;
using MatrixUtils.Desktop;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -29,7 +29,6 @@ public partial class App : Application {
// ReSharper disable once AsyncVoidMethod
public override async void OnFrameworkInitializationCompleted() {
var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(Environment.GetCommandLineArgs());
- builder.Services.AddTransient<MainWindowViewModel>();
ConfigureServices(builder.Services);
Host = builder.Build();
@@ -47,10 +46,11 @@ public partial class App : Application {
}
private static IServiceProvider ConfigureServices(IServiceCollection services) {
+ var cfg = CommandLineConfiguration.FromProcessArgs();
services.AddRoryLibMatrixServices(new() {
AppName = "ModerationClient",
});
- services.AddSingleton<CommandLineConfiguration>(CommandLineConfiguration.FromProcessArgs());
+ services.AddSingleton<CommandLineConfiguration>(cfg);
services.AddSingleton<MatrixAuthenticationService>();
services.AddSingleton<ModerationClientConfiguration>();
@@ -72,9 +72,15 @@ public partial class App : Application {
services.AddTransient<ClientView>();
// Register ViewModels
+ services.AddTransient<MainWindowViewModel>();
services.AddTransient<ClientViewModel>();
services.AddTransient<UserManagementViewModel>();
+ if (cfg.TestConfiguration is not null) {
+ services.AddSingleton(cfg.TestConfiguration);
+ services.AddHostedService<TestRunner>();
+ }
+
return services.BuildServiceProvider();
}
}
\ No newline at end of file
diff --git a/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs b/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs
new file mode 100644
index 0000000..76d5aa9
--- /dev/null
+++ b/ModerationClient/Models/SpaceTreeNodes/RoomNode.cs
@@ -0,0 +1,14 @@
+using ArcaneLibs;
+
+namespace ModerationClient.Models.SpaceTreeNodes;
+
+public class RoomNode : NotifyPropertyChanged {
+ private string? _name;
+
+ public string RoomID { get; set; }
+
+ public string? Name {
+ get => _name;
+ set => SetField(ref _name, value);
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/Models/SpaceTreeNodes/SpaceNode.cs b/ModerationClient/Models/SpaceTreeNodes/SpaceNode.cs
new file mode 100644
index 0000000..b8042ae
--- /dev/null
+++ b/ModerationClient/Models/SpaceTreeNodes/SpaceNode.cs
@@ -0,0 +1,15 @@
+using System.Collections.ObjectModel;
+
+namespace ModerationClient.Models.SpaceTreeNodes;
+
+public class SpaceNode : RoomNode {
+ private bool _isExpanded = false;
+
+ public SpaceNode(bool includeSelf = true) {
+ if(includeSelf)
+ ChildRooms = [this];
+ }
+
+ public ObservableCollection<SpaceNode> ChildSpaces { get; set; } = [];
+ public ObservableCollection<RoomNode> ChildRooms { get; set; } = [];
+}
\ No newline at end of file
diff --git a/ModerationClient/ModerationClient.csproj b/ModerationClient/ModerationClient.csproj
index 9876af9..c64d0c3 100644
--- a/ModerationClient/ModerationClient.csproj
+++ b/ModerationClient/ModerationClient.csproj
@@ -9,7 +9,6 @@
</PropertyGroup>
<ItemGroup>
- <Folder Include="Models\"/>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
diff --git a/ModerationClient/Program.cs b/ModerationClient/Program.cs
index 9229194..82e10aa 100644
--- a/ModerationClient/Program.cs
+++ b/ModerationClient/Program.cs
@@ -1,10 +1,12 @@
-using Avalonia;
+using Avalonia;
using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
namespace ModerationClient;
-internal sealed class Program
-{
+internal sealed 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.
@@ -18,4 +20,13 @@ internal sealed class Program
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
-}
\ No newline at end of file
+
+ // private static FileStream f = new("/dev/input/by-path/platform-pcspkr-event-spkr", FileMode.Open, FileAccess.Write, FileShare.Write, 24);
+ public static void Beep(short freq, short duration) {
+ // f.Write([..new byte[16], 0x12, 0x00, 0x02, 0x00, (byte)(freq & 0xFF), (byte)((freq >> 8) & 0xFF), 0x00, 0x00]);
+ // if (duration > 0) {
+ // Thread.Sleep(duration);
+ // f.Write([..new byte[16], 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00]);
+ // }
+ }
+}
diff --git a/ModerationClient/Services/ClientContainer.cs b/ModerationClient/Services/ClientContainer.cs
new file mode 100644
index 0000000..fa3abef
--- /dev/null
+++ b/ModerationClient/Services/ClientContainer.cs
@@ -0,0 +1,8 @@
+namespace ModerationClient.Services;
+
+public class ClientContainer {
+ public ClientContainer(MatrixAuthenticationService authService, CommandLineConfiguration cfg)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/Services/CommandLineConfiguration.cs b/ModerationClient/Services/CommandLineConfiguration.cs
index 63c3691..4f7da2d 100644
--- a/ModerationClient/Services/CommandLineConfiguration.cs
+++ b/ModerationClient/Services/CommandLineConfiguration.cs
@@ -2,15 +2,14 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Text.Json;
using ArcaneLibs;
-using Microsoft.Extensions.Logging;
+using ArcaneLibs.Extensions;
namespace ModerationClient.Services;
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public record CommandLineConfiguration {
- private readonly string? _loginData;
-
public static CommandLineConfiguration FromProcessArgs() {
// logger.LogInformation("Command line arguments: " + string.Join(", ", Environment.GetCommandLineArgs()));
CommandLineConfiguration cfg = FromSerialised(Environment.GetCommandLineArgs());
@@ -27,24 +26,27 @@ public record CommandLineConfiguration {
if (!string.IsNullOrWhiteSpace(cfg.LoginData)) {
File.WriteAllText(Path.Combine(cfg.ProfileDirectory, "login.json"), cfg.LoginData);
}
+
+
return cfg;
}
-
public string[] Serialise() {
+ var current = FromProcessArgs();
List<string> args = new();
- if (Profile != "default") args.AddRange(["--profile", Profile]);
+ if (Profile != current.Profile) args.AddRange(["--profile", Profile]);
if (IsTemporary) args.Add("--temporary");
if (Math.Abs(Scale - 1f) > float.Epsilon) args.AddRange(["--scale", Scale.ToString()]);
- if (ProfileDirectory != Util.ExpandPath("$HOME/.local/share/ModerationClient/default")) args.AddRange(["--profile-dir", ProfileDirectory]);
- if (!string.IsNullOrWhiteSpace(_loginData)) args.AddRange(["--login-data", _loginData!]);
+ if (ProfileDirectory != current.ProfileDirectory) args.AddRange(["--profile-dir", ProfileDirectory]);
+ if (!string.IsNullOrWhiteSpace(_loginData) && _loginData != current.LoginData) args.AddRange(["--login-data", _loginData!]);
+ if (TestConfiguration is not null && TestConfiguration != current.TestConfiguration) args.AddRange(["--test-config", TestConfiguration!.ToJson()]);
return args.ToArray();
}
-
public static CommandLineConfiguration FromSerialised(string[] args) {
CommandLineConfiguration cfg = new();
for (var i = 0; i < args.Length; i++) {
switch (args[i]) {
case "--profile":
+ case "-p":
cfg = cfg with { Profile = args[++i] };
break;
case "--temporary":
@@ -59,12 +61,16 @@ public record CommandLineConfiguration {
case "--login-data":
cfg = cfg with { LoginData = args[++i] };
break;
+ case "--test-config":
+ cfg = cfg with { testConfiguration = args[++i] };
+ break;
}
}
return cfg;
}
+ private readonly string? _loginData;
public string Profile { get; init; } = "default";
public bool IsTemporary { get; init; }
@@ -78,4 +84,15 @@ public record CommandLineConfiguration {
_loginData = value;
}
}
+
+ private string? testConfiguration {
+ get => TestConfiguration?.ToJson();
+ init => TestConfiguration = value is null ? null : JsonSerializer.Deserialize<TestConfig>(value);
+ }
+
+ public TestConfig? TestConfiguration { get; init; }
+
+ public class TestConfig {
+ public List<string> Mxids { get; set; } = new();
+ }
}
\ No newline at end of file
diff --git a/ModerationClient/Services/FileStorageProvider.cs b/ModerationClient/Services/FileStorageProvider.cs
index 3658369..5b43ce4 100644
--- a/ModerationClient/Services/FileStorageProvider.cs
+++ b/ModerationClient/Services/FileStorageProvider.cs
@@ -1,19 +1,28 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using ArcaneLibs.Extensions;
-using LibMatrix.Extensions;
using LibMatrix.Interfaces.Services;
-using Microsoft.Extensions.Logging;
-namespace MatrixUtils.Abstractions;
+namespace ModerationClient.Services;
public class FileStorageProvider : IStorageProvider {
- private readonly ILogger<FileStorageProvider> _logger;
+ // private readonly ILogger<FileStorageProvider> _logger;
+ private static readonly JsonSerializerOptions Options = new() {
+ WriteIndented = true
+ };
+
+ private static readonly EnumerationOptions EnumOpts = new EnumerationOptions() {
+ MatchType = MatchType.Simple,
+ AttributesToSkip = FileAttributes.None,
+ IgnoreInaccessible = false,
+ RecurseSubdirectories = true
+ };
public string TargetPath { get; }
@@ -30,25 +39,58 @@ public class FileStorageProvider : IStorageProvider {
}
}
- public async Task SaveObjectAsync<T>(string key, T value) => await File.WriteAllTextAsync(Path.Join(TargetPath, key), value?.ToJson());
+ public async Task SaveObjectAsync<T>(string key, T value) {
+ EnsureContainingDirectoryExists(GetFullPath(key));
+ await using var fileStream = File.Create(GetFullPath(key));
+ await JsonSerializer.SerializeAsync(fileStream, value, Options);
+ }
[RequiresUnreferencedCode("This API uses reflection to deserialize JSON")]
- public async Task<T?> LoadObjectAsync<T>(string key) => JsonSerializer.Deserialize<T>(await File.ReadAllTextAsync(Path.Join(TargetPath, key)));
+ public async Task<T?> LoadObjectAsync<T>(string key) {
+ await using var fileStream = File.OpenRead(GetFullPath(key));
+ return JsonSerializer.Deserialize<T>(fileStream);
+ }
- public Task<bool> ObjectExistsAsync(string key) => Task.FromResult(File.Exists(Path.Join(TargetPath, key)));
+ public Task<bool> ObjectExistsAsync(string key) => Task.FromResult(File.Exists(GetFullPath(key)));
- public Task<List<string>> GetAllKeysAsync() => Task.FromResult(Directory.GetFiles(TargetPath).Select(Path.GetFileName).ToList());
+ public async Task<IEnumerable<string>> GetAllKeysAsync() {
+ var sw = Stopwatch.StartNew();
+ // var result = Directory.EnumerateFiles(TargetPath, "*", SearchOption.AllDirectories)
+ var result = Directory.EnumerateFiles(TargetPath, "*", EnumOpts)
+ .Select(s => s.Replace(TargetPath, "").TrimStart('/'));
+ // Console.WriteLine($"GetAllKeysAsync got {result.Count()} results in {sw.ElapsedMilliseconds}ms");
+ // Environment.Exit(0);
+ return result;
+ }
public Task DeleteObjectAsync(string key) {
- File.Delete(Path.Join(TargetPath, key));
+ File.Delete(GetFullPath(key));
return Task.CompletedTask;
}
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));
+ EnsureContainingDirectoryExists(GetFullPath(key));
+ await using var fileStream = File.Create(GetFullPath(key));
await stream.CopyToAsync(fileStream);
}
- public Task<Stream?> LoadStreamAsync(string key) => Task.FromResult<Stream?>(File.Exists(Path.Join(TargetPath, key)) ? File.OpenRead(Path.Join(TargetPath, key)) : null);
-}
+ public Task<Stream?> LoadStreamAsync(string key) => Task.FromResult<Stream?>(File.Exists(GetFullPath(key)) ? File.OpenRead(GetFullPath(key)) : null);
+
+ public Task CopyObjectAsync(string sourceKey, string destKey) {
+ EnsureContainingDirectoryExists(GetFullPath(destKey));
+ File.Copy(GetFullPath(sourceKey), GetFullPath(destKey));
+ return Task.CompletedTask;
+ }
+
+ public Task MoveObjectAsync(string sourceKey, string destKey) {
+ EnsureContainingDirectoryExists(GetFullPath(destKey));
+ File.Move(GetFullPath(sourceKey), GetFullPath(destKey));
+ return Task.CompletedTask;
+ }
+
+ private string GetFullPath(string key) => Path.Join(TargetPath, key);
+
+ private void EnsureContainingDirectoryExists(string path) {
+ Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException());
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/Services/MatrixAuthenticationService.cs b/ModerationClient/Services/MatrixAuthenticationService.cs
index 7e9ce70..46ec067 100644
--- a/ModerationClient/Services/MatrixAuthenticationService.cs
+++ b/ModerationClient/Services/MatrixAuthenticationService.cs
@@ -1,16 +1,12 @@
-using System;
using System.IO;
using System.Text.Json;
-using System.Threading;
using System.Threading.Tasks;
using ArcaneLibs;
using ArcaneLibs.Extensions;
-using Avalonia.Controls.Diagnostics;
using LibMatrix;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
using LibMatrix.Services;
-using MatrixUtils.Desktop;
using Microsoft.Extensions.Logging;
namespace ModerationClient.Services;
diff --git a/ModerationClient/Services/ModerationClientConfiguration.cs b/ModerationClient/Services/ModerationClientConfiguration.cs
index f770fef..3cc4ffb 100644
--- a/ModerationClient/Services/ModerationClientConfiguration.cs
+++ b/ModerationClient/Services/ModerationClientConfiguration.cs
@@ -1,10 +1,5 @@
-using System;
-using System.Collections;
using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
using ArcaneLibs;
-using ArcaneLibs.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
diff --git a/ModerationClient/Services/StatusBarService.cs b/ModerationClient/Services/StatusBarService.cs
new file mode 100644
index 0000000..57aff21
--- /dev/null
+++ b/ModerationClient/Services/StatusBarService.cs
@@ -0,0 +1,19 @@
+using System;
+using ArcaneLibs;
+
+namespace ModerationClient.Services;
+
+public class StatusBarService : NotifyPropertyChanged {
+ private string _statusText = "Ready";
+ private bool _isBusy;
+
+ public string StatusText {
+ get => _statusText + " " + DateTime.Now.ToString("u")[..^1];
+ set => SetField(ref _statusText, value);
+ }
+
+ public bool IsBusy {
+ get => _isBusy;
+ set => SetField(ref _isBusy, value);
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/Services/TestRunner.cs b/ModerationClient/Services/TestRunner.cs
new file mode 100644
index 0000000..dbacf99
--- /dev/null
+++ b/ModerationClient/Services/TestRunner.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+
+namespace ModerationClient.Services;
+
+public class TestRunner(CommandLineConfiguration.TestConfig testConfig, MatrixAuthenticationService mas) : IHostedService {
+ public async Task StartAsync(CancellationToken cancellationToken) {
+ Console.WriteLine("TestRunner: Starting test runner");
+ mas.PropertyChanged += (_, args) => {
+ if (args.PropertyName == nameof(MatrixAuthenticationService.IsLoggedIn) && mas.IsLoggedIn) {
+ Console.WriteLine("TestRunner: Logged in, starting test");
+ _ = Run();
+ }
+ };
+ }
+
+ public async Task StopAsync(CancellationToken cancellationToken) {
+ Console.WriteLine("TestRunner: Stopping test runner");
+ }
+
+ private async Task Run() {
+ var hs = mas.Homeserver!;
+ Console.WriteLine("TestRunner: Running test on homeserver " + hs);
+ foreach (var mxid in testConfig.Mxids) {
+ var room = await hs.CreateRoom(new() {
+ Name = mxid,
+ Invite = testConfig.Mxids
+ });
+
+ await room.SendMessageEventAsync(new("test"));
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/ViewModels/ClientViewModel.cs b/ModerationClient/ViewModels/ClientViewModel.cs
index 312b46a..fb3681e 100644
--- a/ModerationClient/ViewModels/ClientViewModel.cs
+++ b/ModerationClient/ViewModels/ClientViewModel.cs
@@ -1,17 +1,20 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Text.Json;
using System.Threading.Tasks;
+using ArcaneLibs;
using ArcaneLibs.Collections;
+using LibMatrix;
using LibMatrix.EventTypes.Spec.State;
using LibMatrix.Helpers;
using LibMatrix.Responses;
-using MatrixUtils.Abstractions;
using Microsoft.Extensions.Logging;
+using ModerationClient.Models.SpaceTreeNodes;
using ModerationClient.Services;
namespace ModerationClient.ViewModels;
@@ -22,7 +25,13 @@ public partial class ClientViewModel : ViewModelBase {
_authService = authService;
_cfg = cfg;
DisplayedSpaces.Add(_allRoomsNode = new AllRoomsSpaceNode(this));
- _ = Task.Run(Run);
+ DisplayedSpaces.Add(DirectMessages = new SpaceNode(false) { Name = "Direct messages" });
+ _ = Task.Run(Run).ContinueWith(x => {
+ if (x.IsFaulted) {
+ Status = "Critical error running client view model: " + x.Exception?.Message;
+ _logger.LogError(x.Exception, "Error running client view model.");
+ }
+ });
}
private readonly ILogger<ClientViewModel> _logger;
@@ -33,6 +42,9 @@ public partial class ClientViewModel : ViewModelBase {
private string _status = "Loading...";
public ObservableCollection<SpaceNode> DisplayedSpaces { get; } = [];
public ObservableDictionary<string, RoomNode> AllRooms { get; } = new();
+ public SpaceNode DirectMessages { get; }
+
+ public bool Paused { get; set; } = false;
public SpaceNode CurrentSpace {
get => _currentSpace ?? _allRoomsNode;
@@ -45,25 +57,50 @@ public partial class ClientViewModel : ViewModelBase {
}
public async Task Run() {
- Status = "Interrupted.";
- return;
+ Console.WriteLine("Running client view model loop...");
+ ArgumentNullException.ThrowIfNull(_authService.Homeserver, nameof(_authService.Homeserver));
+ // var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache")));
+ var store = new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache"));
+ Console.WriteLine($"Sync store at {store.TargetPath}");
+
+ var sh = new SyncHelper(_authService.Homeserver, _logger, storageProvider: store) {
+ // MinimumDelay = TimeSpan.FromSeconds(1)
+ };
+ Console.WriteLine("Sync helper created.");
+
+ //optimise - we create a new scope here to make ssr go out of scope
+ // if((await sh.GetUnoptimisedStoreCount()) > 1000)
+ {
+ Console.WriteLine("RUN - Optimising sync store...");
+ Status = "Optimising sync store, please wait...";
+ var ssr = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: store);
+ Console.WriteLine("Created sync state resolver...");
+ Status = "Optimising sync store, please wait... Creating new snapshot...";
+ await ssr.OptimiseStore();
+ Status = "Optimising sync store, please wait... Deleting old intermediate snapshots...";
+ await ssr.RemoveOldSnapshots();
+ }
+
+ var unoptimised = await sh.GetUnoptimisedStoreCount(); // this is slow, so we cache
Status = "Doing initial sync...";
- var sh = new SyncStateResolver(_authService.Homeserver, _logger, storageProvider: new FileStorageProvider(Path.Combine(_cfg.ProfileDirectory, "syncCache")));
- // var res = await sh.SyncAsync();
- //await sh.OptimiseStore();
- while (true) {
- // Status = "Syncing...";
- var res = await sh.ContinueAsync();
- Status = $"Processing sync... {res.next.NextBatch}";
- await ApplySpaceChanges(res.next);
- //OnPropertyChanged(nameof(CurrentSpace));
- //OnPropertyChanged(nameof(CurrentSpace.ChildRooms));
- // Console.WriteLine($"mow A={AllRooms.Count}|D={DisplayedSpaces.Count}");
- // for (int i = 0; i < GC.MaxGeneration; i++) {
- // GC.Collect(i, GCCollectionMode.Forced, blocking: true);
- // GC.WaitForPendingFinalizers();
- // }
- Status = "Syncing...";
+ await foreach (var res in sh.EnumerateSyncAsync()) {
+ Program.Beep((short)250, 0);
+ Status = $"Processing sync... {res.NextBatch}";
+ await ApplySyncChanges(res);
+
+ Program.Beep(0, 0);
+ if (Paused) {
+ Status = "Sync loop interrupted... Press pause/break to resume.";
+ while (Paused) await Task.Delay(1000);
+ }
+ else Status = $"Syncing... {unoptimised++} unoptimised sync responses...";
+ }
+ }
+
+ private async Task ApplySyncChanges(SyncResponse newSync) {
+ await ApplySpaceChanges(newSync);
+ if (newSync.AccountData?.Events?.FirstOrDefault(x => x.Type == "m.direct") is { } evt) {
+ await ApplyDirectMessagesChanges(evt);
}
}
@@ -71,20 +108,25 @@ public partial class ClientViewModel : ViewModelBase {
List<Task> tasks = [];
foreach (var room in newSync.Rooms?.Join ?? []) {
if (!AllRooms.ContainsKey(room.Key)) {
- AllRooms.Add(room.Key, new RoomNode { Name = "Loading..." });
+ // AllRooms.Add(room.Key, new RoomNode { Name = "Loading..." });
+ AllRooms.Add(room.Key, new RoomNode { Name = "", RoomID = room.Key });
}
if (room.Value.State?.Events is not null) {
var nameEvent = room.Value.State!.Events!.FirstOrDefault(x => x.Type == "m.room.name" && x.StateKey == "");
- AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? "";
- if (string.IsNullOrWhiteSpace(AllRooms[room.Key].Name)) {
- AllRooms[room.Key].Name = "Loading...";
- tasks.Add(_authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync().ContinueWith(r => AllRooms[room.Key].Name = r.Result));
- }
+ if (nameEvent is not null)
+ AllRooms[room.Key].Name = (nameEvent?.TypedContent as RoomNameEventContent)?.Name ?? "";
+ }
+
+ if (string.IsNullOrWhiteSpace(AllRooms[room.Key].Name)) {
+ AllRooms[room.Key].Name = "Loading...";
+ tasks.Add(_authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync().ContinueWith(r => AllRooms[room.Key].Name = r.Result));
+ // Status = $"Getting room name for {room.Key}...";
+ // AllRooms[room.Key].Name = await _authService.Homeserver!.GetRoom(room.Key).GetNameOrFallbackAsync();
}
}
-
- await Task.WhenAll(tasks);
+
+ await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying room changes...");
return;
@@ -111,20 +153,59 @@ public partial class ClientViewModel : ViewModelBase {
handledRoomIds.Add(roomId);
}
}
-}
-public class SpaceNode : RoomNode {
- public ObservableCollection<SpaceNode> ChildSpaces { get; set; } = [];
- public ObservableCollection<RoomNode> ChildRooms { get; set; } = [];
-}
+ private async Task ApplyDirectMessagesChanges(StateEventResponse evt) {
+ _logger.LogCritical("Direct messages updated!");
+ var dms = evt.RawContent.Deserialize<Dictionary<string, string[]?>>();
+ List<Task> tasks = [];
+ foreach (var (userId, roomIds) in dms) {
+ if (roomIds is null || roomIds.Length == 0) continue;
+ var space = DirectMessages.ChildSpaces.FirstOrDefault(x => x.RoomID == userId);
+ if (space is null) {
+ space = new SpaceNode { Name = userId, RoomID = userId };
+ tasks.Add(_authService.Homeserver!.GetProfileAsync(userId)
+ .ContinueWith(r => space.Name = string.IsNullOrWhiteSpace(r.Result?.DisplayName) ? userId : r.Result.DisplayName));
+ DirectMessages.ChildSpaces.Add(space);
+ }
+
+ foreach (var roomId in roomIds) {
+ var room = space.ChildRooms.FirstOrDefault(x => x.RoomID == roomId);
+ if (room is null) {
+ room = AllRooms.TryGetValue(roomId, out var existing) ? existing : new RoomNode { Name = "Unknown: " + roomId, RoomID = roomId };
+ space.ChildRooms.Add(room);
+ }
+ }
-public class RoomNode {
- public string Name { get; set; }
+ foreach (var spaceChildRoom in space.ChildRooms.ToList()) {
+ if (!roomIds.Contains(spaceChildRoom.RoomID)) {
+ space.ChildRooms.Remove(spaceChildRoom);
+ }
+ }
+ }
+
+ await AwaitTasks(tasks, "Waiting for {0}/{1} tasks while applying DM changes...");
+ }
+
+ private async Task AwaitTasks(List<Task> tasks, string message) {
+ if (tasks.Count > 0) {
+ int total = tasks.Count;
+ while (tasks.Any(x => !x.IsCompleted)) {
+ int incomplete = tasks.Count(x => !x.IsCompleted);
+ Program.Beep((short)MathUtil.Map(incomplete, 0, total, 20, 7500), 5);
+ // Program.Beep(0, 0);
+ Status = string.Format(message, incomplete, total);
+ await Task.WhenAny(tasks);
+ tasks.RemoveAll(x => x.IsCompleted);
+ }
+
+ Program.Beep(0, 0);
+ }
+ }
}
// implementation details
public class AllRoomsSpaceNode : SpaceNode {
- public AllRoomsSpaceNode(ClientViewModel vm) {
+ public AllRoomsSpaceNode(ClientViewModel vm) : base(false) {
Name = "All rooms";
vm.AllRooms.CollectionChanged += (_, args) => {
switch (args.Action) {
diff --git a/ModerationClient/ViewModels/MainWindowViewModel.cs b/ModerationClient/ViewModels/MainWindowViewModel.cs
index be64de4..5cd5c45 100644
--- a/ModerationClient/ViewModels/MainWindowViewModel.cs
+++ b/ModerationClient/ViewModels/MainWindowViewModel.cs
@@ -1,7 +1,6 @@
using System;
using Avalonia;
using ModerationClient.Services;
-using ModerationClient.Views;
namespace ModerationClient.ViewModels;
@@ -9,10 +8,10 @@ public partial class MainWindowViewModel(MatrixAuthenticationService authService
// public MainWindow? MainWindow { get; set; }
private float _scale = 1.0f;
- private ViewModelBase _currentViewModel = new LoginViewModel(authService);
+ private ViewModelBase? _currentViewModel = null;
private Size _physicalSize = new Size(300, 220);
- public ViewModelBase CurrentViewModel {
+ public ViewModelBase? CurrentViewModel {
get => _currentViewModel;
set => SetProperty(ref _currentViewModel, value);
}
diff --git a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
index 7a2ad63..90020d6 100644
--- a/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
+++ b/ModerationClient/ViewModels/UserManagement/UserManagementViewModel.cs
@@ -1,20 +1,10 @@
using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
-using ArcaneLibs.Collections;
using ArcaneLibs.Extensions;
-using LibMatrix.EventTypes.Spec.State;
-using LibMatrix.Helpers;
using LibMatrix.Homeservers;
using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses;
-using LibMatrix.Responses;
-using MatrixUtils.Abstractions;
using Microsoft.Extensions.Logging;
using ModerationClient.Services;
@@ -51,6 +41,7 @@ public partial class UserManagementViewModel : ViewModelBase {
}
await foreach (var user in synapse.Admin.SearchUsersAsync(chunkLimit: 100)) {
+ Program.Beep(250, 1);
Console.WriteLine("USERMANAGER GOT USER: " + user.ToJson(indent:false, ignoreNull: true));
Users.Add(JsonSerializer.Deserialize<User>(user.ToJson())!);
}
@@ -58,6 +49,6 @@ public partial class UserManagementViewModel : ViewModelBase {
}
}
-public class User : AdminUserListResult.AdminUserListResultUser {
+public class User : SynapseAdminUserListResult.SynapseAdminUserListResultUser {
}
\ No newline at end of file
diff --git a/ModerationClient/Views/MainWindow/ClientView.axaml b/ModerationClient/Views/MainWindow/ClientView.axaml
index ba030e4..e0cd4e0 100644
--- a/ModerationClient/Views/MainWindow/ClientView.axaml
+++ b/ModerationClient/Views/MainWindow/ClientView.axaml
@@ -4,36 +4,42 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:views="clr-namespace:ModerationClient.Views"
xmlns:viewModels="clr-namespace:ModerationClient.ViewModels"
+ xmlns:spaceTreeNodes="clr-namespace:ModerationClient.Models.SpaceTreeNodes"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ModerationClient.Views.ClientView"
x:DataType="viewModels:ClientViewModel">
<Grid Width="{Binding $parent.Width}" Height="{Binding $parent.Height}" RowDefinitions="*, Auto">
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
- <ColumnDefinition Width="128" MinWidth="16" />
+ <ColumnDefinition Width="256" MinWidth="16" />
<ColumnDefinition Width="1" />
- <ColumnDefinition Width="128" MinWidth="16" />
+ <ColumnDefinition Width="256" MinWidth="16" />
<ColumnDefinition Width="1" />
<ColumnDefinition Width="*" MinWidth="16" />
</Grid.ColumnDefinitions>
- <TreeView Grid.Column="0" Background="Red" ItemsSource="{CompiledBinding DisplayedSpaces}" SelectedItem="{CompiledBinding CurrentSpace}">
+ <TreeView Grid.Column="0" Background="#202020" ItemsSource="{CompiledBinding DisplayedSpaces}" SelectedItem="{CompiledBinding CurrentSpace}">
<TreeView.ItemTemplate>
- <TreeDataTemplate ItemsSource="{Binding ChildSpaces}">
+ <!-- <TreeView.Styles> -->
+ <!-- <Style Selector="TreeViewItem"> -->
+ <!-- <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> -->
+ <!-- </Style> -->
+ <!-- </TreeView.Styles> -->
+ <TreeDataTemplate ItemsSource="{Binding ChildSpaces}" DataType="spaceTreeNodes:SpaceNode">
<TextBlock Text="{Binding Name}" Height="20" />
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<GridSplitter Grid.Column="1" Background="Black" ResizeDirection="Columns" />
<!-- <Rectangle Grid.Column="2" Fill="Green" /> -->
- <ListBox Grid.Column="2" Background="Green" ItemsSource="{CompiledBinding CurrentSpace.ChildRooms}">
+ <ListBox Grid.Column="2" Background="#242424" ItemsSource="{CompiledBinding CurrentSpace.ChildRooms}">
<ListBox.ItemTemplate>
- <DataTemplate DataType="viewModels:RoomNode">
+ <DataTemplate DataType="spaceTreeNodes:RoomNode">
<TextBlock Text="{CompiledBinding Name}" Height="20" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="3" Background="Black" ResizeDirection="Columns" />
- <Rectangle Grid.Column="4" Fill="Blue" />
+ <Rectangle Grid.Column="4" Fill="#282828" />
</Grid>
<Grid Grid.Row="1" ColumnDefinitions="Auto, *, Auto" Background="Black">
<Label Grid.Column="2" Content="{CompiledBinding Status}" />
diff --git a/ModerationClient/Views/MainWindow/ClientView.axaml.cs b/ModerationClient/Views/MainWindow/ClientView.axaml.cs
index 894e807..5b0f62d 100644
--- a/ModerationClient/Views/MainWindow/ClientView.axaml.cs
+++ b/ModerationClient/Views/MainWindow/ClientView.axaml.cs
@@ -1,5 +1,4 @@
using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
namespace ModerationClient.Views;
diff --git a/ModerationClient/Views/MainWindow/LoginView.axaml.cs b/ModerationClient/Views/MainWindow/LoginView.axaml.cs
index 5e84ace..ea2f59d 100644
--- a/ModerationClient/Views/MainWindow/LoginView.axaml.cs
+++ b/ModerationClient/Views/MainWindow/LoginView.axaml.cs
@@ -1,12 +1,12 @@
using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
-using Avalonia.Markup.Xaml;
using ModerationClient.ViewModels;
namespace ModerationClient.Views;
public partial class LoginView : UserControl {
+ private LoginViewModel? ViewModel => DataContext as LoginViewModel;
public LoginView() {
InitializeComponent();
}
diff --git a/ModerationClient/Views/MainWindow/MainWindow.axaml b/ModerationClient/Views/MainWindow/MainWindow.axaml
index ef13553..c45b296 100644
--- a/ModerationClient/Views/MainWindow/MainWindow.axaml
+++ b/ModerationClient/Views/MainWindow/MainWindow.axaml
@@ -17,23 +17,28 @@
<!-- </Design.DataContext> -->
<Grid RowDefinitions="Auto, *">
- <Grid Grid.Row="0" ColumnDefinitions="Auto, *, Auto" x:Name="TopPanel">
- <StackPanel Orientation="Horizontal" Grid.Column="0">
- <Label Content="{CompiledBinding Scale}" />
- <Label>x</Label>
- <Rectangle Width="32" />
- <Label Content="{CompiledBinding ChildTargetWidth}" />
- <Label>x</Label>
- <Label Content="{CompiledBinding ChildTargetHeight}" />
- </StackPanel>
- <Label Grid.Column="2">Press '?' for keybinds</Label>
- </Grid>
+
<Viewbox Grid.Row="1">
- <ContentControl
- Width="{CompiledBinding ChildTargetWidth}"
- Background="#222222"
- Height="{CompiledBinding ChildTargetHeight}"
- Content="{CompiledBinding CurrentViewModel}" />
+ <Grid RowDefinitions="Auto, *"
+ Background="#202020"
+ Width="{CompiledBinding ChildTargetWidth}"
+ Height="{CompiledBinding ChildTargetHeight}">
+ <Grid Grid.Row="0" ColumnDefinitions="Auto, *, Auto" x:Name="TopPanel" Background="#000000">
+ <StackPanel Orientation="Horizontal" Grid.Column="0">
+ <Label>[F1 -]</Label>
+ <Label Content="{CompiledBinding Scale}" />
+ <Label>x</Label>
+ <Label>[+ F2]</Label>
+ <Rectangle Width="32" />
+ <Label>VRes =</Label>
+ <Label Content="{CompiledBinding ChildTargetWidth}" />
+ <Label>x</Label>
+ <Label Content="{CompiledBinding ChildTargetHeight}" />
+ </StackPanel>
+ <Label Grid.Column="2">Press '?' for keybinds</Label>
+ </Grid>
+ <ContentControl Grid.Row="1" Content="{CompiledBinding CurrentViewModel}" />
+ </Grid>
</Viewbox>
</Grid>
</Window>
\ No newline at end of file
diff --git a/ModerationClient/Views/MainWindow/MainWindow.axaml.cs b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
index 01027c1..cc3534d 100644
--- a/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
+++ b/ModerationClient/Views/MainWindow/MainWindow.axaml.cs
@@ -1,23 +1,55 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using ArcaneLibs.Extensions;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Diagnostics;
using Avalonia.Input;
+using LibMatrix.Responses;
+using LibMatrix.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModerationClient.Services;
using ModerationClient.ViewModels;
+#if DEBUG
+using Avalonia.Diagnostics;
+#endif
+
namespace ModerationClient.Views.MainWindow;
public partial class MainWindow : Window {
+ private readonly CommandLineConfiguration _cfg;
+
public MainWindow(CommandLineConfiguration cfg, MainWindowViewModel dataContext, IHostApplicationLifetime appLifetime) {
+ _cfg = cfg;
InitializeComponent();
DataContext = dataContext;
_ = dataContext.AuthService.LoadProfileAsync().ContinueWith(x => {
if (x.IsFaulted) {
Console.WriteLine("Failed to load profile: " + x.Exception);
}
+
+ dataContext.CurrentViewModel = dataContext.AuthService.IsLoggedIn
+ ? App.Current.Host.Services.GetRequiredService<ClientViewModel>()
+ : new LoginViewModel(dataContext.AuthService);
+
+ if (!dataContext.AuthService.IsLoggedIn) {
+ dataContext.AuthService.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == nameof(MatrixAuthenticationService.IsLoggedIn)) {
+ if (dataContext.AuthService.IsLoggedIn) {
+ // dataContext.CurrentViewModel = new ClientViewModel(dataContext.AuthService);
+ dataContext.CurrentViewModel = App.Current.Host.Services.GetRequiredService<ClientViewModel>();
+ // var window = App.Current.Host.Services.GetRequiredService<UserManagementWindow>();
+ // window.Show();
+ }
+ else {
+ dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService);
+ }
+ }
+ };
+ }
});
Console.WriteLine("mainwnd");
#if DEBUG
@@ -35,36 +67,23 @@ public partial class MainWindow : Window {
return;
}
- viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ // viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height);
break;
}
}
};
- TopPanel.PropertyChanged += (_, args) => {
- if (args.Property.Name == nameof(Visual.Bounds)) {
- if (DataContext is not MainWindowViewModel viewModel) {
- Console.WriteLine("WARN: MainWindowViewModel is null, ignoring TopPanel.Bounds change!");
- return;
- }
-
- viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
- }
- };
-
- dataContext.AuthService.PropertyChanged += (sender, args) => {
- if (args.PropertyName == nameof(MatrixAuthenticationService.IsLoggedIn)) {
- if (dataContext.AuthService.IsLoggedIn) {
- // dataContext.CurrentViewModel = new ClientViewModel(dataContext.AuthService);
- dataContext.CurrentViewModel = App.Current.Host.Services.GetRequiredService<ClientViewModel>();
- var window = App.Current.Host.Services.GetRequiredService<UserManagementWindow>();
- window.Show();
- }
- else {
- dataContext.CurrentViewModel = new LoginViewModel(dataContext.AuthService);
- }
- }
- };
+ // TopPanel.PropertyChanged += (_, args) => {
+ // if (args.Property.Name == nameof(Visual.Bounds)) {
+ // if (DataContext is not MainWindowViewModel viewModel) {
+ // Console.WriteLine("WARN: MainWindowViewModel is null, ignoring TopPanel.Bounds change!");
+ // return;
+ // }
+ //
+ // viewModel.PhysicalSize = new Size(ClientSize.Width, ClientSize.Height - TopPanel.Bounds.Height);
+ // }
+ // };
dataContext.Scale = cfg.Scale;
Width *= cfg.Scale;
@@ -76,9 +95,13 @@ public partial class MainWindow : Window {
});
}
- protected override void OnKeyDown(KeyEventArgs e) => OnKeyDown(this, e);
+ protected override void OnKeyDown(KeyEventArgs e) => OnKeyDown(this, e).ContinueWith(t => {
+ if (t.IsFaulted) {
+ Console.WriteLine("OnKeyDown faulted: " + t.Exception);
+ }
+ });
- private void OnKeyDown(object? _, KeyEventArgs e) {
+ private async Task OnKeyDown(object? _, KeyEventArgs e) {
if (DataContext is not MainWindowViewModel viewModel) {
Console.WriteLine($"WARN: DataContext is {DataContext?.GetType().Name ?? "null"}, ignoring key press!");
return;
@@ -100,6 +123,11 @@ public partial class MainWindow : Window {
viewModel.Scale = 5.0f;
}
}
+ else if (e.Key == Key.Pause) {
+ if (viewModel.CurrentViewModel is ClientViewModel clientViewModel) {
+ clientViewModel.Paused = !clientViewModel.Paused;
+ }
+ }
else if (e.KeyModifiers == KeyModifiers.Control) {
if (e.Key == Key.K) {
if (viewModel.CurrentViewModel is ClientViewModel clientViewModel) {
@@ -107,7 +135,7 @@ public partial class MainWindow : Window {
}
else Console.WriteLine("WARN: CurrentViewModel is not ClientViewModel, ignoring Quick Switcher");
}
- else if (e.Key == Key.U ) {
+ else if (e.Key == Key.U) {
Console.WriteLine("UserManagementWindow invoked");
var window = App.Current.Host.Services.GetRequiredService<UserManagementWindow>();
window.Show();
@@ -116,8 +144,31 @@ public partial class MainWindow : Window {
Console.WriteLine("Launching new process");
System.Diagnostics.Process.Start(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName, Environment.GetCommandLineArgs());
}
- else if (e.Key == Key.F9) {
-
+ else if (e.Key == Key.F9) { }
+ else if (e.Key == Key.D) {
+ List<LoginResponse> mxids = new();
+ var hsps = App.Current.Services.GetRequiredService<HomeserverProviderService>();
+ var rhs = await hsps.GetRemoteHomeserver("matrixunittests.rory.gay", enableServer: false);
+ for (int i = 0; i < 64; i++) {
+ Console.WriteLine("Debugging invoked");
+ var main = await rhs.RegisterAsync(Guid.NewGuid().ToString(), "password");
+ mxids.Add(main);
+ Console.WriteLine($"Registered: {main.UserId} {main.AccessToken} - {mxids.Count}");
+ }
+
+ foreach (var mxid in mxids) {
+ Console.WriteLine("Launching new process: ");
+ var args = (_cfg with {
+ Profile = "mut-" + mxid.UserId,
+ ProfileDirectory = null,
+ LoginData = mxid.ToJson(),
+ TestConfiguration = new() {
+ Mxids = mxids.Select(x => x.UserId).ToList()
+ }
+ }).Serialise();
+ Console.WriteLine(string.Join(' ', args));
+ System.Diagnostics.Process.Start(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName, args);
+ }
}
}
}
diff --git a/Test/Program.cs b/Test/Program.cs
new file mode 100644
index 0000000..2739294
--- /dev/null
+++ b/Test/Program.cs
@@ -0,0 +1,21 @@
+using System.Text.Json;
+using LibMatrix;
+using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses;
+
+// var src1 = new SynapseCollectionResult<EventIdResponse>();
+// var src2 = new SynapseCollectionResult<EventIdResponse>(chunkKey: "meow", nextTokenKey: "woof", prevTokenKey: "bark", totalKey: "purr");
+//
+// for (int i = 0; i < 10000000; i++) {
+// src1.Chunk.Add(new EventIdResponse { EventId = Guid.NewGuid().ToString() });
+// src2.Chunk.Add(new EventIdResponse { EventId = Guid.NewGuid().ToString() });
+// }
+//
+// File.WriteAllText("src1.json", JsonSerializer.Serialize(src1, new JsonSerializerOptions(){WriteIndented = true}));
+// File.WriteAllText("src2.json", JsonSerializer.Serialize(src2, new JsonSerializerOptions(){WriteIndented = true}));
+
+using var stream1 = File.OpenRead("src1.json");
+var dst1 = new SynapseCollectionResult<EventIdResponse>().FromJson(stream1, (item) => {
+ ArgumentNullException.ThrowIfNull(item.EventId);
+});
+
+var a = new StateEventResponse();
\ No newline at end of file
diff --git a/Test/Test.csproj b/Test/Test.csproj
new file mode 100644
index 0000000..39961c1
--- /dev/null
+++ b/Test/Test.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net8.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\LibMatrix\LibMatrix\LibMatrix.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
+ </ItemGroup>
+
+</Project>
|