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 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 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(); } /// public async Task 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(); } /// 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(); } /// public async Task 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); } } /// public async Task ValidateWorkshopStatus(CancellationToken cancellationToken) { IWorkshopService.WorkshopStatus status = await GetWorkshopStatus(cancellationToken); if (!status.IsReachable && !cancellationToken.IsCancellationRequested) await _router.Navigate($"workshop/offline/{status.Message}"); return status.IsReachable; } /// 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)); } } /// public List GetInstalledEntries() { return _entryRepository.GetAll().Select(e => new InstalledEntry(e)).ToList(); } /// public InstalledEntry? GetInstalledEntry(IEntryDetails entry) { EntryEntity? entity = _entryRepository.GetByEntryId(entry.Id); if (entity == null) return null; return new InstalledEntry(entity); } /// public void RemoveInstalledEntry(InstalledEntry installedEntry) { _entryRepository.Remove(installedEntry.Entity); } /// public void SaveInstalledEntry(InstalledEntry entry) { entry.Save(); _entryRepository.Save(entry.Entity); } /// public void Initialize() { if (_initialized) throw new ArtemisWorkshopException("Workshop service is already initialized"); RemoveOrphanedFiles(); _pluginManagementService.AdditionalPluginDirectories.AddRange(GetInstalledEntries().Where(e => e.EntryType == EntryType.Plugin).Select(e => e.GetReleaseDirectory())); _initialized = true; } private void RemoveOrphanedFiles() { List 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); } } }