mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Core - Added API for retrieving current suspended device providers
Core - Added events for plugin removal, entry installlation/uninstallation Workshop - Remove the related workshop entry when manually removing a plugin or profile Workshop - Prevent installing profiles with missing plugins and show a dialog with which plugins are missing
This commit is contained in:
1 parent
86f78940b1
commit
648b7765ef
20 files changed
+440
-106
No files matched your search
+7
-7
@@ -58,7 +58,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
|
||||
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "Workshop") ?? _profileService.CreateProfileCategory("Workshop", true);
|
||||
ProfileConfiguration imported = await _profileService.ImportProfile(stream, category, true, true, null);
|
||||
installedEntry.SetMetadata("ProfileId", imported.ProfileId);
|
||||
|
||||
|
||||
// Update the release and return the profile configuration
|
||||
UpdateRelease(installedEntry, release);
|
||||
return EntryInstallResult.FromSuccess(installedEntry);
|
||||
@@ -66,17 +66,17 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
|
||||
|
||||
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!installedEntry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||
return EntryUninstallResult.FromFailure("Local reference does not contain a GUID");
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find the profile if still there
|
||||
ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
|
||||
if (profile != null)
|
||||
_profileService.RemoveProfileConfiguration(profile);
|
||||
if (installedEntry.TryGetMetadata("ProfileId", out Guid profileId))
|
||||
{
|
||||
ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
|
||||
if (profile != null)
|
||||
_profileService.RemoveProfileConfiguration(profile);
|
||||
}
|
||||
|
||||
// Remove the release
|
||||
_workshopService.RemoveInstalledEntry(installedEntry);
|
||||
|
||||
@@ -18,7 +18,10 @@ public class WorkshopLayoutProvider : ILayoutProvider
|
||||
/// <inheritdoc />
|
||||
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
|
||||
{
|
||||
InstalledEntry? layoutEntry = _workshopService.GetInstalledEntries().FirstOrDefault(e => e.EntryId.ToString() == device.LayoutSelection.Parameter);
|
||||
if (!long.TryParse(device.LayoutSelection.Parameter, out long entryId))
|
||||
return null;
|
||||
|
||||
InstalledEntry? layoutEntry = _workshopService.GetInstalledEntry(entryId);
|
||||
if (layoutEntry == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -1,25 +1,136 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
|
||||
namespace Artemis.WebClient.Workshop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an interface for managing workshop services.
|
||||
/// </summary>
|
||||
public interface IWorkshopService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the icon for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A stream containing the icon.</returns>
|
||||
Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the icon for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <param name="icon">The stream containing the icon.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An API result.</returns>
|
||||
Task<ApiResult> SetEntryIcon(long entryId, Stream icon, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads an image for a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <param name="request">The image upload request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An API result.</returns>
|
||||
Task<ApiResult> UploadEntryImage(long entryId, ImageUploadRequest request, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an image by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the image.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task DeleteEntryImage(Guid id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the workshop.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The status of the workshop.</returns>
|
||||
Task<WorkshopStatus> GetWorkshopStatus(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Validates the status of the workshop.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A boolean indicating whether the workshop is reachable.</returns>
|
||||
Task<bool> ValidateWorkshopStatus(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <param name="entryType">The type of the entry.</param>
|
||||
Task NavigateToEntry(long entryId, EntryType entryType);
|
||||
|
||||
/// <summary>
|
||||
/// Installs a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry to install.</param>
|
||||
/// <param name="release">The release of the entry.</param>
|
||||
/// <param name="progress">The progress of the installation.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<EntryInstallResult> InstallEntry(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls a specific entry.
|
||||
/// </summary>
|
||||
/// <param name="installedEntry">The installed entry to uninstall.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<EntryUninstallResult> UninstallEntry(InstalledEntry installedEntry, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all installed entries.
|
||||
/// </summary>
|
||||
/// <returns>A list of all installed entries.</returns>
|
||||
List<InstalledEntry> GetInstalledEntries();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific installed entry.
|
||||
/// </summary>
|
||||
/// <param name="entryId">The ID of the entry.</param>
|
||||
/// <returns>The installed entry.</returns>
|
||||
InstalledEntry? GetInstalledEntry(long entryId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installed plugin entry for a specific plugin.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
/// <returns>The installed entry.</returns>
|
||||
InstalledEntry? GetInstalledEntryByPlugin(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installed plugin entry for a specific profile.
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile.</param>
|
||||
/// <returns>The installed entry.</returns>
|
||||
InstalledEntry? GetInstalledEntryByProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific installed entry for storage.
|
||||
/// </summary>
|
||||
/// <param name="installedEntry">The installed entry to remove.</param>
|
||||
void RemoveInstalledEntry(InstalledEntry installedEntry);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a specific installed entry to storage.
|
||||
/// </summary>
|
||||
/// <param name="entry">The installed entry to save.</param>
|
||||
void SaveInstalledEntry(InstalledEntry entry);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the workshop service.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of the workshop.
|
||||
/// </summary>
|
||||
public record WorkshopStatus(bool IsReachable, string Message);
|
||||
|
||||
event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
|
||||
|
||||
public event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
|
||||
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
||||
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
||||
}
|
||||
@@ -4,7 +4,9 @@ using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Workshop;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.WebClient.Workshop.Exceptions;
|
||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Serilog;
|
||||
@@ -17,16 +19,26 @@ public class WorkshopService : IWorkshopService
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IRouter _router;
|
||||
private readonly IEntryRepository _entryRepository;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly Lazy<IPluginManagementService> _pluginManagementService;
|
||||
private readonly Lazy<IProfileService> _profileService;
|
||||
private readonly EntryInstallationHandlerFactory _factory;
|
||||
private bool _initialized;
|
||||
|
||||
public WorkshopService(ILogger logger, IHttpClientFactory httpClientFactory, IRouter router, IEntryRepository entryRepository, IPluginManagementService pluginManagementService)
|
||||
public WorkshopService(ILogger logger,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IRouter router,
|
||||
IEntryRepository entryRepository,
|
||||
Lazy<IPluginManagementService> pluginManagementService,
|
||||
Lazy<IProfileService> profileService,
|
||||
EntryInstallationHandlerFactory factory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_router = router;
|
||||
_entryRepository = entryRepository;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_profileService = profileService;
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<Stream?> GetEntryIcon(long entryId, CancellationToken cancellationToken)
|
||||
@@ -145,6 +157,32 @@ public class WorkshopService : IWorkshopService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntryInstallResult> InstallEntry(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
IEntryInstallationHandler handler = _factory.CreateHandler(entry.EntryType);
|
||||
EntryInstallResult result = await handler.InstallAsync(entry, release, progress, cancellationToken);
|
||||
if (result.IsSuccess && result.Entry != null)
|
||||
OnEntryInstalled?.Invoke(this, result.Entry);
|
||||
else
|
||||
_logger.Warning("Failed to install entry {EntryId}: {Message}", entry.Id, result.Message);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EntryUninstallResult> UninstallEntry(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||
{
|
||||
IEntryInstallationHandler handler = _factory.CreateHandler(installedEntry.EntryType);
|
||||
EntryUninstallResult result = await handler.UninstallAsync(installedEntry, cancellationToken);
|
||||
if (result.IsSuccess)
|
||||
OnEntryUninstalled?.Invoke(this, installedEntry);
|
||||
else
|
||||
_logger.Warning("Failed to uninstall entry {EntryId}: {Message}", installedEntry.EntryId, result.Message);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<InstalledEntry> GetInstalledEntries()
|
||||
{
|
||||
@@ -161,6 +199,18 @@ public class WorkshopService : IWorkshopService
|
||||
return new InstalledEntry(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public InstalledEntry? GetInstalledEntryByPlugin(Plugin plugin)
|
||||
{
|
||||
return GetInstalledEntries().FirstOrDefault(e => e.TryGetMetadata("PluginId", out Guid pluginId) && pluginId == plugin.Guid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public InstalledEntry? GetInstalledEntryByProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
return GetInstalledEntries().FirstOrDefault(e => e.TryGetMetadata("ProfileId", out Guid pluginId) && pluginId == profileConfiguration.ProfileId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveInstalledEntry(InstalledEntry installedEntry)
|
||||
{
|
||||
@@ -172,7 +222,7 @@ public class WorkshopService : IWorkshopService
|
||||
{
|
||||
entry.Save();
|
||||
_entryRepository.Save(entry.Entity);
|
||||
|
||||
|
||||
OnInstalledEntrySaved?.Invoke(this, entry);
|
||||
}
|
||||
|
||||
@@ -189,10 +239,13 @@ public class WorkshopService : IWorkshopService
|
||||
|
||||
RemoveOrphanedFiles();
|
||||
|
||||
_pluginManagementService.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
|
||||
_pluginManagementService.Value.AdditionalPluginDirectories.AddRange(GetInstalledEntries()
|
||||
.Where(e => e.EntryType == EntryType.Plugin)
|
||||
.Select(e => e.GetReleaseDirectory()));
|
||||
|
||||
_pluginManagementService.Value.PluginRemoved += PluginManagementServiceOnPluginRemoved;
|
||||
_profileService.Value.ProfileRemoved += ProfileServiceOnProfileRemoved;
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -233,6 +286,28 @@ public class WorkshopService : IWorkshopService
|
||||
_logger.Warning(e, "Failed to remove orphaned workshop entry at {Directory}", directory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProfileServiceOnProfileRemoved(object? sender, ProfileConfigurationEventArgs e)
|
||||
{
|
||||
InstalledEntry? entry = GetInstalledEntryByProfile(e.ProfileConfiguration);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
_logger.Information("Profile {Profile} was removed, uninstalling entry", e.ProfileConfiguration);
|
||||
Task.Run(() => UninstallEntry(entry, CancellationToken.None));
|
||||
}
|
||||
|
||||
private void PluginManagementServiceOnPluginRemoved(object? sender, PluginEventArgs e)
|
||||
{
|
||||
InstalledEntry? entry = GetInstalledEntryByPlugin(e.Plugin);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
_logger.Information("Plugin {Plugin} was removed, uninstalling entry", e.Plugin);
|
||||
Task.Run(() => UninstallEntry(entry, CancellationToken.None));
|
||||
}
|
||||
|
||||
public event EventHandler<InstalledEntry>? OnInstalledEntrySaved;
|
||||
public event EventHandler<InstalledEntry>? OnEntryUninstalled;
|
||||
public event EventHandler<InstalledEntry>? OnEntryInstalled;
|
||||
}
|
||||
Reference in new issue
Block a user