diff --git a/ModerationClient/Services/CommandLineConfiguration.cs b/ModerationClient/Services/CommandLineConfiguration.cs
new file mode 100644
index 0000000..4debd5c
--- /dev/null
+++ b/ModerationClient/Services/CommandLineConfiguration.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using ArcaneLibs;
+using Microsoft.Extensions.Logging;
+
+namespace ModerationClient.Services;
+
+public class CommandLineConfiguration {
+ public CommandLineConfiguration(ILogger<CommandLineConfiguration> logger) {
+ var args = Environment.GetCommandLineArgs();
+ logger.LogInformation("Command line arguments: " + string.Join(", ", args));
+ for (var i = 0; i < args.Length; i++) {
+ logger.LogInformation("Processing argument: " + args[i]);
+ switch (args[i]) {
+ case "--profile":
+ case "-p":
+ if (args.Length <= i + 1 || args[i + 1].StartsWith("-")) {
+ throw new ArgumentException("No profile specified");
+ }
+
+ Profile = args[++i];
+ logger.LogInformation("Set profile to: " + Profile);
+ break;
+ case "--temporary":
+ IsTemporary = true;
+ logger.LogInformation("Using temporary profile");
+ break;
+ case "--profile-dir":
+ ProfileDirectory = args[++i];
+ break;
+ case "--scale":
+ Scale = float.Parse(args[++i]);
+ break;
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(ProfileDirectory))
+ ProfileDirectory = IsTemporary
+ ? Directory.CreateTempSubdirectory("ModerationClient-tmp").FullName
+ : Util.ExpandPath($"$HOME/.local/share/ModerationClient/{Profile}");
+
+ logger.LogInformation("Profile directory: " + ProfileDirectory);
+ Directory.CreateDirectory(ProfileDirectory);
+ }
+
+ public string Profile { get; private set; } = "default";
+ public bool IsTemporary { get; private set; }
+
+ public string ProfileDirectory { get; private set; }
+ public float Scale { get; private set; } = 1f;
+}
\ No newline at end of file
diff --git a/ModerationClient/Services/FileStorageProvider.cs b/ModerationClient/Services/FileStorageProvider.cs
new file mode 100644
index 0000000..3658369
--- /dev/null
+++ b/ModerationClient/Services/FileStorageProvider.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+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;
+
+public class FileStorageProvider : IStorageProvider {
+ private readonly ILogger<FileStorageProvider> _logger;
+
+ public string TargetPath { get; }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="FileStorageProvider" />.
+ /// </summary>
+ /// <param name="targetPath"></param>
+ public FileStorageProvider(string targetPath) {
+ // new Logger<FileStorageProvider>(new LoggerFactory()).LogInformation("test");
+ Console.WriteLine($"Initialised FileStorageProvider with path {targetPath}");
+ TargetPath = targetPath;
+ if (!Directory.Exists(targetPath)) {
+ Directory.CreateDirectory(targetPath);
+ }
+ }
+
+ public async Task SaveObjectAsync<T>(string key, T value) => await File.WriteAllTextAsync(Path.Join(TargetPath, key), value?.ToJson());
+
+ [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 Task<bool> ObjectExistsAsync(string key) => Task.FromResult(File.Exists(Path.Join(TargetPath, key)));
+
+ public Task<List<string>> GetAllKeysAsync() => Task.FromResult(Directory.GetFiles(TargetPath).Select(Path.GetFileName).ToList());
+
+ public Task DeleteObjectAsync(string key) {
+ File.Delete(Path.Join(TargetPath, 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));
+ 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);
+}
diff --git a/ModerationClient/Services/MatrixAuthenticationService.cs b/ModerationClient/Services/MatrixAuthenticationService.cs
new file mode 100644
index 0000000..69f8810
--- /dev/null
+++ b/ModerationClient/Services/MatrixAuthenticationService.cs
@@ -0,0 +1,56 @@
+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;
+
+public class MatrixAuthenticationService(ILogger<MatrixAuthenticationService> logger, HomeserverProviderService hsProvider, CommandLineConfiguration cfg) : NotifyPropertyChanged {
+ private bool _isLoggedIn = false;
+ public string Profile => cfg.Profile;
+ public AuthenticatedHomeserverGeneric? Homeserver { get; private set; }
+
+ public bool IsLoggedIn {
+ get => _isLoggedIn;
+ private set => SetField(ref _isLoggedIn, value);
+ }
+
+ public async Task LoadProfileAsync() {
+ if (!File.Exists(Util.ExpandPath($"{cfg.ProfileDirectory}/login.json")!)) return;
+ var loginJson = await File.ReadAllTextAsync(Util.ExpandPath($"{cfg.ProfileDirectory}/login.json")!);
+ var login = JsonSerializer.Deserialize<LoginResponse>(loginJson);
+ if (login is null) return;
+ try {
+ Homeserver = await hsProvider.GetAuthenticatedWithToken(login.Homeserver, login.AccessToken);
+ IsLoggedIn = true;
+ }
+ catch (MatrixException e) {
+ if (e is not { Error: MatrixException.ErrorCodes.M_UNKNOWN_TOKEN }) throw;
+ }
+ }
+
+ public async Task LoginAsync(string username, string password) {
+ Directory.CreateDirectory(Util.ExpandPath($"{cfg.ProfileDirectory}")!);
+ var mxidParts = username.Split(':', 2);
+ var res = await hsProvider.Login(mxidParts[1], username, password);
+ await File.WriteAllTextAsync(Path.Combine(cfg.ProfileDirectory, "login.json"), res.ToJson());
+ IsLoggedIn = true;
+
+ // Console.WriteLine("Login result: " + res.ToJson());
+ }
+
+ public async Task LogoutAsync() {
+ Directory.Delete(Util.ExpandPath($"{cfg.ProfileDirectory}")!, true);
+ IsLoggedIn = false;
+ }
+}
\ No newline at end of file
diff --git a/ModerationClient/Services/ModerationClientConfiguration.cs b/ModerationClient/Services/ModerationClientConfiguration.cs
new file mode 100644
index 0000000..f770fef
--- /dev/null
+++ b/ModerationClient/Services/ModerationClientConfiguration.cs
@@ -0,0 +1,31 @@
+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;
+
+namespace MatrixUtils.Desktop;
+
+public class ModerationClientConfiguration
+{
+ private ILogger<ModerationClientConfiguration> _logger;
+
+ [RequiresUnreferencedCode("Uses reflection binding")]
+ public ModerationClientConfiguration(ILogger<ModerationClientConfiguration> logger, IConfiguration config, HostBuilderContext host)
+ {
+ _logger = logger;
+ logger.LogInformation("Loading configuration for environment: {}...", host.HostingEnvironment.EnvironmentName);
+ config.GetSection("ModerationClient").Bind(this);
+ DataStoragePath = Util.ExpandPath(DataStoragePath);
+ CacheStoragePath = Util.ExpandPath(CacheStoragePath);
+ }
+
+ public string? DataStoragePath { get; set; } = "";
+ public string? CacheStoragePath { get; set; } = "";
+ public string? SentryDsn { get; set; }
+}
\ No newline at end of file
|