Submission wizard - Upload entry icon

Entry lists - Show entry icon
This commit is contained in:
Robert committed 2023-08-16 20:01:28 +02:00
1 parent 6014bb9e3c
commit 176a28761f
11 files changed
+214 -39

No files matched your search

@@ -44,6 +44,7 @@ public static class ContainerExtensions
container.Register<IAuthenticationRepository, AuthenticationRepository>(Reuse.Singleton);
container.Register<IAuthenticationService, AuthenticationService>(Reuse.Singleton);
container.Register<IWorkshopService, WorkshopService>(Reuse.Singleton);
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
@@ -1,5 +1,5 @@
query GetCategories {
categories {
categories(order: {name: ASC}) {
id
name
icon
@@ -1,5 +1,5 @@
query GetEntries($filter: EntryFilterInput $skip: Int $take: Int) {
entries(where: $filter skip: $skip take: $take) {
entries(where: $filter skip: $skip take: $take, order: {createdAt: DESC}) {
totalCount
items {
id
@@ -26,6 +26,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
private readonly BehaviorSubject<bool> _isLoggedInSubject = new(false);
private AuthenticationToken? _token;
private bool _noStoredRefreshToken;
public AuthenticationService(IHttpClientFactory httpClientFactory, IDiscoveryCache discoveryCache, IAuthenticationRepository authenticationRepository)
{
@@ -50,7 +51,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
private void SetCurrentUser(TokenResponse response)
{
_token = new AuthenticationToken(response);
_authenticationRepository.SetRefreshToken(_token.RefreshToken);
SetStoredRefreshToken(_token.RefreshToken);
JwtSecurityTokenHandler handler = new();
JwtSecurityToken? token = handler.ReadJwtToken(response.IdentityToken);
@@ -80,7 +81,10 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
if (response.IsError)
{
if (response.Error is OidcConstants.TokenErrors.ExpiredToken or OidcConstants.TokenErrors.InvalidGrant)
{
SetStoredRefreshToken(null);
return false;
}
throw new ArtemisWebClientException("Failed to request refresh token: " + response.Error);
}
@@ -118,8 +122,9 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
{
// If not logged in, attempt to auto login first
if (!_isLoggedInSubject.Value)
await AutoLogin();
await InternalAutoLogin();
// If there is no token, even after an auto-login, there's no bearer to add
if (_token == null)
return null;
@@ -142,14 +147,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
try
{
if (!force && _isLoggedInSubject.Value)
return true;
string? refreshToken = _authenticationRepository.GetRefreshToken();
if (refreshToken == null)
return false;
return await UseRefreshToken(refreshToken);
return await InternalAutoLogin(force);
}
finally
{
@@ -157,6 +155,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
}
}
/// <inheritdoc />
public async Task Login(CancellationToken cancellationToken)
{
@@ -240,8 +239,36 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
{
_token = null;
_claims.Clear();
_authenticationRepository.SetRefreshToken(null);
SetStoredRefreshToken(null);
_isLoggedInSubject.OnNext(false);
}
private async Task<bool> InternalAutoLogin(bool force = false)
{
if (!force && _isLoggedInSubject.Value)
return true;
if (_noStoredRefreshToken)
return false;
string? refreshToken = GetStoredRefreshToken();
if (refreshToken == null)
return false;
return await UseRefreshToken(refreshToken);
}
private string? GetStoredRefreshToken()
{
string? token = _authenticationRepository.GetRefreshToken();
_noStoredRefreshToken = token == null;
return token;
}
private void SetStoredRefreshToken(string? token)
{
_authenticationRepository.SetRefreshToken(token);
_noStoredRefreshToken = token == null;
}
}
@@ -0,0 +1,101 @@
using System.Net.Http.Headers;
using Artemis.UI.Shared.Services.MainWindow;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.UploadHandlers;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
namespace Artemis.WebClient.Workshop.Services;
public class WorkshopService : IWorkshopService
{
private readonly Dictionary<Guid, Stream> _entryIconCache = new();
private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _iconCacheLock = new(1);
public WorkshopService(IHttpClientFactory httpClientFactory, IMainWindowService mainWindowService)
{
_httpClientFactory = httpClientFactory;
mainWindowService.MainWindowClosed += (_, _) => Dispatcher.UIThread.InvokeAsync(async () =>
{
await Task.Delay(1000);
ClearCache();
});
}
/// <inheritdoc />
public async Task<Bitmap?> GetEntryIcon(Guid entryId, CancellationToken cancellationToken)
{
await _iconCacheLock.WaitAsync(cancellationToken);
try
{
if (_entryIconCache.TryGetValue(entryId, out Stream? cachedBitmap))
{
cachedBitmap.Seek(0, SeekOrigin.Begin);
return new Bitmap(cachedBitmap);
}
}
finally
{
_iconCacheLock.Release();
}
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
try
{
HttpResponseMessage response = await client.GetAsync($"entries/{entryId}/icon", cancellationToken);
response.EnsureSuccessStatusCode();
Stream data = await response.Content.ReadAsStreamAsync(cancellationToken);
_entryIconCache[entryId] = data;
return new Bitmap(data);
}
catch (HttpRequestException)
{
// ignored
return null;
}
}
public async Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken)
{
icon.Seek(0, SeekOrigin.Begin);
// Submit the archive
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
// Construct the request
MultipartFormDataContent content = new();
ProgressableStreamContent streamContent = new(icon, progress);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
content.Add(streamContent, "file", "file.png");
// Submit
HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/icon", content, cancellationToken);
if (!response.IsSuccessStatusCode)
return ImageUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
return ImageUploadResult.FromSuccess();
}
private void ClearCache()
{
try
{
List<Stream> values = _entryIconCache.Values.ToList();
_entryIconCache.Clear();
foreach (Stream bitmap in values)
bitmap.Dispose();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
public interface IWorkshopService
{
Task<Bitmap?> GetEntryIcon(Guid entryId, CancellationToken cancellationToken);
Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
}
@@ -0,0 +1,21 @@
namespace Artemis.WebClient.Workshop.UploadHandlers;
public class ImageUploadResult
{
public bool IsSuccess { get; set; }
public string? Message { get; set; }
public static ImageUploadResult FromFailure(string? message)
{
return new ImageUploadResult
{
IsSuccess = false,
Message = message
};
}
public static ImageUploadResult FromSuccess()
{
return new ImageUploadResult {IsSuccess = true};
}
}