mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profiles - Added workshop installing
This commit is contained in:
parent
4143cc2de8
commit
671c587df6
@ -76,8 +76,9 @@ public interface IProfileService : IArtemisService
|
|||||||
/// Creates a new profile category and saves it to persistent storage.
|
/// Creates a new profile category and saves it to persistent storage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the new profile category, must be unique.</param>
|
/// <param name="name">The name of the new profile category, must be unique.</param>
|
||||||
|
/// <param name="addToTop">A boolean indicating whether or not to add the category to the top.</param>
|
||||||
/// <returns>The newly created profile category.</returns>
|
/// <returns>The newly created profile category.</returns>
|
||||||
ProfileCategory CreateProfileCategory(string name);
|
ProfileCategory CreateProfileCategory(string name, bool addToTop = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Permanently deletes the provided profile category.
|
/// Permanently deletes the provided profile category.
|
||||||
|
|||||||
@ -286,12 +286,26 @@ internal class ProfileService : IProfileService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ProfileCategory CreateProfileCategory(string name)
|
public ProfileCategory CreateProfileCategory(string name, bool addToTop = false)
|
||||||
{
|
{
|
||||||
ProfileCategory profileCategory;
|
ProfileCategory profileCategory;
|
||||||
lock (_profileRepository)
|
lock (_profileRepository)
|
||||||
|
{
|
||||||
|
if (addToTop)
|
||||||
|
{
|
||||||
|
profileCategory = new ProfileCategory(name, 1);
|
||||||
|
foreach (ProfileCategory category in _profileCategories)
|
||||||
|
{
|
||||||
|
category.Order++;
|
||||||
|
category.Save();
|
||||||
|
_profileCategoryRepository.Save(category.Entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
|
profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
_profileCategories.Add(profileCategory);
|
_profileCategories.Add(profileCategory);
|
||||||
SaveProfileCategory(profileCategory);
|
SaveProfileCategory(profileCategory);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,13 @@ using System.IO;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
|
||||||
namespace Artemis.UI.Extensions
|
namespace Artemis.UI.Shared.Extensions
|
||||||
{
|
{
|
||||||
public static class HttpClientProgressExtensions
|
public static class HttpClientProgressExtensions
|
||||||
{
|
{
|
||||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<float>? progress, CancellationToken cancellationToken)
|
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<StreamProgress>? progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
@ -23,13 +24,10 @@ namespace Artemis.UI.Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Such progress and contentLength much reporting Wow!
|
// Such progress and contentLength much reporting Wow!
|
||||||
Progress<long> progressWrapper = new(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
await download.CopyToAsync(destination, 81920, progress, contentLength, cancellationToken);
|
||||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
|
||||||
|
|
||||||
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress, CancellationToken cancellationToken)
|
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<StreamProgress> progress, long? contentLength, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (bufferSize < 0)
|
if (bufferSize < 0)
|
||||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
@ -49,7 +47,7 @@ namespace Artemis.UI.Extensions
|
|||||||
{
|
{
|
||||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
progress?.Report(totalBytesRead);
|
progress?.Report(new StreamProgress(totalBytesRead, contentLength ?? totalBytesRead));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
|
||||||
namespace Artemis.UI.Extensions;
|
namespace Artemis.UI.Extensions;
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ public static class ZipArchiveExtensions
|
|||||||
/// <param name="overwriteFiles">A boolean indicating whether to override existing files</param>
|
/// <param name="overwriteFiles">A boolean indicating whether to override existing files</param>
|
||||||
/// <param name="progress">The progress to report to.</param>
|
/// <param name="progress">The progress to report to.</param>
|
||||||
/// <param name="cancellationToken">A cancellation token</param>
|
/// <param name="cancellationToken">A cancellation token</param>
|
||||||
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress<float> progress, CancellationToken cancellationToken)
|
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (source == null)
|
if (source == null)
|
||||||
throw new ArgumentNullException(nameof(source));
|
throw new ArgumentNullException(nameof(source));
|
||||||
@ -28,7 +29,7 @@ public static class ZipArchiveExtensions
|
|||||||
{
|
{
|
||||||
ZipArchiveEntry entry = source.Entries[index];
|
ZipArchiveEntry entry = source.Entries[index];
|
||||||
entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
|
entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
|
||||||
progress.Report((index + 1f) / source.Entries.Count * 100f);
|
progress.Report(new StreamProgress(index + 1, source.Entries.Count));
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Artemis.UI.Screens.Settings;
|
|||||||
using Artemis.UI.Screens.Settings.Updating;
|
using Artemis.UI.Screens.Settings.Updating;
|
||||||
using Artemis.UI.Screens.SurfaceEditor;
|
using Artemis.UI.Screens.SurfaceEditor;
|
||||||
using Artemis.UI.Screens.Workshop;
|
using Artemis.UI.Screens.Workshop;
|
||||||
|
using Artemis.UI.Screens.Workshop.Home;
|
||||||
using Artemis.UI.Screens.Workshop.Layout;
|
using Artemis.UI.Screens.Workshop.Layout;
|
||||||
using Artemis.UI.Screens.Workshop.Profile;
|
using Artemis.UI.Screens.Workshop.Profile;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
@ -21,6 +22,7 @@ public static class Routes
|
|||||||
{
|
{
|
||||||
Children = new List<IRouterRegistration>()
|
Children = new List<IRouterRegistration>()
|
||||||
{
|
{
|
||||||
|
new RouteRegistration<WorkshopOfflineViewModel>("offline/{message:string}"),
|
||||||
new RouteRegistration<ProfileListViewModel>("profiles/{page:int}"),
|
new RouteRegistration<ProfileListViewModel>("profiles/{page:int}"),
|
||||||
new RouteRegistration<ProfileDetailsViewModel>("profiles/{entryId:guid}"),
|
new RouteRegistration<ProfileDetailsViewModel>("profiles/{entryId:guid}"),
|
||||||
new RouteRegistration<LayoutListViewModel>("layouts/{page:int}"),
|
new RouteRegistration<LayoutListViewModel>("layouts/{page:int}"),
|
||||||
|
|||||||
@ -78,14 +78,14 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
|
|||||||
.Subscribe(e => profileConfigurations.RemoveMany(profileConfigurations.Items.Where(c => c == e.EventArgs.ProfileConfiguration)))
|
.Subscribe(e => profileConfigurations.RemoveMany(profileConfigurations.Items.Where(c => c == e.EventArgs.ProfileConfiguration)))
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
|
|
||||||
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
|
|
||||||
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
|
|
||||||
});
|
|
||||||
|
|
||||||
profileConfigurations.Edit(updater =>
|
profileConfigurations.Edit(updater =>
|
||||||
{
|
{
|
||||||
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
|
updater.Clear();
|
||||||
updater.Add(profileConfiguration);
|
updater.AddRange(profileCategory.ProfileConfigurations);
|
||||||
|
});
|
||||||
|
|
||||||
|
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
|
||||||
|
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
x:DataType="home:WorkshopHomeViewModel">
|
x:DataType="home:WorkshopHomeViewModel">
|
||||||
<Border Classes="router-container">
|
<Border Classes="router-container">
|
||||||
<Grid RowDefinitions="200,*,*">
|
<Grid RowDefinitions="200,*,*">
|
||||||
|
<ProgressBar ZIndex="999" IsIndeterminate="True" IsVisible="{CompiledBinding !WorkshopReachable}" Grid.Row="0" VerticalAlignment="Top"></ProgressBar>
|
||||||
|
|
||||||
<Image Grid.Row="0"
|
<Image Grid.Row="0"
|
||||||
Grid.RowSpan="2"
|
Grid.RowSpan="2"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
@ -72,7 +74,6 @@
|
|||||||
<TextBlock Classes="h4" Margin="0 15 0 5">Recently updated</TextBlock>
|
<TextBlock Classes="h4" Margin="0 15 0 5">Recently updated</TextBlock>
|
||||||
<TextBlock>Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop.</TextBlock>
|
<TextBlock>Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop.</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Artemis.UI.Screens.Workshop.SubmissionWizard;
|
using Artemis.UI.Screens.Workshop.SubmissionWizard;
|
||||||
@ -6,6 +7,8 @@ using Artemis.UI.Shared;
|
|||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services;
|
using Artemis.UI.Shared.Services;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop.Home;
|
namespace Artemis.UI.Screens.Workshop.Home;
|
||||||
@ -13,21 +16,38 @@ namespace Artemis.UI.Screens.Workshop.Home;
|
|||||||
public class WorkshopHomeViewModel : ActivatableViewModelBase, IWorkshopViewModel
|
public class WorkshopHomeViewModel : ActivatableViewModelBase, IWorkshopViewModel
|
||||||
{
|
{
|
||||||
private readonly IWindowService _windowService;
|
private readonly IWindowService _windowService;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
private bool _workshopReachable;
|
||||||
|
|
||||||
public WorkshopHomeViewModel(IRouter router, IWindowService windowService)
|
public WorkshopHomeViewModel(IRouter router, IWindowService windowService, IWorkshopService workshopService)
|
||||||
{
|
{
|
||||||
_windowService = windowService;
|
_windowService = windowService;
|
||||||
AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission);
|
_workshopService = workshopService;
|
||||||
Navigate = ReactiveCommand.CreateFromTask<string>(async r => await router.Navigate(r));
|
|
||||||
|
AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable));
|
||||||
|
Navigate = ReactiveCommand.CreateFromTask<string>(async r => await router.Navigate(r), this.WhenAnyValue(vm => vm.WorkshopReachable));
|
||||||
|
|
||||||
|
this.WhenActivated((CompositeDisposable _) => Dispatcher.UIThread.InvokeAsync(ValidateWorkshopStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReactiveCommand<Unit, Unit> AddSubmission { get; }
|
public ReactiveCommand<Unit, Unit> AddSubmission { get; }
|
||||||
public ReactiveCommand<string, Unit> Navigate { get; }
|
public ReactiveCommand<string, Unit> Navigate { get; }
|
||||||
|
|
||||||
|
public bool WorkshopReachable
|
||||||
|
{
|
||||||
|
get => _workshopReachable;
|
||||||
|
private set => RaiseAndSetIfChanged(ref _workshopReachable, value);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ExecuteAddSubmission(CancellationToken arg)
|
private async Task ExecuteAddSubmission(CancellationToken arg)
|
||||||
{
|
{
|
||||||
await _windowService.ShowDialogAsync<SubmissionWizardViewModel, bool>();
|
await _windowService.ShowDialogAsync<SubmissionWizardViewModel, bool>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ValidateWorkshopStatus()
|
||||||
|
{
|
||||||
|
WorkshopReachable = await _workshopService.ValidateWorkshopStatus();
|
||||||
|
}
|
||||||
|
|
||||||
public EntryType? EntryType => null;
|
public EntryType? EntryType => null;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:home="clr-namespace:Artemis.UI.Screens.Workshop.Home"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="550"
|
||||||
|
x:Class="Artemis.UI.Screens.Workshop.Home.WorkshopOfflineView"
|
||||||
|
x:DataType="home:WorkshopOfflineViewModel">
|
||||||
|
<Border Classes="router-container">
|
||||||
|
<Panel>
|
||||||
|
<ProgressBar ZIndex="999" IsIndeterminate="True" IsVisible="{CompiledBinding Retry.IsExecuting^}" VerticalAlignment="Top"></ProgressBar>
|
||||||
|
|
||||||
|
<StackPanel Margin="0 75 0 0">
|
||||||
|
<StackPanel.Styles>
|
||||||
|
<Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextAlignment" Value="Center"></Setter>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
|
</StackPanel.Styles>
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">Could not reach the workshop</TextBlock>
|
||||||
|
<TextBlock Text="{CompiledBinding Message}" MaxWidth="600" Classes="subtitle"/>
|
||||||
|
<avalonia:MaterialIcon Kind="LanDisconnect" Width="120" Height="120" Margin="0 60"></avalonia:MaterialIcon>
|
||||||
|
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">Please ensure you are connected to the internet.</TextBlock>
|
||||||
|
<TextBlock Margin="0 10" Classes="subtitle">If this keeps occuring, hit us up on Discord</TextBlock>
|
||||||
|
|
||||||
|
<Button HorizontalAlignment="Center" Margin="0 20" Command="{CompiledBinding Retry}">Retry</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Home;
|
||||||
|
|
||||||
|
public partial class WorkshopOfflineView : ReactiveUserControl<WorkshopOfflineViewModel>
|
||||||
|
{
|
||||||
|
public WorkshopOfflineView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Reactive;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.UI.Shared;
|
||||||
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.Services;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.Workshop.Home;
|
||||||
|
|
||||||
|
public class WorkshopOfflineViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopOfflineParameters>, IWorkshopViewModel
|
||||||
|
{
|
||||||
|
private readonly IRouter _router;
|
||||||
|
private readonly IWorkshopService _workshopService;
|
||||||
|
private string _message;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public WorkshopOfflineViewModel(IWorkshopService workshopService, IRouter router)
|
||||||
|
{
|
||||||
|
_workshopService = workshopService;
|
||||||
|
_router = router;
|
||||||
|
|
||||||
|
Retry = ReactiveCommand.CreateFromTask(ExecuteRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReactiveCommand<Unit, Unit> Retry { get; }
|
||||||
|
|
||||||
|
public string Message
|
||||||
|
{
|
||||||
|
get => _message;
|
||||||
|
set => RaiseAndSetIfChanged(ref _message, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnNavigating(WorkshopOfflineParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Message = parameters.Message;
|
||||||
|
return base.OnNavigating(parameters, args, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteRetry(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
IWorkshopService.WorkshopStatus status = await _workshopService.GetWorkshopStatus();
|
||||||
|
if (status.IsReachable)
|
||||||
|
await _router.Navigate("workshop");
|
||||||
|
|
||||||
|
Message = status.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntryType? EntryType => null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WorkshopOfflineParameters
|
||||||
|
{
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
@ -88,27 +88,34 @@
|
|||||||
<Border Classes="card-separator" />
|
<Border Classes="card-separator" />
|
||||||
<Button CornerRadius="8"
|
<Button CornerRadius="8"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Stretch">
|
HorizontalContentAlignment="Stretch"
|
||||||
|
Command="{CompiledBinding DownloadLatestRelease}">
|
||||||
<Grid ColumnDefinitions="Auto,*">
|
<Grid ColumnDefinitions="Auto,*">
|
||||||
<!-- Icon -->
|
<!-- Icon -->
|
||||||
<Border Grid.Column="0"
|
<Border Grid.Column="0"
|
||||||
CornerRadius="8"
|
CornerRadius="4"
|
||||||
Background="{StaticResource SystemAccentColor}"
|
Background="{StaticResource SystemAccentColor}"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0 6"
|
Margin="0 6"
|
||||||
Width="45"
|
Width="50"
|
||||||
Height="45"
|
Height="50"
|
||||||
ClipToBounds="True">
|
ClipToBounds="True">
|
||||||
<avalonia:MaterialIcon Kind="Download"></avalonia:MaterialIcon>
|
<avalonia:MaterialIcon Kind="Download"></avalonia:MaterialIcon>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<StackPanel Grid.Column="1" Margin="10 6">
|
<StackPanel Grid.Column="1" Margin="10 0" VerticalAlignment="Center">
|
||||||
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
<TextBlock Text="{CompiledBinding Entry.LatestRelease.Version, FallbackValue=Version}"></TextBlock>
|
||||||
<TextBlock Classes="subtitle">
|
<TextBlock Classes="subtitle">
|
||||||
<avalonia:MaterialIcon Kind="BoxOutline" />
|
<avalonia:MaterialIcon Kind="BoxOutline" />
|
||||||
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
<Run Text="{CompiledBinding Entry.LatestRelease.DownloadSize, Converter={StaticResource BytesToStringConverter}, Mode=OneWay}"></Run>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<TextBlock Classes="subtitle"
|
||||||
|
ToolTip.Tip="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}}">
|
||||||
|
<avalonia:MaterialIcon Kind="Calendar" />
|
||||||
|
<Run>Created</Run>
|
||||||
|
<Run Text="{CompiledBinding Entry.LatestRelease.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"></Run>
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Reactive;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Artemis.Core;
|
||||||
using Artemis.UI.Screens.Workshop.Parameters;
|
using Artemis.UI.Screens.Workshop.Parameters;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Routing;
|
using Artemis.UI.Shared.Routing;
|
||||||
|
using Artemis.UI.Shared.Services;
|
||||||
|
using Artemis.UI.Shared.Services.Builders;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
using Artemis.WebClient.Workshop;
|
using Artemis.WebClient.Workshop;
|
||||||
|
using Artemis.WebClient.Workshop.DownloadHandlers;
|
||||||
|
using Artemis.WebClient.Workshop.DownloadHandlers.Implementations;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using StrawberryShake;
|
using StrawberryShake;
|
||||||
|
|
||||||
@ -14,18 +21,24 @@ namespace Artemis.UI.Screens.Workshop.Profile;
|
|||||||
public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopDetailParameters>, IWorkshopViewModel
|
public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase, WorkshopDetailParameters>, IWorkshopViewModel
|
||||||
{
|
{
|
||||||
private readonly IWorkshopClient _client;
|
private readonly IWorkshopClient _client;
|
||||||
|
private readonly ProfileEntryDownloadHandler _downloadHandler;
|
||||||
|
private readonly INotificationService _notificationService;
|
||||||
private readonly ObservableAsPropertyHelper<DateTimeOffset?> _updatedAt;
|
private readonly ObservableAsPropertyHelper<DateTimeOffset?> _updatedAt;
|
||||||
private IGetEntryById_Entry? _entry;
|
private IGetEntryById_Entry? _entry;
|
||||||
|
|
||||||
public ProfileDetailsViewModel(IWorkshopClient client)
|
public ProfileDetailsViewModel(IWorkshopClient client, ProfileEntryDownloadHandler downloadHandler, INotificationService notificationService)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_downloadHandler = downloadHandler;
|
||||||
|
_notificationService = notificationService;
|
||||||
_updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt);
|
_updatedAt = this.WhenAnyValue(vm => vm.Entry).Select(e => e?.LatestRelease?.CreatedAt ?? e?.CreatedAt).ToProperty(this, vm => vm.UpdatedAt);
|
||||||
|
|
||||||
|
DownloadLatestRelease = ReactiveCommand.CreateFromTask(ExecuteDownloadLatestRelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset? UpdatedAt => _updatedAt.Value;
|
public ReactiveCommand<Unit, Unit> DownloadLatestRelease { get; }
|
||||||
|
|
||||||
public EntryType? EntryType => null;
|
public DateTimeOffset? UpdatedAt => _updatedAt.Value;
|
||||||
|
|
||||||
public IGetEntryById_Entry? Entry
|
public IGetEntryById_Entry? Entry
|
||||||
{
|
{
|
||||||
@ -46,4 +59,18 @@ public class ProfileDetailsViewModel : RoutableScreen<ActivatableViewModelBase,
|
|||||||
|
|
||||||
Entry = result.Data?.Entry;
|
Entry = result.Data?.Entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteDownloadLatestRelease(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (Entry?.LatestRelease == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EntryInstallResult<ProfileConfiguration> result = await _downloadHandler.InstallProfileAsync(Entry.LatestRelease.Id, new Progress<StreamProgress>(), cancellationToken);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
_notificationService.CreateNotification().WithTitle("Profile installed").WithSeverity(NotificationSeverity.Success).Show();
|
||||||
|
else
|
||||||
|
_notificationService.CreateNotification().WithTitle("Failed to install profile").WithMessage(result.Message).WithSeverity(NotificationSeverity.Error).Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntryType? EntryType => null;
|
||||||
}
|
}
|
||||||
@ -15,23 +15,31 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
</StackPanel.Styles>
|
</StackPanel.Styles>
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}" TextAlignment="Center" TextWrapping="Wrap">
|
|
||||||
|
<TextBlock IsVisible="{CompiledBinding !Finished}" Theme="{StaticResource TitleTextBlockStyle}" TextAlignment="Center" TextWrapping="Wrap">
|
||||||
Uploading your submission...
|
Uploading your submission...
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock TextAlignment="Center" TextWrapping="Wrap">
|
|
||||||
Wooo, the final step, that was pretty easy, right!?
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<ProgressBar Margin="0 15 0 0"
|
<ProgressBar IsVisible="{CompiledBinding !Finished}"
|
||||||
|
Margin="0 15 0 0"
|
||||||
Width="380"
|
Width="380"
|
||||||
IsVisible="{CompiledBinding !Finished}"
|
|
||||||
IsIndeterminate="{CompiledBinding ProgressIndeterminate}"
|
IsIndeterminate="{CompiledBinding ProgressIndeterminate}"
|
||||||
Value="{CompiledBinding ProgressPercentage}"></ProgressBar>
|
Value="{CompiledBinding ProgressPercentage}">
|
||||||
|
</ProgressBar>
|
||||||
|
|
||||||
<StackPanel IsVisible="{CompiledBinding Finished}" Margin="0 100 0 0">
|
<StackPanel IsVisible="{CompiledBinding Succeeded}">
|
||||||
<Lottie Path="/Assets/Animations/success.json" RepeatCount="1" Width="300" Height="300"></Lottie>
|
<Lottie Path="/Assets/Animations/success.json" RepeatCount="1" Width="250" Height="250" Margin="0 100"></Lottie>
|
||||||
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">All done! Hit finish to view your submission.</TextBlock>
|
<TextBlock Theme="{StaticResource TitleTextBlockStyle}">All done! Hit finish to view your submission.</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{CompiledBinding Failed}">
|
||||||
|
<TextBlock FontSize="140" Margin="0 100">😢</TextBlock>
|
||||||
|
<TextBlock Theme="{StaticResource SubtitleTextBlockStyle}">
|
||||||
|
Unfortunately something went wrong while uploading your submission.
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock>Hit finish to view your submission, from there you can try to upload a new release.</TextBlock>
|
||||||
|
<TextBlock Margin="0 10" Classes="subtitle">If this keeps occuring, hit us up on Discord</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -28,8 +28,10 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
private readonly ObservableAsPropertyHelper<int> _progressPercentage;
|
private readonly ObservableAsPropertyHelper<int> _progressPercentage;
|
||||||
private readonly ObservableAsPropertyHelper<bool> _progressIndeterminate;
|
private readonly ObservableAsPropertyHelper<bool> _progressIndeterminate;
|
||||||
|
|
||||||
private bool _finished;
|
|
||||||
private Guid? _entryId;
|
private Guid? _entryId;
|
||||||
|
private bool _finished;
|
||||||
|
private bool _succeeded;
|
||||||
|
private bool _failed;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public UploadStepViewModel(IWorkshopClient workshopClient, IWorkshopService workshopService, EntryUploadHandlerFactory entryUploadHandlerFactory, IWindowService windowService, IRouter router)
|
public UploadStepViewModel(IWorkshopClient workshopClient, IWorkshopService workshopService, EntryUploadHandlerFactory entryUploadHandlerFactory, IWindowService windowService, IRouter router)
|
||||||
@ -69,6 +71,18 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
set => RaiseAndSetIfChanged(ref _finished, value);
|
set => RaiseAndSetIfChanged(ref _finished, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Succeeded
|
||||||
|
{
|
||||||
|
get => _succeeded;
|
||||||
|
set => RaiseAndSetIfChanged(ref _succeeded, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Failed
|
||||||
|
{
|
||||||
|
get => _failed;
|
||||||
|
set => RaiseAndSetIfChanged(ref _failed, value);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ExecuteUpload(CancellationToken cancellationToken)
|
public async Task ExecuteUpload(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IOperationResult<IAddEntryResult> result = await _workshopClient.AddEntry.ExecuteAsync(new CreateEntryInput
|
IOperationResult<IAddEntryResult> result = await _workshopClient.AddEntry.ExecuteAsync(new CreateEntryInput
|
||||||
@ -85,6 +99,7 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
if (result.IsErrorResult() || entryId == null)
|
if (result.IsErrorResult() || entryId == null)
|
||||||
{
|
{
|
||||||
await _windowService.ShowConfirmContentDialog("Failed to create workshop entry", result.Errors.ToString() ?? "Not even an error message", "Close", null);
|
await _windowService.ShowConfirmContentDialog("Failed to create workshop entry", result.Errors.ToString() ?? "Not even an error message", "Close", null);
|
||||||
|
State.ChangeScreen<SubmitStepViewModel>();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,21 +128,27 @@ public class UploadStepViewModel : SubmissionViewModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
_entryId = entryId;
|
_entryId = entryId;
|
||||||
Finished = true;
|
Succeeded = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
// Something went wrong when creating a release :c
|
// Something went wrong when creating a release :c
|
||||||
// We'll keep the workshop entry so that the user can make changes and try again
|
// We'll keep the workshop entry so that the user can make changes and try again
|
||||||
|
Failed = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Finished = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExecuteContinue()
|
private async Task ExecuteContinue()
|
||||||
{
|
{
|
||||||
|
State.Finish();
|
||||||
|
|
||||||
if (_entryId == null)
|
if (_entryId == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
State.Finish();
|
|
||||||
switch (State.EntryType)
|
switch (State.EntryType)
|
||||||
{
|
{
|
||||||
case EntryType.Layout:
|
case EntryType.Layout:
|
||||||
|
|||||||
@ -9,6 +9,8 @@ using System.Threading.Tasks;
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Exceptions;
|
using Artemis.UI.Exceptions;
|
||||||
using Artemis.UI.Extensions;
|
using Artemis.UI.Extensions;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
using Artemis.WebClient.Updating;
|
using Artemis.WebClient.Updating;
|
||||||
using Octodiff.Core;
|
using Octodiff.Core;
|
||||||
using Octodiff.Diagnostics;
|
using Octodiff.Diagnostics;
|
||||||
@ -32,7 +34,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
private IGetReleaseById_PublishedRelease _release = null!;
|
private IGetReleaseById_PublishedRelease _release = null!;
|
||||||
private IGetReleaseById_PublishedRelease_Artifacts _artifact = null!;
|
private IGetReleaseById_PublishedRelease_Artifacts _artifact = null!;
|
||||||
|
|
||||||
private Progress<float> _stepProgress = new();
|
private Progress<StreamProgress> _stepProgress = new();
|
||||||
private string _status = string.Empty;
|
private string _status = string.Empty;
|
||||||
private float _floatProgress;
|
private float _floatProgress;
|
||||||
|
|
||||||
@ -69,9 +71,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
|
|
||||||
public async Task InstallAsync(CancellationToken cancellationToken)
|
public async Task InstallAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_stepProgress = new Progress<float>();
|
_stepProgress = new Progress<StreamProgress>();
|
||||||
|
|
||||||
((IProgress<float>) _progress).Report(0);
|
|
||||||
|
|
||||||
Status = "Retrieving details";
|
Status = "Retrieving details";
|
||||||
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
|
_logger.Information("Retrieving details for release {ReleaseId}", _releaseId);
|
||||||
@ -99,7 +99,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
{
|
{
|
||||||
// 10 - 50%
|
// 10 - 50%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.4f);
|
void StepProgressOnProgressChanged(object? sender, StreamProgress e) => ((IProgress<float>) _progress).Report(10f + e.ProgressPercentage * 0.4f);
|
||||||
|
|
||||||
Status = "Downloading...";
|
Status = "Downloading...";
|
||||||
await using MemoryStream stream = new();
|
await using MemoryStream stream = new();
|
||||||
@ -113,7 +113,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
{
|
{
|
||||||
// 50 - 60%
|
// 50 - 60%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(50f + e * 0.1f);
|
void StepProgressOnProgressChanged(object? sender, StreamProgress e) => ((IProgress<float>) _progress).Report(50f + e.ProgressPercentage * 0.1f);
|
||||||
|
|
||||||
Status = "Patching...";
|
Status = "Patching...";
|
||||||
await using FileStream newFileStream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
await using FileStream newFileStream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
@ -139,7 +139,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
{
|
{
|
||||||
// 10 - 60%
|
// 10 - 60%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(10f + e * 0.5f);
|
void StepProgressOnProgressChanged(object? sender, StreamProgress e) => ((IProgress<float>) _progress).Report(10f + e.ProgressPercentage * 0.5f);
|
||||||
|
|
||||||
Status = "Downloading...";
|
Status = "Downloading...";
|
||||||
await using FileStream stream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
await using FileStream stream = new(Path.Combine(Constants.UpdatingFolder, $"{_release.Version}.zip"), FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
@ -155,7 +155,7 @@ public class ReleaseInstaller : CorePropertyChanged
|
|||||||
{
|
{
|
||||||
// 60 - 100%
|
// 60 - 100%
|
||||||
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
_stepProgress.ProgressChanged += StepProgressOnProgressChanged;
|
||||||
void StepProgressOnProgressChanged(object? sender, float e) => ((IProgress<float>) _progress).Report(60f + e * 0.4f);
|
void StepProgressOnProgressChanged(object? sender, StreamProgress e) => ((IProgress<float>) _progress).Report(60f + e.ProgressPercentage * 0.4f);
|
||||||
|
|
||||||
Status = "Extracting...";
|
Status = "Extracting...";
|
||||||
// Ensure the directory is empty
|
// Ensure the directory is empty
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
using Artemis.Web.Workshop.Entities;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop.DownloadHandlers;
|
||||||
|
|
||||||
|
public class EntryInstallResult<T>
|
||||||
|
{
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public T? Result { get; set; }
|
||||||
|
|
||||||
|
public static EntryInstallResult<T> FromFailure(string? message)
|
||||||
|
{
|
||||||
|
return new EntryInstallResult<T>
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntryInstallResult<T> FromSuccess(T installationResult)
|
||||||
|
{
|
||||||
|
return new EntryInstallResult<T>
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
Result = installationResult
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop.DownloadHandlers;
|
||||||
|
|
||||||
|
public interface IEntryDownloadHandler
|
||||||
|
{
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.UI.Shared.Extensions;
|
||||||
|
using Artemis.UI.Shared.Utilities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Artemis.WebClient.Workshop.DownloadHandlers.Implementations;
|
||||||
|
|
||||||
|
public class ProfileEntryDownloadHandler : IEntryDownloadHandler
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly IProfileService _profileService;
|
||||||
|
|
||||||
|
public ProfileEntryDownloadHandler(IHttpClientFactory httpClientFactory, IProfileService profileService)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_profileService = profileService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EntryInstallResult<ProfileConfiguration>> InstallProfileAsync(Guid releaseId, Progress<StreamProgress> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME);
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
await client.DownloadDataAsync($"releases/download/{releaseId}", stream, progress, cancellationToken);
|
||||||
|
|
||||||
|
using ZipArchive zipArchive = new(stream, ZipArchiveMode.Read);
|
||||||
|
List<ZipArchiveEntry> profiles = zipArchive.Entries.Where(e => e.Name.EndsWith("json", StringComparison.InvariantCultureIgnoreCase)).ToList();
|
||||||
|
ZipArchiveEntry userProfileEntry = profiles.First();
|
||||||
|
ProfileConfigurationExportModel profile = await GetProfile(userProfileEntry);
|
||||||
|
|
||||||
|
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == "Workshop") ?? _profileService.CreateProfileCategory("Workshop", true);
|
||||||
|
ProfileConfiguration profileConfiguration = _profileService.ImportProfile(category, profile, true, true, null);
|
||||||
|
return EntryInstallResult<ProfileConfiguration>.FromSuccess(profileConfiguration);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return EntryInstallResult<ProfileConfiguration>.FromFailure(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ProfileConfigurationExportModel> GetProfile(ZipArchiveEntry userProfileEntry)
|
||||||
|
{
|
||||||
|
await using Stream stream = userProfileEntry.Open();
|
||||||
|
using StreamReader reader = new(stream);
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(await reader.ReadToEndAsync(), IProfileService.ExportSettings)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Artemis.WebClient.Workshop.DownloadHandlers;
|
||||||
using Artemis.WebClient.Workshop.Extensions;
|
using Artemis.WebClient.Workshop.Extensions;
|
||||||
using Artemis.WebClient.Workshop.Repositories;
|
using Artemis.WebClient.Workshop.Repositories;
|
||||||
using Artemis.WebClient.Workshop.Services;
|
using Artemis.WebClient.Workshop.Services;
|
||||||
@ -48,5 +49,6 @@ public static class ContainerExtensions
|
|||||||
|
|
||||||
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
|
container.Register<EntryUploadHandlerFactory>(Reuse.Transient);
|
||||||
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
|
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryUploadHandler>(), Reuse.Transient);
|
||||||
|
container.RegisterMany(workshopAssembly, type => type.IsAssignableTo<IEntryDownloadHandler>(), Reuse.Transient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,6 @@
|
|||||||
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using Artemis.UI.Shared.Routing;
|
||||||
using Artemis.UI.Shared.Services.MainWindow;
|
using Artemis.UI.Shared.Services.MainWindow;
|
||||||
using Artemis.UI.Shared.Utilities;
|
using Artemis.UI.Shared.Utilities;
|
||||||
using Artemis.WebClient.Workshop.UploadHandlers;
|
using Artemis.WebClient.Workshop.UploadHandlers;
|
||||||
@ -11,11 +13,13 @@ public class WorkshopService : IWorkshopService
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<Guid, Stream> _entryIconCache = new();
|
private readonly Dictionary<Guid, Stream> _entryIconCache = new();
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly IRouter _router;
|
||||||
private readonly SemaphoreSlim _iconCacheLock = new(1);
|
private readonly SemaphoreSlim _iconCacheLock = new(1);
|
||||||
|
|
||||||
public WorkshopService(IHttpClientFactory httpClientFactory, IMainWindowService mainWindowService)
|
public WorkshopService(IHttpClientFactory httpClientFactory, IMainWindowService mainWindowService, IRouter router)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_router = router;
|
||||||
mainWindowService.MainWindowClosed += (_, _) => Dispatcher.UIThread.InvokeAsync(async () =>
|
mainWindowService.MainWindowClosed += (_, _) => Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
@ -43,6 +47,31 @@ public class WorkshopService : IWorkshopService
|
|||||||
return ImageUploadResult.FromSuccess();
|
return ImageUploadResult.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<IWorkshopService.WorkshopStatus> GetWorkshopStatus()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Don't use the workshop client which adds auth headers
|
||||||
|
HttpClient client = _httpClientFactory.CreateClient();
|
||||||
|
HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, WorkshopConstants.WORKSHOP_URL + "/status"));
|
||||||
|
return new IWorkshopService.WorkshopStatus(response.IsSuccessStatusCode, response.StatusCode.ToString());
|
||||||
|
}
|
||||||
|
catch (HttpRequestException e)
|
||||||
|
{
|
||||||
|
return new IWorkshopService.WorkshopStatus(false, e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<bool> ValidateWorkshopStatus()
|
||||||
|
{
|
||||||
|
IWorkshopService.WorkshopStatus status = await GetWorkshopStatus();
|
||||||
|
if (!status.IsReachable)
|
||||||
|
await _router.Navigate($"workshop/offline/{status.Message}");
|
||||||
|
return status.IsReachable;
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearCache()
|
private void ClearCache()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -63,4 +92,8 @@ public class WorkshopService : IWorkshopService
|
|||||||
public interface IWorkshopService
|
public interface IWorkshopService
|
||||||
{
|
{
|
||||||
Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
|
Task<ImageUploadResult> SetEntryIcon(Guid entryId, Progress<StreamProgress> progress, Stream icon, CancellationToken cancellationToken);
|
||||||
|
Task<WorkshopStatus> GetWorkshopStatus();
|
||||||
|
Task<bool> ValidateWorkshopStatus();
|
||||||
|
|
||||||
|
public record WorkshopStatus(bool IsReachable, string Message);
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user