1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

238 lines
8.8 KiB
C#

using System.Net.Http.Headers;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Workshop;
using Artemis.Storage.Repositories.Interfaces;
using Artemis.UI.Shared.Routing;
using Artemis.WebClient.Workshop.Exceptions;
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
using Artemis.WebClient.Workshop.Models;
using Serilog;
namespace Artemis.WebClient.Workshop.Services;
public class WorkshopService : IWorkshopService
{
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IRouter _router;
private readonly IEntryRepository _entryRepository;
private readonly IPluginManagementService _pluginManagementService;
private bool _initialized;
public WorkshopService(ILogger logger, IHttpClientFactory httpClientFactory, IRouter router, IEntryRepository entryRepository, IPluginManagementService pluginManagementService)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_router = router;
_entryRepository = entryRepository;
_pluginManagementService = pluginManagementService;
}
public async Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken)
{
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
try
{
HttpResponseMessage response = await client.GetAsync($"entries/{entryId}/icon", cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStreamAsync(cancellationToken);
}
catch (HttpRequestException)
{
// ignored
return null;
}
}
public async Task<ApiResult> SetEntryIcon(long entryId, 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();
StreamContent streamContent = new(icon);
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 ApiResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
return ApiResult.FromSuccess();
}
/// <inheritdoc />
public async Task<ApiResult> UploadEntryImage(long entryId, ImageUploadRequest request, CancellationToken cancellationToken)
{
request.File.Seek(0, SeekOrigin.Begin);
// Submit the archive
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
// Construct the request
MultipartFormDataContent content = new();
StreamContent streamContent = new(request.File);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
content.Add(streamContent, "file", "file.png");
content.Add(new StringContent(request.Name), "Name");
if (request.Description != null)
content.Add(new StringContent(request.Description), "Description");
// Submit
HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/image", content, cancellationToken);
if (!response.IsSuccessStatusCode)
return ApiResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}");
return ApiResult.FromSuccess();
}
/// <inheritdoc />
public async Task DeleteEntryImage(Guid id, CancellationToken cancellationToken)
{
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
HttpResponseMessage response = await client.DeleteAsync($"images/manage/{id}", cancellationToken);
response.EnsureSuccessStatusCode();
}
/// <inheritdoc />
public async Task<IWorkshopService.WorkshopStatus> GetWorkshopStatus(CancellationToken cancellationToken)
{
try
{
// Don't use the workshop client which adds auth headers
HttpClient client = _httpClientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, WorkshopConstants.WORKSHOP_URL + "/status"), cancellationToken);
return new IWorkshopService.WorkshopStatus(response.IsSuccessStatusCode, response.StatusCode.ToString());
}
catch (OperationCanceledException e)
{
return new IWorkshopService.WorkshopStatus(false, e.Message);
}
catch (HttpRequestException e)
{
return new IWorkshopService.WorkshopStatus(false, e.Message);
}
}
/// <inheritdoc />
public async Task<bool> ValidateWorkshopStatus(CancellationToken cancellationToken)
{
IWorkshopService.WorkshopStatus status = await GetWorkshopStatus(cancellationToken);
if (!status.IsReachable && !cancellationToken.IsCancellationRequested)
await _router.Navigate($"workshop/offline/{status.Message}");
return status.IsReachable;
}
/// <inheritdoc />
public async Task NavigateToEntry(long entryId, EntryType entryType)
{
switch (entryType)
{
case EntryType.Profile:
await _router.Navigate($"workshop/entries/profiles/details/{entryId}");
break;
case EntryType.Layout:
await _router.Navigate($"workshop/entries/layouts/details/{entryId}");
break;
case EntryType.Plugin:
await _router.Navigate($"workshop/entries/plugins/details/{entryId}");
break;
default:
throw new ArgumentOutOfRangeException(nameof(entryType));
}
}
/// <inheritdoc />
public List<InstalledEntry> GetInstalledEntries()
{
return _entryRepository.GetAll().Select(e => new InstalledEntry(e)).ToList();
}
/// <inheritdoc />
public InstalledEntry? GetInstalledEntry(long entryId)
{
EntryEntity? entity = _entryRepository.GetByEntryId(entryId);
if (entity == null)
return null;
return new InstalledEntry(entity);
}
/// <inheritdoc />
public void RemoveInstalledEntry(InstalledEntry installedEntry)
{
_entryRepository.Remove(installedEntry.Entity);
}
/// <inheritdoc />
public void SaveInstalledEntry(InstalledEntry entry)
{
entry.Save();
_entryRepository.Save(entry.Entity);
OnInstalledEntrySaved?.Invoke(this, entry);
}
/// <inheritdoc />
public void Initialize()
{
if (_initialized)
throw new ArtemisWorkshopException("Workshop service is already initialized");
try
{
if (!Directory.Exists(Constants.WorkshopFolder))
Directory.CreateDirectory(Constants.WorkshopFolder);
RemoveOrphanedFiles();
_pluginManagementService.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
.Where(e => e.EntryType == EntryType.Plugin)
.Select(e => e.GetReleaseDirectory()));
_initialized = true;
}
catch (Exception e)
{
_logger.Error(e, "Failed to initialize workshop service");
}
}
private void RemoveOrphanedFiles()
{
List<InstalledEntry> entries = GetInstalledEntries();
foreach (string directory in Directory.GetDirectories(Constants.WorkshopFolder))
{
InstalledEntry? installedEntry = entries.FirstOrDefault(e => e.GetDirectory().FullName == directory);
if (installedEntry == null)
RemoveOrphanedDirectory(directory);
else
{
DirectoryInfo currentReleaseDirectory = installedEntry.GetReleaseDirectory();
foreach (string releaseDirectory in Directory.GetDirectories(directory))
{
if (releaseDirectory != currentReleaseDirectory.FullName)
RemoveOrphanedDirectory(releaseDirectory);
}
}
}
}
private void RemoveOrphanedDirectory(string directory)
{
_logger.Information("Removing orphaned workshop entry at {Directory}", directory);
try
{
Directory.Delete(directory, true);
}
catch (Exception e)
{
_logger.Warning(e, "Failed to remove orphaned workshop entry at {Directory}", directory);
}
}
public event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
}