From 6fee6cf37801548af79d542cdfec33bde22c4ab0 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 30 Mar 2017 14:31:39 +0200 Subject: [PATCH 01/21] VS 2017 migration --- GeekLearning.Storage.sln | 29 ++++--- global.json | 6 -- .../GeekLearning.Storage.BasicSample.csproj | 40 ++++++++++ .../GeekLearning.Storage.BasicSample.xproj | 19 ----- .../project.json | 61 --------------- .../GeekLearning.Storage.Azure.csproj | 34 +++++++++ .../GeekLearning.Storage.Azure.xproj | 21 ----- .../Properties/AssemblyInfo.cs | 22 ------ src/GeekLearning.Storage.Azure/project.json | 30 -------- .../FileSystemExtendedPropertiesExtensions.cs | 9 ++- ...ystem.ExtendedProperties.FileSystem.csproj | 33 ++++++++ ...System.ExtendedProperties.FileSystem.xproj | 21 ----- .../Properties/AssemblyInfo.cs | 19 ----- .../project.json | 22 ------ ...kLearning.Storage.FileSystem.Server.csproj | 32 ++++++++ ...ekLearning.Storage.FileSystem.Server.xproj | 21 ----- .../Properties/AssemblyInfo.cs | 22 ------ .../project.json | 24 ------ .../GeekLearning.Storage.FileSystem.csproj | 32 ++++++++ .../GeekLearning.Storage.FileSystem.xproj | 21 ----- .../Properties/AssemblyInfo.cs | 22 ------ .../project.json | 24 ------ .../GeekLearning.Storage.csproj | 22 ++++++ .../GeekLearning.Storage.xproj | 21 ----- .../Properties/AssemblyInfo.cs | 22 ------ src/GeekLearning.Storage/project.json | 20 ----- ...ekLearning.Storage.Integration.Test.csproj | 53 +++++++++++++ ...eekLearning.Storage.Integration.Test.xproj | 22 ------ .../Properties/AssemblyInfo.cs | 19 ----- .../StoresFixture.cs | 76 ++++++++++--------- .../appsettings.json | 3 +- .../project.json | 49 ------------ 32 files changed, 309 insertions(+), 562 deletions(-) delete mode 100644 global.json create mode 100644 samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj delete mode 100644 samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.xproj delete mode 100644 samples/GeekLearning.Storage.BasicSample/project.json create mode 100644 src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj delete mode 100644 src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.xproj delete mode 100644 src/GeekLearning.Storage.Azure/Properties/AssemblyInfo.cs delete mode 100644 src/GeekLearning.Storage.Azure/project.json create mode 100644 src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj delete mode 100644 src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.xproj delete mode 100644 src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Properties/AssemblyInfo.cs delete mode 100644 src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/project.json create mode 100644 src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj delete mode 100644 src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj delete mode 100644 src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs delete mode 100644 src/GeekLearning.Storage.FileSystem.Server/project.json create mode 100644 src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj delete mode 100644 src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.xproj delete mode 100644 src/GeekLearning.Storage.FileSystem/Properties/AssemblyInfo.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/project.json create mode 100644 src/GeekLearning.Storage/GeekLearning.Storage.csproj delete mode 100644 src/GeekLearning.Storage/GeekLearning.Storage.xproj delete mode 100644 src/GeekLearning.Storage/Properties/AssemblyInfo.cs delete mode 100644 src/GeekLearning.Storage/project.json create mode 100644 tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj delete mode 100644 tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.xproj delete mode 100644 tests/GeekLearning.Storage.Integration.Test/Properties/AssemblyInfo.cs delete mode 100644 tests/GeekLearning.Storage.Integration.Test/project.json diff --git a/GeekLearning.Storage.sln b/GeekLearning.Storage.sln index 1c39f88..aa1ace3 100644 --- a/GeekLearning.Storage.sln +++ b/GeekLearning.Storage.sln @@ -1,18 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.10 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage", "src\GeekLearning.Storage\GeekLearning.Storage.xproj", "{1F419C53-73C6-4460-B284-6A49AE41C596}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{FBAC4C17-D755-49A9-959D-18FD6B95B543}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.Azure", "src\GeekLearning.Storage.Azure\GeekLearning.Storage.Azure.xproj", "{FD8BB8F9-9AF5-4C12-B962-9E08C30B01E2}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.FileSystem", "src\GeekLearning.Storage.FileSystem\GeekLearning.Storage.FileSystem.xproj", "{4A12B042-76B3-471B-9235-F653E1ABE3C0}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.BasicSample", "samples\GeekLearning.Storage.BasicSample\GeekLearning.Storage.BasicSample.xproj", "{63416AEA-DA51-4D62-B566-DB7D9BC800DC}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2DAF5EF9-8F8E-4C51-BE2D-8D63CA143360}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{6BEB33C6-FA17-4F58-ACC3-83C1EB28B604}" @@ -20,18 +12,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{6BEB33C6 .gitattributes = .gitattributes .gitignore = .gitignore GitVersion.yml = GitVersion.yml - global.json = global.json LICENSE.md = LICENSE.md README.md = README.md EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.FileSystem.Server", "src\GeekLearning.Storage.FileSystem.Server\GeekLearning.Storage.FileSystem.Server.xproj", "{9D94CD6C-9451-449A-BED2-1C07D624A8E0}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{520AB1D3-C501-40FD-ACEB-7CC0D1F00B90}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.Integration.Test", "tests\GeekLearning.Storage.Integration.Test\GeekLearning.Storage.Integration.Test.xproj", "{590B21B0-2AFA-4329-82AD-EF180C50EB5C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage", "src\GeekLearning.Storage\GeekLearning.Storage.csproj", "{1F419C53-73C6-4460-B284-6A49AE41C596}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.Azure", "src\GeekLearning.Storage.Azure\GeekLearning.Storage.Azure.csproj", "{FD8BB8F9-9AF5-4C12-B962-9E08C30B01E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.FileSystem", "src\GeekLearning.Storage.FileSystem\GeekLearning.Storage.FileSystem.csproj", "{4A12B042-76B3-471B-9235-F653E1ABE3C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.BasicSample", "samples\GeekLearning.Storage.BasicSample\GeekLearning.Storage.BasicSample.csproj", "{63416AEA-DA51-4D62-B566-DB7D9BC800DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.FileSystem.Server", "src\GeekLearning.Storage.FileSystem.Server\GeekLearning.Storage.FileSystem.Server.csproj", "{9D94CD6C-9451-449A-BED2-1C07D624A8E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.Integration.Test", "tests\GeekLearning.Storage.Integration.Test\GeekLearning.Storage.Integration.Test.csproj", "{590B21B0-2AFA-4329-82AD-EF180C50EB5C}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem", "src\GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem\GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.xproj", "{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem", "src\GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem\GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj", "{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/global.json b/global.json deleted file mode 100644 index 65e97c5..0000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "projects": [ "src", "tests", "samples" ], - "sdk": { - "version": "1.0.0-preview2-1-003177" - } -} \ No newline at end of file diff --git a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj new file mode 100644 index 0000000..d9d597a --- /dev/null +++ b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj @@ -0,0 +1,40 @@ + + + + netcoreapp1.1 + true + GeekLearning.Storage.BasicSample + Exe + GeekLearning.Storage.BasicSample + 1.1.1 + $(PackageTargetFallback);portable-net45+win8 + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.xproj b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.xproj deleted file mode 100644 index 1a43cd3..0000000 --- a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.xproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 63416aea-da51-4d62-b566-db7d9bc800dc - GeekLearning.Storage.BasicSample - .\obj - .\bin\ - v4.5.2 - - - 2.0 - - - diff --git a/samples/GeekLearning.Storage.BasicSample/project.json b/samples/GeekLearning.Storage.BasicSample/project.json deleted file mode 100644 index cb6d25b..0000000 --- a/samples/GeekLearning.Storage.BasicSample/project.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "dependencies": { - "Microsoft.NETCore.App": { - "version": "1.1.0", - "type": "platform" - }, - "Microsoft.AspNetCore.Mvc": "1.1.0", - "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0", - "Microsoft.AspNetCore.Server.Kestrel": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Logging": "1.1.0", - "Microsoft.Extensions.Logging.Console": "1.1.0", - "Microsoft.Extensions.Logging.Debug": "1.1.0", - "Microsoft.Extensions.Options": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - - "GeekLearning.Storage": "*", - "GeekLearning.Storage.Azure": "*", - "GeekLearning.Storage.FileSystem": "*", - "GeekLearning.Storage.FileSystem.Server": "*" - }, - - "tools": { - "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" - }, - - "frameworks": { - "netcoreapp1.1": { - "imports": [ - "portable-net45+win8" - ] - } - }, - - "buildOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true - }, - - "runtimeOptions": { - "configProperties": { - "System.GC.Server": true - } - }, - - "publishOptions": { - "include": [ - "wwwroot", - "Views", - "appsettings.json", - "web.config", - "Templates" - ] - }, - - "scripts": { - "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ] - } -} diff --git a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj new file mode 100644 index 0000000..9165231 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj @@ -0,0 +1,34 @@ + + + + Azure Storage Provider for Geek Learning Storage Abstractions. + 0.0.1 + Geek Learning;Cyprien Autexier;Adrien Siffermann + net45;netstandard1.3 + GeekLearning.Storage.Azure + GeekLearning.Storage.Azure + 1.6.1 + $(PackageTargetFallback);portable-net45+win8 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.xproj b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.xproj deleted file mode 100644 index 7a8c151..0000000 --- a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - fd8bb8f9-9af5-4c12-b962-9e08c30b01e2 - GeekLearning.Storage.Azure - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/GeekLearning.Storage.Azure/Properties/AssemblyInfo.cs b/src/GeekLearning.Storage.Azure/Properties/AssemblyInfo.cs deleted file mode 100644 index 0b37cfb..0000000 --- a/src/GeekLearning.Storage.Azure/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GeekLearning.Storage.Azure")] -[assembly: AssemblyDescription("Azure Storage Provider for Geek Learning Storage Abstractions.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Geek Learning")] -[assembly: AssemblyProduct("GeekLearning.Storage")] -[assembly: AssemblyCopyright("Copyright © Geek Learning 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fd8bb8f9-9af5-4c12-b962-9e08c30b01e2")] diff --git a/src/GeekLearning.Storage.Azure/project.json b/src/GeekLearning.Storage.Azure/project.json deleted file mode 100644 index 9fc3c0a..0000000 --- a/src/GeekLearning.Storage.Azure/project.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version": "0.0.1-*", - "description": "Azure Storage Provider for Geek Learning Storage Abstractions.", - "authors": [ "Geek Learning", "Cyprien Autexier", "Adrien Siffermann" ], - "packOptions": { - "tags": [], - "projectUrl": "", - "licenseUrl": "" - }, - - "dependencies": { - "NETStandard.Library": "1.6.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0", - "Microsoft.Extensions.Options": "1.1.0", - "Microsoft.Extensions.FileSystemGlobbing": "1.1.0", - - "WindowsAzure.Storage": "8.0.0", - - "GeekLearning.Storage": "*" - }, - - "frameworks": { - "net45": {}, - "netstandard1.3": { - "imports": [ - "portable-net45+win8" - ] - } - } -} diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/FileSystemExtendedPropertiesExtensions.cs b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/FileSystemExtendedPropertiesExtensions.cs index 5d489bd..a124b75 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/FileSystemExtendedPropertiesExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/FileSystemExtendedPropertiesExtensions.cs @@ -8,9 +8,14 @@ public static class FileSystemExtendedPropertiesExtensions { - public static IServiceCollection AddFileSystemExtendedProperties(this IServiceCollection services, Action configure) + public static IServiceCollection AddFileSystemExtendedProperties(this IServiceCollection services, Action configure = null) { - services.Configure(configure); + if (configure == null) + { + configure = o => { }; + } + + services.Configure(configure); services.AddTransient(); return services; } diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj new file mode 100644 index 0000000..4aac9ec --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj @@ -0,0 +1,33 @@ + + + + File System based extended properties storage provider for the FileSystem provider. + 0.0.1 + Geek Learning;Adrien Siffermann;Cyprien Autexier + net45;netstandard1.3 + GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem + GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem + 1.6.1 + false + false + false + + + + + + + + + + + + + + + + + + + + diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.xproj b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.xproj deleted file mode 100644 index 7354067..0000000 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 8c02ebbe-9ec8-4f47-9464-5a94bde25a8f - GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem - .\obj - .\bin\ - v4.6.2 - - - - 2.0 - - - diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Properties/AssemblyInfo.cs b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Properties/AssemblyInfo.cs deleted file mode 100644 index 6c872e6..0000000 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8c02ebbe-9ec8-4f47-9464-5a94bde25a8f")] diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/project.json b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/project.json deleted file mode 100644 index af100df..0000000 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/project.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "version": "0.0.1-*", - "description": "File System based extended properties storage provider for the FileSystem provider.", - "authors": [ "Geek Learning", "Adrien Siffermann", "Cyprien Autexier" ], - "packOptions": { - "tags": [], - "projectUrl": "", - "licenseUrl": "" - }, - - "dependencies": { - "NETStandard.Library": "1.6.1", - "Newtonsoft.Json": "9.0.1", - - "GeekLearning.Storage.FileSystem": "*" - }, - - "frameworks": { - "net45": {}, - "netstandard1.3": {} - } -} diff --git a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj new file mode 100644 index 0000000..d82a11c --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj @@ -0,0 +1,32 @@ + + + + Geek Learning File Server based on FileSystem Storage Provider. + 0.0.1 + Geek Learning;Cyprien Autexier;Adrien Siffermann + net451;netstandard1.4 + GeekLearning.Storage.FileSystem.Server + GeekLearning.Storage.FileSystem.Server + 1.6.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj deleted file mode 100644 index c90d136..0000000 --- a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 9d94cd6c-9451-449a-bed2-1c07d624a8e0 - GeekLearning.Storage.FileSystem.Server - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs b/src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs deleted file mode 100644 index f86cb40..0000000 --- a/src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GeekLearning.Storage.FileSystem.Server")] -[assembly: AssemblyDescription("Geek Learning File Server based on FileSystem Storage Provider.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Geek Learning")] -[assembly: AssemblyProduct("GeekLearning.Storage")] -[assembly: AssemblyCopyright("Copyright © Geek Learning 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9d94cd6c-9451-449a-bed2-1c07d624a8e0")] diff --git a/src/GeekLearning.Storage.FileSystem.Server/project.json b/src/GeekLearning.Storage.FileSystem.Server/project.json deleted file mode 100644 index a809676..0000000 --- a/src/GeekLearning.Storage.FileSystem.Server/project.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "0.0.1-*", - "description": "Geek Learning File Server based on FileSystem Storage Provider.", - "authors": [ "Geek Learning", "Cyprien Autexier", "Adrien Siffermann" ], - "packOptions": { - "tags": [], - "projectUrl": "", - "licenseUrl": "" - }, - - "dependencies": { - "NETStandard.Library": "1.6.1", - "Microsoft.AspNetCore.Http.Abstractions": "1.1.0", - "Microsoft.Extensions.Logging.Abstractions": "1.1.0", - "Microsoft.IdentityModel.Tokens": "5.1.0", - - "GeekLearning.Storage.FileSystem": "*" - }, - - "frameworks": { - "net451": {}, - "netstandard1.4": {} - } -} diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj new file mode 100644 index 0000000..24c0baa --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj @@ -0,0 +1,32 @@ + + + + FileSystem Provider for Geek Learning Storage Abstractions. + 0.0.1 + Geek Learning;Cyprien Autexier;Adrien Siffermann + net45;netstandard1.3 + GeekLearning.Storage.FileSystem + GeekLearning.Storage.FileSystem + 1.6.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.xproj b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.xproj deleted file mode 100644 index 97fe046..0000000 --- a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 4a12b042-76b3-471b-9235-f653e1abe3c0 - GeekLearning.Storage.FileSystem - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/GeekLearning.Storage.FileSystem/Properties/AssemblyInfo.cs b/src/GeekLearning.Storage.FileSystem/Properties/AssemblyInfo.cs deleted file mode 100644 index 4d9786a..0000000 --- a/src/GeekLearning.Storage.FileSystem/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GeekLearning.Storage.FileSystem")] -[assembly: AssemblyDescription("Geek Learning FileSystem Storage Provider")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Geek Learning")] -[assembly: AssemblyProduct("GeekLearning.Storage")] -[assembly: AssemblyCopyright("Copyright © Geek Learning 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4a12b042-76b3-471b-9235-f653e1abe3c0")] diff --git a/src/GeekLearning.Storage.FileSystem/project.json b/src/GeekLearning.Storage.FileSystem/project.json deleted file mode 100644 index e708da4..0000000 --- a/src/GeekLearning.Storage.FileSystem/project.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "0.0.1-*", - "description": "FileSystem Provider for Geek Learning Storage Abstractions.", - "authors": [ "Geek Learning", "Cyprien Autexier", "Adrien Siffermann" ], - "packOptions": { - "tags": [], - "projectUrl": "", - "licenseUrl": "" - }, - - "dependencies": { - "NETStandard.Library": "1.6.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0", - "Microsoft.Extensions.Options": "1.1.0", - "Microsoft.Extensions.FileSystemGlobbing": "1.1.0", - - "GeekLearning.Storage": "*" - }, - - "frameworks": { - "net45": {}, - "netstandard1.3": {} - } -} diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj new file mode 100644 index 0000000..e61058f --- /dev/null +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -0,0 +1,22 @@ + + + + File Storage abstractions with providers. + 0.0.1 + Geek Learning;Cyprien Autexier;Adrien Siffermann + netstandard1.1 + GeekLearning.Storage + GeekLearning.Storage + 1.6.1 + + + + + + + + + + + + diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.xproj b/src/GeekLearning.Storage/GeekLearning.Storage.xproj deleted file mode 100644 index a5570c8..0000000 --- a/src/GeekLearning.Storage/GeekLearning.Storage.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 1f419c53-73c6-4460-b284-6a49ae41c596 - GeekLearning.Storage - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/GeekLearning.Storage/Properties/AssemblyInfo.cs b/src/GeekLearning.Storage/Properties/AssemblyInfo.cs deleted file mode 100644 index 82a6b4c..0000000 --- a/src/GeekLearning.Storage/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("GeekLearning.Storage")] -[assembly: AssemblyDescription("File Storage abstractions with providers.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Geek Learning")] -[assembly: AssemblyProduct("GeekLearning.Storage")] -[assembly: AssemblyCopyright("Copyright © Geek Learning 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("1f419c53-73c6-4460-b284-6a49ae41c596")] diff --git a/src/GeekLearning.Storage/project.json b/src/GeekLearning.Storage/project.json deleted file mode 100644 index e8a24b8..0000000 --- a/src/GeekLearning.Storage/project.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.0.1-*", - "description": "File Storage abstractions with providers.", - "authors": [ "Geek Learning", "Cyprien Autexier", "Adrien Siffermann" ], - "packOptions": { - "tags": [], - "projectUrl": "", - "licenseUrl": "" - }, - - "dependencies": { - "NETStandard.Library": "1.6.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0", - "Microsoft.Extensions.Options": "1.1.0" - }, - - "frameworks": { - "netstandard1.1": {} - } -} diff --git a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj new file mode 100644 index 0000000..9dd8e42 --- /dev/null +++ b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj @@ -0,0 +1,53 @@ + + + + netcoreapp1.0;net451 + GeekLearning.Storage.Integration.Test + GeekLearning.Storage.Integration.Test + true + $(PackageTargetFallback);dotnet;portable-net45+win8 + 1.0.4 + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.xproj b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.xproj deleted file mode 100644 index bb63879..0000000 --- a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.xproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 590b21b0-2afa-4329-82ad-ef180c50eb5c - GeekLearning.Storage.Integration.Test - .\obj - .\bin\ - v4.5.2 - - - 2.0 - - - - - - \ No newline at end of file diff --git a/tests/GeekLearning.Storage.Integration.Test/Properties/AssemblyInfo.cs b/tests/GeekLearning.Storage.Integration.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index 07776c7..0000000 --- a/tests/GeekLearning.Storage.Integration.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("GeekLearning.Storage.Integration.Test")] -[assembly: AssemblyTrademark("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("590b21b0-2afa-4329-82ad-ef180c50eb5c")] diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 19e3e75..3d000ee 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -39,8 +39,8 @@ public StoresFixture() services.AddStorage() .AddAzureStorage() - .AddFileSystemStorage(BasePath) - .AddFileSystemExtendedProperties(o => { }); + .AddFileSystemStorage(this.FileSystemRootPath) + .AddFileSystemExtendedProperties(); services.Configure(Configuration.GetSection("Storage")); services.Configure(Configuration.GetSection("TestStore")); @@ -50,18 +50,50 @@ public StoresFixture() ResetStores(); } + public IConfigurationRoot Configuration { get; } + + public IServiceProvider Services { get; } + + public string BasePath { get; } + + public string FileSystemRootPath => Path.Combine(this.BasePath, "FileVault"); + + public void Dispose() + { + this.DeleteRootResources(); + } + + private void DeleteRootResources() + { + if (this.container != null) + { + this.container.DeleteIfExistsAsync().Wait(); + } + + if (Directory.Exists(this.FileSystemRootPath)) + { + Directory.Delete(this.FileSystemRootPath, true); + } + } + private void ResetStores() { - ResetAzureStore(); - ResetFileSystemStore(); + this.DeleteRootResources(); + this.ResetAzureStore(); + this.ResetFileSystemStore(); } private void ResetFileSystemStore() { + if (!Directory.Exists(this.FileSystemRootPath)) + { + Directory.CreateDirectory(this.FileSystemRootPath); + } + var directoryName = Configuration["Storage:Stores:filesystem:Parameters:Path"]; var process = Process.Start(new ProcessStartInfo("robocopy.exe") { - Arguments = $"\"{Path.Combine(BasePath, "SampleDirectory")}\" \"{Path.Combine(BasePath, directoryName)}\" /MIR" + Arguments = $"\"{Path.Combine(this.BasePath, "SampleDirectory")}\" \"{Path.Combine(this.FileSystemRootPath, directoryName)}\" /MIR" }); if (!process.WaitForExit(30000)) @@ -72,7 +104,7 @@ private void ResetFileSystemStore() private void ResetAzureStore() { - var azCopy = System.IO.Path.Combine( + var azCopy = Path.Combine( Environment.ExpandEnvironmentVariables(Configuration["AzCopyPath"]), "AzCopy.exe"); @@ -83,12 +115,12 @@ private void ResetAzureStore() var client = cloudStorageAccount.CreateCloudBlobClient(); - container = client.GetContainerReference(containerName); - container.CreateAsync().Wait(); + this.container = client.GetContainerReference(containerName); + this.container.CreateAsync().Wait(); var process = Process.Start(new ProcessStartInfo(azCopy) { - Arguments = $"/Source:\"{System.IO.Path.Combine(BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{key} /S" + Arguments = $"/Source:\"{Path.Combine(this.BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{key} /S" }); if (!process.WaitForExit(30000)) @@ -96,31 +128,5 @@ private void ResetAzureStore() throw new TimeoutException("Azure store was not reset properly"); } } - - public IConfigurationRoot Configuration { get; } - - public IServiceProvider Services { get; } - - public string BasePath { get; } - - public void Dispose() - { - container.DeleteIfExistsAsync().Wait(); - - var fileSystemPath = Configuration["Storage:Stores:filesystem:Parameters:Path"]; - var folderNameFormat = Configuration["Storage:ExtendedPropertiesFolderNameFormat"]; - - var directoryName = Path.Combine(BasePath, fileSystemPath); - if (Directory.Exists(directoryName)) - { - Directory.Delete(directoryName, true); - } - - directoryName = Path.Combine(BasePath, string.Format(folderNameFormat, fileSystemPath)); - if (Directory.Exists(directoryName)) - { - Directory.Delete(directoryName, true); - } - } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index 7b59261..2c8e21c 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -15,8 +15,7 @@ "Container": "templates" } } - }, - "ExtendedPropertiesFolderNameFormat": ".{0}-extended-properties" + } }, "TestStore": { "Provider": "FileSystem", diff --git a/tests/GeekLearning.Storage.Integration.Test/project.json b/tests/GeekLearning.Storage.Integration.Test/project.json deleted file mode 100644 index 56f3c44..0000000 --- a/tests/GeekLearning.Storage.Integration.Test/project.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "version": "1.0.0-*", - - "buildOptions": { - "copyToOutput": [ - "SampleDirectory/**/*.*", - "appsettings.json", - "appsettings.*.json" - ] - }, - - "testRunner": "xunit", - - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "dotnet-test-xunit": "2.2.0-preview2-build1029", - "xunit": "2.2.0-beta2-build3300", - "xunit.runner.visualstudio": "2.2.0-beta2-build1149", - "Microsoft.Extensions.DependencyInjection": "1.1.0", - "Microsoft.Extensions.PlatformAbstractions": "1.1.0", - "Microsoft.Extensions.Configuration": "1.1.0", - "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0", - "Microsoft.Extensions.Configuration.Json": "1.1.0", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", - "Microsoft.Extensions.Options": "1.1.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0", - - "GeekLearning.Storage": "*", - "GeekLearning.Storage.Azure": "*", - "GeekLearning.Storage.FileSystem": "*", - "GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem": "*" - }, - - "frameworks": { - "netcoreapp1.0": { - "imports": [ "dotnet", "portable-net45+win8" ], - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.1" - } - } - }, - "net451": { - "frameworkAssemblies": {}, - "dependencies": {} - } - } -} From d17d6700efdf353d4f88a72d448a4f3905c0a01a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 30 Mar 2017 20:05:57 +0200 Subject: [PATCH 02/21] Strongly typed configuration for providers and scopes, proper exceptions from StorageFactory --- .../Startup.cs | 7 +-- .../appsettings.json | 17 +++++- .../AzureStorageExtensions.cs | 17 ++++++ .../AzureStorageManagerOptions.cs | 16 ----- .../AzureStorageProvider.cs | 17 ++++-- .../ProviderOptions.cs | 13 +++++ .../StoreOptions.cs | 9 +++ .../Internal/ExtendedPropertiesProvider.cs | 1 - .../FileSystemStorageServerMiddleware.cs | 11 ++-- .../FileSystemOptions.cs | 7 --- .../FileSystemStorageExtensions.cs | 25 +++++++- .../FileSystemStorageProvider.cs | 26 ++++----- .../ProviderOptions.cs | 13 +++++ .../StoreOptions.cs | 7 +++ .../Exceptions/BadStoreProviderException.cs | 12 ++++ .../Exceptions/ProviderNotFoundException.cs | 11 ++++ .../Exceptions/StoreNotFoundException.cs | 11 ++++ .../GeekLearning.Storage.csproj | 2 + src/GeekLearning.Storage/IProviderOptions.cs | 12 ++++ .../IProviderStoreOptions.cs | 6 ++ src/GeekLearning.Storage/IStorageProvider.cs | 2 + .../IStorageStoreOptions.cs | 4 +- src/GeekLearning.Storage/IStoreExtensions.cs | 36 +++--------- .../Internal/ConfigureProviderOptions.cs | 48 +++++++++++++++ .../Internal/GenericStoreProxy.cs | 58 ++++--------------- .../Internal/StorageFactory.cs | 38 ++++++++---- .../Internal/StorageProviderBase.cs | 43 ++++++++++++++ src/GeekLearning.Storage/StorageExtensions.cs | 8 +++ src/GeekLearning.Storage/StorageOptions.cs | 5 +- src/GeekLearning.Storage/StoreBase.cs | 2 +- .../TestStore.cs | 4 +- 31 files changed, 345 insertions(+), 143 deletions(-) delete mode 100644 src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/ProviderOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/StoreOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/ProviderOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs create mode 100644 src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs create mode 100644 src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs create mode 100644 src/GeekLearning.Storage/IProviderOptions.cs create mode 100644 src/GeekLearning.Storage/IProviderStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs create mode 100644 src/GeekLearning.Storage/Internal/StorageProviderBase.cs diff --git a/samples/GeekLearning.Storage.BasicSample/Startup.cs b/samples/GeekLearning.Storage.BasicSample/Startup.cs index e9fb65d..eb16301 100644 --- a/samples/GeekLearning.Storage.BasicSample/Startup.cs +++ b/samples/GeekLearning.Storage.BasicSample/Startup.cs @@ -33,15 +33,14 @@ public void ConfigureServices(IServiceCollection services) byte[] signingKey = new byte[512]; rng.GetBytes(signingKey); - services.AddStorage() + services.AddStorage(this.Configuration.GetSection("Storage")) .AddAzureStorage() .AddFileSystemStorage(HostingEnvironement.ContentRootPath) - .AddFileSystemStorageServer(options=> { + .AddFileSystemStorageServer(options => + { options.SigningKey = signingKey; options.BaseUri = new Uri("http://localhost:11149/"); }); - - services.Configure(Configuration.GetSection("Storage")); services.AddScoped(); } diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 6d3d0ab..aff87c6 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,12 +8,27 @@ } }, "Storage": { + "Providers": { + "Azure": { + "DefaultConnectionString": "vdsvdz" + }, + "FileSystem": { + "RootPath": "" + } + }, "Stores": { "Templates": { "Provider": "FileSystem", "Parameters": { "Path": "Templates", - "Access" : "Public" + "Access": "Public" + } + }, + "Youpi": { + "Provider": "Azure", + "Parameters": { + "ConnectionString": "hfeioa", + "Container": "Public" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index 45cae55..82c2853 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -1,12 +1,29 @@ namespace GeekLearning.Storage { using Azure; + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Options; public static class AzureStorageExtensions { public static IServiceCollection AddAzureStorage(this IServiceCollection services) + { + return services + .AddSingleton, ConfigureProviderOptions>() + .AddAzureStorageServices(); + } + + public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddAzureStorageServices(); + } + + private static IServiceCollection AddAzureStorageServices(this IServiceCollection services) { services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; diff --git a/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs b/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs deleted file mode 100644 index 917a72d..0000000 --- a/src/GeekLearning.Storage.Azure/AzureStorageManagerOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - using System.Collections.Generic; - - public class AzureStorageManagerOptions - { - public Dictionary SubStores { get; set; } - - public class SubStore - { - public string Container { get; set; } - - public string ConnectionString { get; set; } - } - } -} diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index b0086bc..1989964 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -1,14 +1,23 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Options; using Storage; - public class AzureStorageProvider : IStorageProvider + public class AzureStorageProvider : StorageProviderBase { - public string Name => "Azure"; + public const string ProviderName = "Azure"; - public IStore BuildStore(string storeName, IStorageStoreOptions storeOptions) + public AzureStorageProvider(IOptions options) + : base(options) { - return new AzureStore(storeName, storeOptions.Parameters["ConnectionString"], storeOptions.Parameters["Container"]); + } + + public override string Name => ProviderName; + + protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + { + return new AzureStore(storeName, storeOptions.ConnectionString, storeOptions.Container); } } } diff --git a/src/GeekLearning.Storage.Azure/ProviderOptions.cs b/src/GeekLearning.Storage.Azure/ProviderOptions.cs new file mode 100644 index 0000000..a9940d6 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/ProviderOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.Azure +{ + using System.Collections.Generic; + + public class ProviderOptions : IProviderOptions + { + public string ProviderName => AzureStorageProvider.ProviderName; + + public string DefaultConnectionString { get; set; } + + public IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/StoreOptions.cs b/src/GeekLearning.Storage.Azure/StoreOptions.cs new file mode 100644 index 0000000..6bf96a8 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/StoreOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Azure +{ + public class StoreOptions : IProviderStoreOptions + { + public string ConnectionString { get; set; } + + public string Container { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs index afdf947..b5a639c 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs @@ -9,7 +9,6 @@ public class ExtendedPropertiesProvider : IExtendedPropertiesProvider { private readonly FileSystemExtendedPropertiesOptions options; - private readonly IStorageFactory storageFactory; public ExtendedPropertiesProvider( IOptions options) diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index d9387f8..c3d8d43 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -38,11 +38,12 @@ public async Task Invoke(HttpContext context) && storeOptions.Provider == "FileSystem") { string access; - if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") - { - context.Response.StatusCode = StatusCodes.Status403Forbidden; - return; - } + // TODO: Fix options! + //if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") + //{ + // context.Response.StatusCode = StatusCodes.Status403Forbidden; + // return; + //} IStore store = storageFactory.GetStore(storeName, storeOptions); diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs deleted file mode 100644 index 5476e6a..0000000 --- a/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - public class FileSystemOptions - { - public string RootPath { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index 53d2646..cbae076 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -1,14 +1,37 @@ namespace GeekLearning.Storage { using FileSystem; + using GeekLearning.Storage.Internal; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Options; public static class FileSystemStorageExtensions { public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, string rootPath) { - services.Configure(options => options.RootPath = rootPath); + return services + .Configure(options => options.RootPath = rootPath) + .AddFileSystemStorage(); + } + + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) + { + return services + .AddSingleton, ConfigureProviderOptions>() + .AddFileSystemStorageServices(); + } + + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddFileSystemStorageServices(); + } + + private static IServiceCollection AddFileSystemStorageServices(this IServiceCollection services) + { services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 5b7cb9d..966c513 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -1,31 +1,29 @@ namespace GeekLearning.Storage.FileSystem { - using Microsoft.Extensions.DependencyInjection; + using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - using System; - public class FileSystemStorageProvider : IStorageProvider + public class FileSystemStorageProvider : StorageProviderBase { - private IOptions options; - private IServiceProvider serviceProvider; + public const string ProviderName = "FileSystem"; + private readonly IPublicUrlProvider publicUrlProvider; + private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStorageProvider(IOptions options, IServiceProvider serviceProvider) + public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) + : base(options) { - this.options = options; - this.serviceProvider = serviceProvider; + this.publicUrlProvider = publicUrlProvider; + this.extendedPropertiesProvider = extendedPropertiesProvider; } - public string Name => "FileSystem"; + public override string Name => ProviderName; - public IStore BuildStore(string storeName, IStorageStoreOptions storeOptions) + protected override IStore BuildStore(string storeName, StoreOptions storeOptions) { - var publicUrlProvider = this.serviceProvider.GetService(); - var extendedPropertiesProvider = this.serviceProvider.GetService(); - return new FileSystemStore( storeName, - storeOptions.Parameters["Path"], + storeOptions.Path, this.options.Value.RootPath, publicUrlProvider, extendedPropertiesProvider); diff --git a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs new file mode 100644 index 0000000..0864742 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.FileSystem +{ + using System.Collections.Generic; + + public class ProviderOptions : IProviderOptions + { + public string ProviderName => FileSystemStorageProvider.ProviderName; + + public string RootPath { get; set; } + + public IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs new file mode 100644 index 0000000..8205b37 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.FileSystem +{ + public class StoreOptions : IProviderStoreOptions + { + public string Path { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs b/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs new file mode 100644 index 0000000..a062288 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadStoreProviderException.cs @@ -0,0 +1,12 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadStoreProviderException : Exception + { + public BadStoreProviderException(string providerName, string storeName) + : base($"The store '{storeName}' was not configured with the provider '{providerName}'. Unable to build it.") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs new file mode 100644 index 0000000..1e108b9 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class ProviderNotFoundException : Exception + { + public ProviderNotFoundException(string providerName) : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs new file mode 100644 index 0000000..b161382 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class StoreNotFoundException : Exception + { + public StoreNotFoundException(string storeName) : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") + { + } + } +} diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index e61058f..4b03abb 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -11,8 +11,10 @@ + + diff --git a/src/GeekLearning.Storage/IProviderOptions.cs b/src/GeekLearning.Storage/IProviderOptions.cs new file mode 100644 index 0000000..73e5f50 --- /dev/null +++ b/src/GeekLearning.Storage/IProviderOptions.cs @@ -0,0 +1,12 @@ +namespace GeekLearning.Storage +{ + using System.Collections.Generic; + + public interface IProviderOptions + where TStoreOptions: class, IProviderStoreOptions + { + string ProviderName { get; } + + IReadOnlyDictionary Stores { get; set; } + } +} diff --git a/src/GeekLearning.Storage/IProviderStoreOptions.cs b/src/GeekLearning.Storage/IProviderStoreOptions.cs new file mode 100644 index 0000000..3c0f837 --- /dev/null +++ b/src/GeekLearning.Storage/IProviderStoreOptions.cs @@ -0,0 +1,6 @@ +namespace GeekLearning.Storage +{ + public interface IProviderStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index 5ac5670..b2c8dfb 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -4,6 +4,8 @@ public interface IStorageProvider { string Name { get; } + IStore BuildStore(string storeName); + IStore BuildStore(string storeName, IStorageStoreOptions storeOptions); } } diff --git a/src/GeekLearning.Storage/IStorageStoreOptions.cs b/src/GeekLearning.Storage/IStorageStoreOptions.cs index 4807f7b..0bc6855 100644 --- a/src/GeekLearning.Storage/IStorageStoreOptions.cs +++ b/src/GeekLearning.Storage/IStorageStoreOptions.cs @@ -1,11 +1,11 @@ namespace GeekLearning.Storage { - using System.Collections.Generic; + using Microsoft.Extensions.Configuration; public interface IStorageStoreOptions { string Provider { get; } - Dictionary Parameters { get; } + IConfigurationSection Parameters { get; } } } diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs index 36fa987..e6880c2 100644 --- a/src/GeekLearning.Storage/IStoreExtensions.cs +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -6,48 +6,30 @@ public static class IStoreExtensions { public static Task ListAsync(this IStore store, string path, bool recursive = false, bool withMetadata = false) - { - return store.ListAsync(path, recursive: recursive, withMetadata: withMetadata); - } + => store.ListAsync(path, recursive: recursive, withMetadata: withMetadata); public static Task ListAsync(this IStore store, string path, string searchPattern, bool recursive = false, bool withMetadata = false) - { - return store.ListAsync(path, searchPattern, recursive: recursive, withMetadata: withMetadata); - } + => store.ListAsync(path, searchPattern, recursive: recursive, withMetadata: withMetadata); public static Task DeleteAsync(this IStore store, string path) - { - return store.DeleteAsync(new Internal.PrivateFileReference(path)); - } + => store.DeleteAsync(new Internal.PrivateFileReference(path)); public static Task GetAsync(this IStore store, string path, bool withMetadata = false) - { - return store.GetAsync(new Internal.PrivateFileReference(path), withMetadata: withMetadata); - } + => store.GetAsync(new Internal.PrivateFileReference(path), withMetadata: withMetadata); public static Task ReadAsync(this IStore store, string path) - { - return store.ReadAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAsync(new Internal.PrivateFileReference(path)); public static Task ReadAllBytesAsync(this IStore store, string path) - { - return store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); public static Task ReadAllTextAsync(this IStore store, string path) - { - return store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); - } + => store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); public static Task SaveAsync(this IStore store, byte[] data, string path, string contentType) - { - return store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); - } + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); public static Task SaveAsync(this IStore store, Stream data, string path, string contentType) - { - return store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); - } + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); } } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs new file mode 100644 index 0000000..d6d12c2 --- /dev/null +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -0,0 +1,48 @@ +namespace GeekLearning.Storage.Internal +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + using System.Linq; + + public class ConfigureProviderOptions : IConfigureOptions + where TProviderOptions : class, IProviderOptions, new() + where TStoreOptions: class, IProviderStoreOptions, new() + { + private readonly IOptions storageOptions; + + public ConfigureProviderOptions(IOptions storageOptions) + { + this.storageOptions = storageOptions; + } + + public void Configure(TProviderOptions options) + { + var storageOptionsValue = this.storageOptions.Value; + + if (storageOptionsValue == null) + { + return; + } + + if (storageOptionsValue.Providers != null + && storageOptionsValue.Providers.TryGetValue(options.ProviderName, out var providerConfigurationSection)) + { + ConfigurationBinder.Bind(providerConfigurationSection, options); + } + + if (storageOptionsValue.Stores != null) + { + options.Stores = storageOptionsValue.Stores + .Where(skvp => skvp.Value.Provider == options.ProviderName) + .ToDictionary( + skvp => skvp.Key, + skvp => + { + var storeOptions = new TStoreOptions(); + ConfigurationBinder.Bind(skvp.Value.Parameters, storeOptions); + return storeOptions; + }); + } + } + } +} diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 228bea9..0e740f9 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -15,62 +15,26 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } - public string Name - { - get - { - return innerStore.Name; - } - } + public string Name => innerStore.Name; - public Task DeleteAsync(IPrivateFileReference file) - { - return innerStore.DeleteAsync(file); - } + public Task DeleteAsync(IPrivateFileReference file) => innerStore.DeleteAsync(file); - public Task GetAsync(Uri file, bool withMetadata) - { - return innerStore.GetAsync(file, withMetadata); - } + public Task GetAsync(Uri file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); - public Task GetAsync(IPrivateFileReference file, bool withMetadata) - { - return innerStore.GetAsync(file, withMetadata); - } + public Task GetAsync(IPrivateFileReference file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, bool recursive, bool withMetadata) - { - return innerStore.ListAsync(path, recursive, withMetadata); - } + public Task ListAsync(string path, bool recursive, bool withMetadata) => innerStore.ListAsync(path, recursive, withMetadata); - public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) - { - return innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - } + public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - public Task ReadAllBytesAsync(IPrivateFileReference file) - { - return innerStore.ReadAllBytesAsync(file); - } + public Task ReadAllBytesAsync(IPrivateFileReference file) => innerStore.ReadAllBytesAsync(file); - public Task ReadAllTextAsync(IPrivateFileReference file) - { - return innerStore.ReadAllTextAsync(file); - } + public Task ReadAllTextAsync(IPrivateFileReference file) => innerStore.ReadAllTextAsync(file); - public Task ReadAsync(IPrivateFileReference file) - { - return innerStore.ReadAsync(file); - } + public Task ReadAsync(IPrivateFileReference file) => innerStore.ReadAsync(file); - public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) - { - return innerStore.SaveAsync(data, file, contentType); - } + public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); - public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) - { - return innerStore.SaveAsync(data, file, contentType); - } + public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); } } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index 609e566..b8c8a5b 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -17,21 +17,19 @@ public StorageFactory(IEnumerable storageProviders, IOptions x.Name == configuration.Provider).BuildStore(storeName, configuration); + return this.GetProvider(configuration.Provider).BuildStore(storeName, configuration); } public IStore GetStore(string storeName) { - var conf = this.options.Value.Stores[storeName]; - return this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + return this.GetProvider(this.GetStoreConfiguration(storeName).Provider).BuildStore(storeName); } public bool TryGetStore(string storeName, out IStore store) { - StorageOptions.StorageStoreOptions conf; - if (this.options.Value.Stores.TryGetValue(storeName, out conf)) + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) { - store = this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + store = this.GetProvider(configuration.Provider).BuildStore(storeName); return true; } @@ -41,12 +39,11 @@ public bool TryGetStore(string storeName, out IStore store) public bool TryGetStore(string storeName, out IStore store, string provider) { - StorageOptions.StorageStoreOptions conf; - if (this.options.Value.Stores.TryGetValue(storeName, out conf)) + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) { - if (provider == conf.Provider) + if (provider == configuration.Provider) { - store = this.storageProviders.FirstOrDefault(x => x.Name == conf.Provider).BuildStore(storeName, conf); + store = this.GetProvider(configuration.Provider).BuildStore(storeName); return true; } } @@ -54,5 +51,26 @@ public bool TryGetStore(string storeName, out IStore store, string provider) store = null; return false; } + + private IStorageProvider GetProvider(string providerName) + { + var provider = this.storageProviders.FirstOrDefault(p => p.Name == providerName); + if (provider == null) + { + throw new Exceptions.ProviderNotFoundException(providerName); + } + + return provider; + } + + private StorageOptions.StorageStoreOptions GetStoreConfiguration(string storeName) + { + if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + { + return configuration; + } + + throw new Exceptions.StoreNotFoundException(storeName); + } } } diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs new file mode 100644 index 0000000..78143fb --- /dev/null +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -0,0 +1,43 @@ +namespace GeekLearning.Storage.Internal +{ + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + + public abstract class StorageProviderBase : IStorageProvider + where TProviderOptions : class, IProviderOptions, new() + where TStoreOptions : class, IProviderStoreOptions, new() + { + protected readonly IOptions options; + + public StorageProviderBase(IOptions options) + { + this.options = options; + } + + public abstract string Name { get; } + + public IStore BuildStore(string storeName) + { + if (this.options.Value.Stores.TryGetValue(storeName, out var storeOptions)) + { + return this.BuildStore(storeName, storeOptions); + } + + throw new Exceptions.BadStoreProviderException(this.Name, storeName); + } + + public IStore BuildStore(string storeName, IStorageStoreOptions storageStoreOptions) + { + if (storageStoreOptions.Provider != this.Name) + { + throw new Exceptions.BadStoreProviderException(this.Name, storeName); + } + + var storeOptions = new TStoreOptions(); + ConfigurationBinder.Bind(storageStoreOptions.Parameters, storeOptions); + return this.BuildStore(storeName, storeOptions); + } + + protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); + } +} diff --git a/src/GeekLearning.Storage/StorageExtensions.cs b/src/GeekLearning.Storage/StorageExtensions.cs index 7fabe92..b352a2a 100644 --- a/src/GeekLearning.Storage/StorageExtensions.cs +++ b/src/GeekLearning.Storage/StorageExtensions.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage { + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -11,5 +12,12 @@ public static IServiceCollection AddStorage(this IServiceCollection services) services.TryAdd(ServiceDescriptor.Transient(typeof(IStore<>), typeof(Internal.GenericStoreProxy<>))); return services; } + + public static IServiceCollection AddStorage(this IServiceCollection services, IConfiguration configuration) + { + return services + .Configure(configuration) + .AddStorage(); + } } } diff --git a/src/GeekLearning.Storage/StorageOptions.cs b/src/GeekLearning.Storage/StorageOptions.cs index f26e4cf..8da6e79 100644 --- a/src/GeekLearning.Storage/StorageOptions.cs +++ b/src/GeekLearning.Storage/StorageOptions.cs @@ -1,16 +1,19 @@ namespace GeekLearning.Storage { + using Microsoft.Extensions.Configuration; using System.Collections.Generic; public class StorageOptions { + public Dictionary Providers { get; set; } + public Dictionary Stores { get; set; } public class StorageStoreOptions : IStorageStoreOptions { public string Provider { get; set; } - public Dictionary Parameters { get; set; } + public IConfigurationSection Parameters { get; set; } } } } diff --git a/src/GeekLearning.Storage/StoreBase.cs b/src/GeekLearning.Storage/StoreBase.cs index 86ea12f..23a38ed 100644 --- a/src/GeekLearning.Storage/StoreBase.cs +++ b/src/GeekLearning.Storage/StoreBase.cs @@ -9,6 +9,6 @@ public StoreBase(string storeName, IStorageFactory storageFactory) this.store = storageFactory.GetStore(storeName); } - public IStore Store { get { return this.store; } } + public IStore Store => this.store; } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index 1df5a25..f4d4e7b 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,11 +1,11 @@ namespace GeekLearning.Storage.Integration.Test { - using System.Collections.Generic; + using Microsoft.Extensions.Configuration; public class TestStore : IStorageStoreOptions { public string Provider { get; set; } - public Dictionary Parameters { get; set; } + public IConfigurationSection Parameters { get; set; } } } From d6139ad7d816dfe023b596812c7d27d8315c33e0 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Wed, 12 Apr 2017 18:18:45 +0200 Subject: [PATCH 03/21] New configuration --- README.md | 6 +- .../appsettings.json | 58 +++++++--- .../AzureStorageExtensions.cs | 6 +- .../AzureStorageProvider.cs | 9 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 33 +++--- .../Configuration/AzureParsedOptions.cs | 37 ++++++ .../AzureProviderInstanceOptions.cs | 11 ++ .../Configuration/AzureScopedStoreOptions.cs | 8 ++ .../Configuration/AzureStoreOptions.cs | 11 ++ .../ProviderOptions.cs | 13 --- .../StoreOptions.cs | 9 -- .../FileSystemStorageServerMiddleware.cs | 12 +- .../Configuration/FileSystemParsedOptions.cs | 35 ++++++ .../FileSystemProviderInstanceOptions.cs | 9 ++ .../FileSystemScopedStoreOptions.cs | 8 ++ .../Configuration/FileSystemStoreOptions.cs | 28 +++++ .../FileSystemStorageExtensions.cs | 7 +- .../FileSystemStorageProvider.cs | 11 +- .../FileSystemStore.cs | 39 ++++--- .../ProviderOptions.cs | 13 --- .../StoreOptions.cs | 7 -- .../Configuration/AccessLevel.cs | 9 ++ .../Configuration/ConfigurationExtensions.cs | 108 ++++++++++++++++++ .../Configuration/INamedElementOptions.cs | 7 ++ .../Configuration/IParsedOptions.cs | 20 ++++ .../Configuration/IProviderInstanceOptions.cs | 7 ++ .../Configuration/IScopedStoreOptions.cs | 7 ++ .../Configuration/IStoreOptions.cs | 13 +++ .../Configuration/ProviderInstanceOptions.cs | 9 ++ .../Configuration/ScopedStoreOptions.cs | 7 ++ .../Configuration/StorageOptions.cs | 43 +++++++ .../Configuration/StoreOptions.cs | 15 +++ .../Exceptions/BadProviderConfiguration.cs | 17 +++ .../Exceptions/BadStoreConfiguration.cs | 17 +++ .../Exceptions/ProviderNotFoundException.cs | 3 +- .../Exceptions/StoreNotFoundException.cs | 3 +- src/GeekLearning.Storage/IProviderOptions.cs | 12 -- .../IProviderStoreOptions.cs | 6 - src/GeekLearning.Storage/IStorageFactory.cs | 4 +- src/GeekLearning.Storage/IStorageProvider.cs | 4 +- .../IStorageStoreOptions.cs | 11 -- src/GeekLearning.Storage/IStore{TOptions}.cs | 4 +- .../Internal/ConfigureProviderOptions.cs | 52 ++++----- .../Internal/GenericStoreProxy.cs | 8 +- .../Internal/StorageFactory.cs | 77 ++++++++----- .../Internal/StorageProviderBase.cs | 35 +++--- src/GeekLearning.Storage/StorageOptions.cs | 19 --- ... => StorageServiceCollectionExtensions.cs} | 3 +- .../DeleteTests.cs | 2 +- .../GenericIStoreTests.cs | 2 +- .../ListTests.cs | 14 +-- .../ReadTests.cs | 14 +-- .../StoresFixture.cs | 90 ++++++++++----- .../TestStore.cs | 20 +++- .../UpdateTests.cs | 14 +-- .../appsettings.json | 67 ++++++++--- 56 files changed, 799 insertions(+), 314 deletions(-) create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs delete mode 100644 src/GeekLearning.Storage.Azure/ProviderOptions.cs delete mode 100644 src/GeekLearning.Storage.Azure/StoreOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/ProviderOptions.cs delete mode 100644 src/GeekLearning.Storage.FileSystem/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/AccessLevel.cs create mode 100644 src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs create mode 100644 src/GeekLearning.Storage/Configuration/INamedElementOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IParsedOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/IStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/StorageOptions.cs create mode 100644 src/GeekLearning.Storage/Configuration/StoreOptions.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs create mode 100644 src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs delete mode 100644 src/GeekLearning.Storage/IProviderOptions.cs delete mode 100644 src/GeekLearning.Storage/IProviderStoreOptions.cs delete mode 100644 src/GeekLearning.Storage/IStorageStoreOptions.cs delete mode 100644 src/GeekLearning.Storage/StorageOptions.cs rename src/GeekLearning.Storage/{StorageExtensions.cs => StorageServiceCollectionExtensions.cs} (90%) diff --git a/README.md b/README.md index 920c59c..b8d5220 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=nuget:%20primitives)](https://www.nuget.org/packages/GeekLearning.Storage/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=nuget:%20filesystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=nuget:%20azure%20 storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet:%20Abstractions)](https://www.nuget.org/packages/GeekLearning.Storage/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=NuGet:%20FileSystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=NuGet:%20Azure%20Storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index aff87c6..4d8074f 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,28 +8,54 @@ } }, "Storage": { + "Providers": { - "Azure": { - "DefaultConnectionString": "vdsvdz" + "FirstAzure": { + "Type": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=default;AccountKey=;EndpointSuffix=core.windows.net" }, - "FileSystem": { - "RootPath": "" + "AnotherAzure": { + "Type": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" + }, + "FirstFileSystem": { + "Type": "FileSystem", + "RootPath": "C:/First" + }, + "AnotherFileSystem": { + "Type": "FileSystem", + "RootPath": "D:/Another" } }, + "Stores": { - "Templates": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates", - "Access": "Public" - } + "Youpi1": { + "ProviderName": "FirstFileSystem" + }, + "Youpi2": { + "ProviderName": "FirstFileSystem", + "AccessLevel": "Public", + "FolderName": "AnotherPath" + }, + "Youpi4": { + "ProviderName": "FirstAzure", + "AccessLevel": "Private" + }, + "Youpa2": { + "ProviderType": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=excpetionaccount;AccountKey=;EndpointSuffix=core.windows.net" + } + }, + + "ScopedStores": { + "Youpi3": { + "ProviderName": "AnotherFileSystem", + "FolderNameFormat": "AnotherPath-{0}" }, - "Youpi": { - "Provider": "Azure", - "Parameters": { - "ConnectionString": "hfeioa", - "Container": "Public" - } + "Youpa": { + "ProviderName": "AnotherAzure", + "AccessLevel": "Confidential", + "FolderNameFormat": "Youpa-{0}" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index 82c2853..cc70c51 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage { using Azure; + using GeekLearning.Storage.Azure.Configuration; + using GeekLearning.Storage.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,14 +14,14 @@ public static class AzureStorageExtensions public static IServiceCollection AddAzureStorage(this IServiceCollection services) { return services - .AddSingleton, ConfigureProviderOptions>() + .AddSingleton, ConfigureProviderOptions>() .AddAzureStorageServices(); } public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) { return services - .Configure(configuration) + .Configure(configuration) .AddAzureStorageServices(); } diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index 1989964..87aca75 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -1,23 +1,24 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Azure.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - public class AzureStorageProvider : StorageProviderBase + public class AzureStorageProvider : StorageProviderBase { public const string ProviderName = "Azure"; - public AzureStorageProvider(IOptions options) + public AzureStorageProvider(IOptions options) : base(options) { } public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + protected override IStore BuildStore(string storeName, AzureStoreOptions storeOptions) { - return new AzureStore(storeName, storeOptions.ConnectionString, storeOptions.Container); + return new AzureStore(storeOptions); } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index ff125a7..f09da9d 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.Azure { + using GeekLearning.Storage.Azure.Configuration; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.Core; @@ -11,28 +12,30 @@ public class AzureStore : IStore { - private Lazy client; - private Lazy container; + private readonly AzureStoreOptions storeOptions; + private readonly Lazy client; + private readonly Lazy container; - public AzureStore(string storeName, string connectionString, string containerName) + public AzureStore(AzureStoreOptions storeOptions) { - this.Name = storeName; + this.storeOptions = storeOptions; - if (string.IsNullOrWhiteSpace(connectionString)) - { - throw new ArgumentNullException("connectionString"); - } + // TODO: Create Validate method in IStoreOptions + //if (string.IsNullOrWhiteSpace(connectionString)) + //{ + // throw new ArgumentNullException("connectionString"); + //} - if (string.IsNullOrWhiteSpace(containerName)) - { - throw new ArgumentNullException("containerName"); - } + //if (string.IsNullOrWhiteSpace(containerName)) + //{ + // throw new ArgumentNullException("containerName"); + //} - this.client = new Lazy(() => CloudStorageAccount.Parse(connectionString).CreateCloudBlobClient()); - this.container = new Lazy(() => this.client.Value.GetContainerReference(containerName)); + this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); + this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); } - public string Name { get; } + public string Name => this.storeOptions.Name; public async Task ListAsync(string path, bool recursive, bool withMetadata) { diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs new file mode 100644 index 0000000..251f5bf --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs @@ -0,0 +1,37 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + + public class AzureParsedOptions : IParsedOptions + { + public string Name => AzureStorageProvider.ProviderName; + + public IReadOnlyDictionary ParsedProviderInstances { get; set; } + + public IReadOnlyDictionary ParsedStores { get; set; } + + public IReadOnlyDictionary ParsedScopedStores { get; set; } + + public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstanceOptions providerInstanceOptions = null) + { + storeOptions.FolderName = storeOptions.FolderName.ToLowerInvariant(); + + if (providerInstanceOptions == null + || storeOptions.ProviderName != providerInstanceOptions.Name) + { + return; + } + + if (string.IsNullOrEmpty(storeOptions.ConnectionString)) + { + storeOptions.ConnectionString = providerInstanceOptions.ConnectionString; + } + + if (string.IsNullOrEmpty(storeOptions.ConnectionStringName)) + { + storeOptions.ConnectionStringName = providerInstanceOptions.ConnectionStringName; + } + } + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs new file mode 100644 index 0000000..b97f168 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureProviderInstanceOptions.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureProviderInstanceOptions : ProviderInstanceOptions + { + public string ConnectionString { get; set; } + + public string ConnectionStringName { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs new file mode 100644 index 0000000..4c74736 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs @@ -0,0 +1,8 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureScopedStoreOptions : ScopedStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs new file mode 100644 index 0000000..4401fde --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Azure.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class AzureStoreOptions : StoreOptions + { + public string ConnectionString { get; set; } + + public string ConnectionStringName { get; set; } + } +} diff --git a/src/GeekLearning.Storage.Azure/ProviderOptions.cs b/src/GeekLearning.Storage.Azure/ProviderOptions.cs deleted file mode 100644 index a9940d6..0000000 --- a/src/GeekLearning.Storage.Azure/ProviderOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - using System.Collections.Generic; - - public class ProviderOptions : IProviderOptions - { - public string ProviderName => AzureStorageProvider.ProviderName; - - public string DefaultConnectionString { get; set; } - - public IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage.Azure/StoreOptions.cs b/src/GeekLearning.Storage.Azure/StoreOptions.cs deleted file mode 100644 index 6bf96a8..0000000 --- a/src/GeekLearning.Storage.Azure/StoreOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace GeekLearning.Storage.Azure -{ - public class StoreOptions : IProviderStoreOptions - { - public string ConnectionString { get; set; } - - public string Container { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index c3d8d43..e2bd947 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.FileSystem.Server { + using GeekLearning.Storage.FileSystem.Configuration; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -12,14 +13,14 @@ public class FileSystemStorageServerMiddleware private ILogger logger; private IOptions serverOptions; - private IOptions storageOptions; + private FileSystemParsedOptions fileSystemParsedOptions; public FileSystemStorageServerMiddleware(RequestDelegate next, IOptions serverOptions, ILogger logger, - IOptions storageOptions) + IOptions fileSystemParsedOptions) { - this.storageOptions = storageOptions; + this.fileSystemParsedOptions = fileSystemParsedOptions.Value; this.next = next; this.serverOptions = serverOptions; this.logger = logger; @@ -33,9 +34,8 @@ public async Task Invoke(HttpContext context) var storeName = context.Request.Path.Value.Substring(1, subPathStart - 1); var storageFactory = context.RequestServices.GetRequiredService(); - StorageOptions.StorageStoreOptions storeOptions; - if (this.storageOptions.Value.Stores.TryGetValue(storeName, out storeOptions) - && storeOptions.Provider == "FileSystem") + if (this.fileSystemParsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions) + && storeOptions.ProviderType == "FileSystem") { string access; // TODO: Fix options! diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs new file mode 100644 index 0000000..c98956e --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -0,0 +1,35 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + + public class FileSystemParsedOptions : IParsedOptions + { + public string Name => FileSystemStorageProvider.ProviderName; + + public string RootPath { get; set; } + + public IReadOnlyDictionary ParsedProviderInstances { get; set; } + + public IReadOnlyDictionary ParsedStores { get; set; } + + public IReadOnlyDictionary ParsedScopedStores { get; set; } + + public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) + { + if (string.IsNullOrEmpty(storeOptions.RootPath)) + { + if (providerInstanceOptions != null + && storeOptions.ProviderName == providerInstanceOptions.Name + && !string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + { + storeOptions.RootPath = providerInstanceOptions.RootPath; + } + else + { + storeOptions.RootPath = this.RootPath; + } + } + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs new file mode 100644 index 0000000..bcf5e3c --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemProviderInstanceOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class FileSystemProviderInstanceOptions : ProviderInstanceOptions + { + public string RootPath { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs new file mode 100644 index 0000000..035cc73 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs @@ -0,0 +1,8 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + + public class FileSystemScopedStoreOptions : ScopedStoreOptions + { + } +} diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs new file mode 100644 index 0000000..af93561 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs @@ -0,0 +1,28 @@ +namespace GeekLearning.Storage.FileSystem.Configuration +{ + using GeekLearning.Storage.Configuration; + using System.IO; + + public class FileSystemStoreOptions : StoreOptions + { + public string RootPath { get; set; } + + public string AbsolutePath + { + get + { + if (string.IsNullOrEmpty(this.RootPath)) + { + return this.FolderName; + } + + if (string.IsNullOrEmpty(this.FolderName)) + { + return this.RootPath; + } + + return Path.Combine(this.RootPath, this.FolderName); + } + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index cbae076..0b85c9b 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -1,6 +1,7 @@ namespace GeekLearning.Storage { using FileSystem; + using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,21 +13,21 @@ public static class FileSystemStorageExtensions public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, string rootPath) { return services - .Configure(options => options.RootPath = rootPath) + .Configure(options => options.RootPath = rootPath) .AddFileSystemStorage(); } public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) { return services - .AddSingleton, ConfigureProviderOptions>() + .AddSingleton, ConfigureProviderOptions>() .AddFileSystemStorageServices(); } public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) { return services - .Configure(configuration) + .Configure(configuration) .AddFileSystemStorageServices(); } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 966c513..3f5b26f 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -1,16 +1,17 @@ namespace GeekLearning.Storage.FileSystem { + using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; using Microsoft.Extensions.Options; using Storage; - public class FileSystemStorageProvider : StorageProviderBase + public class FileSystemStorageProvider : StorageProviderBase { public const string ProviderName = "FileSystem"; private readonly IPublicUrlProvider publicUrlProvider; private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) + public FileSystemStorageProvider(IOptions options, IPublicUrlProvider publicUrlProvider = null, IExtendedPropertiesProvider extendedPropertiesProvider = null) : base(options) { this.publicUrlProvider = publicUrlProvider; @@ -19,12 +20,10 @@ public FileSystemStorageProvider(IOptions options, IPublicUrlPr public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, StoreOptions storeOptions) + protected override IStore BuildStore(string storeName, FileSystemStoreOptions storeOptions) { return new FileSystemStore( - storeName, - storeOptions.Path, - this.options.Value.RootPath, + storeOptions, publicUrlProvider, extendedPropertiesProvider); } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 32c0fd6..5983c96 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.FileSystem { + using GeekLearning.Storage.FileSystem.Configuration; using System; using System.Collections.Generic; using System.IO; @@ -9,33 +10,35 @@ public class FileSystemStore : IStore { + private readonly FileSystemStoreOptions storeOptions; private readonly IPublicUrlProvider publicUrlProvider; private readonly IExtendedPropertiesProvider extendedPropertiesProvider; - public FileSystemStore(string storeName, string path, string rootPath, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) + public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - if (Path.IsPathRooted(path)) - { - this.AbsolutePath = path; - } - else - { - this.AbsolutePath = Path.Combine(rootPath, path); - } - - this.Name = storeName; + // TODO: Implement Validate method on options + //if (string.IsNullOrEmpty(path)) + //{ + // throw new ArgumentNullException("path"); + //} + + //if (Path.IsPathRooted(path)) + //{ + // this.AbsolutePath = path; + //} + //else + //{ + // this.AbsolutePath = Path.Combine(rootPath, path); + //} + + this.storeOptions = storeOptions; this.publicUrlProvider = publicUrlProvider; this.extendedPropertiesProvider = extendedPropertiesProvider; } - public string Name { get; } + public string Name => storeOptions.Name; - internal string AbsolutePath { get; } + internal string AbsolutePath => storeOptions.AbsolutePath; public async Task ListAsync(string path, bool recursive, bool withMetadata) { diff --git a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs b/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs deleted file mode 100644 index 0864742..0000000 --- a/src/GeekLearning.Storage.FileSystem/ProviderOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - using System.Collections.Generic; - - public class ProviderOptions : IProviderOptions - { - public string ProviderName => FileSystemStorageProvider.ProviderName; - - public string RootPath { get; set; } - - public IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs b/src/GeekLearning.Storage.FileSystem/StoreOptions.cs deleted file mode 100644 index 8205b37..0000000 --- a/src/GeekLearning.Storage.FileSystem/StoreOptions.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace GeekLearning.Storage.FileSystem -{ - public class StoreOptions : IProviderStoreOptions - { - public string Path { get; set; } - } -} diff --git a/src/GeekLearning.Storage/Configuration/AccessLevel.cs b/src/GeekLearning.Storage/Configuration/AccessLevel.cs new file mode 100644 index 0000000..3442ada --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/AccessLevel.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public enum AccessLevel + { + Private = 0, + Confidential = 1, + Public = 2, + } +} diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs new file mode 100644 index 0000000..a740d42 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -0,0 +1,108 @@ +namespace GeekLearning.Storage.Configuration +{ + using Microsoft.Extensions.Configuration; + using System.Collections.Generic; + using System.Linq; + + public static class ConfigurationExtensions + { + public static IReadOnlyDictionary Parse(this Dictionary unparsedConfiguration) + where TOptions : class, INamedElementOptions, new() + { + if (unparsedConfiguration == null) + { + return new Dictionary(); + } + + return unparsedConfiguration + .ToDictionary( + kvp => kvp.Key, + kvp => BindOptions(kvp)); + } + + public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, IScopedStoreOptions + { + parsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions); + if (storeOptions != null) + { + return storeOptions; + } + + parsedOptions.ParsedScopedStores.TryGetValue(storeName, out var scopedStoreOptions); + if (scopedStoreOptions != null) + { + return scopedStoreOptions; + } + + if (throwIfNotFound) + { + throw new Exceptions.StoreNotFoundException(storeName); + } + + return null; + } + + public static void Compute(this TStoreOptions parsedStore, TParsedOptions options) + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + if (string.IsNullOrEmpty(parsedStore.FolderName)) + { + parsedStore.FolderName = parsedStore.Name; + } + + TInstanceOptions instanceOptions = null; + if (!string.IsNullOrEmpty(parsedStore.ProviderName)) + { + options.ParsedProviderInstances.TryGetValue(parsedStore.ProviderName, out instanceOptions); + if (instanceOptions == null) + { + return; + } + + parsedStore.ProviderType = instanceOptions.Type; + } + + options.BindStoreOptions(parsedStore, instanceOptions); + } + + public static TStoreOptions ParseStoreOptions(this IStoreOptions storeOptions, TParsedOptions options) + where TParsedOptions : class, IParsedOptions, new() + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + if (!(storeOptions is TStoreOptions parsedStoreOptions)) + { + parsedStoreOptions = new TStoreOptions + { + Name = storeOptions.Name, + ProviderName = storeOptions.ProviderName, + ProviderType = storeOptions.ProviderType, + AccessLevel = storeOptions.AccessLevel, + FolderName = storeOptions.FolderName, + }; + } + + parsedStoreOptions.Compute(options); + return parsedStoreOptions; + } + + private static TOptions BindOptions(KeyValuePair kvp) + where TOptions : class, INamedElementOptions, new() + { + var options = new TOptions + { + Name = kvp.Key, + }; + + ConfigurationBinder.Bind(kvp.Value, options); + return options; + } + } +} diff --git a/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs b/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs new file mode 100644 index 0000000..b262f4c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/INamedElementOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface INamedElementOptions + { + string Name { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs new file mode 100644 index 0000000..833187c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -0,0 +1,20 @@ +namespace GeekLearning.Storage.Configuration +{ + using System.Collections.Generic; + + public interface IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, IScopedStoreOptions + { + string Name { get; } + + IReadOnlyDictionary ParsedProviderInstances { get; set; } + + IReadOnlyDictionary ParsedStores { get; set; } + + IReadOnlyDictionary ParsedScopedStores { get; set; } + + void BindStoreOptions(TStoreOptions storeOptions, TInstanceOptions providerInstanceOptions = null); + } +} diff --git a/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs b/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs new file mode 100644 index 0000000..1f8d240 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IProviderInstanceOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IProviderInstanceOptions : INamedElementOptions + { + string Type { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs new file mode 100644 index 0000000..52aeed9 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IScopedStoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IScopedStoreOptions : IStoreOptions + { + string FolderNameFormat { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs new file mode 100644 index 0000000..c55478e --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IStoreOptions : INamedElementOptions + { + string ProviderName { get; set; } + + string ProviderType { get; set; } + + AccessLevel AccessLevel { get; set; } + + string FolderName { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs b/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs new file mode 100644 index 0000000..ae0e77c --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ProviderInstanceOptions.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public class ProviderInstanceOptions : IProviderInstanceOptions + { + public string Name { get; set; } + + public string Type { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs b/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs new file mode 100644 index 0000000..8dd4fe2 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/ScopedStoreOptions.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage.Configuration +{ + public class ScopedStoreOptions : StoreOptions, IScopedStoreOptions + { + public string FolderNameFormat { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StorageOptions.cs b/src/GeekLearning.Storage/Configuration/StorageOptions.cs new file mode 100644 index 0000000..821db5d --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/StorageOptions.cs @@ -0,0 +1,43 @@ +namespace GeekLearning.Storage.Configuration +{ + using Microsoft.Extensions.Configuration; + using System; + using System.Collections.Generic; + + public class StorageOptions : IParsedOptions + { + internal const string GlobalOptionsName = "Global"; + + private readonly Lazy> parsedProviderInstances; + private readonly Lazy> parsedStores; + private readonly Lazy> parsedScopedStores; + + public StorageOptions() + { + this.parsedProviderInstances = new Lazy>( + () => this.Providers.Parse()); + this.parsedStores = new Lazy>( + () => this.Stores.Parse()); + this.parsedScopedStores = new Lazy>( + () => this.ScopedStores.Parse()); + } + + public string Name => GlobalOptionsName; + + public Dictionary Providers { get; set; } + + public Dictionary Stores { get; set; } + + public Dictionary ScopedStores { get; set; } + + public IReadOnlyDictionary ParsedProviderInstances { get => this.parsedProviderInstances.Value; set { } } + + public IReadOnlyDictionary ParsedStores { get => this.parsedStores.Value; set { } } + + public IReadOnlyDictionary ParsedScopedStores { get => this.parsedScopedStores.Value; set { } } + + public void BindStoreOptions(StoreOptions storeOptions, ProviderInstanceOptions providerInstanceOptions) + { + } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StoreOptions.cs b/src/GeekLearning.Storage/Configuration/StoreOptions.cs new file mode 100644 index 0000000..c200b76 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/StoreOptions.cs @@ -0,0 +1,15 @@ +namespace GeekLearning.Storage.Configuration +{ + public class StoreOptions : IStoreOptions + { + public string Name { get; set; } + + public string ProviderName { get; set; } + + public string ProviderType { get; set; } + + public AccessLevel AccessLevel { get; set; } + + public string FolderName { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs new file mode 100644 index 0000000..95a38a8 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadProviderConfiguration.cs @@ -0,0 +1,17 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadProviderConfiguration : Exception + { + public BadProviderConfiguration(string providerName) + : base($"The provider '{providerName}' was not properly configured.") + { + } + + public BadProviderConfiguration(string providerName, string details) + : base($"The providerName '{providerName}' was not properly configured. {details}") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs new file mode 100644 index 0000000..5ceb28e --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs @@ -0,0 +1,17 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadStoreConfiguration : Exception + { + public BadStoreConfiguration(string storeName) + : base($"The store '{storeName}' was not properly configured.") + { + } + + public BadStoreConfiguration(string storeName, string details) + : base($"The store '{storeName}' was not properly configured. {details}") + { + } + } +} diff --git a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs index 1e108b9..af57f98 100644 --- a/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs +++ b/src/GeekLearning.Storage/Exceptions/ProviderNotFoundException.cs @@ -4,7 +4,8 @@ public class ProviderNotFoundException : Exception { - public ProviderNotFoundException(string providerName) : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") + public ProviderNotFoundException(string providerName) + : base($"The configured provider '{providerName}' was not found. Did you forget to register providers in your Startup.ConfigureServices?") { } } diff --git a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs index b161382..b93f841 100644 --- a/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs +++ b/src/GeekLearning.Storage/Exceptions/StoreNotFoundException.cs @@ -4,7 +4,8 @@ public class StoreNotFoundException : Exception { - public StoreNotFoundException(string storeName) : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") + public StoreNotFoundException(string storeName) + : base($"The configured store '{storeName}' was not found. Did you configure it properly in your appsettings.json?") { } } diff --git a/src/GeekLearning.Storage/IProviderOptions.cs b/src/GeekLearning.Storage/IProviderOptions.cs deleted file mode 100644 index 73e5f50..0000000 --- a/src/GeekLearning.Storage/IProviderOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace GeekLearning.Storage -{ - using System.Collections.Generic; - - public interface IProviderOptions - where TStoreOptions: class, IProviderStoreOptions - { - string ProviderName { get; } - - IReadOnlyDictionary Stores { get; set; } - } -} diff --git a/src/GeekLearning.Storage/IProviderStoreOptions.cs b/src/GeekLearning.Storage/IProviderStoreOptions.cs deleted file mode 100644 index 3c0f837..0000000 --- a/src/GeekLearning.Storage/IProviderStoreOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace GeekLearning.Storage -{ - public interface IProviderStoreOptions - { - } -} diff --git a/src/GeekLearning.Storage/IStorageFactory.cs b/src/GeekLearning.Storage/IStorageFactory.cs index 4bf8398..a63dd9a 100644 --- a/src/GeekLearning.Storage/IStorageFactory.cs +++ b/src/GeekLearning.Storage/IStorageFactory.cs @@ -1,8 +1,10 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStorageFactory { - IStore GetStore(string storeName, IStorageStoreOptions configuration); + IStore GetStore(string storeName, IStoreOptions configuration); IStore GetStore(string storeName); diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index b2c8dfb..07d5c5d 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -1,11 +1,13 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStorageProvider { string Name { get; } IStore BuildStore(string storeName); - IStore BuildStore(string storeName, IStorageStoreOptions storeOptions); + IStore BuildStore(string storeName, IStoreOptions storeOptions); } } diff --git a/src/GeekLearning.Storage/IStorageStoreOptions.cs b/src/GeekLearning.Storage/IStorageStoreOptions.cs deleted file mode 100644 index 0bc6855..0000000 --- a/src/GeekLearning.Storage/IStorageStoreOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace GeekLearning.Storage -{ - using Microsoft.Extensions.Configuration; - - public interface IStorageStoreOptions - { - string Provider { get; } - - IConfigurationSection Parameters { get; } - } -} diff --git a/src/GeekLearning.Storage/IStore{TOptions}.cs b/src/GeekLearning.Storage/IStore{TOptions}.cs index 709f787..c278242 100644 --- a/src/GeekLearning.Storage/IStore{TOptions}.cs +++ b/src/GeekLearning.Storage/IStore{TOptions}.cs @@ -1,7 +1,9 @@ namespace GeekLearning.Storage { + using Configuration; + public interface IStore : IStore - where TOptions : class, IStorageStoreOptions, new() + where TOptions : class, IStoreOptions, new() { } } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index d6d12c2..2931fa7 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -1,48 +1,48 @@ namespace GeekLearning.Storage.Internal { - using Microsoft.Extensions.Configuration; + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Options; using System.Linq; - public class ConfigureProviderOptions : IConfigureOptions - where TProviderOptions : class, IProviderOptions, new() - where TStoreOptions: class, IProviderStoreOptions, new() + public class ConfigureProviderOptions : IConfigureOptions + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() { - private readonly IOptions storageOptions; + private readonly StorageOptions storageOptions; public ConfigureProviderOptions(IOptions storageOptions) { - this.storageOptions = storageOptions; + this.storageOptions = storageOptions.Value; } - public void Configure(TProviderOptions options) + public void Configure(TParsedOptions options) { - var storageOptionsValue = this.storageOptions.Value; - - if (storageOptionsValue == null) + if (this.storageOptions == null) { return; } - if (storageOptionsValue.Providers != null - && storageOptionsValue.Providers.TryGetValue(options.ProviderName, out var providerConfigurationSection)) - { - ConfigurationBinder.Bind(providerConfigurationSection, options); - } + options.ParsedProviderInstances = this.storageOptions.Providers.Parse() + .Where(kvp => kvp.Value.Type == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - if (storageOptionsValue.Stores != null) + var parsedStores = this.storageOptions.Stores.Parse(); + var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); + + foreach (var parsedStore in parsedStores) { - options.Stores = storageOptionsValue.Stores - .Where(skvp => skvp.Value.Provider == options.ProviderName) - .ToDictionary( - skvp => skvp.Key, - skvp => - { - var storeOptions = new TStoreOptions(); - ConfigurationBinder.Bind(skvp.Value.Parameters, storeOptions); - return storeOptions; - }); + parsedStore.Value.Compute(options); } + + options.ParsedStores = parsedStores + .Where(kvp => kvp.Value.ProviderType == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + options.ParsedScopedStores = parsedScopedStores + .Where(kvp => kvp.Value.ProviderType == options.Name) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 0e740f9..08dde70 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -1,17 +1,23 @@ namespace GeekLearning.Storage.Internal { + using Configuration; using Microsoft.Extensions.Options; using System; using System.IO; using System.Threading.Tasks; public class GenericStoreProxy : IStore, IStore - where TOptions : class, IStorageStoreOptions, new() + where TOptions : class, IStoreOptions, new() { private IStore innerStore; public GenericStoreProxy(IStorageFactory factory, IOptions options) { + if (options == null) + { + throw new ArgumentNullException("options", "Unable to build generic Store. Did you forget to configure your options?"); + } + this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index b8c8a5b..e43f178 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -1,49 +1,57 @@ namespace GeekLearning.Storage.Internal { + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Options; using System.Collections.Generic; using System.Linq; public class StorageFactory : IStorageFactory { - private IOptions options; - private IEnumerable storageProviders; + private StorageOptions options; + private IReadOnlyDictionary storageProviders; public StorageFactory(IEnumerable storageProviders, IOptions options) { - this.storageProviders = storageProviders; - this.options = options; + this.storageProviders = storageProviders.ToDictionary(sp => sp.Name, sp => sp); + this.options = options.Value; } - public IStore GetStore(string storeName, IStorageStoreOptions configuration) + public IStore GetStore(string storeName, IStoreOptions configuration) { - return this.GetProvider(configuration.Provider).BuildStore(storeName, configuration); + return this.GetProvider(configuration).BuildStore(storeName, configuration); } public IStore GetStore(string storeName) { - return this.GetProvider(this.GetStoreConfiguration(storeName).Provider).BuildStore(storeName); + return this.GetProvider(this.options.GetStoreConfiguration(storeName)).BuildStore(storeName); } public bool TryGetStore(string storeName, out IStore store) { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); + if (configuration != null) { - store = this.GetProvider(configuration.Provider).BuildStore(storeName); - return true; + var provider = this.GetProvider(configuration, throwIfNotFound: false); + if (provider != null) + { + store = provider.BuildStore(storeName); + return true; + } } store = null; return false; } - public bool TryGetStore(string storeName, out IStore store, string provider) + public bool TryGetStore(string storeName, out IStore store, string providerName) { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); + if (configuration != null) { - if (provider == configuration.Provider) + var provider = this.GetProvider(configuration, throwIfNotFound: false); + if (provider != null && provider.Name == providerName) { - store = this.GetProvider(configuration.Provider).BuildStore(storeName); + store = provider.BuildStore(storeName); return true; } } @@ -52,25 +60,42 @@ public bool TryGetStore(string storeName, out IStore store, string provider) return false; } - private IStorageProvider GetProvider(string providerName) + private IStorageProvider GetProvider(IStoreOptions configuration, bool throwIfNotFound = true) { - var provider = this.storageProviders.FirstOrDefault(p => p.Name == providerName); - if (provider == null) + string providerTypeName = null; + if (!string.IsNullOrEmpty(configuration.ProviderType)) + { + providerTypeName = configuration.ProviderType; + } + else if (!string.IsNullOrEmpty(configuration.ProviderName)) + { + this.options.ParsedProviderInstances.TryGetValue(configuration.ProviderName, out var providerInstanceOptions); + if (providerInstanceOptions != null) + { + providerTypeName = providerInstanceOptions.Type; + } + else if (throwIfNotFound) + { + throw new Exceptions.BadProviderConfiguration(configuration.ProviderName, "Unable to find it in the configuration."); + } + } + else if (throwIfNotFound) { - throw new Exceptions.ProviderNotFoundException(providerName); + throw new Exceptions.BadStoreConfiguration(configuration.Name, "You have to set either 'ProviderType' or 'ProviderName' on Store configuration."); } - return provider; - } + if (string.IsNullOrEmpty(providerTypeName)) + { + return null; + } - private StorageOptions.StorageStoreOptions GetStoreConfiguration(string storeName) - { - if (this.options.Value.Stores.TryGetValue(storeName, out var configuration)) + this.storageProviders.TryGetValue(providerTypeName, out var provider); + if (provider == null && throwIfNotFound) { - return configuration; + throw new Exceptions.ProviderNotFoundException(providerTypeName); } - throw new Exceptions.StoreNotFoundException(storeName); - } + return provider; + } } } diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs index 78143fb..bb967e6 100644 --- a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -1,41 +1,38 @@ namespace GeekLearning.Storage.Internal { - using Microsoft.Extensions.Configuration; + using Configuration; using Microsoft.Extensions.Options; - public abstract class StorageProviderBase : IStorageProvider - where TProviderOptions : class, IProviderOptions, new() - where TStoreOptions : class, IProviderStoreOptions, new() + public abstract class StorageProviderBase : IStorageProvider + where TParsedOptions : class, IParsedOptions, new() + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() { - protected readonly IOptions options; + protected readonly TParsedOptions options; - public StorageProviderBase(IOptions options) + public StorageProviderBase(IOptions options) { - this.options = options; + this.options = options.Value; } public abstract string Name { get; } public IStore BuildStore(string storeName) { - if (this.options.Value.Stores.TryGetValue(storeName, out var storeOptions)) - { - return this.BuildStore(storeName, storeOptions); - } - - throw new Exceptions.BadStoreProviderException(this.Name, storeName); + return this.BuildStore(storeName, this.options.GetStoreConfiguration(storeName)); } - public IStore BuildStore(string storeName, IStorageStoreOptions storageStoreOptions) + public IStore BuildStore(string storeName, IStoreOptions storeOptions) { - if (storageStoreOptions.Provider != this.Name) + if (storeOptions.ProviderType != this.Name) { throw new Exceptions.BadStoreProviderException(this.Name, storeName); } - - var storeOptions = new TStoreOptions(); - ConfigurationBinder.Bind(storageStoreOptions.Parameters, storeOptions); - return this.BuildStore(storeName, storeOptions); + + return this.BuildStore( + storeName, + storeOptions.ParseStoreOptions(options)); } protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); diff --git a/src/GeekLearning.Storage/StorageOptions.cs b/src/GeekLearning.Storage/StorageOptions.cs deleted file mode 100644 index 8da6e79..0000000 --- a/src/GeekLearning.Storage/StorageOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace GeekLearning.Storage -{ - using Microsoft.Extensions.Configuration; - using System.Collections.Generic; - - public class StorageOptions - { - public Dictionary Providers { get; set; } - - public Dictionary Stores { get; set; } - - public class StorageStoreOptions : IStorageStoreOptions - { - public string Provider { get; set; } - - public IConfigurationSection Parameters { get; set; } - } - } -} diff --git a/src/GeekLearning.Storage/StorageExtensions.cs b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs similarity index 90% rename from src/GeekLearning.Storage/StorageExtensions.cs rename to src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs index b352a2a..e08e39c 100644 --- a/src/GeekLearning.Storage/StorageExtensions.cs +++ b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs @@ -1,10 +1,11 @@ namespace GeekLearning.Storage { + using Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; - public static class StorageExtensions + public static class StorageServiceCollectionExtensions { public static IServiceCollection AddStorage(this IServiceCollection services) { diff --git a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs index 2ddf857..2fba5d4 100644 --- a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs @@ -16,7 +16,7 @@ public DeleteTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(Delete)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task Delete(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs b/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs index 94299db..5a813a3 100644 --- a/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/GenericIStoreTests.cs @@ -7,7 +7,7 @@ using Xunit; [Collection(nameof(IntegrationCollection))] - [Trait("Kind", "Integration")] + [Trait("Operation", "GenericIStore"), Trait("Kind", "Integration")] public class GenericIStoreTests { private StoresFixture storeFixture; diff --git a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs index 1c1c0d1..4275408 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs @@ -17,7 +17,7 @@ public ListTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ListRootFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListRootFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task ListRootFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListEmptyPathFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -55,7 +55,7 @@ public async Task ListEmptyPathFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListSubDirectoryFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -74,7 +74,7 @@ public async Task ListSubDirectoryFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -93,7 +93,7 @@ public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ExtensionGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -112,7 +112,7 @@ public async Task ExtensionGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task FileNameGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task FileNameGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task FileNameGlobbingAtRoot(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs index 9b45aa0..56e0518 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs @@ -17,7 +17,7 @@ public ReadTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromRootFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -31,7 +31,7 @@ public async Task ReadAllTextFromRootFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -45,7 +45,7 @@ public async Task ReadAllTextFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllBytesFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -61,7 +61,7 @@ public async Task ReadAllBytesFromSubdirectoryFile(string storeName) } } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -80,7 +80,7 @@ public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string stor } - [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadFileFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -101,7 +101,7 @@ public async Task ReadFileFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -118,7 +118,7 @@ public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string store } - [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListThenReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 3d000ee..783755f 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -1,7 +1,9 @@ namespace GeekLearning.Storage.Integration.Test { + using GeekLearning.Storage.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; @@ -10,12 +12,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; + using GeekLearning.Storage.Azure.Configuration; + using GeekLearning.Storage.FileSystem.Configuration; public class StoresFixture : IDisposable { - private CloudStorageAccount cloudStorageAccount; - private CloudBlobContainer container; - public StoresFixture() { this.BasePath = PlatformServices.Default.Application.ApplicationBasePath; @@ -27,8 +28,8 @@ public StoresFixture() .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.development.json", optional: true) .AddInMemoryCollection(new KeyValuePair[] { - new KeyValuePair("Storage:Stores:azure:Parameters:Container", containerId), - new KeyValuePair("TestStore:Parameters:Container", containerId) + new KeyValuePair("Storage:Stores:Store3:FolderName", $"Store3-{containerId}"), + new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}") }); this.Configuration = builder.Build(); @@ -46,7 +47,10 @@ public StoresFixture() services.Configure(Configuration.GetSection("TestStore")); this.Services = services.BuildServiceProvider(); - + this.StorageOptions = this.Services.GetService>().Value; + this.AzureParsedOptions = this.Services.GetService>().Value; + this.FileSystemParsedOptions = this.Services.GetService>().Value; + this.TestStoreOptions = this.Services.GetService>().Value.ParseStoreOptions(this.FileSystemParsedOptions); ResetStores(); } @@ -58,6 +62,14 @@ public StoresFixture() public string FileSystemRootPath => Path.Combine(this.BasePath, "FileVault"); + public StorageOptions StorageOptions { get; } + + public AzureParsedOptions AzureParsedOptions { get; } + + public FileSystemParsedOptions FileSystemParsedOptions { get; } + + public FileSystemStoreOptions TestStoreOptions { get; } + public void Dispose() { this.DeleteRootResources(); @@ -65,9 +77,13 @@ public void Dispose() private void DeleteRootResources() { - if (this.container != null) + foreach (var parsedStoreKvp in this.AzureParsedOptions.ParsedStores) { - this.container.DeleteIfExistsAsync().Wait(); + var cloudStorageAccount = CloudStorageAccount.Parse(parsedStoreKvp.Value.ConnectionString); + var client = cloudStorageAccount.CreateCloudBlobClient(); + var container = client.GetContainerReference(parsedStoreKvp.Value.FolderName); + + container.DeleteIfExistsAsync().Wait(); } if (Directory.Exists(this.FileSystemRootPath)) @@ -79,53 +95,69 @@ private void DeleteRootResources() private void ResetStores() { this.DeleteRootResources(); - this.ResetAzureStore(); - this.ResetFileSystemStore(); + this.ResetAzureStores(); + this.ResetFileSystemStores(); } - private void ResetFileSystemStore() + private void ResetFileSystemStores() { if (!Directory.Exists(this.FileSystemRootPath)) { Directory.CreateDirectory(this.FileSystemRootPath); } - var directoryName = Configuration["Storage:Stores:filesystem:Parameters:Path"]; + foreach (var parsedStoreKvp in this.FileSystemParsedOptions.ParsedStores) + { + ResetFileSystemStore(parsedStoreKvp.Key, parsedStoreKvp.Value.AbsolutePath); + } + + ResetFileSystemStore(this.TestStoreOptions.Name, this.TestStoreOptions.AbsolutePath); + } + + private void ResetFileSystemStore(string storeName, string absolutePath) + { var process = Process.Start(new ProcessStartInfo("robocopy.exe") { - Arguments = $"\"{Path.Combine(this.BasePath, "SampleDirectory")}\" \"{Path.Combine(this.FileSystemRootPath, directoryName)}\" /MIR" + Arguments = $"\"{Path.Combine(this.BasePath, "SampleDirectory")}\" \"{absolutePath}\" /MIR" }); if (!process.WaitForExit(30000)) { - throw new TimeoutException("File system store was not reset properly"); + process.Kill(); + throw new TimeoutException($"FileSystem Store '{storeName}' was not reset properly."); } } - private void ResetAzureStore() + private void ResetAzureStores() { var azCopy = Path.Combine( Environment.ExpandEnvironmentVariables(Configuration["AzCopyPath"]), "AzCopy.exe"); - cloudStorageAccount = CloudStorageAccount.Parse(Configuration["Storage:Stores:azure:Parameters:ConnectionString"]); - var key = cloudStorageAccount.Credentials.ExportBase64EncodedKey(); - var containerName = Configuration["Storage:Stores:azure:Parameters:Container"]; - var dest = cloudStorageAccount.BlobStorageUri.PrimaryUri.ToString() + containerName; + foreach (var parsedStoreKvp in this.AzureParsedOptions.ParsedStores) + { + var cloudStorageAccount = CloudStorageAccount.Parse(parsedStoreKvp.Value.ConnectionString); + var cloudStoragekey = cloudStorageAccount.Credentials.ExportBase64EncodedKey(); + var containerName = parsedStoreKvp.Value.FolderName; - var client = cloudStorageAccount.CreateCloudBlobClient(); + var dest = cloudStorageAccount.BlobStorageUri.PrimaryUri.ToString() + containerName; - this.container = client.GetContainerReference(containerName); - this.container.CreateAsync().Wait(); + var client = cloudStorageAccount.CreateCloudBlobClient(); - var process = Process.Start(new ProcessStartInfo(azCopy) - { - Arguments = $"/Source:\"{Path.Combine(this.BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{key} /S" - }); + var container = client.GetContainerReference(containerName); + container.CreateIfNotExistsAsync().Wait(); - if (!process.WaitForExit(30000)) - { - throw new TimeoutException("Azure store was not reset properly"); + var arguments = $"/Source:\"{Path.Combine(this.BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{cloudStoragekey} /S /y"; + var process = Process.Start(new ProcessStartInfo(azCopy) + { + Arguments = arguments + }); + + if (!process.WaitForExit(30000)) + { + process.Kill(); + throw new TimeoutException($"Azure Store '{parsedStoreKvp.Key}' was not reset properly."); + } } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index f4d4e7b..47f0cc9 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,11 +1,23 @@ namespace GeekLearning.Storage.Integration.Test { - using Microsoft.Extensions.Configuration; + using GeekLearning.Storage.Configuration; - public class TestStore : IStorageStoreOptions + public class TestStore : IStoreOptions { - public string Provider { get; set; } + public TestStore() + { + this.Name = "TestStore"; + this.ProviderType = "FileSystem"; + } - public IConfigurationSection Parameters { get; set; } + public string ProviderName { get; set; } + + public string ProviderType { get; set; } + + public AccessLevel AccessLevel { get; set; } + + public string FolderName { get; set; } + + public string Name { get; set; } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 4d5124e..2b2e7a5 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -20,7 +20,7 @@ public UpdateTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(WriteAllText)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task WriteAllText(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task WriteAllText(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ETagShouldBeTheSameWithSameContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -51,7 +51,7 @@ public async Task ETagShouldBeTheSameWithSameContent(string storeName) Assert.Equal(savedReference.Properties.ETag, readReference.Properties.ETag); } - [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -67,7 +67,7 @@ public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) Assert.NotEqual(savedReference.Properties.ETag, updatedReference.Properties.ETag); } - [Theory(DisplayName = nameof(SaveStream)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task SaveStream(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -83,7 +83,7 @@ public async Task SaveStream(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task AddMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -107,7 +107,7 @@ public async Task AddMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task SaveMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("azure"), InlineData("filesystem")] + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] public async Task ListMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index 2c8e21c..b3e65e3 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -1,26 +1,59 @@ { + "ConnectionStrings": { + "ConnectionStringFromAppSettings": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "AzCopyPath": "%ProgramFiles(x86)%\\Microsoft SDKs\\Azure\\AzCopy", + "Storage": { + + "Providers": { + "FirstAzure": { + "Type": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "AnotherAzure": { + "Type": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" + }, + "FirstFileSystem": { + "Type": "FileSystem" + }, + "AnotherFileSystem": { + "Type": "FileSystem", + "RootPath": ".." + } + }, + "Stores": { - "filesystem": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates" - } + "Store1": { + "ProviderName": "FirstFileSystem" + }, + "Store2": { + "ProviderName": "FirstFileSystem", + "AccessLevel": "Public", + "FolderName": "AnotherPath" }, - "azure": { - "Provider": "Azure", - "Parameters": { - "ConnectionString": "YourConnectionString", - "Container": "templates" - } + "Store3": { + "ProviderName": "FirstAzure", + "AccessLevel": "Private" + }, + "Store4": { + "ProviderType": "Azure", + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + } + }, + + "ScopedStores": { + "ScopedStore1": { + "ProviderName": "AnotherFileSystem", + "FolderNameFormat": "AnotherPath-{0}" + }, + "ScopedStore2": { + "ProviderName": "AnotherAzure", + "AccessLevel": "Confidential", + "FolderNameFormat": "AnotherPath-{0}" } - } - }, - "TestStore": { - "Provider": "FileSystem", - "Parameters": { - "Path": "Templates" } } } From 989f7bed0c7a5b3f77b4b822370ecc58911359c7 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 16:23:12 +0200 Subject: [PATCH 04/21] ConnectionStringName parameter mapping --- .../Configuration/AzureParsedOptions.cs | 36 ++++++++++++++++--- .../Configuration/FileSystemParsedOptions.cs | 15 ++++++-- .../Configuration/ConfigurationExtensions.cs | 11 +++++- .../Configuration/IParsedOptions.cs | 4 +++ .../Configuration/StorageOptions.cs | 16 ++++++--- .../Internal/ConfigureProviderOptions.cs | 7 ++++ .../StorageServiceCollectionExtensions.cs | 28 +++++++++++++-- .../DeleteTests.cs | 2 +- .../ListTests.cs | 14 ++++---- .../ReadTests.cs | 14 ++++---- .../StoresFixture.cs | 2 +- .../UpdateTests.cs | 14 ++++---- .../appsettings.json | 7 ++++ 13 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs index 251f5bf..cd97a62 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureParsedOptions.cs @@ -7,16 +7,47 @@ public class AzureParsedOptions : IParsedOptions AzureStorageProvider.ProviderName; + public IReadOnlyDictionary ConnectionStrings { get; set; } + public IReadOnlyDictionary ParsedProviderInstances { get; set; } public IReadOnlyDictionary ParsedStores { get; set; } public IReadOnlyDictionary ParsedScopedStores { get; set; } + public void BindProviderInstanceOptions(AzureProviderInstanceOptions providerInstanceOptions) + { + if (!string.IsNullOrEmpty(providerInstanceOptions.ConnectionStringName) + && string.IsNullOrEmpty(providerInstanceOptions.ConnectionString)) + { + if (!this.ConnectionStrings.ContainsKey(providerInstanceOptions.ConnectionStringName)) + { + throw new Exceptions.BadProviderConfiguration( + providerInstanceOptions.Name, + $"The ConnectionString '{providerInstanceOptions.ConnectionStringName}' cannot be found. Did you call AddStorage with the ConfigurationRoot?"); + } + + providerInstanceOptions.ConnectionString = this.ConnectionStrings[providerInstanceOptions.ConnectionStringName]; + } + } + public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstanceOptions providerInstanceOptions = null) { storeOptions.FolderName = storeOptions.FolderName.ToLowerInvariant(); + if (!string.IsNullOrEmpty(storeOptions.ConnectionStringName) + && string.IsNullOrEmpty(storeOptions.ConnectionString)) + { + if (!this.ConnectionStrings.ContainsKey(storeOptions.ConnectionStringName)) + { + throw new Exceptions.BadStoreConfiguration( + storeOptions.Name, + $"The ConnectionString '{storeOptions.ConnectionStringName}' cannot be found. Did you call AddStorage with the ConfigurationRoot?"); + } + + storeOptions.ConnectionString = this.ConnectionStrings[storeOptions.ConnectionStringName]; + } + if (providerInstanceOptions == null || storeOptions.ProviderName != providerInstanceOptions.Name) { @@ -27,11 +58,6 @@ public void BindStoreOptions(AzureStoreOptions storeOptions, AzureProviderInstan { storeOptions.ConnectionString = providerInstanceOptions.ConnectionString; } - - if (string.IsNullOrEmpty(storeOptions.ConnectionStringName)) - { - storeOptions.ConnectionStringName = providerInstanceOptions.ConnectionStringName; - } } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs index c98956e..91d8253 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -7,7 +7,7 @@ public class FileSystemParsedOptions : IParsedOptions FileSystemStorageProvider.ProviderName; - public string RootPath { get; set; } + public IReadOnlyDictionary ConnectionStrings { get; set; } public IReadOnlyDictionary ParsedProviderInstances { get; set; } @@ -15,13 +15,22 @@ public class FileSystemParsedOptions : IParsedOptions ParsedScopedStores { get; set; } + public string RootPath { get; set; } + + public void BindProviderInstanceOptions(FileSystemProviderInstanceOptions providerInstanceOptions) + { + if (string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + { + providerInstanceOptions.RootPath = this.RootPath; + } + } + public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) { if (string.IsNullOrEmpty(storeOptions.RootPath)) { if (providerInstanceOptions != null - && storeOptions.ProviderName == providerInstanceOptions.Name - && !string.IsNullOrEmpty(providerInstanceOptions.RootPath)) + && storeOptions.ProviderName == providerInstanceOptions.Name) { storeOptions.RootPath = providerInstanceOptions.RootPath; } diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs index a740d42..1408083 100644 --- a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -6,7 +6,7 @@ public static class ConfigurationExtensions { - public static IReadOnlyDictionary Parse(this Dictionary unparsedConfiguration) + public static IReadOnlyDictionary Parse(this IReadOnlyDictionary unparsedConfiguration) where TOptions : class, INamedElementOptions, new() { if (unparsedConfiguration == null) @@ -45,6 +45,15 @@ public static IStoreOptions GetStoreConfiguration(this TInstanceOptions parsedProviderInstance, TParsedOptions options) + where TParsedOptions : class, IParsedOptions + where TInstanceOptions : class, IProviderInstanceOptions, new() + where TStoreOptions : class, IStoreOptions, new() + where TScopedStoreOptions : class, IScopedStoreOptions, new() + { + options.BindProviderInstanceOptions(parsedProviderInstance); + } + public static void Compute(this TStoreOptions parsedStore, TParsedOptions options) where TParsedOptions : class, IParsedOptions where TInstanceOptions : class, IProviderInstanceOptions, new() diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs index 833187c..cc7d18c 100644 --- a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -9,12 +9,16 @@ public interface IParsedOptions ConnectionStrings { get; set; } + IReadOnlyDictionary ParsedProviderInstances { get; set; } IReadOnlyDictionary ParsedStores { get; set; } IReadOnlyDictionary ParsedScopedStores { get; set; } + void BindProviderInstanceOptions(TInstanceOptions providerInstanceOptions); + void BindStoreOptions(TStoreOptions storeOptions, TInstanceOptions providerInstanceOptions = null); } } diff --git a/src/GeekLearning.Storage/Configuration/StorageOptions.cs b/src/GeekLearning.Storage/Configuration/StorageOptions.cs index 821db5d..9d02e23 100644 --- a/src/GeekLearning.Storage/Configuration/StorageOptions.cs +++ b/src/GeekLearning.Storage/Configuration/StorageOptions.cs @@ -6,7 +6,7 @@ public class StorageOptions : IParsedOptions { - internal const string GlobalOptionsName = "Global"; + public const string DefaultConfigurationSectionName = "Storage"; private readonly Lazy> parsedProviderInstances; private readonly Lazy> parsedStores; @@ -22,13 +22,15 @@ public StorageOptions() () => this.ScopedStores.Parse()); } - public string Name => GlobalOptionsName; + public string Name => DefaultConfigurationSectionName; - public Dictionary Providers { get; set; } + public IReadOnlyDictionary Providers { get; set; } - public Dictionary Stores { get; set; } + public IReadOnlyDictionary Stores { get; set; } - public Dictionary ScopedStores { get; set; } + public IReadOnlyDictionary ScopedStores { get; set; } + + public IReadOnlyDictionary ConnectionStrings { get; set; } public IReadOnlyDictionary ParsedProviderInstances { get => this.parsedProviderInstances.Value; set { } } @@ -36,6 +38,10 @@ public StorageOptions() public IReadOnlyDictionary ParsedScopedStores { get => this.parsedScopedStores.Value; set { } } + public void BindProviderInstanceOptions(ProviderInstanceOptions providerInstanceOptions) + { + } + public void BindStoreOptions(StoreOptions storeOptions, ProviderInstanceOptions providerInstanceOptions) { } diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index 2931fa7..1cc2e20 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -24,10 +24,17 @@ public void Configure(TParsedOptions options) return; } + options.ConnectionStrings = this.storageOptions.ConnectionStrings; + options.ParsedProviderInstances = this.storageOptions.Providers.Parse() .Where(kvp => kvp.Value.Type == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + foreach (var parsedProviderInstance in options.ParsedProviderInstances) + { + parsedProviderInstance.Value.Compute(options); + } + var parsedStores = this.storageOptions.Stores.Parse(); var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); diff --git a/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs index e08e39c..073d149 100644 --- a/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs +++ b/src/GeekLearning.Storage/StorageServiceCollectionExtensions.cs @@ -4,6 +4,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using System.Collections.Generic; + using System.Linq; public static class StorageServiceCollectionExtensions { @@ -14,10 +16,32 @@ public static IServiceCollection AddStorage(this IServiceCollection services) return services; } - public static IServiceCollection AddStorage(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddStorage(this IServiceCollection services, IConfigurationSection configurationSection) { return services - .Configure(configuration) + .Configure(configurationSection) + .AddStorage(); + } + + public static IServiceCollection AddStorage(this IServiceCollection services, IConfigurationRoot configurationRoot) + { + return services + .Configure(configurationRoot.GetSection(StorageOptions.DefaultConfigurationSectionName)) + .Configure(storageOptions => + { + var connectionStrings = new Dictionary(); + ConfigurationBinder.Bind(configurationRoot.GetSection("ConnectionStrings"), connectionStrings); + + if (storageOptions.ConnectionStrings != null) + { + foreach (var existingConnectionString in storageOptions.ConnectionStrings) + { + connectionStrings[existingConnectionString.Key] = existingConnectionString.Value; + } + } + + storageOptions.ConnectionStrings = connectionStrings; + }) .AddStorage(); } } diff --git a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs index 2fba5d4..da582bb 100644 --- a/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/DeleteTests.cs @@ -16,7 +16,7 @@ public DeleteTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(Delete)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task Delete(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs index 4275408..99b6615 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ListTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ListTests.cs @@ -17,7 +17,7 @@ public ListTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListRootFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListRootFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task ListRootFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListEmptyPathFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -55,7 +55,7 @@ public async Task ListEmptyPathFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListSubDirectoryFiles(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -74,7 +74,7 @@ public async Task ListSubDirectoryFiles(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -93,7 +93,7 @@ public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ExtensionGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ExtensionGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -112,7 +112,7 @@ public async Task ExtensionGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(FileNameGlobbing)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task FileNameGlobbing(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task FileNameGlobbing(string storeName) Assert.Empty(unexpectedFiles); } - [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(FileNameGlobbingAtRoot)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task FileNameGlobbingAtRoot(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs index 56e0518..ed607fd 100644 --- a/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/ReadTests.cs @@ -17,7 +17,7 @@ public ReadTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromRootFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -31,7 +31,7 @@ public async Task ReadAllTextFromRootFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -45,7 +45,7 @@ public async Task ReadAllTextFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllBytesFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -61,7 +61,7 @@ public async Task ReadAllBytesFromSubdirectoryFile(string storeName) } } - [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllBytesFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -80,7 +80,7 @@ public async Task ReadAllBytesFromSubdirectoryFileUsingFileReference(string stor } - [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadFileFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -101,7 +101,7 @@ public async Task ReadFileFromSubdirectoryFile(string storeName) Assert.Equal(expectedText, actualText); } - [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ReadAllTextFromSubdirectoryFileUsingFileReference)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -118,7 +118,7 @@ public async Task ReadAllTextFromSubdirectoryFileUsingFileReference(string store } - [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListThenReadAllTextFromSubdirectoryFile)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListThenReadAllTextFromSubdirectoryFile(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 783755f..689efb2 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -38,7 +38,7 @@ public StoresFixture() services.AddOptions(); - services.AddStorage() + services.AddStorage(Configuration) .AddAzureStorage() .AddFileSystemStorage(this.FileSystemRootPath) .AddFileSystemExtendedProperties(); diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 2b2e7a5..50958cb 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -20,7 +20,7 @@ public UpdateTests(StoresFixture fixture) this.storeFixture = fixture; } - [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(WriteAllText)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task WriteAllText(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -36,7 +36,7 @@ public async Task WriteAllText(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ETagShouldBeTheSameWithSameContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ETagShouldBeTheSameWithSameContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -51,7 +51,7 @@ public async Task ETagShouldBeTheSameWithSameContent(string storeName) Assert.Equal(savedReference.Properties.ETag, readReference.Properties.ETag); } - [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ETagShouldBeDifferentWithDifferentContent)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -67,7 +67,7 @@ public async Task ETagShouldBeDifferentWithDifferentContent(string storeName) Assert.NotEqual(savedReference.Properties.ETag, updatedReference.Properties.ETag); } - [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(SaveStream)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task SaveStream(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -83,7 +83,7 @@ public async Task SaveStream(string storeName) Assert.Equal(textToWrite, readFromWrittenFile); } - [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(AddMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task AddMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -107,7 +107,7 @@ public async Task AddMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(SaveMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task SaveMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); @@ -131,7 +131,7 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } - [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4")] + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListMetatadaRoundtrip(string storeName) { var storageFactory = this.storeFixture.Services.GetRequiredService(); diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index b3e65e3..c887a81 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -41,6 +41,13 @@ "Store4": { "ProviderType": "Azure", "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net" + }, + "Store5": { + "ProviderName": "AnotherAzure" + }, + "Store6": { + "ProviderType": "Azure", + "ConnectionStringName": "ConnectionStringFromAppSettings" } }, From 1ed9fc6f67648b5ebb1e67b12680ac9ef13f166a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 16:57:18 +0200 Subject: [PATCH 05/21] Fix tests (randomize Azure container names) --- tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index 689efb2..bfcf5a7 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -29,7 +29,9 @@ public StoresFixture() .AddJsonFile($"appsettings.development.json", optional: true) .AddInMemoryCollection(new KeyValuePair[] { new KeyValuePair("Storage:Stores:Store3:FolderName", $"Store3-{containerId}"), - new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}") + new KeyValuePair("Storage:Stores:Store4:FolderName", $"Store4-{containerId}"), + new KeyValuePair("Storage:Stores:Store5:FolderName", $"Store5-{containerId}"), + new KeyValuePair("Storage:Stores:Store6:FolderName", $"Store6-{containerId}"), }); this.Configuration = builder.Build(); From 308a3ebf269be24a3c811a41603d7ea3ad3f9700 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 18:32:19 +0200 Subject: [PATCH 06/21] ScopedStores and Init stores --- .../AzureStorageExtensions.cs | 9 ---- .../AzureStorageProvider.cs | 2 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 5 +++ .../Configuration/AzureScopedStoreOptions.cs | 3 +- .../Configuration/FileSystemParsedOptions.cs | 8 ++++ .../FileSystemScopedStoreOptions.cs | 3 +- .../FileSystemStorageExtensions.cs | 15 ++----- .../FileSystemStorageProvider.cs | 2 +- .../FileSystemStore.cs | 10 +++++ .../Configuration/ConfigurationExtensions.cs | 23 ++++++++--- .../Configuration/IParsedOptions.cs | 2 +- .../Exceptions/BadScopedStoreConfiguration.cs | 22 ++++++++++ src/GeekLearning.Storage/IStorageFactory.cs | 2 + src/GeekLearning.Storage/IStorageProvider.cs | 2 + src/GeekLearning.Storage/IStore.cs | 2 + .../Internal/ConfigureProviderOptions.cs | 10 +++-- .../Internal/GenericStoreProxy.cs | 24 ++++++----- .../Internal/StorageFactory.cs | 5 +++ .../Internal/StorageProviderBase.cs | 25 +++++++++-- .../ScopedStoresTests.cs | 41 +++++++++++++++++++ .../StoresFixture.cs | 7 ++++ .../appsettings.json | 2 +- 22 files changed, 175 insertions(+), 49 deletions(-) create mode 100644 src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs create mode 100644 tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs diff --git a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs index cc70c51..3381d6c 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageExtensions.cs @@ -2,9 +2,7 @@ { using Azure; using GeekLearning.Storage.Azure.Configuration; - using GeekLearning.Storage.Configuration; using GeekLearning.Storage.Internal; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -18,13 +16,6 @@ public static IServiceCollection AddAzureStorage(this IServiceCollection service .AddAzureStorageServices(); } - public static IServiceCollection AddAzureStorage(this IServiceCollection services, IConfiguration configuration) - { - return services - .Configure(configuration) - .AddAzureStorageServices(); - } - private static IServiceCollection AddAzureStorageServices(this IServiceCollection services) { services.TryAddEnumerable(ServiceDescriptor.Transient()); diff --git a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs index 87aca75..c04dc42 100644 --- a/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs +++ b/src/GeekLearning.Storage.Azure/AzureStorageProvider.cs @@ -16,7 +16,7 @@ public AzureStorageProvider(IOptions options) public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, AzureStoreOptions storeOptions) + protected override IStore BuildStoreInternal(string storeName, AzureStoreOptions storeOptions) { return new AzureStore(storeOptions); } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index f09da9d..7cf8cd5 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -37,6 +37,11 @@ public AzureStore(AzureStoreOptions storeOptions) public string Name => this.storeOptions.Name; + public Task InitAsync() + { + return this.container.Value.CreateIfNotExistsAsync(); + } + public async Task ListAsync(string path, bool recursive, bool withMetadata) { if (string.IsNullOrWhiteSpace(path)) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs index 4c74736..c43eded 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureScopedStoreOptions.cs @@ -2,7 +2,8 @@ { using GeekLearning.Storage.Configuration; - public class AzureScopedStoreOptions : ScopedStoreOptions + public class AzureScopedStoreOptions : AzureStoreOptions, IScopedStoreOptions { + public string FolderNameFormat { get; set; } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs index 91d8253..7294bff 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemParsedOptions.cs @@ -2,6 +2,7 @@ { using GeekLearning.Storage.Configuration; using System.Collections.Generic; + using System.IO; public class FileSystemParsedOptions : IParsedOptions { @@ -23,6 +24,13 @@ public void BindProviderInstanceOptions(FileSystemProviderInstanceOptions provid { providerInstanceOptions.RootPath = this.RootPath; } + else + { + if (!Path.IsPathRooted(providerInstanceOptions.RootPath)) + { + providerInstanceOptions.RootPath = Path.Combine(this.RootPath, providerInstanceOptions.RootPath); + } + } } public void BindStoreOptions(FileSystemStoreOptions storeOptions, FileSystemProviderInstanceOptions providerInstanceOptions = null) diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs index 035cc73..c417df2 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemScopedStoreOptions.cs @@ -2,7 +2,8 @@ { using GeekLearning.Storage.Configuration; - public class FileSystemScopedStoreOptions : ScopedStoreOptions + public class FileSystemScopedStoreOptions : FileSystemStoreOptions, IScopedStoreOptions { + public string FolderNameFormat { get; set; } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs index 0b85c9b..73825ca 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageExtensions.cs @@ -3,7 +3,6 @@ using FileSystem; using GeekLearning.Storage.FileSystem.Configuration; using GeekLearning.Storage.Internal; - using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -14,25 +13,19 @@ public static IServiceCollection AddFileSystemStorage(this IServiceCollection se { return services .Configure(options => options.RootPath = rootPath) - .AddFileSystemStorage(); - } - - public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) - { - return services - .AddSingleton, ConfigureProviderOptions>() .AddFileSystemStorageServices(); } - public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) { - return services - .Configure(configuration) + return services + .Configure(options => options.RootPath = System.IO.Directory.GetCurrentDirectory()) .AddFileSystemStorageServices(); } private static IServiceCollection AddFileSystemStorageServices(this IServiceCollection services) { + services.AddSingleton, ConfigureProviderOptions>(); services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 3f5b26f..ae9916d 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -20,7 +20,7 @@ public FileSystemStorageProvider(IOptions options, IPub public override string Name => ProviderName; - protected override IStore BuildStore(string storeName, FileSystemStoreOptions storeOptions) + protected override IStore BuildStoreInternal(string storeName, FileSystemStoreOptions storeOptions) { return new FileSystemStore( storeOptions, diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 5983c96..d08b8ed 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -40,6 +40,16 @@ public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider p internal string AbsolutePath => storeOptions.AbsolutePath; + public Task InitAsync() + { + if (!Directory.Exists(this.AbsolutePath)) + { + Directory.CreateDirectory(this.AbsolutePath); + } + + return Task.FromResult(0); + } + public async Task ListAsync(string path, bool recursive, bool withMetadata) { var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.AbsolutePath : Path.Combine(this.AbsolutePath, path); diff --git a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs index 1408083..79f487f 100644 --- a/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs +++ b/src/GeekLearning.Storage/Configuration/ConfigurationExtensions.cs @@ -20,10 +20,10 @@ public static IReadOnlyDictionary Parse(this IReadOn kvp => BindOptions(kvp)); } - public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + public static TStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) where TInstanceOptions : class, IProviderInstanceOptions where TStoreOptions : class, IStoreOptions - where TScopedStoreOptions : class, IScopedStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { parsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions); if (storeOptions != null) @@ -31,6 +31,19 @@ public static IStoreOptions GetStoreConfiguration(this IParsedOptions parsedOptions, string storeName, bool throwIfNotFound = true) + where TInstanceOptions : class, IProviderInstanceOptions + where TStoreOptions : class, IStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions + { parsedOptions.ParsedScopedStores.TryGetValue(storeName, out var scopedStoreOptions); if (scopedStoreOptions != null) { @@ -49,7 +62,7 @@ public static void Compute where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { options.BindProviderInstanceOptions(parsedProviderInstance); } @@ -58,7 +71,7 @@ public static void Compute where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { if (string.IsNullOrEmpty(parsedStore.FolderName)) { @@ -84,7 +97,7 @@ public static TStoreOptions ParseStoreOptions, new() where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { if (!(storeOptions is TStoreOptions parsedStoreOptions)) { diff --git a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs index cc7d18c..942f935 100644 --- a/src/GeekLearning.Storage/Configuration/IParsedOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IParsedOptions.cs @@ -5,7 +5,7 @@ public interface IParsedOptions where TInstanceOptions : class, IProviderInstanceOptions where TStoreOptions : class, IStoreOptions - where TScopedStoreOptions : class, IScopedStoreOptions + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { string Name { get; } diff --git a/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs new file mode 100644 index 0000000..508bf3c --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/BadScopedStoreConfiguration.cs @@ -0,0 +1,22 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class BadScopedStoreConfiguration : Exception + { + public BadScopedStoreConfiguration(string storeName) + : base($"The scoped store '{storeName}' was not properly configured.") + { + } + + public BadScopedStoreConfiguration(string storeName, string details) + : base($"The scoped store '{storeName}' was not properly configured. {details}") + { + } + + public BadScopedStoreConfiguration(string storeName, string details, Exception innerException) + : base($"The scoped store '{storeName}' was not properly configured. {details}", innerException) + { + } + } +} diff --git a/src/GeekLearning.Storage/IStorageFactory.cs b/src/GeekLearning.Storage/IStorageFactory.cs index a63dd9a..48ccad4 100644 --- a/src/GeekLearning.Storage/IStorageFactory.cs +++ b/src/GeekLearning.Storage/IStorageFactory.cs @@ -8,6 +8,8 @@ public interface IStorageFactory IStore GetStore(string storeName); + IStore GetScopedStore(string storeName, params object[] args); + bool TryGetStore(string storeName, out IStore store); bool TryGetStore(string storeName, out IStore store, string provider); diff --git a/src/GeekLearning.Storage/IStorageProvider.cs b/src/GeekLearning.Storage/IStorageProvider.cs index 07d5c5d..353be9f 100644 --- a/src/GeekLearning.Storage/IStorageProvider.cs +++ b/src/GeekLearning.Storage/IStorageProvider.cs @@ -9,5 +9,7 @@ public interface IStorageProvider IStore BuildStore(string storeName); IStore BuildStore(string storeName, IStoreOptions storeOptions); + + IStore BuildScopedStore(string storeName, params object[] args); } } diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index 889d16f..eb869de 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -8,6 +8,8 @@ public interface IStore { string Name { get; } + Task InitAsync(); + Task ListAsync(string path, bool recursive, bool withMetadata); Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata); diff --git a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs index 1cc2e20..556b92e 100644 --- a/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs +++ b/src/GeekLearning.Storage/Internal/ConfigureProviderOptions.cs @@ -8,7 +8,7 @@ public class ConfigureProviderOptions where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions, new() { private readonly StorageOptions storageOptions; @@ -36,8 +36,6 @@ public void Configure(TParsedOptions options) } var parsedStores = this.storageOptions.Stores.Parse(); - var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); - foreach (var parsedStore in parsedStores) { parsedStore.Value.Compute(options); @@ -47,6 +45,12 @@ public void Configure(TParsedOptions options) .Where(kvp => kvp.Value.ProviderType == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + var parsedScopedStores = this.storageOptions.ScopedStores.Parse(); + foreach (var parsedScopedStore in parsedScopedStores) + { + parsedScopedStore.Value.Compute(options); + } + options.ParsedScopedStores = parsedScopedStores .Where(kvp => kvp.Value.ProviderType == options.Name) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 08dde70..8dd2508 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -21,26 +21,28 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) this.innerStore = factory.GetStore(nameof(TOptions), options.Value); } - public string Name => innerStore.Name; + public string Name => this.innerStore.Name; - public Task DeleteAsync(IPrivateFileReference file) => innerStore.DeleteAsync(file); + public Task InitAsync() => this.innerStore.InitAsync(); - public Task GetAsync(Uri file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); + public Task DeleteAsync(IPrivateFileReference file) => this.innerStore.DeleteAsync(file); - public Task GetAsync(IPrivateFileReference file, bool withMetadata) => innerStore.GetAsync(file, withMetadata); + public Task GetAsync(Uri file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, bool recursive, bool withMetadata) => innerStore.ListAsync(path, recursive, withMetadata); + public Task GetAsync(IPrivateFileReference file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => innerStore.ListAsync(path, searchPattern, recursive, withMetadata); + public Task ListAsync(string path, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, recursive, withMetadata); - public Task ReadAllBytesAsync(IPrivateFileReference file) => innerStore.ReadAllBytesAsync(file); + public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - public Task ReadAllTextAsync(IPrivateFileReference file) => innerStore.ReadAllTextAsync(file); + public Task ReadAllBytesAsync(IPrivateFileReference file) => this.innerStore.ReadAllBytesAsync(file); - public Task ReadAsync(IPrivateFileReference file) => innerStore.ReadAsync(file); + public Task ReadAllTextAsync(IPrivateFileReference file) => this.innerStore.ReadAllTextAsync(file); - public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); + public Task ReadAsync(IPrivateFileReference file) => this.innerStore.ReadAsync(file); - public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => innerStore.SaveAsync(data, file, contentType); + public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + + public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); } } diff --git a/src/GeekLearning.Storage/Internal/StorageFactory.cs b/src/GeekLearning.Storage/Internal/StorageFactory.cs index e43f178..8ff8cc4 100644 --- a/src/GeekLearning.Storage/Internal/StorageFactory.cs +++ b/src/GeekLearning.Storage/Internal/StorageFactory.cs @@ -26,6 +26,11 @@ public IStore GetStore(string storeName) return this.GetProvider(this.options.GetStoreConfiguration(storeName)).BuildStore(storeName); } + public IStore GetScopedStore(string storeName, params object[] args) + { + return this.GetProvider(this.options.GetScopedStoreConfiguration(storeName)).BuildScopedStore(storeName, args); + } + public bool TryGetStore(string storeName, out IStore store) { var configuration = this.options.GetStoreConfiguration(storeName, throwIfNotFound: false); diff --git a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs index bb967e6..96acdaa 100644 --- a/src/GeekLearning.Storage/Internal/StorageProviderBase.cs +++ b/src/GeekLearning.Storage/Internal/StorageProviderBase.cs @@ -1,5 +1,6 @@ namespace GeekLearning.Storage.Internal { + using System; using Configuration; using Microsoft.Extensions.Options; @@ -7,7 +8,7 @@ public abstract class StorageProviderBase, new() where TInstanceOptions : class, IProviderInstanceOptions, new() where TStoreOptions : class, IStoreOptions, new() - where TScopedStoreOptions : class, IScopedStoreOptions, new() + where TScopedStoreOptions : class, TStoreOptions, IScopedStoreOptions { protected readonly TParsedOptions options; @@ -20,7 +21,7 @@ public StorageProviderBase(IOptions options) public IStore BuildStore(string storeName) { - return this.BuildStore(storeName, this.options.GetStoreConfiguration(storeName)); + return this.BuildStoreInternal(storeName, this.options.GetStoreConfiguration(storeName)); } public IStore BuildStore(string storeName, IStoreOptions storeOptions) @@ -30,11 +31,27 @@ public IStore BuildStore(string storeName, IStoreOptions storeOptions) throw new Exceptions.BadStoreProviderException(this.Name, storeName); } - return this.BuildStore( + return this.BuildStoreInternal( storeName, storeOptions.ParseStoreOptions(options)); } - protected abstract IStore BuildStore(string storeName, TStoreOptions storeOptions); + public IStore BuildScopedStore(string storeName, params object[] args) + { + var scopedStoreOptions = this.options.GetScopedStoreConfiguration(storeName); + + try + { + scopedStoreOptions.FolderName = string.Format(scopedStoreOptions.FolderNameFormat, args); + } + catch (Exception ex) + { + throw new Exceptions.BadScopedStoreConfiguration(storeName, "Cannot format folder name. See InnerException for details.", ex); + } + + return this.BuildStoreInternal(storeName, scopedStoreOptions.ParseStoreOptions(options)); + } + + protected abstract IStore BuildStoreInternal(string storeName, TStoreOptions storeOptions); } } diff --git a/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs b/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs new file mode 100644 index 0000000..ea01d2b --- /dev/null +++ b/tests/GeekLearning.Storage.Integration.Test/ScopedStoresTests.cs @@ -0,0 +1,41 @@ +namespace GeekLearning.Storage.Integration.Test +{ + using Microsoft.Extensions.DependencyInjection; + using Storage; + using System; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + [Collection(nameof(IntegrationCollection))] + [Trait("Operation", "ScopedStores"), Trait("Kind", "Integration")] + public class ScopedStoresTests + { + private StoresFixture storeFixture; + + public ScopedStoresTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(ScopedStoreUpdate)), InlineData("ScopedStore1"), InlineData("ScopedStore2")] + public async Task ScopedStoreUpdate(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var formatArg = Guid.NewGuid(); + var store = storageFactory.GetScopedStore(storeName, formatArg); + + await store.InitAsync(); + + var textToWrite = "The answer is 42"; + var filePath = "Update/42.txt"; + + await store.SaveAsync(Encoding.UTF8.GetBytes(textToWrite), filePath, "text/plain"); + + var readFromWrittenFile = await store.ReadAllTextAsync(filePath); + + Assert.Equal(textToWrite, readFromWrittenFile); + } + } +} diff --git a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs index bfcf5a7..4017599 100644 --- a/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs +++ b/tests/GeekLearning.Storage.Integration.Test/StoresFixture.cs @@ -64,6 +64,8 @@ public StoresFixture() public string FileSystemRootPath => Path.Combine(this.BasePath, "FileVault"); + public string FileSystemSecondaryRootPath => Path.Combine(this.BasePath, "FileVault2"); + public StorageOptions StorageOptions { get; } public AzureParsedOptions AzureParsedOptions { get; } @@ -92,6 +94,11 @@ private void DeleteRootResources() { Directory.Delete(this.FileSystemRootPath, true); } + + if (Directory.Exists(this.FileSystemSecondaryRootPath)) + { + Directory.Delete(this.FileSystemSecondaryRootPath, true); + } } private void ResetStores() diff --git a/tests/GeekLearning.Storage.Integration.Test/appsettings.json b/tests/GeekLearning.Storage.Integration.Test/appsettings.json index c887a81..3876bad 100644 --- a/tests/GeekLearning.Storage.Integration.Test/appsettings.json +++ b/tests/GeekLearning.Storage.Integration.Test/appsettings.json @@ -21,7 +21,7 @@ }, "AnotherFileSystem": { "Type": "FileSystem", - "RootPath": ".." + "RootPath": "../FileVault2" } }, From cf5d398dafc7413382b2481447f9f16fb9baf52a Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 13 Apr 2017 18:43:37 +0200 Subject: [PATCH 07/21] Check new AccessLevel parameter in FileSystem.Server --- .../FileSystemStorageServerMiddleware.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs index e2bd947..a2a515a 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs +++ b/src/GeekLearning.Storage.FileSystem.Server/FileSystemStorageServerMiddleware.cs @@ -35,15 +35,13 @@ public async Task Invoke(HttpContext context) var storageFactory = context.RequestServices.GetRequiredService(); if (this.fileSystemParsedOptions.ParsedStores.TryGetValue(storeName, out var storeOptions) - && storeOptions.ProviderType == "FileSystem") + && storeOptions.ProviderType == FileSystemStorageProvider.ProviderName) { - string access; - // TODO: Fix options! - //if (!storeOptions.Parameters.TryGetValue("Access", out access) && access != "Public") - //{ - // context.Response.StatusCode = StatusCodes.Status403Forbidden; - // return; - //} + if (storeOptions.AccessLevel != Storage.Configuration.AccessLevel.Public) + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return; + } IStore store = storageFactory.GetStore(storeName, storeOptions); From e6a290eb170126491e6f68a8b14cb6a4571e98af Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 11:19:35 +0200 Subject: [PATCH 08/21] Update framework packages and fix sample project --- .../GeekLearning.Storage.BasicSample.csproj | 26 +++++----- .../appsettings.json | 49 ++----------------- .../GeekLearning.Storage.Azure.csproj | 8 +-- ...ystem.ExtendedProperties.FileSystem.csproj | 2 +- ...kLearning.Storage.FileSystem.Server.csproj | 6 +-- .../GeekLearning.Storage.FileSystem.csproj | 6 +-- .../GeekLearning.Storage.csproj | 8 +-- ...ekLearning.Storage.Integration.Test.csproj | 22 ++++----- 8 files changed, 42 insertions(+), 85 deletions(-) diff --git a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj index d9d597a..ceb2ee2 100644 --- a/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj +++ b/samples/GeekLearning.Storage.BasicSample/GeekLearning.Storage.BasicSample.csproj @@ -1,4 +1,4 @@ - + netcoreapp1.1 @@ -6,7 +6,7 @@ GeekLearning.Storage.BasicSample Exe GeekLearning.Storage.BasicSample - 1.1.1 + 1.1.2 $(PackageTargetFallback);portable-net45+win8 @@ -24,17 +24,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 4d8074f..614aef4 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -8,54 +8,11 @@ } }, "Storage": { - - "Providers": { - "FirstAzure": { - "Type": "Azure", - "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=default;AccountKey=;EndpointSuffix=core.windows.net" - }, - "AnotherAzure": { - "Type": "Azure", - "ConnectionStringName": "ConnectionStringFromAppSettings" - }, - "FirstFileSystem": { - "Type": "FileSystem", - "RootPath": "C:/First" - }, - "AnotherFileSystem": { - "Type": "FileSystem", - "RootPath": "D:/Another" - } - }, - "Stores": { - "Youpi1": { - "ProviderName": "FirstFileSystem" - }, - "Youpi2": { - "ProviderName": "FirstFileSystem", + "Templates": { + "ProviderType": "FileSystem", "AccessLevel": "Public", - "FolderName": "AnotherPath" - }, - "Youpi4": { - "ProviderName": "FirstAzure", - "AccessLevel": "Private" - }, - "Youpa2": { - "ProviderType": "Azure", - "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=excpetionaccount;AccountKey=;EndpointSuffix=core.windows.net" - } - }, - - "ScopedStores": { - "Youpi3": { - "ProviderName": "AnotherFileSystem", - "FolderNameFormat": "AnotherPath-{0}" - }, - "Youpa": { - "ProviderName": "AnotherAzure", - "AccessLevel": "Confidential", - "FolderNameFormat": "Youpa-{0}" + "FolderName": "Templates" } } } diff --git a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj index 9165231..05046db 100644 --- a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj +++ b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj index 4aac9ec..a49d417 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj index d82a11c..01b1a2a 100644 --- a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj +++ b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj index 24c0baa..bae7d7f 100644 --- a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj +++ b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index 4b03abb..2e70721 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj index 9dd8e42..2a5b84e 100644 --- a/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj +++ b/tests/GeekLearning.Storage.Integration.Test/GeekLearning.Storage.Integration.Test.csproj @@ -6,7 +6,7 @@ GeekLearning.Storage.Integration.Test true $(PackageTargetFallback);dotnet;portable-net45+win8 - 1.0.4 + 1.1.2 @@ -23,18 +23,18 @@ - + - - - + + + - - - - - - + + + + + + From 73f50c014c3ff79415c9548de3ddbdc7f09ada54 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 15:30:55 +0200 Subject: [PATCH 09/21] Validate options --- .../appsettings.json | 3 +- src/GeekLearning.Storage.Azure/AzureStore.cs | 31 +++++++------ .../Configuration/AzureStoreOptions.cs | 21 +++++++++ .../Configuration/FileSystemStoreOptions.cs | 21 +++++++++ .../FileSystemStore.cs | 15 +----- .../Configuration/IOptionError.cs | 9 ++++ .../Configuration/IStoreOptions.cs | 4 ++ .../Configuration/OptionError.cs | 9 ++++ .../Configuration/StoreOptions.cs | 46 +++++++++++++++++++ .../Exceptions/BadStoreConfiguration.cs | 10 ++++ .../TestStore.cs | 7 +++ 11 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 src/GeekLearning.Storage/Configuration/IOptionError.cs create mode 100644 src/GeekLearning.Storage/Configuration/OptionError.cs diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index 614aef4..22ffc9d 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -11,8 +11,7 @@ "Stores": { "Templates": { "ProviderType": "FileSystem", - "AccessLevel": "Public", - "FolderName": "Templates" + "AccessLevel": "Public" } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 7cf8cd5..0151860 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -18,19 +18,9 @@ public class AzureStore : IStore public AzureStore(AzureStoreOptions storeOptions) { - this.storeOptions = storeOptions; - - // TODO: Create Validate method in IStoreOptions - //if (string.IsNullOrWhiteSpace(connectionString)) - //{ - // throw new ArgumentNullException("connectionString"); - //} - - //if (string.IsNullOrWhiteSpace(containerName)) - //{ - // throw new ArgumentNullException("containerName"); - //} + storeOptions.Validate(); + this.storeOptions = storeOptions; this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); } @@ -39,7 +29,22 @@ public AzureStore(AzureStoreOptions storeOptions) public Task InitAsync() { - return this.container.Value.CreateIfNotExistsAsync(); + BlobContainerPublicAccessType accessType; + switch (this.storeOptions.AccessLevel) + { + case Storage.Configuration.AccessLevel.Public: + accessType = BlobContainerPublicAccessType.Container; + break; + case Storage.Configuration.AccessLevel.Confidential: + accessType = BlobContainerPublicAccessType.Blob; + break; + case Storage.Configuration.AccessLevel.Private: + default: + accessType = BlobContainerPublicAccessType.Off; + break; + } + + return this.container.Value.CreateIfNotExistsAsync(accessType, null, null); } public async Task ListAsync(string path, bool recursive, bool withMetadata) diff --git a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs index 4401fde..c841405 100644 --- a/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs +++ b/src/GeekLearning.Storage.Azure/Configuration/AzureStoreOptions.cs @@ -1,11 +1,32 @@ namespace GeekLearning.Storage.Azure.Configuration { using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + using System.Linq; public class AzureStoreOptions : StoreOptions { public string ConnectionString { get; set; } public string ConnectionStringName { get; set; } + + public override IEnumerable Validate(bool throwOnError = true) + { + var baseErrors = base.Validate(throwOnError); + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.ConnectionString)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.ConnectionString)); + } + + var finalErrors = baseErrors.Concat(optionErrors); + if (throwOnError && finalErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, finalErrors); + } + + return finalErrors; + } } } diff --git a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs index af93561..7b3ad91 100644 --- a/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs +++ b/src/GeekLearning.Storage.FileSystem/Configuration/FileSystemStoreOptions.cs @@ -2,6 +2,8 @@ { using GeekLearning.Storage.Configuration; using System.IO; + using System.Collections.Generic; + using System.Linq; public class FileSystemStoreOptions : StoreOptions { @@ -24,5 +26,24 @@ public string AbsolutePath return Path.Combine(this.RootPath, this.FolderName); } } + + public override IEnumerable Validate(bool throwOnError = true) + { + var baseErrors = base.Validate(throwOnError); + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.AbsolutePath)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.AbsolutePath)); + } + + var finalErrors = baseErrors.Concat(optionErrors); + if (throwOnError && finalErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, finalErrors); + } + + return finalErrors; + } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index d08b8ed..724cfd4 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -16,20 +16,7 @@ public class FileSystemStore : IStore public FileSystemStore(FileSystemStoreOptions storeOptions, IPublicUrlProvider publicUrlProvider, IExtendedPropertiesProvider extendedPropertiesProvider) { - // TODO: Implement Validate method on options - //if (string.IsNullOrEmpty(path)) - //{ - // throw new ArgumentNullException("path"); - //} - - //if (Path.IsPathRooted(path)) - //{ - // this.AbsolutePath = path; - //} - //else - //{ - // this.AbsolutePath = Path.Combine(rootPath, path); - //} + storeOptions.Validate(); this.storeOptions = storeOptions; this.publicUrlProvider = publicUrlProvider; diff --git a/src/GeekLearning.Storage/Configuration/IOptionError.cs b/src/GeekLearning.Storage/Configuration/IOptionError.cs new file mode 100644 index 0000000..8647dc6 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/IOptionError.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public interface IOptionError + { + string PropertyName { get; } + + string ErrorMessage { get; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs index c55478e..099b390 100644 --- a/src/GeekLearning.Storage/Configuration/IStoreOptions.cs +++ b/src/GeekLearning.Storage/Configuration/IStoreOptions.cs @@ -1,5 +1,7 @@ namespace GeekLearning.Storage.Configuration { + using System.Collections.Generic; + public interface IStoreOptions : INamedElementOptions { string ProviderName { get; set; } @@ -9,5 +11,7 @@ public interface IStoreOptions : INamedElementOptions AccessLevel AccessLevel { get; set; } string FolderName { get; set; } + + IEnumerable Validate(bool throwOnError = true); } } diff --git a/src/GeekLearning.Storage/Configuration/OptionError.cs b/src/GeekLearning.Storage/Configuration/OptionError.cs new file mode 100644 index 0000000..b8447c3 --- /dev/null +++ b/src/GeekLearning.Storage/Configuration/OptionError.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage.Configuration +{ + public class OptionError : IOptionError + { + public string PropertyName { get; set; } + + public string ErrorMessage { get; set; } + } +} diff --git a/src/GeekLearning.Storage/Configuration/StoreOptions.cs b/src/GeekLearning.Storage/Configuration/StoreOptions.cs index c200b76..66f292c 100644 --- a/src/GeekLearning.Storage/Configuration/StoreOptions.cs +++ b/src/GeekLearning.Storage/Configuration/StoreOptions.cs @@ -1,7 +1,13 @@ namespace GeekLearning.Storage.Configuration { + using System; + using System.Collections.Generic; + using System.Linq; + public class StoreOptions : IStoreOptions { + private const string MissingPropertyErrorMessage = "{0} should be defined."; + public string Name { get; set; } public string ProviderName { get; set; } @@ -11,5 +17,45 @@ public class StoreOptions : IStoreOptions public AccessLevel AccessLevel { get; set; } public string FolderName { get; set; } + + public virtual IEnumerable Validate(bool throwOnError = true) + { + var optionErrors = new List(); + + if (string.IsNullOrEmpty(this.Name)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.Name)); + } + + if (string.IsNullOrEmpty(this.ProviderName) && string.IsNullOrEmpty(this.ProviderType)) + { + optionErrors.Add(new OptionError + { + PropertyName = "Provider", + ErrorMessage = $"You should set either a {nameof(this.ProviderType)} or a {nameof(this.ProviderName)} for each Store." + }); + } + + if (string.IsNullOrEmpty(this.FolderName)) + { + this.PushMissingPropertyError(optionErrors, nameof(this.FolderName)); + } + + if (throwOnError && optionErrors.Any()) + { + throw new Exceptions.BadStoreConfiguration(this.Name, optionErrors); + } + + return optionErrors; + } + + protected void PushMissingPropertyError(List optionErrors, string propertyName) + { + optionErrors.Add(new OptionError + { + PropertyName = propertyName, + ErrorMessage = string.Format(MissingPropertyErrorMessage, propertyName) + }); + } } } diff --git a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs index 5ceb28e..caae3eb 100644 --- a/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs +++ b/src/GeekLearning.Storage/Exceptions/BadStoreConfiguration.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage.Exceptions { using System; + using System.Collections.Generic; + using System.Linq; public class BadStoreConfiguration : Exception { @@ -13,5 +15,13 @@ public BadStoreConfiguration(string storeName, string details) : base($"The store '{storeName}' was not properly configured. {details}") { } + + public BadStoreConfiguration(string storeName, IEnumerable errors) + : this(storeName, string.Join(" | ", errors.Select(e => e.ErrorMessage))) + { + this.Errors = errors; + } + + public IEnumerable Errors { get; } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs index 47f0cc9..ab5c465 100644 --- a/tests/GeekLearning.Storage.Integration.Test/TestStore.cs +++ b/tests/GeekLearning.Storage.Integration.Test/TestStore.cs @@ -1,6 +1,8 @@ namespace GeekLearning.Storage.Integration.Test { using GeekLearning.Storage.Configuration; + using System.Collections.Generic; + using System.Linq; public class TestStore : IStoreOptions { @@ -19,5 +21,10 @@ public TestStore() public string FolderName { get; set; } public string Name { get; set; } + + public IEnumerable Validate(bool throwOnError = true) + { + return Enumerable.Empty(); + } } } From bd662612b22e5dbfe6cb6c39bc0ebdbed4087921 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Fri, 19 May 2017 16:05:41 +0200 Subject: [PATCH 10/21] Update Readme.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8d5220..932882b 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,4 @@ We don't support for Amazon S3, but it is one of our high priority objective. You can head to our introduction [blog post](http://geeklearning.io/dotnet-core-storage-cloud-or-file-system-storage-made-easy/), or to the [wiki](https://github.com/geeklearningio/gl-dotnet-storage/wiki). + From f524cc67676a42775c65e7fd0f8be2596aac363e Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 23 May 2017 15:49:28 +0200 Subject: [PATCH 11/21] Shared Access abstractions --- README.md | 4 +--- src/GeekLearning.Storage.Azure/AzureStore.cs | 9 +++++++++ .../GeekLearning.Storage.csproj | 4 ++++ src/GeekLearning.Storage/IFileReference.cs | 2 ++ src/GeekLearning.Storage/ISharedAccessPolicy.cs | 13 +++++++++++++ src/GeekLearning.Storage/IStore.cs | 2 ++ .../SharedAccessPermissions.cs | 16 ++++++++++++++++ 7 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/GeekLearning.Storage/ISharedAccessPolicy.cs create mode 100644 src/GeekLearning.Storage/SharedAccessPermissions.cs diff --git a/README.md b/README.md index 932882b..71b2523 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet:%20Abstractions)](https://www.nuget.org/packages/GeekLearning.Storage/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=NuGet:%20FileSystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=NuGet:%20Azure%20Storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet)](https://www.nuget.org/packages/GeekLearning.Storage/) [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 0151860..55494ea 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -23,6 +23,15 @@ public AzureStore(AzureStoreOptions storeOptions) this.storeOptions = storeOptions; this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); + + + var policy = new SharedAccessBlobPolicy() + { + + }; + + this.container.Value.GetSharedAccessSignature() + } public string Name => this.storeOptions.Name; diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index 2e70721..d796858 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index b89f712..d9be0d0 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -24,5 +24,7 @@ public interface IFileReference : IPrivateFileReference Task GetExpirableUriAsync(); Task SavePropertiesAsync(); + + Task GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/ISharedAccessPolicy.cs b/src/GeekLearning.Storage/ISharedAccessPolicy.cs new file mode 100644 index 0000000..c701baf --- /dev/null +++ b/src/GeekLearning.Storage/ISharedAccessPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage +{ + using System; + + public interface ISharedAccessPolicy + { + DateTimeOffset? StartTime { get; } + + DateTimeOffset? ExpiryTime { get; } + + SharedAccessPermissions Permissions { get; } + } +} diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index eb869de..c7cf6f2 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -29,5 +29,7 @@ public interface IStore Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType); Task SaveAsync(Stream data, IPrivateFileReference file, string contentType); + + Task GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/SharedAccessPermissions.cs b/src/GeekLearning.Storage/SharedAccessPermissions.cs new file mode 100644 index 0000000..e06d14f --- /dev/null +++ b/src/GeekLearning.Storage/SharedAccessPermissions.cs @@ -0,0 +1,16 @@ +namespace GeekLearning.Storage +{ + using System; + + [Flags] + public enum SharedAccessPermissions + { + None = 0, + Read = 1, + Write = 2, + Delete = 4, + List = 8, + Add = 16, + Create = 32 + } +} From cfe91a9c238eb73ce24e7b7bed42fc5b4a3ce156 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 23 May 2017 16:39:04 +0200 Subject: [PATCH 12/21] Azure Metadata encoding --- .../Internal/AzureFileProperties.cs | 27 ++++++++++++++++++- .../Internal/AzureFileReference.cs | 9 +++---- .../UpdateTests.cs | 24 +++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs index 47a1614..8d02273 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs @@ -3,11 +3,15 @@ using Microsoft.WindowsAzure.Storage.Blob; using System; using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading.Tasks; public class AzureFileProperties : IFileProperties { private const string DefaultCacheControl = "max-age=300, must-revalidate"; private readonly ICloudBlob cloudBlob; + private readonly Dictionary decodedMetadata; public AzureFileProperties(ICloudBlob cloudBlob) { @@ -16,6 +20,15 @@ public AzureFileProperties(ICloudBlob cloudBlob) { this.cloudBlob.Properties.CacheControl = DefaultCacheControl; } + + if (this.cloudBlob.Metadata != null) + { + decodedMetadata = this.cloudBlob.Metadata.ToDictionary(m => m.Key, m => WebUtility.HtmlDecode(m.Value)); + } + else + { + decodedMetadata = new Dictionary(); + } } public DateTimeOffset? LastModified => this.cloudBlob.Properties.LastModified; @@ -36,6 +49,18 @@ public string CacheControl set { this.cloudBlob.Properties.CacheControl = value; } } - public IDictionary Metadata => this.cloudBlob.Metadata; + public IDictionary Metadata => this.decodedMetadata; + + internal async Task SaveAsync() + { + await this.cloudBlob.SetPropertiesAsync(); + + foreach (var meta in this.decodedMetadata) + { + this.cloudBlob.Metadata[meta.Key] = WebUtility.HtmlEncode(meta.Value); + } + + await this.cloudBlob.SetMetadataAsync(); + } } } diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index 65f6024..9a0bf70 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -8,13 +8,13 @@ public class AzureFileReference : IFileReference { - private Lazy propertiesLazy; + private Lazy propertiesLazy; public AzureFileReference(string path, ICloudBlob cloudBlob, bool withMetadata) { this.Path = path; this.CloudBlob = cloudBlob; - this.propertiesLazy = new Lazy(() => + this.propertiesLazy = new Lazy(() => { if (withMetadata && cloudBlob.Metadata != null && cloudBlob.Properties != null) { @@ -84,10 +84,9 @@ public async Task ReadAllBytesAsync() return (await this.ReadInMemoryAsync()).ToArray(); } - public async Task SavePropertiesAsync() + public Task SavePropertiesAsync() { - await this.CloudBlob.SetPropertiesAsync(); - await this.CloudBlob.SetMetadataAsync(); + return this.propertiesLazy.Value.SaveAsync(); } } } diff --git a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs index 50958cb..c82e0dd 100644 --- a/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs +++ b/tests/GeekLearning.Storage.Integration.Test/UpdateTests.cs @@ -131,6 +131,30 @@ public async Task SaveMetatadaRoundtrip(string storeName) Assert.Equal(id, actualId); } + [Theory(DisplayName = nameof(SaveEncodedMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] + public async Task SaveEncodedMetatadaRoundtrip(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var testFile = "Metadata/TextFile.txt"; + + var file = await store.GetAsync(testFile, withMetadata: true); + + var name = "ï"; + + file.Properties.Metadata["name"] = name; + + await file.SavePropertiesAsync(); + + file = await store.GetAsync(testFile, withMetadata: true); + + var actualName = file.Properties.Metadata["name"]; + + Assert.Equal(name, actualName); + } + [Theory(DisplayName = nameof(ListMetatadaRoundtrip)), InlineData("Store1"), InlineData("Store2"), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] public async Task ListMetatadaRoundtrip(string storeName) { From 9acc1e19bca7161914970b82f9cee4f6d3e8540d Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 23 May 2017 15:49:28 +0200 Subject: [PATCH 13/21] Shared Access abstractions --- README.md | 4 +--- src/GeekLearning.Storage.Azure/AzureStore.cs | 9 +++++++++ .../GeekLearning.Storage.csproj | 4 ++++ src/GeekLearning.Storage/IFileReference.cs | 2 ++ src/GeekLearning.Storage/ISharedAccessPolicy.cs | 13 +++++++++++++ src/GeekLearning.Storage/IStore.cs | 2 ++ .../SharedAccessPermissions.cs | 16 ++++++++++++++++ 7 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/GeekLearning.Storage/ISharedAccessPolicy.cs create mode 100644 src/GeekLearning.Storage/SharedAccessPermissions.cs diff --git a/README.md b/README.md index 932882b..71b2523 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet:%20Abstractions)](https://www.nuget.org/packages/GeekLearning.Storage/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.FileSystem.svg?style=flat-square&label=NuGet:%20FileSystem)](https://www.nuget.org/packages/GeekLearning.Storage.FileSystem/) -[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.Azure.svg?style=flat-square&label=NuGet:%20Azure%20Storage)](https://www.nuget.org/packages/GeekLearning.Storage.Azure/) +[![NuGet Version](http://img.shields.io/nuget/v/GeekLearning.Storage.svg?style=flat-square&label=NuGet)](https://www.nuget.org/packages/GeekLearning.Storage/) [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 0151860..55494ea 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -23,6 +23,15 @@ public AzureStore(AzureStoreOptions storeOptions) this.storeOptions = storeOptions; this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); + + + var policy = new SharedAccessBlobPolicy() + { + + }; + + this.container.Value.GetSharedAccessSignature() + } public string Name => this.storeOptions.Name; diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index 2e70721..d796858 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index b89f712..d9be0d0 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -24,5 +24,7 @@ public interface IFileReference : IPrivateFileReference Task GetExpirableUriAsync(); Task SavePropertiesAsync(); + + Task GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/ISharedAccessPolicy.cs b/src/GeekLearning.Storage/ISharedAccessPolicy.cs new file mode 100644 index 0000000..c701baf --- /dev/null +++ b/src/GeekLearning.Storage/ISharedAccessPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage +{ + using System; + + public interface ISharedAccessPolicy + { + DateTimeOffset? StartTime { get; } + + DateTimeOffset? ExpiryTime { get; } + + SharedAccessPermissions Permissions { get; } + } +} diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index eb869de..c7cf6f2 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -29,5 +29,7 @@ public interface IStore Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType); Task SaveAsync(Stream data, IPrivateFileReference file, string contentType); + + Task GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/SharedAccessPermissions.cs b/src/GeekLearning.Storage/SharedAccessPermissions.cs new file mode 100644 index 0000000..e06d14f --- /dev/null +++ b/src/GeekLearning.Storage/SharedAccessPermissions.cs @@ -0,0 +1,16 @@ +namespace GeekLearning.Storage +{ + using System; + + [Flags] + public enum SharedAccessPermissions + { + None = 0, + Read = 1, + Write = 2, + Delete = 4, + List = 8, + Add = 16, + Create = 32 + } +} From 48cd906a53854aaba227672dbdf6148e3633b2e5 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 30 May 2017 16:36:41 +0200 Subject: [PATCH 14/21] Secured Urls for Azure Storage --- src/GeekLearning.Storage.Azure/AzureStore.cs | 58 +++++++++++++--- .../Internal/AzureFileReference.cs | 17 +++-- .../FileSystemStore.cs | 5 ++ .../Internal/FileSystemFileReference.cs | 10 +-- src/GeekLearning.Storage/IFileReference.cs | 2 - src/GeekLearning.Storage/IStore.cs | 2 +- .../Internal/GenericStoreProxy.cs | 2 + .../SharedAccessPolicy.cs | 13 ++++ .../SharedAccessTests.cs | 68 +++++++++++++++++++ 9 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 src/GeekLearning.Storage/SharedAccessPolicy.cs create mode 100644 tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 55494ea..0554c2f 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -23,15 +23,6 @@ public AzureStore(AzureStoreOptions storeOptions) this.storeOptions = storeOptions; this.client = new Lazy(() => CloudStorageAccount.Parse(storeOptions.ConnectionString).CreateCloudBlobClient()); this.container = new Lazy(() => this.client.Value.GetContainerReference(storeOptions.FolderName)); - - - var policy = new SharedAccessBlobPolicy() - { - - }; - - this.container.Value.GetSharedAccessSignature() - } public string Name => this.storeOptions.Name; @@ -191,6 +182,55 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return reference; } + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + { + var adHocPolicy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = policy.StartTime, + SharedAccessExpiryTime = policy.ExpiryTime, + Permissions = FromGenericToAzure(policy.Permissions), + }; + + return Task.FromResult(this.container.Value.GetSharedAccessSignature(adHocPolicy)); + } + + internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermissions permissions) + { + var result = SharedAccessBlobPermissions.None; + + if (permissions.HasFlag(SharedAccessPermissions.Add)) + { + result |= SharedAccessBlobPermissions.Add; + } + + if (permissions.HasFlag(SharedAccessPermissions.Create)) + { + result |= SharedAccessBlobPermissions.Create; + } + + if (permissions.HasFlag(SharedAccessPermissions.Delete)) + { + result |= SharedAccessBlobPermissions.Delete; + } + + if (permissions.HasFlag(SharedAccessPermissions.List)) + { + result |= SharedAccessBlobPermissions.List; + } + + if (permissions.HasFlag(SharedAccessPermissions.Read)) + { + result |= SharedAccessBlobPermissions.Read; + } + + if (permissions.HasFlag(SharedAccessPermissions.Write)) + { + result |= SharedAccessBlobPermissions.Write; + } + + return result; + } + private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) { var azureFile = file as Internal.AzureFileReference; diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index 9a0bf70..01f60c8 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -61,11 +61,6 @@ public Task UpdateAsync(Stream stream) return this.CloudBlob.UploadFromStreamAsync(stream); } - public Task GetExpirableUriAsync() - { - throw new NotImplementedException(); - } - public async Task ReadToStreamAsync(Stream targetStream) { await this.CloudBlob.DownloadRangeToStreamAsync(targetStream, null, null); @@ -88,5 +83,17 @@ public Task SavePropertiesAsync() { return this.propertiesLazy.Value.SaveAsync(); } + + public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + { + var adHocPolicy = new SharedAccessBlobPolicy() + { + SharedAccessStartTime = policy.StartTime, + SharedAccessExpiryTime = policy.ExpiryTime, + Permissions = AzureStore.FromGenericToAzure(policy.Permissions), + }; + + return Task.FromResult(this.CloudBlob.GetSharedAccessSignature(adHocPolicy)); + } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 724cfd4..dfa731b 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -148,6 +148,11 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return fileReference; } + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + { + throw new NotSupportedException(); + } + private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) { var fileSystemFile = file as Internal.FileSystemFileReference; diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs index 6ed1d9a..71dab02 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -60,11 +60,6 @@ public Task DeleteAsync() return Task.FromResult(true); } - public Task GetExpirableUriAsync() - { - throw new NotImplementedException(); - } - public Task ReadAllBytesAsync() { return Task.FromResult(File.ReadAllBytes(this.FileSystemPath)); @@ -109,5 +104,10 @@ public Task SavePropertiesAsync() this, (this.Properties as FileSystemFileProperties).ExtendedProperties); } + + public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + { + throw new NotSupportedException(); + } } } diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index d9be0d0..2d344a0 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -21,8 +21,6 @@ public interface IFileReference : IPrivateFileReference Task UpdateAsync(Stream stream); - Task GetExpirableUriAsync(); - Task SavePropertiesAsync(); Task GetSharedAccessSignature(ISharedAccessPolicy policy); diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index c7cf6f2..009147f 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -30,6 +30,6 @@ public interface IStore Task SaveAsync(Stream data, IPrivateFileReference file, string contentType); - Task GetSharedAccessSignature(ISharedAccessPolicy policy); + Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 8dd2508..6c85671 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -44,5 +44,7 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + + public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) => this.innerStore.GetSharedAccessSignatureAsync(policy); } } diff --git a/src/GeekLearning.Storage/SharedAccessPolicy.cs b/src/GeekLearning.Storage/SharedAccessPolicy.cs new file mode 100644 index 0000000..5ad55b9 --- /dev/null +++ b/src/GeekLearning.Storage/SharedAccessPolicy.cs @@ -0,0 +1,13 @@ +namespace GeekLearning.Storage +{ + using System; + + public class SharedAccessPolicy : ISharedAccessPolicy + { + public DateTimeOffset? StartTime { get; set; } + + public DateTimeOffset? ExpiryTime { get; set; } + + public SharedAccessPermissions Permissions { get; set; } + } +} diff --git a/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs b/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs new file mode 100644 index 0000000..5a43bc2 --- /dev/null +++ b/tests/GeekLearning.Storage.Integration.Test/SharedAccessTests.cs @@ -0,0 +1,68 @@ +namespace GeekLearning.Storage.Integration.Test +{ + using Storage; + using Microsoft.Extensions.DependencyInjection; + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using Xunit; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Auth; + using Microsoft.WindowsAzure.Storage.Blob; + using Microsoft.Extensions.Options; + using GeekLearning.Storage.Azure.Configuration; + using System.Linq; + + [Collection(nameof(IntegrationCollection))] + [Trait("Operation", "SharedAccess"), Trait("Kind", "Integration")] + public class SharedAccessTests + { + private readonly StoresFixture storeFixture; + + public SharedAccessTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(StoreSharedAccess)), InlineData("Store3"), InlineData("Store4"), InlineData("Store5"), InlineData("Store6")] + public async Task StoreSharedAccess(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + var options = this.storeFixture.Services.GetRequiredService>(); + + var store = storageFactory.GetStore(storeName); + + options.Value.ParsedStores.TryGetValue(storeName, out var storeOptions); + + var sharedAccessSignature = await store.GetSharedAccessSignatureAsync(new SharedAccessPolicy + { + ExpiryTime = DateTime.UtcNow.AddHours(24), + Permissions = SharedAccessPermissions.List, + }); + + var account = CloudStorageAccount.Parse(storeOptions.ConnectionString); + + var accountSAS = new StorageCredentials(sharedAccessSignature); + var accountWithSAS = new CloudStorageAccount(accountSAS, account.Credentials.AccountName, endpointSuffix: null, useHttps: true); + var blobClientWithSAS = accountWithSAS.CreateCloudBlobClient(); + var containerWithSAS = blobClientWithSAS.GetContainerReference(storeOptions.FolderName); + + BlobContinuationToken continuationToken = null; + List results = new List(); + + do + { + var response = await containerWithSAS.ListBlobsSegmentedAsync(continuationToken); + continuationToken = response.ContinuationToken; + results.AddRange(response.Results); + } + while (continuationToken != null); + + var filesFromStore = await store.ListAsync(null, false, false); + + Assert.Equal(filesFromStore.Length, results.OfType().Count()); + } + } +} From 55fb6677b9b56f4ccf4c14130fd37962fabe6e92 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 30 May 2017 16:56:32 +0200 Subject: [PATCH 15/21] Generalized async return types --- .../Controllers/SampleController.cs | 4 +-- .../Controllers/ValuesController.cs | 4 +-- src/GeekLearning.Storage.Azure/AzureStore.cs | 29 +++++++++---------- .../GeekLearning.Storage.Azure.csproj | 2 +- .../Internal/AzureFileReference.cs | 12 ++++---- .../Internal/ExtendedPropertiesProvider.cs | 6 ++-- .../FileSystemStore.cs | 27 +++++++++-------- .../IExtendedPropertiesProvider.cs | 2 +- .../Internal/FileSystemFileReference.cs | 15 +++++----- .../GeekLearning.Storage.csproj | 1 + src/GeekLearning.Storage/IFileReference.cs | 8 ++--- src/GeekLearning.Storage/IStore.cs | 20 ++++++------- src/GeekLearning.Storage/IStoreExtensions.cs | 16 +++++----- .../Internal/GenericStoreProxy.cs | 20 ++++++------- 14 files changed, 82 insertions(+), 84 deletions(-) diff --git a/samples/GeekLearning.Storage.BasicSample/Controllers/SampleController.cs b/samples/GeekLearning.Storage.BasicSample/Controllers/SampleController.cs index 433bb5a..58c60fa 100644 --- a/samples/GeekLearning.Storage.BasicSample/Controllers/SampleController.cs +++ b/samples/GeekLearning.Storage.BasicSample/Controllers/SampleController.cs @@ -17,14 +17,14 @@ public SampleController(IStorageFactory storageFactory) } [HttpGet] - public async Task> Get() + public async ValueTask> Get() { var summaries = await this.sharedAssets.ListAsync("summaries", "*.txt", recursive: true, withMetadata: false); return summaries.Select(x => x.Path); } [HttpGet] - public async Task Get(string path) + public async ValueTask Get(string path) { var summary = await this.sharedAssets.GetAsync(path); return await summary.ReadAllTextAsync(); diff --git a/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs b/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs index affe249..1550312 100644 --- a/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs +++ b/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs @@ -16,13 +16,13 @@ public ValuesController(TemplatesStore templates) } [HttpGet] - public async Task> Get() + public async ValueTask> Get() { return new string[] { await templates.Store.ReadAllTextAsync("json.json"), "value2" }; } [HttpGet("files")] - public async Task> Get(int id) + public async ValueTask> Get(int id) { var files = await templates.Store.ListAsync(""); return files.Select(x => x.PublicUrl); diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 0554c2f..b345a9a 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -47,7 +47,7 @@ public Task InitAsync() return this.container.Value.CreateIfNotExistsAsync(accessType, null, null); } - public async Task ListAsync(string path, bool recursive, bool withMetadata) + public async ValueTask ListAsync(string path, bool recursive, bool withMetadata) { if (string.IsNullOrWhiteSpace(path)) { @@ -75,7 +75,7 @@ public async Task ListAsync(string path, bool recursive, bool return results.OfType().Select(blob => new Internal.AzureFileReference(blob, withMetadata: withMetadata)).ToArray(); } - public async Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) + public async ValueTask ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) { if (string.IsNullOrWhiteSpace(path)) { @@ -121,12 +121,12 @@ public async Task ListAsync(string path, string searchPattern, return filteredResults.Files.Select(x => pathMap[path + x.Path]).ToArray(); } - public async Task GetAsync(IPrivateFileReference file, bool withMetadata) + public async ValueTask GetAsync(IPrivateFileReference file, bool withMetadata) { return await this.InternalGetAsync(file, withMetadata); } - public async Task GetAsync(Uri uri, bool withMetadata) + public async ValueTask GetAsync(Uri uri, bool withMetadata) { return await this.InternalGetAsync(uri, withMetadata); } @@ -137,25 +137,25 @@ public async Task DeleteAsync(IPrivateFileReference file) await fileReference.DeleteAsync(); } - public async Task ReadAsync(IPrivateFileReference file) + public async ValueTask ReadAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadInMemoryAsync(); } - public async Task ReadAllBytesAsync(IPrivateFileReference file) + public async ValueTask ReadAllBytesAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadAllBytesAsync(); } - public async Task ReadAllTextAsync(IPrivateFileReference file) + public async ValueTask ReadAllTextAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadAllTextAsync(); } - public async Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) { using (var stream = new SyncMemoryStream(data, 0, data.Length)) { @@ -163,7 +163,7 @@ public async Task SaveAsync(byte[] data, IPrivateFileReference f } } - public async Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) { var blockBlob = this.container.Value.GetBlockBlobReference(file.Path); @@ -182,7 +182,7 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return reference; } - public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + public ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) { var adHocPolicy = new SharedAccessBlobPolicy() { @@ -191,7 +191,7 @@ public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) Permissions = FromGenericToAzure(policy.Permissions), }; - return Task.FromResult(this.container.Value.GetSharedAccessSignature(adHocPolicy)); + return new ValueTask(this.container.Value.GetSharedAccessSignature(adHocPolicy)); } internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermissions permissions) @@ -231,10 +231,9 @@ internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermi return result; } - private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) + private async ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) { - var azureFile = file as Internal.AzureFileReference; - if (azureFile != null) + if (file is Internal.AzureFileReference azureFile) { return azureFile; } @@ -242,7 +241,7 @@ internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermi return await this.InternalGetAsync(new Uri(file.Path, UriKind.Relative), withMetadata); } - private async Task InternalGetAsync(Uri uri, bool withMetadata) + private async ValueTask InternalGetAsync(Uri uri, bool withMetadata) { try { diff --git a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj index 05046db..3bb06fd 100644 --- a/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj +++ b/src/GeekLearning.Storage.Azure/GeekLearning.Storage.Azure.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index 01f60c8..eebbb59 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -43,12 +43,12 @@ public Task DeleteAsync() return this.CloudBlob.DeleteAsync(); } - public async Task ReadAsync() + public async ValueTask ReadAsync() { return await this.ReadInMemoryAsync(); } - public async Task ReadInMemoryAsync() + public async ValueTask ReadInMemoryAsync() { var memoryStream = new MemoryStream(); await this.CloudBlob.DownloadRangeToStreamAsync(memoryStream, null, null); @@ -66,7 +66,7 @@ public async Task ReadToStreamAsync(Stream targetStream) await this.CloudBlob.DownloadRangeToStreamAsync(targetStream, null, null); } - public async Task ReadAllTextAsync() + public async ValueTask ReadAllTextAsync() { using (var reader = new StreamReader(await this.CloudBlob.OpenReadAsync(AccessCondition.GenerateEmptyCondition(), new BlobRequestOptions(), new OperationContext()))) { @@ -74,7 +74,7 @@ public async Task ReadAllTextAsync() } } - public async Task ReadAllBytesAsync() + public async ValueTask ReadAllBytesAsync() { return (await this.ReadInMemoryAsync()).ToArray(); } @@ -84,7 +84,7 @@ public Task SavePropertiesAsync() return this.propertiesLazy.Value.SaveAsync(); } - public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + public ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy) { var adHocPolicy = new SharedAccessBlobPolicy() { @@ -93,7 +93,7 @@ public Task GetSharedAccessSignature(ISharedAccessPolicy policy) Permissions = AzureStore.FromGenericToAzure(policy.Permissions), }; - return Task.FromResult(this.CloudBlob.GetSharedAccessSignature(adHocPolicy)); + return new ValueTask(this.CloudBlob.GetSharedAccessSignature(adHocPolicy)); } } } diff --git a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs index b5a639c..01e7a14 100644 --- a/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs +++ b/src/GeekLearning.Storage.FileSystem.ExtendedProperties.FileSystem/Internal/ExtendedPropertiesProvider.cs @@ -16,16 +16,16 @@ public ExtendedPropertiesProvider( this.options = options.Value; } - public Task GetExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file) + public ValueTask GetExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file) { var extendedPropertiesPath = this.GetExtendedPropertiesPath(storeAbsolutePath, file); if (!File.Exists(extendedPropertiesPath)) { - return Task.FromResult(new FileExtendedProperties()); + return new ValueTask(new FileExtendedProperties()); } var content = File.ReadAllText(extendedPropertiesPath); - return Task.FromResult(JsonConvert.DeserializeObject(content)); + return new ValueTask(JsonConvert.DeserializeObject(content)); } public Task SaveExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file, FileExtendedProperties extendedProperties) diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index dfa731b..13593fa 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -37,7 +37,7 @@ public Task InitAsync() return Task.FromResult(0); } - public async Task ListAsync(string path, bool recursive, bool withMetadata) + public async ValueTask ListAsync(string path, bool recursive, bool withMetadata) { var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.AbsolutePath : Path.Combine(this.AbsolutePath, path); @@ -57,7 +57,7 @@ public async Task ListAsync(string path, bool recursive, bool return result.ToArray(); } - public async Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) + public async ValueTask ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) { var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.AbsolutePath : Path.Combine(this.AbsolutePath, path); @@ -81,12 +81,12 @@ public async Task ListAsync(string path, string searchPattern, return result.ToArray(); } - public async Task GetAsync(IPrivateFileReference file, bool withMetadata) + public async ValueTask GetAsync(IPrivateFileReference file, bool withMetadata) { return await this.InternalGetAsync(file, withMetadata); } - public async Task GetAsync(Uri uri, bool withMetadata) + public async ValueTask GetAsync(Uri uri, bool withMetadata) { if (uri.IsAbsoluteUri) { @@ -102,25 +102,25 @@ public async Task DeleteAsync(IPrivateFileReference file) await fileReference.DeleteAsync(); } - public async Task ReadAsync(IPrivateFileReference file) + public async ValueTask ReadAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadAsync(); } - public async Task ReadAllBytesAsync(IPrivateFileReference file) + public async ValueTask ReadAllBytesAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadAllBytesAsync(); } - public async Task ReadAllTextAsync(IPrivateFileReference file) + public async ValueTask ReadAllTextAsync(IPrivateFileReference file) { var fileReference = await this.InternalGetAsync(file); return await fileReference.ReadAllTextAsync(); } - public async Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) { using (var stream = new MemoryStream(data, 0, data.Length)) { @@ -128,7 +128,7 @@ public async Task SaveAsync(byte[] data, IPrivateFileReference f } } - public async Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) { var fileReference = await this.InternalGetAsync(file, withMetadata: true, checkIfExists: false); this.EnsurePathExists(fileReference.FileSystemPath); @@ -148,15 +148,14 @@ public async Task SaveAsync(Stream data, IPrivateFileReference f return fileReference; } - public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) + public ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) { throw new NotSupportedException(); } - private async Task InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) + private async ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) { - var fileSystemFile = file as Internal.FileSystemFileReference; - if (fileSystemFile != null) + if (file is Internal.FileSystemFileReference fileSystemFile) { return fileSystemFile; } @@ -164,7 +163,7 @@ public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) return await this.InternalGetAsync(file.Path, withMetadata, checkIfExists); } - private async Task InternalGetAsync(string path, bool withMetadata, bool checkIfExists = true) + private async ValueTask InternalGetAsync(string path, bool withMetadata, bool checkIfExists = true) { var fullPath = Path.Combine(this.AbsolutePath, path); if (checkIfExists && !File.Exists(fullPath)) diff --git a/src/GeekLearning.Storage.FileSystem/IExtendedPropertiesProvider.cs b/src/GeekLearning.Storage.FileSystem/IExtendedPropertiesProvider.cs index 843de53..5785103 100644 --- a/src/GeekLearning.Storage.FileSystem/IExtendedPropertiesProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/IExtendedPropertiesProvider.cs @@ -4,7 +4,7 @@ public interface IExtendedPropertiesProvider { - Task GetExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file); + ValueTask GetExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file); Task SaveExtendedPropertiesAsync(string storeAbsolutePath, IPrivateFileReference file, Internal.FileExtendedProperties extendedProperties); } diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs index 71dab02..59102d6 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -60,20 +60,19 @@ public Task DeleteAsync() return Task.FromResult(true); } - public Task ReadAllBytesAsync() + public ValueTask ReadAllBytesAsync() { - return Task.FromResult(File.ReadAllBytes(this.FileSystemPath)); + return new ValueTask(File.ReadAllBytes(this.FileSystemPath)); } - public Task ReadAllTextAsync() + public ValueTask ReadAllTextAsync() { - return Task.FromResult(File.ReadAllText(this.FileSystemPath)); + return new ValueTask(File.ReadAllText(this.FileSystemPath)); } - public Task ReadAsync() + public ValueTask ReadAsync() { - Stream stream = File.OpenRead(this.FileSystemPath); - return Task.FromResult(stream); + return new ValueTask(File.OpenRead(this.FileSystemPath)); } public async Task ReadToStreamAsync(Stream targetStream) @@ -105,7 +104,7 @@ public Task SavePropertiesAsync() (this.Properties as FileSystemFileProperties).ExtendedProperties); } - public Task GetSharedAccessSignature(ISharedAccessPolicy policy) + public ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy) { throw new NotSupportedException(); } diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index d796858..d8fcbc9 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -15,6 +15,7 @@ + diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index 2d344a0..d852349 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -11,11 +11,11 @@ public interface IFileReference : IPrivateFileReference Task ReadToStreamAsync(Stream targetStream); - Task ReadAsync(); + ValueTask ReadAsync(); - Task ReadAllTextAsync(); + ValueTask ReadAllTextAsync(); - Task ReadAllBytesAsync(); + ValueTask ReadAllBytesAsync(); Task DeleteAsync(); @@ -23,6 +23,6 @@ public interface IFileReference : IPrivateFileReference Task SavePropertiesAsync(); - Task GetSharedAccessSignature(ISharedAccessPolicy policy); + ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index 009147f..bf21425 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -10,26 +10,26 @@ public interface IStore Task InitAsync(); - Task ListAsync(string path, bool recursive, bool withMetadata); + ValueTask ListAsync(string path, bool recursive, bool withMetadata); - Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata); + ValueTask ListAsync(string path, string searchPattern, bool recursive, bool withMetadata); - Task GetAsync(IPrivateFileReference file, bool withMetadata); + ValueTask GetAsync(IPrivateFileReference file, bool withMetadata); - Task GetAsync(Uri file, bool withMetadata); + ValueTask GetAsync(Uri file, bool withMetadata); Task DeleteAsync(IPrivateFileReference file); - Task ReadAsync(IPrivateFileReference file); + ValueTask ReadAsync(IPrivateFileReference file); - Task ReadAllBytesAsync(IPrivateFileReference file); + ValueTask ReadAllBytesAsync(IPrivateFileReference file); - Task ReadAllTextAsync(IPrivateFileReference file); + ValueTask ReadAllTextAsync(IPrivateFileReference file); - Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType); + ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType); - Task SaveAsync(Stream data, IPrivateFileReference file, string contentType); + ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType); - Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy); + ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy); } } diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs index e6880c2..30f789f 100644 --- a/src/GeekLearning.Storage/IStoreExtensions.cs +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -5,31 +5,31 @@ public static class IStoreExtensions { - public static Task ListAsync(this IStore store, string path, bool recursive = false, bool withMetadata = false) + public static ValueTask ListAsync(this IStore store, string path, bool recursive = false, bool withMetadata = false) => store.ListAsync(path, recursive: recursive, withMetadata: withMetadata); - public static Task ListAsync(this IStore store, string path, string searchPattern, bool recursive = false, bool withMetadata = false) + public static ValueTask ListAsync(this IStore store, string path, string searchPattern, bool recursive = false, bool withMetadata = false) => store.ListAsync(path, searchPattern, recursive: recursive, withMetadata: withMetadata); public static Task DeleteAsync(this IStore store, string path) => store.DeleteAsync(new Internal.PrivateFileReference(path)); - public static Task GetAsync(this IStore store, string path, bool withMetadata = false) + public static ValueTask GetAsync(this IStore store, string path, bool withMetadata = false) => store.GetAsync(new Internal.PrivateFileReference(path), withMetadata: withMetadata); - public static Task ReadAsync(this IStore store, string path) + public static ValueTask ReadAsync(this IStore store, string path) => store.ReadAsync(new Internal.PrivateFileReference(path)); - public static Task ReadAllBytesAsync(this IStore store, string path) + public static ValueTask ReadAllBytesAsync(this IStore store, string path) => store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); - public static Task ReadAllTextAsync(this IStore store, string path) + public static ValueTask ReadAllTextAsync(this IStore store, string path) => store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); - public static Task SaveAsync(this IStore store, byte[] data, string path, string contentType) + public static ValueTask SaveAsync(this IStore store, byte[] data, string path, string contentType) => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); - public static Task SaveAsync(this IStore store, Stream data, string path, string contentType) + public static ValueTask SaveAsync(this IStore store, Stream data, string path, string contentType) => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index 6c85671..c8582c0 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -27,24 +27,24 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) public Task DeleteAsync(IPrivateFileReference file) => this.innerStore.DeleteAsync(file); - public Task GetAsync(Uri file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); + public ValueTask GetAsync(Uri file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task GetAsync(IPrivateFileReference file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); + public ValueTask GetAsync(IPrivateFileReference file, bool withMetadata) => this.innerStore.GetAsync(file, withMetadata); - public Task ListAsync(string path, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, recursive, withMetadata); + public ValueTask ListAsync(string path, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, recursive, withMetadata); - public Task ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, searchPattern, recursive, withMetadata); + public ValueTask ListAsync(string path, string searchPattern, bool recursive, bool withMetadata) => this.innerStore.ListAsync(path, searchPattern, recursive, withMetadata); - public Task ReadAllBytesAsync(IPrivateFileReference file) => this.innerStore.ReadAllBytesAsync(file); + public ValueTask ReadAllBytesAsync(IPrivateFileReference file) => this.innerStore.ReadAllBytesAsync(file); - public Task ReadAllTextAsync(IPrivateFileReference file) => this.innerStore.ReadAllTextAsync(file); + public ValueTask ReadAllTextAsync(IPrivateFileReference file) => this.innerStore.ReadAllTextAsync(file); - public Task ReadAsync(IPrivateFileReference file) => this.innerStore.ReadAsync(file); + public ValueTask ReadAsync(IPrivateFileReference file) => this.innerStore.ReadAsync(file); - public Task SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + public ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); - public Task SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + public ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); - public Task GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) => this.innerStore.GetSharedAccessSignatureAsync(policy); + public ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) => this.innerStore.GetSharedAccessSignatureAsync(policy); } } From e6fb6e6285be8ae4bb3f42204cd5fc8a6d0e2407 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Tue, 30 May 2017 17:12:49 +0200 Subject: [PATCH 16/21] Fetch properties on an existing file reference --- src/GeekLearning.Storage.Azure/AzureStore.cs | 9 ++----- .../Internal/AzureFileReference.cs | 15 ++++++++++++ .../FileSystemStore.cs | 9 ++----- .../Internal/FileSystemFileReference.cs | 24 ++++++++++++++++++- src/GeekLearning.Storage/IFileReference.cs | 2 ++ 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index b345a9a..f1d91ff 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -231,14 +231,9 @@ internal static SharedAccessBlobPermissions FromGenericToAzure(SharedAccessPermi return result; } - private async ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) + private ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false) { - if (file is Internal.AzureFileReference azureFile) - { - return azureFile; - } - - return await this.InternalGetAsync(new Uri(file.Path, UriKind.Relative), withMetadata); + return this.InternalGetAsync(new Uri(file.Path, UriKind.Relative), withMetadata); } private async ValueTask InternalGetAsync(Uri uri, bool withMetadata) diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index eebbb59..789dfc2 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -9,11 +9,13 @@ public class AzureFileReference : IFileReference { private Lazy propertiesLazy; + private bool withMetadata; public AzureFileReference(string path, ICloudBlob cloudBlob, bool withMetadata) { this.Path = path; this.CloudBlob = cloudBlob; + this.withMetadata = withMetadata; this.propertiesLazy = new Lazy(() => { if (withMetadata && cloudBlob.Metadata != null && cloudBlob.Properties != null) @@ -95,5 +97,18 @@ public ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy) return new ValueTask(this.CloudBlob.GetSharedAccessSignature(adHocPolicy)); } + + public async Task FetchProperties() + { + if (this.withMetadata) + { + return; + } + + await this.CloudBlob.FetchAttributesAsync(); + + this.propertiesLazy = new Lazy(() => new AzureFileProperties(this.CloudBlob)); + this.withMetadata = true; + } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 13593fa..bf95af1 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -153,14 +153,9 @@ public ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy polic throw new NotSupportedException(); } - private async ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) + private ValueTask InternalGetAsync(IPrivateFileReference file, bool withMetadata = false, bool checkIfExists = true) { - if (file is Internal.FileSystemFileReference fileSystemFile) - { - return fileSystemFile; - } - - return await this.InternalGetAsync(file.Path, withMetadata, checkIfExists); + return this.InternalGetAsync(file.Path, withMetadata, checkIfExists); } private async ValueTask InternalGetAsync(string path, bool withMetadata, bool checkIfExists = true) diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs index 59102d6..d038472 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -7,9 +7,10 @@ public class FileSystemFileReference : IFileReference { private readonly FileSystemStore store; - private readonly Lazy propertiesLazy; private readonly Lazy publicUrlLazy; private readonly IExtendedPropertiesProvider extendedPropertiesProvider; + private bool withMetadata; + private Lazy propertiesLazy; public FileSystemFileReference( string filePath, @@ -24,6 +25,7 @@ public FileSystemFileReference( this.Path = path.Replace('\\', '/'); this.store = store; this.extendedPropertiesProvider = extendedPropertiesProvider; + this.withMetadata = withMetadata; this.propertiesLazy = new Lazy(() => { @@ -108,5 +110,25 @@ public ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy) { throw new NotSupportedException(); } + + public async Task FetchProperties() + { + if (this.withMetadata) + { + return; + } + + if (this.extendedPropertiesProvider == null) + { + throw new InvalidOperationException("There is no FileSystem extended properties provider."); + } + + var extendedProperties = await this.extendedPropertiesProvider.GetExtendedPropertiesAsync( + this.store.AbsolutePath, + this); + + this.propertiesLazy = new Lazy(() => new FileSystemFileProperties(this.FileSystemPath, extendedProperties)); + this.withMetadata = true; + } } } diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs index d852349..115101e 100644 --- a/src/GeekLearning.Storage/IFileReference.cs +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -24,5 +24,7 @@ public interface IFileReference : IPrivateFileReference Task SavePropertiesAsync(); ValueTask GetSharedAccessSignature(ISharedAccessPolicy policy); + + Task FetchProperties(); } } From 183e0f2b6c1346e63d67e103fd355d967d5ca534 Mon Sep 17 00:00:00 2001 From: Adrien Siffermann Date: Thu, 8 Jun 2017 15:54:13 +0200 Subject: [PATCH 17/21] Add ContentMD5 property exposure and new Overwrite Policies when saving file --- src/GeekLearning.Storage.Azure/AzureStore.cs | 39 +++++++++++++--- .../Internal/AzureFileProperties.cs | 2 + .../FileSystemStore.cs | 46 ++++++++++++++----- .../GeekLearning.Storage.FileSystem.csproj | 1 + .../Internal/FileExtendedProperties.cs | 2 + .../Internal/FileSystemFileProperties.cs | 2 + .../Exceptions/FileAlreadyExistsException.cs | 12 +++++ .../GeekLearning.Storage.csproj | 1 + src/GeekLearning.Storage/IFileProperties.cs | 2 + src/GeekLearning.Storage/IStore.cs | 4 +- src/GeekLearning.Storage/IStoreExtensions.cs | 8 ++-- .../Internal/GenericStoreProxy.cs | 4 +- src/GeekLearning.Storage/OverwritePolicy.cs | 9 ++++ 13 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 src/GeekLearning.Storage/Exceptions/FileAlreadyExistsException.cs create mode 100644 src/GeekLearning.Storage/OverwritePolicy.cs diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index f1d91ff..fd08c45 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; + using System.Security.Cryptography; using System.Threading.Tasks; public class AzureStore : IStore @@ -155,29 +156,53 @@ public async ValueTask ReadAllTextAsync(IPrivateFileReference file) return await fileReference.ReadAllTextAsync(); } - public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) { using (var stream = new SyncMemoryStream(data, 0, data.Length)) { - return await this.SaveAsync(stream, file, contentType); + return await this.SaveAsync(stream, file, contentType, overwritePolicy); } } - public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) { + var uploadBlob = true; var blockBlob = this.container.Value.GetBlockBlobReference(file.Path); + var blobExists = await blockBlob.ExistsAsync(); - if (await blockBlob.ExistsAsync()) + if (blobExists) { + if (overwritePolicy == OverwritePolicy.Never) + { + throw new Exceptions.FileAlreadyExistsException(this.Name, file.Path); + } + await blockBlob.FetchAttributesAsync(); + + if (overwritePolicy == OverwritePolicy.IfContentModified) + { + using (var md5 = MD5.Create()) + { + data.Seek(0, SeekOrigin.Begin); + var contentMD5 = Convert.ToBase64String(md5.ComputeHash(data)); + data.Seek(0, SeekOrigin.Begin); + uploadBlob = (contentMD5 == blockBlob.Properties.ContentMD5); + } + } } - await blockBlob.UploadFromStreamAsync(data); + if (uploadBlob) + { + await blockBlob.UploadFromStreamAsync(data); + } var reference = new Internal.AzureFileReference(blockBlob, withMetadata: true); - reference.Properties.ContentType = contentType; - await reference.SavePropertiesAsync(); + if (reference.Properties.ContentType != contentType) + { + reference.Properties.ContentType = contentType; + await reference.SavePropertiesAsync(); + } return reference; } diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs index 8d02273..013efe1 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs @@ -49,6 +49,8 @@ public string CacheControl set { this.cloudBlob.Properties.CacheControl = value; } } + public string ContentMD5 => this.cloudBlob.Properties.ContentMD5; + public IDictionary Metadata => this.decodedMetadata; internal async Task SaveAsync() diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index bf95af1..5d8b5af 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -120,28 +120,45 @@ public async ValueTask ReadAllTextAsync(IPrivateFileReference file) return await fileReference.ReadAllTextAsync(); } - public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) { using (var stream = new MemoryStream(data, 0, data.Length)) { - return await this.SaveAsync(stream, file, contentType); + return await this.SaveAsync(stream, file, contentType, overwritePolicy); } } - public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) + public async ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) { var fileReference = await this.InternalGetAsync(file, withMetadata: true, checkIfExists: false); - this.EnsurePathExists(fileReference.FileSystemPath); + var fileExists = File.Exists(fileReference.FileSystemPath); - using (var fileStream = File.Open(fileReference.FileSystemPath, FileMode.Create, FileAccess.Write)) + if (fileExists) { - await data.CopyToAsync(fileStream); + if (overwritePolicy == OverwritePolicy.Never) + { + throw new Exceptions.FileAlreadyExistsException(this.Name, file.Path); + } } var properties = fileReference.Properties as Internal.FileSystemFileProperties; + var hashes = ComputeHashes(data); + + if (!fileExists + || overwritePolicy == OverwritePolicy.Always + || (overwritePolicy == OverwritePolicy.IfContentModified && properties.ContentMD5 != hashes.ContentMD5)) + { + this.EnsurePathExists(fileReference.FileSystemPath); + + using (var fileStream = File.Open(fileReference.FileSystemPath, FileMode.Create, FileAccess.Write)) + { + await data.CopyToAsync(fileStream); + } + } properties.ContentType = contentType; - properties.ExtendedProperties.ETag = GenerateEtag(fileReference.FileSystemPath); + properties.ExtendedProperties.ETag = hashes.ETag; + properties.ExtendedProperties.ContentMD5 = hashes.ContentMD5; await fileReference.SavePropertiesAsync(); @@ -198,18 +215,23 @@ private void EnsurePathExists(string path) } } - private static string GenerateEtag(string fileSystemPath) + private static (string ETag, string ContentMD5) ComputeHashes(Stream stream) { - var etag = string.Empty; - using (var stream = File.Open(fileSystemPath, FileMode.Open, FileAccess.Read)) + var eTag = string.Empty; + var contentMD5 = string.Empty; + + stream.Seek(0, SeekOrigin.Begin); using (var md5 = MD5.Create()) { + stream.Seek(0, SeekOrigin.Begin); var hash = md5.ComputeHash(stream); + stream.Seek(0, SeekOrigin.Begin); + contentMD5 = Convert.ToBase64String(hash); string hex = BitConverter.ToString(hash); - etag = hex.Replace("-", ""); + eTag = $"\"{hex.Replace("-", "")}\""; } - return $"\"{etag}\""; + return (eTag, contentMD5); } } } diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj index bae7d7f..283f2c0 100644 --- a/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj +++ b/src/GeekLearning.Storage.FileSystem/GeekLearning.Storage.FileSystem.csproj @@ -18,6 +18,7 @@ + diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileExtendedProperties.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileExtendedProperties.cs index b644a6f..975429c 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileExtendedProperties.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileExtendedProperties.cs @@ -15,6 +15,8 @@ public FileExtendedProperties() public string CacheControl { get; set; } + public string ContentMD5 { get; set; } + public IDictionary Metadata { get; set; } } } diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileProperties.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileProperties.cs index 4f8288a..1d9e89e 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileProperties.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileProperties.cs @@ -33,6 +33,8 @@ public string CacheControl set { this.extendedProperties.CacheControl = value; } } + public string ContentMD5 => this.extendedProperties.ContentMD5; + public IDictionary Metadata => this.extendedProperties.Metadata; internal FileExtendedProperties ExtendedProperties => this.extendedProperties; diff --git a/src/GeekLearning.Storage/Exceptions/FileAlreadyExistsException.cs b/src/GeekLearning.Storage/Exceptions/FileAlreadyExistsException.cs new file mode 100644 index 0000000..e6b0204 --- /dev/null +++ b/src/GeekLearning.Storage/Exceptions/FileAlreadyExistsException.cs @@ -0,0 +1,12 @@ +namespace GeekLearning.Storage.Exceptions +{ + using System; + + public class FileAlreadyExistsException : Exception + { + public FileAlreadyExistsException(string storeName, string filePath) + : base($"The file {filePath} already exists in Store {storeName}.") + { + } + } +} diff --git a/src/GeekLearning.Storage/GeekLearning.Storage.csproj b/src/GeekLearning.Storage/GeekLearning.Storage.csproj index d8fcbc9..906ec1e 100644 --- a/src/GeekLearning.Storage/GeekLearning.Storage.csproj +++ b/src/GeekLearning.Storage/GeekLearning.Storage.csproj @@ -24,6 +24,7 @@ + diff --git a/src/GeekLearning.Storage/IFileProperties.cs b/src/GeekLearning.Storage/IFileProperties.cs index a786d1b..fd7dea9 100644 --- a/src/GeekLearning.Storage/IFileProperties.cs +++ b/src/GeekLearning.Storage/IFileProperties.cs @@ -15,6 +15,8 @@ public interface IFileProperties string CacheControl { get; set; } + string ContentMD5 { get; } + IDictionary Metadata { get; } } } diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index bf21425..f82c7e8 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -26,9 +26,9 @@ public interface IStore ValueTask ReadAllTextAsync(IPrivateFileReference file); - ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType); + ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always); - ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType); + ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always); ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy); } diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs index 30f789f..e063ad0 100644 --- a/src/GeekLearning.Storage/IStoreExtensions.cs +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -26,10 +26,10 @@ public static ValueTask ReadAllBytesAsync(this IStore store, string path public static ValueTask ReadAllTextAsync(this IStore store, string path) => store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); - public static ValueTask SaveAsync(this IStore store, byte[] data, string path, string contentType) - => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); + public static ValueTask SaveAsync(this IStore store, byte[] data, string path, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType, overwritePolicy); - public static ValueTask SaveAsync(this IStore store, Stream data, string path, string contentType) - => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType); + public static ValueTask SaveAsync(this IStore store, Stream data, string path, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) + => store.SaveAsync(data, new Internal.PrivateFileReference(path), contentType, overwritePolicy); } } diff --git a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs index c8582c0..8d4f4d3 100644 --- a/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs +++ b/src/GeekLearning.Storage/Internal/GenericStoreProxy.cs @@ -41,9 +41,9 @@ public GenericStoreProxy(IStorageFactory factory, IOptions options) public ValueTask ReadAsync(IPrivateFileReference file) => this.innerStore.ReadAsync(file); - public ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + public ValueTask SaveAsync(Stream data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) => this.innerStore.SaveAsync(data, file, contentType, overwritePolicy); - public ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType) => this.innerStore.SaveAsync(data, file, contentType); + public ValueTask SaveAsync(byte[] data, IPrivateFileReference file, string contentType, OverwritePolicy overwritePolicy = OverwritePolicy.Always) => this.innerStore.SaveAsync(data, file, contentType, overwritePolicy); public ValueTask GetSharedAccessSignatureAsync(ISharedAccessPolicy policy) => this.innerStore.GetSharedAccessSignatureAsync(policy); } diff --git a/src/GeekLearning.Storage/OverwritePolicy.cs b/src/GeekLearning.Storage/OverwritePolicy.cs new file mode 100644 index 0000000..394c81e --- /dev/null +++ b/src/GeekLearning.Storage/OverwritePolicy.cs @@ -0,0 +1,9 @@ +namespace GeekLearning.Storage +{ + public enum OverwritePolicy + { + Always = 0, + IfContentModified = 1, + Never = 2, + } +} \ No newline at end of file From ef63b9c42183e8905a2a047b87ab97ad2778d3a1 Mon Sep 17 00:00:00 2001 From: Arnaud Auroux Date: Tue, 13 Jun 2017 15:40:45 +0200 Subject: [PATCH 18/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71b2523..36d10ab 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This library abstracts physical data storage in a way which allows you to transp by configuration. ## Features - + * List files, with globbing support * Read, Write, Delete files * Public file url From 9a2370ebb5488d61fde04544143d4aeab2589196 Mon Sep 17 00:00:00 2001 From: Arnaud Auroux Date: Tue, 13 Jun 2017 16:11:02 +0200 Subject: [PATCH 19/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 36d10ab..71b2523 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This library abstracts physical data storage in a way which allows you to transp by configuration. ## Features - + * List files, with globbing support * Read, Write, Delete files * Public file url From cd19d2d06ba4db004a81e4f089ce4d43f6237165 Mon Sep 17 00:00:00 2001 From: Arnaud Auroux Date: Tue, 13 Jun 2017 17:57:37 +0200 Subject: [PATCH 20/21] Fix azure store upload condition --- src/GeekLearning.Storage.Azure/AzureStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index fd08c45..e2d911a 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -186,7 +186,7 @@ public async ValueTask SaveAsync(Stream data, IPrivateFileRefere data.Seek(0, SeekOrigin.Begin); var contentMD5 = Convert.ToBase64String(md5.ComputeHash(data)); data.Seek(0, SeekOrigin.Begin); - uploadBlob = (contentMD5 == blockBlob.Properties.ContentMD5); + uploadBlob = (contentMD5 != blockBlob.Properties.ContentMD5); } } } From 447aee6ce1b3e48ceb715c3ba37b9c03a8c7da24 Mon Sep 17 00:00:00 2001 From: Arnaud Auroux Date: Tue, 13 Jun 2017 18:56:44 +0200 Subject: [PATCH 21/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71b2523..bd1d1f9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://geeklearning.visualstudio.com/_apis/public/build/definitions/f841b266-7595-4d01-9ee1-4864cf65aa73/27/badge)](#) # Geek Learning Cloud Storage Abstraction - + This library abstracts physical data storage in a way which allows you to transparently switch the underlying provider by configuration.