Workshop - Implemented workshop installation, layout selection and removal

This commit is contained in:
RobertBeekman committed 2024-01-14 12:46:51 +01:00
1 parent dad6a56238
commit 28edabae89
23 files changed
+226 -168

No files matched your search

@@ -5,6 +5,6 @@ namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
public interface IEntryInstallationHandler
{
Task<EntryInstallResult> InstallAsync(IGetEntryById_Entry entry, long releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken);
Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken);
Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken);
}
@@ -1,10 +1,10 @@
using System.Data;
using System.IO.Compression;
using System.IO.Compression;
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Providers;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
@@ -14,15 +14,17 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
private readonly IHttpClientFactory _httpClientFactory;
private readonly IWorkshopService _workshopService;
private readonly IDeviceService _deviceService;
private readonly DefaultLayoutProvider _defaultLayoutProvider;
public LayoutEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService, IDeviceService deviceService)
public LayoutEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService, IDeviceService deviceService, DefaultLayoutProvider defaultLayoutProvider)
{
_httpClientFactory = httpClientFactory;
_workshopService = workshopService;
_deviceService = deviceService;
_defaultLayoutProvider = defaultLayoutProvider;
}
public async Task<EntryInstallResult> InstallAsync(IGetEntryById_Entry entry, long releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken)
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
{
using MemoryStream stream = new();
@@ -30,7 +32,7 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
try
{
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
await client.DownloadDataAsync($"releases/download/{releaseId}", stream, progress, cancellationToken);
await client.DownloadDataAsync($"releases/download/{release.Id}", stream, progress, cancellationToken);
}
catch (Exception e)
{
@@ -38,8 +40,8 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
}
// Ensure there is an installed entry
InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? _workshopService.CreateInstalledEntry(entry);
DirectoryInfo entryDirectory = installedEntry.GetDirectory();
InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? new InstalledEntry(entry, release);
DirectoryInfo entryDirectory = installedEntry.GetReleaseDirectory(release);
// If the folder already exists, remove it so that if the layout now contains less files, old things dont stick around
if (entryDirectory.Exists)
@@ -53,7 +55,11 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
ArtemisLayout layout = new(Path.Combine(entryDirectory.FullName, "layout.xml"));
if (layout.IsValid)
{
installedEntry.ApplyRelease(release);
_workshopService.SaveInstalledEntry(installedEntry);
return EntryInstallResult.FromSuccess(layout);
}
// If the layout ended up being invalid yoink it out again, shoooo
entryDirectory.Delete(true);
@@ -61,21 +67,26 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
return EntryInstallResult.FromFailure("Layout failed to load because it is invalid");
}
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
public Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
{
// Remove the layout from any devices currently using it
foreach (ArtemisDevice device in _deviceService.Devices)
{
if (device.LayoutSelection.Type == WorkshopLayoutProvider.LayoutType && device.LayoutSelection.Parameter == installedEntry.EntryId.ToString())
{
_defaultLayoutProvider.ConfigureDevice(device);
_deviceService.SaveDevice(device);
_deviceService.LoadDeviceLayout(device);
}
}
// Remove from filesystem
DirectoryInfo directory = installedEntry.GetDirectory(true);
DirectoryInfo directory = installedEntry.GetDirectory();
if (directory.Exists)
directory.Delete();
directory.Delete(true);
// Remove entry
_workshopService.RemoveInstalledEntry(installedEntry);
return EntryUninstallResult.FromSuccess();
return Task.FromResult(EntryUninstallResult.FromSuccess());
}
}
@@ -19,7 +19,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
_workshopService = workshopService;
}
public async Task<EntryInstallResult> InstallAsync(IGetEntryById_Entry entry, long releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken)
public async Task<EntryInstallResult> InstallAsync(IEntryDetails entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
{
using MemoryStream stream = new();
@@ -27,7 +27,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
try
{
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
await client.DownloadDataAsync($"releases/download/{releaseId}", stream, progress, cancellationToken);
await client.DownloadDataAsync($"releases/download/{release}", stream, progress, cancellationToken);
}
catch (Exception e)
{
@@ -45,13 +45,13 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
installedEntry.SetMetadata("ProfileId", overwritten.ProfileId);
// Update the release and return the profile configuration
UpdateRelease(releaseId, installedEntry);
UpdateRelease(installedEntry, release);
return EntryInstallResult.FromSuccess(overwritten);
}
}
// Ensure there is an installed entry
installedEntry ??= _workshopService.CreateInstalledEntry(entry);
installedEntry ??= new InstalledEntry(entry, release);
// Add the profile as a fresh import
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "Workshop") ?? _profileService.CreateProfileCategory("Workshop", true);
@@ -59,7 +59,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
installedEntry.SetMetadata("ProfileId", imported.ProfileId);
// Update the release and return the profile configuration
UpdateRelease(releaseId, installedEntry);
UpdateRelease(installedEntry, release);
return EntryInstallResult.FromSuccess(imported);
}
@@ -89,11 +89,9 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
}, cancellationToken);
}
private void UpdateRelease(long releaseId, InstalledEntry installedEntry)
private void UpdateRelease(InstalledEntry installedEntry, IRelease release)
{
installedEntry.ReleaseId = releaseId;
installedEntry.ReleaseVersion = "TODO";
installedEntry.InstalledAt = DateTimeOffset.UtcNow;
installedEntry.ApplyRelease(release);
_workshopService.SaveInstalledEntry(installedEntry);
}
}
@@ -1,6 +1,5 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.WebClient.Workshop.Providers;
@@ -8,34 +7,31 @@ namespace Artemis.WebClient.Workshop.Providers;
public class WorkshopLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Workshop";
private readonly IDeviceService _deviceService;
private readonly IWorkshopService _workshopService;
public WorkshopLayoutProvider(IDeviceService deviceService, IWorkshopService workshopService)
public WorkshopLayoutProvider(IWorkshopService workshopService)
{
_deviceService = deviceService;
_workshopService = workshopService;
}
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
InstalledEntry? layoutEntry = _workshopService.GetInstalledEntries().FirstOrDefault(e => e.EntryType == EntryType.Layout && MatchesDevice(e, device));
InstalledEntry? layoutEntry = _workshopService.GetInstalledEntries().FirstOrDefault(e => e.EntryId.ToString() == device.LayoutSelection.Parameter);
if (layoutEntry == null)
return null;
string layoutPath = Path.Combine(Constants.WorkshopFolder, layoutEntry.EntryId.ToString(), "layout.xml");
string layoutPath = Path.Combine(layoutEntry.GetReleaseDirectory().FullName, "layout.xml");
if (!File.Exists(layoutPath))
return null;
return new ArtemisLayout(layoutPath);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
throw new NotImplementedException();
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
@@ -44,20 +40,17 @@ public class WorkshopLayoutProvider : ILayoutProvider
return device.LayoutSelection.Type == LayoutType;
}
private bool MatchesDevice(InstalledEntry entry, ArtemisDevice device)
{
return entry.TryGetMetadata("DeviceId", out HashSet<string>? deviceIds) && deviceIds.Contains(device.Identifier);
}
/// <summary>
/// Configures the provided device to use this layout provider.
/// </summary>
/// <param name="device">The device to apply the provider to.</param>
public void ConfigureDevice(ArtemisDevice device)
/// <param name="entry">The workshop entry to use as a layout.</param>
public void ConfigureDevice(ArtemisDevice device, InstalledEntry? entry)
{
if (entry != null && entry.EntryType != EntryType.Layout)
throw new InvalidOperationException($"Cannot use a workshop entry of type {entry.EntryType} as a layout");
device.LayoutSelection.Type = LayoutType;
device.LayoutSelection.Parameter = null;
_deviceService.SaveDevice(device);
_deviceService.LoadDeviceLayout(device);
device.LayoutSelection.Parameter = entry?.EntryId.ToString();
}
}
@@ -41,13 +41,17 @@ fragment entryDetails on Entry {
...category
}
latestRelease {
id
version
downloadSize
md5Hash
createdAt
...release
}
images {
...image
}
}
}
fragment release on Release {
id
version
downloadSize
md5Hash
createdAt
}
@@ -14,7 +14,7 @@ public class InstalledEntry
Load();
}
public InstalledEntry(IGetEntryById_Entry entry)
public InstalledEntry(IEntryDetails entry, IRelease release)
{
Entity = new EntryEntity();
@@ -23,6 +23,9 @@ public class InstalledEntry
Author = entry.Author;
Name = entry.Name;
InstalledAt = DateTimeOffset.Now;
ReleaseId = release.Id;
ReleaseVersion = release.Version;
}
public long EntryId { get; set; }
@@ -110,14 +113,30 @@ public class InstalledEntry
/// <summary>
/// Returns the directory info of the entry, where any files would be stored if applicable.
/// </summary>
/// <param name="rootDirectory">A value indicating whether or not to return the root directory of the entry, and not the version.</param>
/// <returns>The directory info of the entry, where any files would be stored if applicable.</returns>
public DirectoryInfo GetDirectory(bool rootDirectory = false)
/// <returns>The directory info of the directory.</returns>
public DirectoryInfo GetDirectory()
{
if (rootDirectory)
return new DirectoryInfo(Path.Combine(Constants.WorkshopFolder, EntryId.ToString()));
string safeVersion = Path.GetInvalidFileNameChars().Aggregate(ReleaseVersion, (current, c) => current.Replace(c, '-'));
return new DirectoryInfo(Path.Combine(Constants.WorkshopFolder, EntryId.ToString(), safeVersion));
return new DirectoryInfo(Path.Combine(Constants.WorkshopFolder, $"{EntryId}-{StringUtilities.UrlFriendly(Name)}"));
}
/// <summary>
/// Returns the directory info of a release of this entry, where any files would be stored if applicable.
/// </summary>
/// <param name="release">The release to use, if none provided the current release is used.</param>
/// <returns>The directory info of the directory.</returns>
public DirectoryInfo GetReleaseDirectory(IRelease? release = null)
{
return new DirectoryInfo(Path.Combine(GetDirectory().FullName, StringUtilities.UrlFriendly(release?.Version ?? ReleaseVersion)));
}
/// <summary>
/// Applies the provided release to the installed entry.
/// </summary>
/// <param name="release">The release to apply.</param>
public void ApplyRelease(IRelease release)
{
ReleaseId = release.Id;
ReleaseVersion = release.Version;
InstalledAt = DateTimeOffset.UtcNow;
}
}
@@ -1,4 +1,3 @@
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
namespace Artemis.WebClient.Workshop.Services;
@@ -13,11 +12,9 @@ public interface IWorkshopService
Task NavigateToEntry(long entryId, EntryType entryType);
List<InstalledEntry> GetInstalledEntries();
InstalledEntry? GetInstalledEntry(IGetEntryById_Entry entry);
InstalledEntry CreateInstalledEntry(IGetEntryById_Entry entry);
InstalledEntry? GetInstalledEntry(IEntryDetails entry);
void RemoveInstalledEntry(InstalledEntry installedEntry);
void SaveInstalledEntry(InstalledEntry entry);
public record WorkshopStatus(bool IsReachable, string Message);
}
@@ -133,7 +133,7 @@ public class WorkshopService : IWorkshopService
}
/// <inheritdoc />
public InstalledEntry? GetInstalledEntry(IGetEntryById_Entry entry)
public InstalledEntry? GetInstalledEntry(IEntryDetails entry)
{
EntryEntity? entity = _entryRepository.GetByEntryId(entry.Id);
if (entity == null)
@@ -143,9 +143,9 @@ public class WorkshopService : IWorkshopService
}
/// <inheritdoc />
public InstalledEntry CreateInstalledEntry(IGetEntryById_Entry entry)
public void AddOrUpdateInstalledEntry(InstalledEntry entry, IRelease release)
{
return new InstalledEntry(entry);
throw new NotImplementedException();
}
/// <inheritdoc />