From 6b4e84c95a72ad8f7daa9984f9e9c92563c11ead Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Thu, 2 Nov 2023 22:29:42 +0100 Subject: [PATCH] Added layout archive generation --- src/Artemis.Core/Artemis.Core.csproj | 7 +- .../Models/Surface/Layout/ArtemisLedLayout.cs | 7 +- .../Artemis.UI.Shared.csproj | 5 ++ src/Artemis.UI/Artemis.UI.csproj | 7 +- .../Image/ImageSubmissionViewModel.cs | 2 - .../Workshop/Layout/LayoutInfoViewModel.cs | 13 ++- .../Profile/ProfileDetailsViewModel.cs | 1 - .../Steps/ImagesStepViewModel.cs | 10 ++- .../Steps/Layout/LayoutInfoStepViewModel.cs | 29 +------ .../Layout/LayoutSelectionStepViewModel.cs | 79 ++++++++++++++++--- .../Steps/UploadStepViewModel.cs | 34 ++++++-- .../Artemis.WebClient.Workshop.csproj | 6 ++ ...emis.WebClient.Workshop.csproj.DotSettings | 4 + .../EntryInstallationHandlerFactory.cs | 3 +- .../ProfileEntryInstallationHandler.cs | 2 +- .../LayoutEntryUploadHandler.cs | 61 +++++++++++--- .../Services/Interfaces/IWorkshopService.cs | 1 + .../Services/WorkshopService.cs | 21 +++++ 18 files changed, 224 insertions(+), 68 deletions(-) create mode 100644 src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj.DotSettings diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index dafe7ff82..68c2ce582 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -47,7 +47,6 @@ - @@ -65,4 +64,10 @@ + + + + ..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll + + \ No newline at end of file diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs index 18fc2490f..55e237b5d 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs @@ -64,7 +64,12 @@ public class ArtemisLedLayout private void ApplyLogicalLayout(LayoutCustomLedDataLogicalLayout logicalLayout) { + string? layoutDirectory = Path.GetDirectoryName(DeviceLayout.FilePath); + LogicalName = logicalLayout.Name; - Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute); + if (layoutDirectory != null && logicalLayout.Image != null) + Image = new Uri(Path.Combine(layoutDirectory, logicalLayout.Image!), UriKind.Absolute); + else + Image = null; } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index bc8e31ad8..e37523b1d 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -29,4 +29,9 @@ + + + ..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll + + diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 0c9471912..e4a63012a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -44,7 +44,6 @@ - @@ -53,4 +52,10 @@ + + + + ..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Image/ImageSubmissionViewModel.cs b/src/Artemis.UI/Screens/Workshop/Image/ImageSubmissionViewModel.cs index 5b0c03cbe..c431bc2cf 100644 --- a/src/Artemis.UI/Screens/Workshop/Image/ImageSubmissionViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Image/ImageSubmissionViewModel.cs @@ -1,9 +1,7 @@ using System.IO; using System.Reactive.Disposables; -using System.Threading.Tasks; using System.Windows.Input; using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; using Avalonia.Media.Imaging; using Avalonia.Threading; using PropertyChanged.SourceGenerator; diff --git a/src/Artemis.UI/Screens/Workshop/Layout/LayoutInfoViewModel.cs b/src/Artemis.UI/Screens/Workshop/Layout/LayoutInfoViewModel.cs index 22ee6a750..b34228082 100644 --- a/src/Artemis.UI/Screens/Workshop/Layout/LayoutInfoViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Layout/LayoutInfoViewModel.cs @@ -9,6 +9,7 @@ using Artemis.Core.Services; using Artemis.UI.Screens.Workshop.Layout.Dialogs; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Artemis.WebClient.Workshop.Handlers.UploadHandlers; using PropertyChanged.SourceGenerator; using ReactiveUI; using ReactiveUI.Validation.Extensions; @@ -24,7 +25,7 @@ public partial class LayoutInfoViewModel : ValidatableViewModelBase [Notify] private Guid _deviceProviderId; [Notify] private string? _deviceProviderIdInput; [Notify] private ICommand? _remove; - + public LayoutInfoViewModel(ArtemisLayout layout, IDeviceService deviceService, IWindowService windowService, @@ -65,4 +66,14 @@ public partial class LayoutInfoViewModel : ValidatableViewModelBase if (deviceProvider != null) DeviceProviderId = deviceProvider.Plugin.Guid; } + + public LayoutInfo ToLayoutInfo() + { + return new LayoutInfo + { + Model = Model ?? string.Empty, + Vendor = Vendor ?? string.Empty, + DeviceProviderId = DeviceProviderId + }; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs index 4dd85cd7f..c413c9eee 100644 --- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsViewModel.cs @@ -11,7 +11,6 @@ using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop; using Artemis.WebClient.Workshop.Handlers.InstallationHandlers; -using Artemis.WebClient.Workshop.Handlers.InstallationHandlers.Implementations; using PropertyChanged.SourceGenerator; using ReactiveUI; using StrawberryShake; diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ImagesStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ImagesStepViewModel.cs index 4e0d1e7c5..411bf7fb3 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ImagesStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/ImagesStepViewModel.cs @@ -14,6 +14,7 @@ namespace Artemis.UI.Screens.Workshop.SubmissionWizard.Steps; public class ImagesStepViewModel : SubmissionViewModel { + private const long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB private readonly IWindowService _windowService; private readonly SourceList _imageStreams; @@ -60,7 +61,14 @@ public class ImagesStepViewModel : SubmissionViewModel if (_imageStreams.Items.Any(i => i is FileStream fs && fs.Name == path)) continue; - FileStream stream = new(path, FileMode.Open); + FileStream stream = new(path, FileMode.Open, FileAccess.Read); + if (stream.Length > MAX_FILE_SIZE) + { + await _windowService.ShowConfirmContentDialog("File too big", $"File {path} exceeds maximum file size of 10 MB", "Skip file", null); + await stream.DisposeAsync(); + continue; + } + _imageStreams.Add(stream); State.Images.Add(stream); } diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs index 4b99fb802..23c3b293e 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutInfoStepViewModel.cs @@ -85,40 +85,15 @@ public partial class LayoutInfoStepViewModel : SubmissionViewModel return; layoutEntrySource.PhysicalLayout = PhysicalLayout; - // layoutEntrySource.LayoutInfo = new List(LayoutInfo.Select(i => i.ToLayoutInfo())); + layoutEntrySource.LayoutInfo = new List(LayoutInfo.Select(i => i.ToLayoutInfo())); if (string.IsNullOrWhiteSpace(State.Name)) State.Name = layoutEntrySource.Layout.RgbLayout.Name ?? ""; if (string.IsNullOrWhiteSpace(State.Summary)) - { State.Summary = !string.IsNullOrWhiteSpace(layoutEntrySource.Layout.RgbLayout.Vendor) ? $"{layoutEntrySource.Layout.RgbLayout.Vendor} {layoutEntrySource.Layout.RgbLayout.Type} device layout" : $"{layoutEntrySource.Layout.RgbLayout.Type} device layout"; - } - - if (string.IsNullOrWhiteSpace(State.Description)) - { - State.Description = $@"### Layout properties -**Name** -{layoutEntrySource.Layout.RgbLayout.Name ?? "N/A"} -**Description** -{layoutEntrySource.Layout.RgbLayout.Description ?? "N/A"} -**Author** -{layoutEntrySource.Layout.RgbLayout.Author ?? "N/A"} -**Type** -{layoutEntrySource.Layout.RgbLayout.Type} -**Vendor** -{layoutEntrySource.Layout.RgbLayout.Vendor ?? "N/A"} -**Model** -{layoutEntrySource.Layout.RgbLayout.Model ?? "N/A"} -**Shape** -{layoutEntrySource.Layout.RgbLayout.Shape} -**Width** -{layoutEntrySource.Layout.RgbLayout.Width}mm -**Height** -{layoutEntrySource.Layout.RgbLayout.Height}mm"; - } - + State.Categories = new List {8}; // Device category, yes this could change but why would it if (State.EntryId == null) State.ChangeScreen(); diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutSelectionStepViewModel.cs index 9dc0899a1..69e4709c1 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Layout/LayoutSelectionStepViewModel.cs @@ -9,6 +9,7 @@ using System.IO; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; +using Artemis.UI.Exceptions; using Artemis.UI.Screens.Workshop.SubmissionWizard.Models; using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Services; @@ -60,15 +61,23 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel if (selected == null || selected.Length != 1) return; - ArtemisLayout layout = new(selected[0], LayoutSource.User); - if (!layout.IsValid) + try { - await _windowService.ShowConfirmContentDialog("Invalid layout file", "The selected file does not appear to be a valid RGB.NET layout file", cancel: null); - return; - } + ArtemisLayout layout = new(selected[0], LayoutSource.User); + if (!layout.IsValid) + { + await _windowService.ShowConfirmContentDialog("Failed to load layout", "The selected file does not appear to be a valid RGB.NET layout file.", "Close", null); + return; + } - SelectedDevice = null; - Layout = layout; + SelectedDevice = null; + Layout = layout; + } + catch (Exception e) + { + await _windowService.ShowConfirmContentDialog("Failed to load layout", "The selected file does not appear to be a valid RGB.NET layout file.\r\n" + e.Message, "Close", null); + throw; + } } private void CreatePreviewDevice(ArtemisLayout? layout) @@ -87,25 +96,69 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel { if (Layout == null) return; + if (!await ValidateLayout(Layout)) + return; State.EntrySource = new LayoutEntrySource(Layout); await Dispatcher.UIThread.InvokeAsync(SetDeviceImages, DispatcherPriority.Background); State.ChangeScreen(); } + private async Task ValidateLayout(ArtemisLayout? layout) + { + if (layout == null) + return true; + string? layoutPath = Path.GetDirectoryName(layout.FilePath); + if (layoutPath == null) + throw new ArtemisUIException($"Could not determine directory of {layout.FilePath}"); + + if (layout.LayoutCustomDeviceData.DeviceImage != null && !File.Exists(Path.Combine(layoutPath, layout.LayoutCustomDeviceData.DeviceImage))) + { + await _windowService.ShowConfirmContentDialog( + "Device image not found", + $"{Path.Combine(layoutPath, layout.LayoutCustomDeviceData.DeviceImage)} does not exist.", + "Close", + null + ); + return false; + } + + foreach (ArtemisLedLayout ledLayout in layout.Leds) + { + if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) + continue; + + foreach (LayoutCustomLedDataLogicalLayout customData in ledLayout.LayoutCustomLedData.LogicalLayouts) + { + if (customData.Image == null || File.Exists(Path.Combine(layoutPath, customData.Image))) + continue; + await _windowService.ShowConfirmContentDialog( + $"Image not found for LED {ledLayout.RgbLayout.Id} ({customData.Name})", + $"{Path.Combine(layoutPath, customData.Image)} does not exist.", + "Close", + null + ); + return false; + } + } + + return true; + } + private void SetDeviceImages() { if (Layout == null) return; - + MemoryStream deviceWithoutLeds = new(); MemoryStream deviceWithLeds = new(); - + using (RenderTargetBitmap image = Layout.RenderLayout(false)) { image.Save(deviceWithoutLeds); deviceWithoutLeds.Seek(0, SeekOrigin.Begin); } + using (RenderTargetBitmap image = Layout.RenderLayout(true)) { image.Save(deviceWithLeds); @@ -116,7 +169,7 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel foreach (Stream stateImage in State.Images) stateImage.Dispose(); State.Images.Clear(); - + // Go through the hassle of resizing the image to 128x128 without losing aspect ratio, padding is added for this State.Icon = ResizeImage(deviceWithoutLeds, 128); State.Images.Add(deviceWithoutLeds); @@ -126,11 +179,11 @@ public partial class LayoutSelectionStepViewModel : SubmissionViewModel private Stream ResizeImage(Stream image, int size) { MemoryStream output = new(); - using MemoryStream input = new(); - + using MemoryStream input = new(); + image.CopyTo(input); input.Seek(0, SeekOrigin.Begin); - + using SKBitmap? sourceBitmap = SKBitmap.Decode(input); int sourceWidth = sourceBitmap.Width; int sourceHeight = sourceBitmap.Height; diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs index c797bb1d8..89814a7fa 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/UploadStepViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -131,12 +132,34 @@ public partial class UploadStepViewModel : SubmissionViewModel State.ChangeScreen(); return null; } - + + foreach (Stream image in State.Images.ToList()) + { + // Upload image + try + { + ImageUploadResult imageUploadResult = await _workshopService.UploadEntryImage(entryId.Value, _progress, image, cancellationToken); + if (!imageUploadResult.IsSuccess) + throw new ArtemisWorkshopException(imageUploadResult.Message); + State.Images.Remove(image); + } + catch (Exception e) + { + // It's not critical if this fails + await _windowService.ShowConfirmContentDialog("Failed to upload image", "Your submission will continue, you can try upload a new image afterwards\r\n" + e.Message, "Continue", null); + } + + if (cancellationToken.IsCancellationRequested) + { + State.ChangeScreen(); + return null; + } + } if (State.Icon == null) return entryId; - // Upload image + // Upload icon try { ImageUploadResult imageUploadResult = await _workshopService.SetEntryIcon(entryId.Value, _progress, State.Icon, cancellationToken); @@ -146,12 +169,7 @@ public partial class UploadStepViewModel : SubmissionViewModel catch (Exception e) { // It's not critical if this fails - await _windowService.ShowConfirmContentDialog( - "Failed to upload icon", - "Your submission will continue, you can try upload a new image afterwards\r\n" + e.Message, - "Continue", - null - ); + await _windowService.ShowConfirmContentDialog("Failed to upload icon", "Your submission will continue, you can try upload a new image afterwards\r\n" + e.Message, "Continue", null); } return entryId; diff --git a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj index 15fc63b9d..0f6acb5f9 100644 --- a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj +++ b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj @@ -53,4 +53,10 @@ + + + + ..\..\..\RGB.NET\bin\net7.0\RGB.NET.Layout.dll + + diff --git a/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj.DotSettings b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj.DotSettings new file mode 100644 index 000000000..ebe5d57a0 --- /dev/null +++ b/src/Artemis.WebClient.Workshop/Artemis.WebClient.Workshop.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallationHandlerFactory.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallationHandlerFactory.cs index ec9c63566..9153d7c5a 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallationHandlerFactory.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/EntryInstallationHandlerFactory.cs @@ -1,5 +1,4 @@ -using Artemis.WebClient.Workshop.Handlers.InstallationHandlers.Implementations; -using DryIoc; +using DryIoc; namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers; diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs index f3473f66a..8b7f33e73 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs @@ -4,7 +4,7 @@ using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Utilities; using Artemis.WebClient.Workshop.Services; -namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers.Implementations; +namespace Artemis.WebClient.Workshop.Handlers.InstallationHandlers; public class ProfileEntryInstallationHandler : IEntryInstallationHandler { diff --git a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs index 90f9869c5..d61a63472 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/UploadHandlers/Implementations/LayoutEntryUploadHandler.cs @@ -1,5 +1,7 @@ -using System.Xml.Serialization; +using System.IO.Compression; +using Artemis.Core; using Artemis.UI.Shared.Utilities; +using Artemis.WebClient.Workshop.Exceptions; using RGB.NET.Layout; namespace Artemis.WebClient.Workshop.Handlers.UploadHandlers; @@ -11,15 +13,56 @@ public class LayoutEntryUploadHandler : IEntryUploadHandler { if (entrySource is not LayoutEntrySource source) throw new InvalidOperationException("Can only create releases for layouts"); - - // Create a copy of the layout, image paths are about to be rewritten - XmlSerializer serializer = new(typeof(DeviceLayout)); - using MemoryStream ms = new(); - await using StreamWriter writer = new(ms); - serializer.Serialize(writer, source.Layout.RgbLayout); - await writer.FlushAsync(); - ms.Seek(0, SeekOrigin.Begin); + + using MemoryStream archiveStream = new(); + using MemoryStream layoutStream = new(); + + source.Layout.RgbLayout.Save(layoutStream); + layoutStream.Seek(0, SeekOrigin.Begin); + + // Create an archive + string? layoutPath = Path.GetDirectoryName(source.Layout.FilePath); + if (layoutPath == null) + throw new ArtemisWorkshopException($"Could not determine directory of {source.Layout.FilePath}"); + + using (ZipArchive archive = new(archiveStream, ZipArchiveMode.Create, true)) + { + // Add the layout to the archive + ZipArchiveEntry archiveEntry = archive.CreateEntry("layout.xml"); + await using (Stream layoutArchiveStream = archiveEntry.Open()) + await layoutStream.CopyToAsync(layoutArchiveStream, cancellationToken); + + // Add the layout image to the archive + CopyImage(layoutPath, source.Layout.LayoutCustomDeviceData.DeviceImage, archive); + + // Add the LED images to the archive + foreach (ArtemisLedLayout ledLayout in source.Layout.Leds) + { + if (ledLayout.LayoutCustomLedData.LogicalLayouts == null) + continue; + foreach (LayoutCustomLedDataLogicalLayout customData in ledLayout.LayoutCustomLedData.LogicalLayouts) + CopyImage(layoutPath, customData.Image, archive); + } + } + + archiveStream.Seek(0, SeekOrigin.Begin); + + string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + string filePath = Path.Combine(desktopPath, "layout-test.zip"); + await using (FileStream fileStream = new(filePath, FileMode.Create, FileAccess.Write)) + { + archiveStream.WriteTo(fileStream); + } return new EntryUploadResult(); } + + private static void CopyImage(string layoutPath, string? imagePath, ZipArchive archive) + { + if (imagePath == null) + return; + + string fullPath = Path.Combine(layoutPath, imagePath); + archive.CreateEntryFromFile(fullPath, imagePath); + } } \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs index 3f2303c00..57553b4ac 100644 --- a/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/Interfaces/IWorkshopService.cs @@ -7,6 +7,7 @@ public interface IWorkshopService { Task GetEntryIcon(long entryId, CancellationToken cancellationToken); Task SetEntryIcon(long entryId, Progress progress, Stream icon, CancellationToken cancellationToken); + Task UploadEntryImage(long entryId, Progress progress, Stream image, CancellationToken cancellationToken); Task GetWorkshopStatus(CancellationToken cancellationToken); Task ValidateWorkshopStatus(CancellationToken cancellationToken); Task NavigateToEntry(long entryId, EntryType entryType); diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs index f40a15375..c2b343981 100644 --- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs @@ -56,6 +56,27 @@ public class WorkshopService : IWorkshopService return ImageUploadResult.FromSuccess(); } + /// + public async Task UploadEntryImage(long entryId, Progress progress, Stream image, CancellationToken cancellationToken) + { + image.Seek(0, SeekOrigin.Begin); + + // Submit the archive + HttpClient client = _httpClientFactory.CreateClient(WorkshopConstants.WORKSHOP_CLIENT_NAME); + + // Construct the request + MultipartFormDataContent content = new(); + ProgressableStreamContent streamContent = new(image, progress); + streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + content.Add(streamContent, "file", "file.png"); + + // Submit + HttpResponseMessage response = await client.PostAsync($"entries/{entryId}/image", content, cancellationToken); + if (!response.IsSuccessStatusCode) + return ImageUploadResult.FromFailure($"{response.StatusCode} - {await response.Content.ReadAsStringAsync(cancellationToken)}"); + return ImageUploadResult.FromSuccess(); + } + /// public async Task GetWorkshopStatus(CancellationToken cancellationToken) {