mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Workshop - Implemented plugin browsing, installation and removal
This commit is contained in:
1 parent
d9df443970
commit
21b8112de5
21 files changed
+410
-35
No files matched your search
+1
@@ -15,6 +15,7 @@ public class EntryInstallationHandlerFactory
|
||||
{
|
||||
return entryType switch
|
||||
{
|
||||
EntryType.Plugin => _container.Resolve<PluginEntryInstallationHandler>(),
|
||||
EntryType.Profile => _container.Resolve<ProfileEntryInstallationHandler>(),
|
||||
EntryType.Layout => _container.Resolve<LayoutEntryInstallationHandler>(),
|
||||
_ => throw new NotSupportedException($"EntryType '{entryType}' is not supported.")
|
||||
|
||||
+3
-2
@@ -14,11 +14,12 @@ public class EntryUninstallResult
|
||||
};
|
||||
}
|
||||
|
||||
public static EntryUninstallResult FromSuccess()
|
||||
public static EntryUninstallResult FromSuccess(string? message = null)
|
||||
{
|
||||
return new EntryUninstallResult
|
||||
{
|
||||
IsSuccess = true
|
||||
IsSuccess = true,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System.IO.Compression;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Extensions;
|
||||
using Artemis.UI.Shared.Utilities;
|
||||
using Artemis.WebClient.Workshop.Exceptions;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
|
||||
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
|
||||
public class PluginEntryInstallationHandler : IEntryInstallationHandler
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
|
||||
public PluginEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService, IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_workshopService = workshopService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
}
|
||||
|
||||
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
// Ensure there is an installed entry
|
||||
InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry);
|
||||
if (installedEntry != null)
|
||||
{
|
||||
// If the folder already exists, we're not going to reinstall the plugin since files may be in use, consider our job done
|
||||
if (installedEntry.GetReleaseDirectory(release).Exists)
|
||||
return EntryInstallResult.FromSuccess(installedEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If none exists yet create one
|
||||
installedEntry = new InstalledEntry(entry, release);
|
||||
// Don't try to install a new plugin into an existing directory since files may be in use, consider our job screwed
|
||||
if (installedEntry.GetReleaseDirectory(release).Exists)
|
||||
return EntryInstallResult.FromFailure("Plugin is new but installation directory is not empty, try restarting Artemis");
|
||||
}
|
||||
|
||||
using MemoryStream stream = new();
|
||||
|
||||
// Download the provided release
|
||||
try
|
||||
{
|
||||
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||
await client.DownloadDataAsync($"releases/download/{release.Id}", stream, progress, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return EntryInstallResult.FromFailure(e.Message);
|
||||
}
|
||||
|
||||
// Create the release directory
|
||||
DirectoryInfo releaseDirectory = installedEntry.GetReleaseDirectory(release);
|
||||
releaseDirectory.Create();
|
||||
|
||||
// Extract the archive
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
using ZipArchive archive = new(stream);
|
||||
archive.ExtractToDirectory(releaseDirectory.FullName);
|
||||
|
||||
// Load the plugin, next time during startup this will happen automatically
|
||||
try
|
||||
{
|
||||
Plugin? plugin = _pluginManagementService.LoadPlugin(releaseDirectory);
|
||||
if (plugin == null)
|
||||
throw new ArtemisWorkshopException("Failed to load plugin, it may be incompatible");
|
||||
|
||||
installedEntry.SetMetadata("PluginId", plugin.Guid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If the plugin ended up being invalid yoink it out again, shoooo
|
||||
try
|
||||
{
|
||||
releaseDirectory.Delete(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored, will get cleaned up as an orphaned file
|
||||
}
|
||||
|
||||
_workshopService.RemoveInstalledEntry(installedEntry);
|
||||
return EntryInstallResult.FromFailure(e.Message);
|
||||
}
|
||||
|
||||
_workshopService.SaveInstalledEntry(installedEntry);
|
||||
return EntryInstallResult.FromSuccess(installedEntry);
|
||||
}
|
||||
|
||||
public Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
|
||||
{
|
||||
// Disable the plugin
|
||||
if (installedEntry.TryGetMetadata("PluginId", out Guid pluginId))
|
||||
{
|
||||
Plugin? plugin = _pluginManagementService.GetAllPlugins().FirstOrDefault(p => p.Guid == pluginId);
|
||||
if (plugin != null)
|
||||
_pluginManagementService.UnloadPlugin(plugin);
|
||||
}
|
||||
|
||||
// Attempt to remove from filesystem
|
||||
DirectoryInfo directory = installedEntry.GetDirectory();
|
||||
string? message = null;
|
||||
try
|
||||
{
|
||||
if (directory.Exists)
|
||||
directory.Delete(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
message = "Failed to clean up files, you may need to restart Artemis";
|
||||
}
|
||||
|
||||
// Remove entry
|
||||
_workshopService.RemoveInstalledEntry(installedEntry);
|
||||
return Task.FromResult(EntryUninstallResult.FromSuccess(message));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ public interface IWorkshopService
|
||||
InstalledEntry? GetInstalledEntry(IEntryDetails entry);
|
||||
void RemoveInstalledEntry(InstalledEntry installedEntry);
|
||||
void SaveInstalledEntry(InstalledEntry entry);
|
||||
void RemoveOrphanedFiles();
|
||||
|
||||
public record WorkshopStatus(bool IsReachable, string Message);
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
using System.Net.Http.Headers;
|
||||
using Artemis.Core;
|
||||
using Artemis.Storage.Entities.Workshop;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
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;
|
||||
|
||||
public WorkshopService(IHttpClientFactory httpClientFactory, IRouter router, IEntryRepository entryRepository)
|
||||
public WorkshopService(ILogger logger, IHttpClientFactory httpClientFactory, IRouter router, IEntryRepository entryRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_router = router;
|
||||
_entryRepository = entryRepository;
|
||||
@@ -117,6 +121,7 @@ public class WorkshopService : IWorkshopService
|
||||
return status.IsReachable;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task NavigateToEntry(long entryId, EntryType entryType)
|
||||
{
|
||||
switch (entryType)
|
||||
@@ -135,6 +140,7 @@ public class WorkshopService : IWorkshopService
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<InstalledEntry> GetInstalledEntries()
|
||||
{
|
||||
return _entryRepository.GetAll().Select(e => new InstalledEntry(e)).ToList();
|
||||
@@ -150,12 +156,6 @@ public class WorkshopService : IWorkshopService
|
||||
return new InstalledEntry(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddOrUpdateInstalledEntry(InstalledEntry entry, IRelease release)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveInstalledEntry(InstalledEntry installedEntry)
|
||||
{
|
||||
@@ -168,4 +168,38 @@ public class WorkshopService : IWorkshopService
|
||||
entry.Save();
|
||||
_entryRepository.Save(entry.Entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ namespace Artemis.WebClient.Workshop;
|
||||
|
||||
public static class WorkshopConstants
|
||||
{
|
||||
// public const string AUTHORITY_URL = "https://localhost:5001";
|
||||
// public const string WORKSHOP_URL = "https://localhost:7281";
|
||||
public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
||||
public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
||||
public const string AUTHORITY_URL = "https://localhost:5001";
|
||||
public const string WORKSHOP_URL = "https://localhost:7281";
|
||||
// public const string AUTHORITY_URL = "https://identity.artemis-rgb.com";
|
||||
// public const string WORKSHOP_URL = "https://workshop.artemis-rgb.com";
|
||||
public const string IDENTITY_CLIENT_NAME = "IdentityApiClient";
|
||||
public const string WORKSHOP_CLIENT_NAME = "WorkshopApiClient";
|
||||
}
|
||||
Reference in new issue
Block a user