From 703f1604b97560e832830b57e5ba33dd3b8b8937 Mon Sep 17 00:00:00 2001 From: Klaus Fischer <klaus.fischer@eloware.com> Date: Thu, 10 Aug 2023 21:31:42 +0200 Subject: [PATCH] feature: split large files --- .reuse/dep5 | 2 +- Examples/ConsoleAppExample/.gitignore | 1 + .../Attachments/metadata.json | 101 ++++++++++++++++++ .../ConsoleAppExample.csproj | 7 ++ Examples/ConsoleAppExample/Program.cs | 2 +- Examples/ConsoleAppExample/SenderDemo.cs | 2 + .../FitConnect.LargeAttachment.csproj | 13 +++ FitConnect.LargeAttachment/LargeAttachment.cs | 61 +++++++++++ FitConnect.sln | 6 ++ FitConnect/Models/Attachment.cs | 5 +- FitConnect/Sender.cs | 36 +------ .../Services/Models/v1/Api/MetadataDto.cs | 6 +- FitConnect/Services/RestCallService.cs | 4 +- Tests/BasicUnitTest/BasicUnitTest.csproj | 1 + 14 files changed, 204 insertions(+), 43 deletions(-) create mode 100644 Examples/ConsoleAppExample/Attachments/metadata.json create mode 100644 FitConnect.LargeAttachment/FitConnect.LargeAttachment.csproj create mode 100644 FitConnect.LargeAttachment/LargeAttachment.cs diff --git a/.reuse/dep5 b/.reuse/dep5 index 75b5e82a..87ed7609 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Files: FitConnect/* Tests/* Examples/* +Files: FitConnect/* Tests/* Examples/* FitConnect.LargeAttachment/* FitConnect.sln test.Dockerfile Test.pdf version.sh FitConnect.sln.DotSettings README.md CHANGELOG.md *.md Dockerfile nuget_upload.sh ConsoleAppExample/* global.json DummyServerForHeaderTests/* clean_repo.sh diff --git a/Examples/ConsoleAppExample/.gitignore b/Examples/ConsoleAppExample/.gitignore index d9ba1b20..82642dc4 100644 --- a/Examples/ConsoleAppExample/.gitignore +++ b/Examples/ConsoleAppExample/.gitignore @@ -1,2 +1,3 @@ appsettings.json encryptionKeys/ +Attachments/OfficeSetup.exe diff --git a/Examples/ConsoleAppExample/Attachments/metadata.json b/Examples/ConsoleAppExample/Attachments/metadata.json new file mode 100644 index 00000000..dd8d9bfc --- /dev/null +++ b/Examples/ConsoleAppExample/Attachments/metadata.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json", + "authenticationInformation": [ + { + "content": "12345", + "type": "identificationReport", + "version": "1.3.5" + } + ], + "contentStructure": { + "attachments": [ + { + "attachmentId": "18c79c4e-cbb5-4c28-a3ee-a3c992562c36", + "description": "Test Attachment", + "filename": "Test.pdf", + "hash": { + "content": "8b1042900c2039f65fe6c4cb1bca31e2a7a04b61d3ca7d9ae9fc4077068b82ad5512fa298385b025db70551113b762064444b87737e45e657a71be5b88b06e59", + "type": "sha512" + }, + "mimeType": "application/pdf", + "purpose": "attachment" + }, + { + "attachmentId": "8fbce5dd-064b-4461-b7f4-060259e7776a", + "description": "Test Attachment #2", + "filename": "Test2.pdf", + "hash": { + "content": "8b1042900c2039f65fe6c4cb1bca31e2a7a04b61d3ca7d9ae9fc4077068b82ad5512fa298385b025db70551113b762064444b87737e45e657a71be5b88b06e59", + "type": "sha512" + }, + "mimeType": "application/pdf", + "purpose": "attachment" + }, + { + "attachmentId": "d9feba27-2799-4e59-89b3-14b05526173f", + "description": "", + "filename": "data.json", + "hash": { + "content": "8a35106639b0a4abf8c1933f17f567b502fb33c60519e45cc6e7f0fcd97670838a13e406d0faf27a0e9c039d004c205de4750f1c28d640a2f663f4a0148cea34", + "type": "sha512" + }, + "mimeType": "application/json", + "purpose": "attachment" + }, + { + "attachmentId": "8a2646bf-1ef2-43e6-8b30-ef70873eb2dd", + "description": "Just a big file", + "filename": "OfficeSetup.exe", + "hash": { + "content": "46e801d5789b4128f63207360ec4a604e1b9bfc1ac45639a3bc8673f7d4cb2fc75c5b4696d2b7964351d426c3f2dd69227e20843e2cd3b52dd4276ba8c0843e5", + "type": "sha512" + }, + "mimeType": "application/pdf", + "purpose": "attachment" + }, + { + "attachmentId": "a1ec355f-c100-4eca-8d14-89810aeeae40", + "description": "The part #1 of the file with the id 8a2646bf-1ef2-43e6-8b30-ef70873eb2dd", + "filename": "8a2646bf-1ef2-43e6-8b30-ef70873eb2dd.part1", + "hash": { + "content": "7adec1ee35cf5ca9555bf4c0f6f322462e25d8627acbebcf06399e2bb70471a407549cc87e81d0c6a36b35d9604c92a7cebaf6b631a07ca246e475e12dcc2f51", + "type": "sha512" + }, + "mimeType": "application/pdf", + "purpose": "attachment" + }, + { + "attachmentId": "142b0c70-7fce-4001-8c62-27f9f5b14f8a", + "description": "The part #2 of the file with the id 8a2646bf-1ef2-43e6-8b30-ef70873eb2dd", + "filename": "8a2646bf-1ef2-43e6-8b30-ef70873eb2dd.part2", + "hash": { + "content": "80b558e3764bd4ae772b70ef452b8ea5fe05db559753ce983ce992f4c47cccc78bdba67b225e331108169f16c7e337d436d3f5399298f7d3dffe5b2b9bd9e3e7", + "type": "sha512" + }, + "mimeType": "application/pdf", + "purpose": "attachment" + } + ], + "data": { + "hash": { + "content": "8a35106639b0a4abf8c1933f17f567b502fb33c60519e45cc6e7f0fcd97670838a13e406d0faf27a0e9c039d004c205de4750f1c28d640a2f663f4a0148cea34", + "type": "sha512" + }, + "submissionSchema": { + "mimeType": "application/json", + "schemaUri": "https://eloware.dev/projects/fit-connect/schemas/simpleSchema.json" + } + } + }, + "paymentInformation": { + "paymentMethod": "CREDITCARD", + "status": "BOOKED", + "transactionId": "12345445", + "transactionReference": "2345566" + }, + "replyChannel": { + "eMail": { + "address": "a@example.com" + } + } +} \ No newline at end of file diff --git a/Examples/ConsoleAppExample/ConsoleAppExample.csproj b/Examples/ConsoleAppExample/ConsoleAppExample.csproj index 54cd4fda..84462ca0 100644 --- a/Examples/ConsoleAppExample/ConsoleAppExample.csproj +++ b/Examples/ConsoleAppExample/ConsoleAppExample.csproj @@ -50,9 +50,16 @@ <None Update="encryptionKeys\set-public-keys.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> + <None Update="Attachments\zoomusInstallerFull.pkg"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="Attachments\OfficeSetup.exe"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\..\FitConnect.LargeAttachment\FitConnect.LargeAttachment.csproj" /> <ProjectReference Include="..\..\FitConnect\FitConnect.csproj" /> </ItemGroup> diff --git a/Examples/ConsoleAppExample/Program.cs b/Examples/ConsoleAppExample/Program.cs index 9c692a33..74bb4980 100644 --- a/Examples/ConsoleAppExample/Program.cs +++ b/Examples/ConsoleAppExample/Program.cs @@ -17,7 +17,7 @@ var config = new ConfigurationBuilder() var logger = LoggerFactory.Create( builder => { builder.AddSimpleConsole(); - builder.SetMinimumLevel(LogLevel.Warning); + builder.SetMinimumLevel(LogLevel.Trace); }).CreateLogger("FIT-Connect"); #endregion diff --git a/Examples/ConsoleAppExample/SenderDemo.cs b/Examples/ConsoleAppExample/SenderDemo.cs index 4d0a8210..b40e66c4 100644 --- a/Examples/ConsoleAppExample/SenderDemo.cs +++ b/Examples/ConsoleAppExample/SenderDemo.cs @@ -2,6 +2,7 @@ using System.Net.Mime; using System.Text; using FitConnect; using FitConnect.Encryption; +using FitConnect.LargeAttachment; using FitConnect.Models; using FitConnect.Models.Api.Metadata; using FitConnect.Models.Api.Set; @@ -43,6 +44,7 @@ public static class SenderDemo { Attachment.FromString("{\"message\":\"Hello World\"}", MediaTypeNames.Application.Json, "data.json") ) + .AddAttachments(await (new LargeAttachment("./Attachments/OfficeSetup.exe", "application/pdf", "Just a big file").LoadAttachment())) .SetAuthenticationInformation(new AuthenticationInformation("12345", AuthenticationInformationType.IdentificationReport, "1.3.5")) .SetPaymentInformation(new PaymentInformation(PaymentMethod.Creditcard, diff --git a/FitConnect.LargeAttachment/FitConnect.LargeAttachment.csproj b/FitConnect.LargeAttachment/FitConnect.LargeAttachment.csproj new file mode 100644 index 00000000..f990f529 --- /dev/null +++ b/FitConnect.LargeAttachment/FitConnect.LargeAttachment.csproj @@ -0,0 +1,13 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\FitConnect\FitConnect.csproj" /> + </ItemGroup> + +</Project> diff --git a/FitConnect.LargeAttachment/LargeAttachment.cs b/FitConnect.LargeAttachment/LargeAttachment.cs new file mode 100644 index 00000000..953d12bf --- /dev/null +++ b/FitConnect.LargeAttachment/LargeAttachment.cs @@ -0,0 +1,61 @@ +using FitConnect.Models; +using Microsoft.AspNetCore.Http; + +namespace FitConnect.LargeAttachment; + +public class LargeAttachment { + private const int MaxAttachmentSize = 3 * 1024 * 1024; + private string FullPath { get; } + + public LargeAttachment(string fileName, string mimeType, string description) { + FullPath = fileName; + Filename = fileName; + MimeType = mimeType; + Description = description; + } + + public string Description { get; set; } + + public string MimeType { get; set; } + + public string Filename { get; set; } + + /// <summary> + /// Loads the attachment from the file system and returns a list of attachments with less than 300MB each + /// </summary> + /// <returns></returns> + public async Task<List<Attachment>> LoadAttachment() { + Guid? id = null; + var result = new List<Attachment>(); + var list = await LoadFileAsync(); + for (var index = 0; index < list.Count; index++) { + var chunk = list[index]; + var filename = index == 0 ? Filename : id.ToString() + ".part" + index; + result.Add(Attachment.FromByteArray(chunk, MimeType, filename, + index == 0 ? Description : $"The part #{index} of the file with the id {id}")); + id ??= result[0].Id; + } + + return result; + } + + /// <summary> + /// Load the file and split it into byte arrays with less than 300MB each if the source file is larger than 300MB + /// </summary> + public async Task<List<byte[]>> LoadFileAsync() { + var fileBytes = new List<byte[]>(); + var bytesRead = 0; + var fileStream = File.Open(FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + do { + var buffer = new byte[MaxAttachmentSize]; + bytesRead = await fileStream.ReadAsync(buffer, 0, MaxAttachmentSize); + if (bytesRead > 0) { + var copy = new byte[bytesRead]; + Array.Copy(buffer, copy, bytesRead); + fileBytes.Add(copy); + } + } while (bytesRead > 0); + + return fileBytes; + } +} diff --git a/FitConnect.sln b/FitConnect.sln index 9c7ca599..97cb550a 100644 --- a/FitConnect.sln +++ b/FitConnect.sln @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests\DummyServerForHeaderT EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValidationTests", "Tests\ValidationTests\ValidationTests.csproj", "{DDEFDA59-FD75-4F25-9BA2-8DAE22B4A0B0}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FitConnect.LargeAttachment", "FitConnect.LargeAttachment\FitConnect.LargeAttachment.csproj", "{8DBA0593-12DC-4791-AA66-BAD9B6573CC7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -68,6 +70,10 @@ Global {DDEFDA59-FD75-4F25-9BA2-8DAE22B4A0B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {DDEFDA59-FD75-4F25-9BA2-8DAE22B4A0B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {DDEFDA59-FD75-4F25-9BA2-8DAE22B4A0B0}.Release|Any CPU.Build.0 = Release|Any CPU + {8DBA0593-12DC-4791-AA66-BAD9B6573CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DBA0593-12DC-4791-AA66-BAD9B6573CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DBA0593-12DC-4791-AA66-BAD9B6573CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DBA0593-12DC-4791-AA66-BAD9B6573CC7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {27115A99-2AE8-42BC-9495-BE2DCEDDF1E8} = {180029B5-8DD3-4594-B34E-6C07AF1C52C5} diff --git a/FitConnect/Models/Attachment.cs b/FitConnect/Models/Attachment.cs index e956b72a..832fd6ce 100644 --- a/FitConnect/Models/Attachment.cs +++ b/FitConnect/Models/Attachment.cs @@ -49,9 +49,12 @@ public class Attachment : AttachmentMetadata { MimeType = MediaTypeNames.Application.Octet; } + protected Attachment() { + } + public Guid Id { get; } = Guid.NewGuid(); - private byte[]? Content { get; init; } + protected byte[]? Content { get; set; } public string? AttachmentAuthentication { get; internal set; } diff --git a/FitConnect/Sender.cs b/FitConnect/Sender.cs index 11420b48..6520687a 100644 --- a/FitConnect/Sender.cs +++ b/FitConnect/Sender.cs @@ -155,7 +155,7 @@ public class Sender : FitConnectClient, ISender { Logger?.LogInformation("Metadata validation check, done"); Logger?.LogInformation("Sending submission"); var encryptedMeta = Encryption.Encrypt(metadata); - Logger?.LogTrace("Encrypted metadata: {EncryptedMeta}", encryptedMeta); + // Logger?.LogTrace("Encrypted metadata: {EncryptedMeta}", encryptedMeta); submission.EncryptedMetadata = encryptedMeta; } } @@ -246,40 +246,6 @@ public class Sender : FitConnectClient, ISender { return base.GetEventLogAsync(submission, true); } - private async Task<Submission> Submit(Submission submission) { - if (submission == null) { - Logger?.LogCritical("Submission is null on submit"); - throw new InvalidOperationException("Submission is not ready"); - } - - if (submission.EncryptedMetadata == null) { - var metadata = CreateMetadata(submission); - submission.GeneratedMetadata = metadata; - Logger?.LogTrace("Metadata: {Metadata}", metadata); - var valid = await JsonHelper.VerifyMetadata(metadata, null, Logger); - if (!valid) { - Logger?.LogError("Sending submission aborted due to validation errors"); - throw new InvalidOperationException("Submission is not ready"); - } - - Logger?.LogInformation("Metadata validation check, done"); - Logger?.LogInformation("Sending submission"); - var encryptedMeta = Encryption.Encrypt(metadata); - Logger?.LogTrace("Encrypted metadata: {EncryptedMeta}", encryptedMeta); - submission.EncryptedMetadata = encryptedMeta; - } - - if (submission.EncryptedData == null) - if (submission.Data != null) - submission.EncryptedData = Encryption.Encrypt(submission.Data); - - var result = await SubmissionService - .SubmitSubmission(submission.Id!, (SubmitSubmissionDto)submission); - - Logger?.LogInformation("Submission sent"); - return submission; - } - /// <summary> /// Creates a new <see cref="SendableSubmission" /> on the FitConnect server. /// </summary> diff --git a/FitConnect/Services/Models/v1/Api/MetadataDto.cs b/FitConnect/Services/Models/v1/Api/MetadataDto.cs index 699f9ed5..a9c2a18b 100644 --- a/FitConnect/Services/Models/v1/Api/MetadataDto.cs +++ b/FitConnect/Services/Models/v1/Api/MetadataDto.cs @@ -117,7 +117,7 @@ namespace FitConnect.Models.Api.Metadata /// Optionale Beschreibung der Anlage /// </summary> [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] - public string Description { get; internal set; } + public string Description { get; protected internal set; } /// <summary> /// Ursprünglicher Dateiname bei Erzeugung oder Upload @@ -125,7 +125,7 @@ namespace FitConnect.Models.Api.Metadata [JsonProperty("filename", NullValueHandling = NullValueHandling.Ignore)] public string Filename { get => _filename; - internal set => _filename = Path.GetFileName(value); + protected internal set => _filename = Path.GetFileName(value); } /// <summary> @@ -140,7 +140,7 @@ namespace FitConnect.Models.Api.Metadata /// Internet Media Type gemäß RFC 2045, z. B. application/pdf. /// </summary> [JsonProperty("mimeType")] - public string MimeType { get; internal set; } + public string MimeType { get; protected internal set; } /// <summary> /// Zweck/Art der Anlage diff --git a/FitConnect/Services/RestCallService.cs b/FitConnect/Services/RestCallService.cs index af4f2819..fd3787ab 100644 --- a/FitConnect/Services/RestCallService.cs +++ b/FitConnect/Services/RestCallService.cs @@ -83,7 +83,7 @@ internal class RestCallService : IRestCallService { if (body != null) { request.Content = new StringContent(body, Encoding.UTF8, contentType); - _logger?.LogTrace("Body: {Body}", body); + // _logger?.LogTrace("Body: {Body}", body); } var response = await client.SendAsync(request); @@ -96,7 +96,7 @@ internal class RestCallService : IRestCallService { if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); - _logger?.LogTrace("Response: {Content}", content); + // _logger?.LogTrace("Response: {Content}", content); return content; } diff --git a/Tests/BasicUnitTest/BasicUnitTest.csproj b/Tests/BasicUnitTest/BasicUnitTest.csproj index 0cc5b3ee..e81004dd 100644 --- a/Tests/BasicUnitTest/BasicUnitTest.csproj +++ b/Tests/BasicUnitTest/BasicUnitTest.csproj @@ -26,6 +26,7 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> + <PackageReference Include="SpecFlow.NUnit" Version="3.9.74" /> </ItemGroup> <ItemGroup> -- GitLab