Devices - Reworking layout loading

This commit is contained in:
RobertBeekman committed 2024-01-09 21:27:51 +01:00
1 parent 9393bf2b68
commit e8590abd61
57 files changed
+824 -349

No files matched your search

@@ -1,7 +1,9 @@
using System.Reflection;
using Artemis.Core.Providers;
using Artemis.WebClient.Workshop.Extensions;
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
using Artemis.WebClient.Workshop.Handlers.UploadHandlers;
using Artemis.WebClient.Workshop.Providers;
using Artemis.WebClient.Workshop.Repositories;
using Artemis.WebClient.Workshop.Services;
using Artemis.WebClient.Workshop.State;
@@ -46,6 +48,7 @@ public static class ContainerExtensions
container.Register<IAuthenticationRepository, AuthenticationRepository>(Reuse.Singleton);
container.Register<IAuthenticationService, AuthenticationService>(Reuse.Singleton);
container.Register<IWorkshopService, WorkshopService>(Reuse.Singleton);
container.Register<ILayoutProvider, WorkshopLayoutProvider>(Reuse.Singleton);
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
@@ -1,4 +1,9 @@
using Artemis.UI.Shared.Extensions;
using System.Data;
using System.IO.Compression;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Utilities;
using Artemis.WebClient.Workshop.Services;
@@ -8,17 +13,17 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IWorkshopService _workshopService;
private readonly IDeviceService _deviceService;
public LayoutEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService)
public LayoutEntryInstallationHandler(IHttpClientFactory httpClientFactory, IWorkshopService workshopService, IDeviceService deviceService)
{
_httpClientFactory = httpClientFactory;
_workshopService = workshopService;
_deviceService = deviceService;
}
public async Task<EntryInstallResult> InstallAsync(IGetEntryById_Entry entry, long releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken)
{
throw new NotImplementedException();
using MemoryStream stream = new();
// Download the provided release
@@ -31,15 +36,46 @@ public class LayoutEntryInstallationHandler : IEntryInstallationHandler
{
return EntryInstallResult.FromFailure(e.Message);
}
// return EntryInstallResult.FromSuccess();
// Ensure there is an installed entry
InstalledEntry installedEntry = _workshopService.GetInstalledEntry(entry) ?? _workshopService.CreateInstalledEntry(entry);
DirectoryInfo entryDirectory = installedEntry.GetDirectory();
// If the folder already exists, remove it so that if the layout now contains less files, old things dont stick around
if (entryDirectory.Exists)
entryDirectory.Delete(true);
entryDirectory.Create();
// Extract the archive, we could go through the hoops of keeping track of progress but this should be so quick it doesn't matter
stream.Seek(0, SeekOrigin.Begin);
using ZipArchive archive = new(stream);
archive.ExtractToDirectory(entryDirectory.FullName);
ArtemisLayout layout = new(Path.Combine(entryDirectory.FullName, "layout.xml"));
if (layout.IsValid)
return EntryInstallResult.FromSuccess(layout);
// If the layout ended up being invalid yoink it out again, shoooo
entryDirectory.Delete(true);
_workshopService.RemoveInstalledEntry(installedEntry);
return EntryInstallResult.FromFailure("Layout failed to load because it is invalid");
}
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
{
throw new NotImplementedException();
if (!Guid.TryParse(installedEntry.LocalReference, out Guid profileId))
return EntryUninstallResult.FromFailure("Local reference does not contain a GUID");
// Remove the layout from any devices currently using it
foreach (ArtemisDevice device in _deviceService.Devices)
{
}
// Remove from filesystem
DirectoryInfo directory = installedEntry.GetDirectory(true);
if (directory.Exists)
directory.Delete();
// Remove entry
_workshopService.RemoveInstalledEntry(installedEntry);
return EntryUninstallResult.FromSuccess();
}
}
@@ -36,13 +36,13 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
// Find existing installation to potentially replace the profile
InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry);
if (installedEntry != null && Guid.TryParse(installedEntry.LocalReference, out Guid profileId))
if (installedEntry != null && installedEntry.TryGetMetadata("ProfileId", out Guid profileId))
{
ProfileConfiguration? existing = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
if (existing != null)
{
ProfileConfiguration overwritten = await _profileService.OverwriteProfile(stream, existing);
installedEntry.LocalReference = overwritten.ProfileId.ToString();
installedEntry.SetMetadata("ProfileId", overwritten.ProfileId);
// Update the release and return the profile configuration
UpdateRelease(releaseId, installedEntry);
@@ -56,8 +56,8 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
// Add the profile as a fresh import
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "Workshop") ?? _profileService.CreateProfileCategory("Workshop", true);
ProfileConfiguration imported = await _profileService.ImportProfile(stream, category, true, true, null);
installedEntry.LocalReference = imported.ProfileId.ToString();
installedEntry.SetMetadata("ProfileId", imported.ProfileId);
// Update the release and return the profile configuration
UpdateRelease(releaseId, installedEntry);
return EntryInstallResult.FromSuccess(imported);
@@ -65,7 +65,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
public async Task<EntryUninstallResult> UninstallAsync(InstalledEntry installedEntry, CancellationToken cancellationToken)
{
if (!Guid.TryParse(installedEntry.LocalReference, out Guid profileId))
if (!installedEntry.TryGetMetadata("ProfileId", out Guid profileId))
return EntryUninstallResult.FromFailure("Local reference does not contain a GUID");
return await Task.Run(() =>
@@ -0,0 +1,48 @@
using Artemis.Core;
using Artemis.Core.Providers;
using Artemis.WebClient.Workshop.Services;
namespace Artemis.WebClient.Workshop.Providers;
public class WorkshopLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Workshop";
private readonly IWorkshopService _workshopService;
public WorkshopLayoutProvider(IWorkshopService workshopService)
{
_workshopService = workshopService;
}
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
InstalledEntry? layoutEntry = _workshopService.GetInstalledEntries().FirstOrDefault(e => e.EntryType == EntryType.Layout && MatchesDevice(e, device));
if (layoutEntry == null)
return null;
string layoutPath = Path.Combine(Constants.WorkshopFolder, layoutEntry.EntryId.ToString(), "layout.xml");
if (!File.Exists(layoutPath))
return null;
return new ArtemisLayout(layoutPath);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
private bool MatchesDevice(InstalledEntry entry, ArtemisDevice device)
{
return entry.TryGetMetadata("DeviceId", out HashSet<string>? deviceIds) && deviceIds.Contains(device.Identifier);
}
}
@@ -1,13 +1,16 @@
using Artemis.Storage.Entities.Workshop;
using System.Diagnostics.CodeAnalysis;
using Artemis.Core;
using Artemis.Storage.Entities.Workshop;
namespace Artemis.WebClient.Workshop.Services;
public class InstalledEntry
{
private Dictionary<string, object> _metadata = new();
internal InstalledEntry(EntryEntity entity)
{
Entity = entity;
Load();
}
@@ -32,8 +35,6 @@ public class InstalledEntry
public string ReleaseVersion { get; set; } = string.Empty;
public DateTimeOffset InstalledAt { get; set; }
public string? LocalReference { get; set; }
internal EntryEntity Entity { get; }
internal void Load()
@@ -48,7 +49,7 @@ public class InstalledEntry
ReleaseVersion = Entity.ReleaseVersion;
InstalledAt = Entity.InstalledAt;
LocalReference = Entity.LocalReference;
_metadata = new Dictionary<string, object>(Entity.Metadata);
}
internal void Save()
@@ -63,6 +64,60 @@ public class InstalledEntry
Entity.ReleaseVersion = ReleaseVersion;
Entity.InstalledAt = InstalledAt;
Entity.LocalReference = LocalReference;
Entity.Metadata = new Dictionary<string, object>(_metadata);
}
/// <summary>
/// Gets the metadata value associated with the specified key.
/// </summary>
/// <param name="key">The key of the value to get.</param>
/// <param name="value">When this method returns, contains the value associated with the specified key, if the key is found and of type <typeparamref name="T"/>;
/// otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.</param>
/// <typeparam name="T">The type of the value.</typeparam>
/// <returns><see langword="true"/> if the metadata contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
public bool TryGetMetadata<T>(string key, [NotNullWhen(true)] out T? value)
{
if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result)
{
value = default;
return false;
}
value = result;
return true;
}
/// <summary>
/// Sets metadata with the provided key to the provided value.
/// </summary>
/// <param name="key">The key of the value to set</param>
/// <param name="value">The value to set.</param>
public void SetMetadata(string key, object value)
{
_metadata.Add(key, value);
}
/// <summary>
/// Removes metadata with the provided key.
/// </summary>
/// <param name="key">The key of the metadata to remove</param>
/// <returns><see langword="true"/> if the element is successfully found and removed; otherwise, <see langword="false"/>.</returns>
public bool RemoveMetadata(string key)
{
return _metadata.Remove(key);
}
/// <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)
{
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));
}
}