mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Submission wizard - Upload entry icon
Entry lists - Show entry icon
This commit is contained in:
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};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user