mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +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:
parent
86f78940b1
commit
648b7765ef
@ -38,12 +38,14 @@ internal class DeviceService : IDeviceService
|
||||
_renderService = renderService;
|
||||
_getLayoutProviders = getLayoutProviders;
|
||||
|
||||
SuspendedDeviceProviders = new ReadOnlyCollection<DeviceProvider>(_suspendedDeviceProviders);
|
||||
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
|
||||
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
|
||||
|
||||
RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DeviceProvider> SuspendedDeviceProviders { get; }
|
||||
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
|
||||
public IReadOnlyCollection<ArtemisDevice> Devices { get; }
|
||||
|
||||
|
||||
@ -10,6 +10,11 @@ namespace Artemis.Core.Services;
|
||||
/// </summary>
|
||||
public interface IDeviceService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a read-only collection containing all enabled but suspended device providers
|
||||
/// </summary>
|
||||
IReadOnlyCollection<DeviceProvider> SuspendedDeviceProviders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only collection containing all enabled devices
|
||||
/// </summary>
|
||||
@ -42,7 +47,7 @@ public interface IDeviceService : IArtemisService
|
||||
/// Applies auto-arranging logic to the surface
|
||||
/// </summary>
|
||||
void AutoArrangeDevices();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Apples the best available to the provided <see cref="ArtemisDevice" />
|
||||
/// </summary>
|
||||
@ -111,7 +116,7 @@ public interface IDeviceService : IArtemisService
|
||||
/// Occurs when a device provider was removed.
|
||||
/// </summary>
|
||||
event EventHandler<DeviceProviderEventArgs> DeviceProviderRemoved;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the surface has had modifications to its LED collection
|
||||
/// </summary>
|
||||
|
||||
@ -186,6 +186,11 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// </summary>
|
||||
event EventHandler<PluginEventArgs> PluginDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin is removed
|
||||
/// </summary>
|
||||
event EventHandler<PluginEventArgs> PluginRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a plugin feature is being enabled
|
||||
/// </summary>
|
||||
|
||||
@ -686,6 +686,8 @@ internal class PluginManagementService : IPluginManagementService
|
||||
|
||||
if (removeSettings)
|
||||
RemovePluginSettings(plugin);
|
||||
|
||||
OnPluginRemoved(new PluginEventArgs(plugin));
|
||||
}
|
||||
|
||||
public void RemovePluginSettings(Plugin plugin)
|
||||
@ -850,6 +852,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
public event EventHandler<PluginEventArgs>? PluginEnabling;
|
||||
public event EventHandler<PluginEventArgs>? PluginEnabled;
|
||||
public event EventHandler<PluginEventArgs>? PluginDisabled;
|
||||
public event EventHandler<PluginEventArgs>? PluginRemoved;
|
||||
|
||||
public event EventHandler<PluginFeatureEventArgs>? PluginFeatureEnabling;
|
||||
public event EventHandler<PluginFeatureEventArgs>? PluginFeatureEnabled;
|
||||
@ -890,6 +893,11 @@ internal class PluginManagementService : IPluginManagementService
|
||||
{
|
||||
PluginDisabled?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnPluginRemoved(PluginEventArgs e)
|
||||
{
|
||||
PluginRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnPluginFeatureEnabling(PluginFeatureEventArgs e)
|
||||
{
|
||||
|
||||
@ -30,7 +30,7 @@ public interface IProfileService : IArtemisService
|
||||
/// Gets or sets a value indicating whether the currently focused profile should receive updates.
|
||||
/// </summary>
|
||||
bool UpdateFocusProfile { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether profiles are rendered each frame by calling their Render method
|
||||
/// </summary>
|
||||
@ -54,7 +54,7 @@ public interface IProfileService : IArtemisService
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to activate.</param>
|
||||
void DeactivateProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
|
||||
/// <see cref="Profile" />s themselves.
|
||||
@ -117,8 +117,9 @@ public interface IProfileService : IArtemisService
|
||||
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash).</param>
|
||||
/// <param name="target">The profile before which to import the profile into the category.</param>
|
||||
/// <returns>The resulting profile configuration.</returns>
|
||||
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", ProfileConfiguration? target = null);
|
||||
|
||||
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported",
|
||||
ProfileConfiguration? target = null);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the provided ZIP archive stream into the provided profile configuration
|
||||
/// </summary>
|
||||
@ -163,5 +164,14 @@ public interface IProfileService : IArtemisService
|
||||
/// Occurs whenever a profile category is removed.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryRemoved;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a profile is added.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a profile is removed.
|
||||
/// </summary>
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileAdded;
|
||||
}
|
||||
@ -26,7 +26,6 @@ internal class ProfileService : IProfileService
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
|
||||
private readonly List<IProfileMigration> _profileMigrators;
|
||||
private readonly List<Exception> _renderExceptions = new();
|
||||
private readonly List<Exception> _updateExceptions = new();
|
||||
|
||||
@ -38,15 +37,13 @@ internal class ProfileService : IProfileService
|
||||
IProfileRepository profileRepository,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IInputService inputService,
|
||||
IDeviceService deviceService,
|
||||
List<IProfileMigration> profileMigrators)
|
||||
IDeviceService deviceService)
|
||||
{
|
||||
_logger = logger;
|
||||
_profileCategoryRepository = profileCategoryRepository;
|
||||
_profileRepository = profileRepository;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_deviceService = deviceService;
|
||||
_profileMigrators = profileMigrators;
|
||||
|
||||
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order).ToList());
|
||||
|
||||
@ -264,6 +261,8 @@ internal class ProfileService : IProfileService
|
||||
|
||||
category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault());
|
||||
SaveProfileCategory(category);
|
||||
|
||||
OnProfileAdded(new ProfileConfigurationEventArgs(configuration));
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@ -279,6 +278,8 @@ internal class ProfileService : IProfileService
|
||||
|
||||
_profileRepository.Remove(profileConfiguration.Entity);
|
||||
_profileCategoryRepository.Save(category.Entity);
|
||||
|
||||
OnProfileRemoved(new ProfileConfigurationEventArgs(profileConfiguration));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -436,8 +437,9 @@ internal class ProfileService : IProfileService
|
||||
/// <inheritdoc />
|
||||
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration);
|
||||
|
||||
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, false, null, profileConfiguration);
|
||||
imported.Name = profileConfiguration.Name;
|
||||
|
||||
RemoveProfileConfiguration(profileConfiguration);
|
||||
SaveProfileCategory(imported.Category);
|
||||
|
||||
@ -588,6 +590,8 @@ internal class ProfileService : IProfileService
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
|
||||
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryAdded;
|
||||
public event EventHandler<ProfileCategoryEventArgs>? ProfileCategoryRemoved;
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileRemoved;
|
||||
public event EventHandler<ProfileConfigurationEventArgs>? ProfileAdded;
|
||||
|
||||
protected virtual void OnProfileActivated(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
@ -610,4 +614,14 @@ internal class ProfileService : IProfileService
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual void OnProfileRemoved(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnProfileAdded(ProfileConfigurationEventArgs e)
|
||||
{
|
||||
ProfileAdded?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,6 @@ using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using Material.Icons;
|
||||
@ -26,7 +24,6 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IWindowService _windowService;
|
||||
private Window? _settingsWindow;
|
||||
[Notify] private bool _canInstallPrerequisites;
|
||||
@ -39,15 +36,13 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
ICoreService coreService,
|
||||
IWindowService windowService,
|
||||
INotificationService notificationService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IWorkshopService workshopService)
|
||||
IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_plugin = plugin;
|
||||
_coreService = coreService;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_workshopService = workshopService;
|
||||
|
||||
Platforms = new ObservableCollection<PluginPlatformViewModel>();
|
||||
if (Plugin.Info.Platforms != null)
|
||||
@ -260,11 +255,7 @@ public partial class PluginViewModel : ActivatableViewModelBase
|
||||
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
InstalledEntry? entry = _workshopService.GetInstalledEntries().FirstOrDefault(e => e.TryGetMetadata("PluginId", out Guid pluginId) && pluginId == Plugin.Guid);
|
||||
if (entry != null)
|
||||
_workshopService.RemoveInstalledEntry(entry);
|
||||
|
||||
|
||||
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
|
||||
}
|
||||
|
||||
|
||||
@ -15,24 +15,29 @@
|
||||
</UserControl.Resources>
|
||||
<Panel>
|
||||
<StackPanel IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<Border Classes="skeleton-text" Margin="0 0 10 0" Width="80" Height="80"></Border>
|
||||
<Border Classes="skeleton-text title" HorizontalAlignment="Stretch"/>
|
||||
<Border Classes="skeleton-text" Width="120"/>
|
||||
<Border Classes="skeleton-text" Width="140" Margin="0 8"/>
|
||||
<Border Classes="skeleton-text" Width="80"/>
|
||||
<Border Classes="skeleton-text"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="30 30 30 0"
|
||||
Width="80"
|
||||
Height="80">
|
||||
</Border>
|
||||
<Border Classes="skeleton-text title" HorizontalAlignment="Stretch" Margin="0 20" />
|
||||
<Border Classes="skeleton-text" Width="120" />
|
||||
<Border Classes="skeleton-text" Width="140" Margin="0 8" />
|
||||
<Border Classes="skeleton-text" Width="80" />
|
||||
<Border Classes="card-separator" Margin="0 15 0 17"></Border>
|
||||
<Border Classes="skeleton-text" Width="120"/>
|
||||
<Border Classes="skeleton-text" Width="120" />
|
||||
<StackPanel Margin="0 10 0 0">
|
||||
<Border Classes="skeleton-text" Width="160"/>
|
||||
<Border Classes="skeleton-text" Width="160"/>
|
||||
<Border Classes="skeleton-text" Width="160" />
|
||||
<Border Classes="skeleton-text" Width="160" />
|
||||
</StackPanel>
|
||||
<Border Classes="skeleton-button"></Border>
|
||||
</StackPanel>
|
||||
<StackPanel IsVisible="{CompiledBinding Entry, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Panel>
|
||||
<Border CornerRadius="6"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0 0 10 0"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="30 30 30 0"
|
||||
Width="80"
|
||||
Height="80"
|
||||
ClipToBounds="True">
|
||||
@ -46,11 +51,13 @@
|
||||
<avalonia:MaterialIcon Kind="ShareVariant" />
|
||||
</Button>
|
||||
</Panel>
|
||||
|
||||
|
||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}"
|
||||
MaxLines="3"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title}"/>
|
||||
TextAlignment="Center"
|
||||
Text="{CompiledBinding Entry.Name, FallbackValue=Title}"
|
||||
Margin="0 15" />
|
||||
|
||||
<TextBlock Classes="subtitle" TextTrimming="CharacterEllipsis" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:dialogs="clr-namespace:Artemis.UI.Screens.Workshop.EntryReleases.Dialogs"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.EntryReleases.Dialogs.DependenciesDialogView"
|
||||
x:DataType="dialogs:DependenciesDialogViewModel">
|
||||
<StackPanel>
|
||||
<TextBlock IsVisible="{CompiledBinding Multiple}">
|
||||
<Run Text="Some or all of the required" />
|
||||
<Run Text="{CompiledBinding EntryTypePlural}" />
|
||||
<Run Text="are not installed. This" />
|
||||
<Run Text="{CompiledBinding DependantType}" />
|
||||
<Run Text="will not work properly without them. The missing" />
|
||||
<Run Text="{CompiledBinding EntryTypePlural}" />
|
||||
<Run Text="are listed below and you can click on them to view them" />
|
||||
</TextBlock>
|
||||
<TextBlock IsVisible="{CompiledBinding !Multiple}">
|
||||
<Run Text="A required" />
|
||||
<Run Text="{CompiledBinding EntryType}" />
|
||||
<Run Text="is not installed. This" />
|
||||
<Run Text="{CompiledBinding DependantType}" />
|
||||
<Run Text="will not work properly without it. The missing" />
|
||||
<Run Text="{CompiledBinding EntryType}" />
|
||||
<Run Text="is listed below and you can click on it to view it" />
|
||||
</TextBlock>
|
||||
|
||||
<ScrollViewer MaxHeight="500" Margin="0 30 0 0">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Dependencies}" >
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{CompiledBinding}" Margin="0 0 0 5"></ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
|
||||
|
||||
public partial class DependenciesDialogView : ReactiveUserControl<DependenciesDialogViewModel>
|
||||
{
|
||||
public DependenciesDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.UI.Screens.Workshop.Entries.List;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Humanizer;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
|
||||
|
||||
public class DependenciesDialogViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
public DependenciesDialogViewModel(IEntrySummary dependant, List<IEntrySummary> dependencies, Func<IEntrySummary, EntryListItemViewModel> getEntryListItemViewModel, IRouter router)
|
||||
{
|
||||
Dependant = dependant;
|
||||
DependantType = dependant.EntryType.Humanize(LetterCasing.LowerCase);
|
||||
EntryType = dependencies.First().EntryType.Humanize(LetterCasing.LowerCase);
|
||||
EntryTypePlural = dependencies.First().EntryType.Humanize(LetterCasing.LowerCase).Pluralize();
|
||||
Dependencies = new ObservableCollection<EntryListItemViewModel>(dependencies.Select(getEntryListItemViewModel));
|
||||
|
||||
this.WhenActivated(d => router.CurrentPath.Skip(1).Subscribe(s => ContentDialog?.Hide()).DisposeWith(d));
|
||||
}
|
||||
|
||||
public string DependantType { get; }
|
||||
public string EntryType { get; }
|
||||
public string EntryTypePlural { get; }
|
||||
public bool Multiple => Dependencies.Count > 1;
|
||||
|
||||
public IEntrySummary Dependant { get; }
|
||||
public ObservableCollection<EntryListItemViewModel> Dependencies { get; }
|
||||
}
|
||||
@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.DryIoc.Factories;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Screens.Workshop.EntryReleases.Dialogs;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
@ -17,7 +18,6 @@ using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Humanizer;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using ReactiveUI;
|
||||
|
||||
@ -30,13 +30,12 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly EntryInstallationHandlerFactory _factory;
|
||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||
private readonly Progress<StreamProgress> _progress = new();
|
||||
private readonly ObservableAsPropertyHelper<bool> _isCurrentVersion;
|
||||
|
||||
[Notify] private IReleaseDetails? _release;
|
||||
[Notify] private float _installProgress;
|
||||
[Notify] private bool _isCurrentVersion;
|
||||
[Notify] private bool _installationInProgress;
|
||||
[Notify] private bool _inDetailsScreen;
|
||||
|
||||
@ -47,7 +46,6 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
IWindowService windowService,
|
||||
IWorkshopService workshopService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
EntryInstallationHandlerFactory factory,
|
||||
ISettingsVmFactory settingsVmFactory)
|
||||
{
|
||||
_router = router;
|
||||
@ -55,18 +53,31 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
_windowService = windowService;
|
||||
_workshopService = workshopService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_factory = factory;
|
||||
_settingsVmFactory = settingsVmFactory;
|
||||
_progress.ProgressChanged += (_, f) => InstallProgress = f.ProgressPercentage;
|
||||
|
||||
_isCurrentVersion = this.WhenAnyValue(vm => vm.Release, vm => vm.InstallationInProgress, (release, _) => release)
|
||||
.Select(r => r != null && _workshopService.GetInstalledEntry(r.Entry.Id)?.ReleaseId == r.Id)
|
||||
.ToProperty(this, vm => vm.IsCurrentVersion);
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_workshopService.OnEntryInstalled += WorkshopServiceOnOnEntryInstalled;
|
||||
_workshopService.OnEntryUninstalled += WorkshopServiceOnOnEntryInstalled;
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_workshopService.OnEntryInstalled -= WorkshopServiceOnOnEntryInstalled;
|
||||
_workshopService.OnEntryUninstalled -= WorkshopServiceOnOnEntryInstalled;
|
||||
}).DisposeWith(d);
|
||||
|
||||
IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id;
|
||||
});
|
||||
|
||||
this.WhenAnyValue(vm => vm.Release).Subscribe(r => IsCurrentVersion = r != null && _workshopService.GetInstalledEntry(r.Entry.Id)?.ReleaseId == r.Id);
|
||||
|
||||
InDetailsScreen = true;
|
||||
}
|
||||
|
||||
public bool IsCurrentVersion => _isCurrentVersion.Value;
|
||||
private void WorkshopServiceOnOnEntryInstalled(object? sender, InstalledEntry e)
|
||||
{
|
||||
IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id;
|
||||
}
|
||||
|
||||
public async Task Close()
|
||||
{
|
||||
@ -79,15 +90,15 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
return;
|
||||
|
||||
// If the entry has missing dependencies, show a dialog
|
||||
foreach (IGetEntryById_Entry_LatestRelease_Dependencies dependency in Release.Dependencies)
|
||||
List<IEntrySummary> missing = Release.Dependencies.Where(d => _workshopService.GetInstalledEntry(d.Id) == null).Cast<IEntrySummary>().ToList();
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
if (_workshopService.GetInstalledEntry(dependency.Id) == null)
|
||||
{
|
||||
if (await _windowService.ShowConfirmContentDialog("Missing dependencies",
|
||||
$"One or more dependencies are missing, this {Release.Entry.EntryType.Humanize(LetterCasing.LowerCase)} won't work without them", "View dependencies"))
|
||||
await _router.GoUp();
|
||||
return;
|
||||
}
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Requirements missing")
|
||||
.WithViewModel(out DependenciesDialogViewModel _, Release.Entry, missing)
|
||||
.WithCloseButtonText("Cancel installation")
|
||||
.ShowAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
@ -95,8 +106,7 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
InstallationInProgress = true;
|
||||
try
|
||||
{
|
||||
IEntryInstallationHandler handler = _factory.CreateHandler(Release.Entry.EntryType);
|
||||
EntryInstallResult result = await handler.InstallAsync(Release.Entry, Release, _progress, _cts.Token);
|
||||
EntryInstallResult result = await _workshopService.InstallEntry(Release.Entry, Release, _progress, _cts.Token);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_notificationService.CreateNotification().WithTitle("Installation succeeded").WithSeverity(NotificationSeverity.Success).Show();
|
||||
@ -145,8 +155,9 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
|
||||
if (installedEntry.EntryType == EntryType.Plugin)
|
||||
await UninstallPluginPrerequisites(installedEntry);
|
||||
|
||||
IEntryInstallationHandler handler = _factory.CreateHandler(installedEntry.EntryType);
|
||||
await handler.UninstallAsync(installedEntry, CancellationToken.None);
|
||||
await _workshopService.UninstallEntry(installedEntry, CancellationToken.None);
|
||||
|
||||
_notificationService.CreateNotification().WithTitle("Entry uninstalled").WithSeverity(NotificationSeverity.Success).Show();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
@ -22,19 +20,24 @@ public partial class EntryReleaseItemViewModel : ActivatableViewModelBase
|
||||
_entry = entry;
|
||||
|
||||
Release = release;
|
||||
UpdateIsCurrentVersion();
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Observable.FromEventPattern<InstalledEntry>(x => _workshopService.OnInstalledEntrySaved += x, x => _workshopService.OnInstalledEntrySaved -= x)
|
||||
.Subscribe(_ => UpdateIsCurrentVersion())
|
||||
.DisposeWith(d);
|
||||
_workshopService.OnEntryInstalled += WorkshopServiceOnOnEntryInstalled;
|
||||
_workshopService.OnEntryUninstalled += WorkshopServiceOnOnEntryInstalled;
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_workshopService.OnEntryInstalled -= WorkshopServiceOnOnEntryInstalled;
|
||||
_workshopService.OnEntryUninstalled -= WorkshopServiceOnOnEntryInstalled;
|
||||
}).DisposeWith(d);
|
||||
|
||||
IsCurrentVersion = _workshopService.GetInstalledEntry(_entry.Id)?.ReleaseId == Release.Id;
|
||||
});
|
||||
}
|
||||
|
||||
public IRelease Release { get; }
|
||||
|
||||
private void UpdateIsCurrentVersion()
|
||||
private void WorkshopServiceOnOnEntryInstalled(object? sender, InstalledEntry e)
|
||||
{
|
||||
IsCurrentVersion = _workshopService.GetInstalledEntry(_entry.Id)?.ReleaseId == Release.Id;
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Providers;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using Material.Icons;
|
||||
using Material.Icons.Avalonia;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using StrawberryShake;
|
||||
|
||||
@ -23,7 +22,6 @@ public partial class LayoutFinderDeviceViewModel : ViewModelBase
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly WorkshopLayoutProvider _layoutProvider;
|
||||
private readonly EntryInstallationHandlerFactory _factory;
|
||||
|
||||
[Notify] private bool _searching;
|
||||
[Notify] private bool _hasLayout;
|
||||
@ -33,18 +31,12 @@ public partial class LayoutFinderDeviceViewModel : ViewModelBase
|
||||
[Notify] private string? _logicalLayout;
|
||||
[Notify] private string? _physicalLayout;
|
||||
|
||||
public LayoutFinderDeviceViewModel(ArtemisDevice device,
|
||||
IWorkshopClient client,
|
||||
IDeviceService deviceService,
|
||||
IWorkshopService workshopService,
|
||||
WorkshopLayoutProvider layoutProvider,
|
||||
EntryInstallationHandlerFactory factory)
|
||||
public LayoutFinderDeviceViewModel(ArtemisDevice device, IWorkshopClient client, IDeviceService deviceService, IWorkshopService workshopService, WorkshopLayoutProvider layoutProvider)
|
||||
{
|
||||
_client = client;
|
||||
_deviceService = deviceService;
|
||||
_workshopService = workshopService;
|
||||
_layoutProvider = layoutProvider;
|
||||
_factory = factory;
|
||||
|
||||
Device = device;
|
||||
DeviceIcon = DetermineDeviceIcon();
|
||||
@ -116,8 +108,7 @@ public partial class LayoutFinderDeviceViewModel : ViewModelBase
|
||||
InstalledEntry? installedEntry = _workshopService.GetInstalledEntry(entry.Id);
|
||||
if (installedEntry == null)
|
||||
{
|
||||
IEntryInstallationHandler installationHandler = _factory.CreateHandler(EntryType.Layout);
|
||||
EntryInstallResult result = await installationHandler.InstallAsync(entry, release, new Progress<StreamProgress>(), CancellationToken.None);
|
||||
EntryInstallResult result = await _workshopService.InstallEntry(entry, release, new Progress<StreamProgress>(), CancellationToken.None);
|
||||
installedEntry = result.Entry;
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Handlers.InstallationHandlers;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
@ -23,23 +22,19 @@ public partial class InstalledTabItemViewModel : ViewModelBase
|
||||
{
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IRouter _router;
|
||||
private readonly EntryInstallationHandlerFactory _factory;
|
||||
private readonly IWindowService _windowService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||
[Notify(Setter.Private)] private bool _isRemoved;
|
||||
|
||||
public InstalledTabItemViewModel(InstalledEntry installedEntry,
|
||||
IWorkshopService workshopService,
|
||||
IRouter router,
|
||||
EntryInstallationHandlerFactory factory,
|
||||
IWindowService windowService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
ISettingsVmFactory settingsVmFactory)
|
||||
{
|
||||
_workshopService = workshopService;
|
||||
_router = router;
|
||||
_factory = factory;
|
||||
_windowService = windowService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_settingsVmFactory = settingsVmFactory;
|
||||
@ -78,9 +73,7 @@ public partial class InstalledTabItemViewModel : ViewModelBase
|
||||
if (InstalledEntry.EntryType == EntryType.Plugin)
|
||||
await UninstallPluginPrerequisites();
|
||||
|
||||
IEntryInstallationHandler handler = _factory.CreateHandler(InstalledEntry.EntryType);
|
||||
await handler.UninstallAsync(InstalledEntry, cancellationToken);
|
||||
IsRemoved = true;
|
||||
await _workshopService.UninstallEntry(InstalledEntry, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task UninstallPluginPrerequisites()
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
@ -15,31 +16,41 @@ namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public partial class InstalledTabViewModel : RoutableScreen
|
||||
{
|
||||
private SourceList<InstalledEntry> _installedEntries = new();
|
||||
|
||||
[Notify] private string? _searchEntryInput;
|
||||
|
||||
public InstalledTabViewModel(IWorkshopService workshopService, IRouter router, Func<InstalledEntry, InstalledTabItemViewModel> getInstalledTabItemViewModel)
|
||||
{
|
||||
SourceList<InstalledEntry> installedEntries = new();
|
||||
IObservable<Func<InstalledEntry, bool>> pluginFilter = this.WhenAnyValue(vm => vm.SearchEntryInput).Throttle(TimeSpan.FromMilliseconds(100)).Select(CreatePredicate);
|
||||
|
||||
installedEntries.Connect()
|
||||
_installedEntries.Connect()
|
||||
.Filter(pluginFilter)
|
||||
.Sort(SortExpressionComparer<InstalledEntry>.Descending(p => p.InstalledAt))
|
||||
.Transform(getInstalledTabItemViewModel)
|
||||
.AutoRefresh(vm => vm.IsRemoved)
|
||||
.Filter(vm => !vm.IsRemoved)
|
||||
.Bind(out ReadOnlyObservableCollection<InstalledTabItemViewModel> installedEntryViewModels)
|
||||
.Subscribe();
|
||||
|
||||
List<InstalledEntry> entries = workshopService.GetInstalledEntries();
|
||||
installedEntries.AddRange(entries);
|
||||
_installedEntries.AddRange(entries);
|
||||
|
||||
Empty = entries.Count == 0;
|
||||
InstalledEntries = installedEntryViewModels;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
workshopService.OnEntryUninstalled += WorkshopServiceOnOnEntryUninstalled;
|
||||
Disposable.Create(() => workshopService.OnEntryUninstalled -= WorkshopServiceOnOnEntryUninstalled).DisposeWith(d);
|
||||
});
|
||||
|
||||
OpenWorkshop = ReactiveCommand.CreateFromTask(async () => await router.Navigate("workshop"));
|
||||
}
|
||||
|
||||
private void WorkshopServiceOnOnEntryUninstalled(object? sender, InstalledEntry e)
|
||||
{
|
||||
_installedEntries.Remove(e);
|
||||
}
|
||||
|
||||
public bool Empty { get; }
|
||||
public ReactiveCommand<Unit, Unit> OpenWorkshop { get; }
|
||||
public ReadOnlyObservableCollection<InstalledTabItemViewModel> InstalledEntries { get; }
|
||||
|
||||
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user