From 50bdf913ff89f7f2ca3a743c0f54795fe3bbeca8 Mon Sep 17 00:00:00 2001 From: Cyprien Autexier Date: Sun, 3 Jul 2016 11:45:46 +0200 Subject: [PATCH 1/2] Api changes Remove aspnet dependency from FileSytemStorage --- GeekLearning.Storage.sln | 8 +- .../Controllers/ValuesController.cs | 2 +- .../Startup.cs | 9 +- .../appsettings.json | 10 +++ src/GeekLearning.Storage.Azure/AzureStore.cs | 88 ++++++++++-------- .../Internal/AzureFileReference.cs | 69 ++++++++++++++ ...ekLearning.Storage.FileSystem.Server.xproj | 21 +++++ .../Properties/AssemblyInfo.cs | 19 ++++ .../project.json | 14 +++ .../FileSystemOptions.cs | 12 +++ .../FileSystemStorageProvider.cs | 10 +-- .../FileSystemStore.cs | 89 +++++++++++++------ ...GeekLearningFileSystemStorageExtensions.cs | 3 +- .../Internal/FileSystemFileReference.cs | 57 ++++++++++++ .../project.json | 1 - src/GeekLearning.Storage/IFileReference.cs | 18 ++++ .../IPrivateFileReference.cs | 7 ++ src/GeekLearning.Storage/IStore.cs | 21 +++-- src/GeekLearning.Storage/IStoreExtensions.cs | 48 ++++++++++ .../Internal/PrivateFileReference.cs | 11 +++ 20 files changed, 435 insertions(+), 82 deletions(-) create mode 100644 src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs create mode 100644 src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj create mode 100644 src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs create mode 100644 src/GeekLearning.Storage.FileSystem.Server/project.json create mode 100644 src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs create mode 100644 src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs create mode 100644 src/GeekLearning.Storage/IFileReference.cs create mode 100644 src/GeekLearning.Storage/IPrivateFileReference.cs create mode 100644 src/GeekLearning.Storage/IStoreExtensions.cs create mode 100644 src/GeekLearning.Storage/Internal/PrivateFileReference.cs diff --git a/GeekLearning.Storage.sln b/GeekLearning.Storage.sln index 0e7b697..3966452 100644 --- a/GeekLearning.Storage.sln +++ b/GeekLearning.Storage.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 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 @@ -25,6 +25,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {590B21B0-2AFA-4329-82AD-EF180C50EB5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {590B21B0-2AFA-4329-82AD-EF180C50EB5C}.Release|Any CPU.ActiveCfg = Release|Any CPU {590B21B0-2AFA-4329-82AD-EF180C50EB5C}.Release|Any CPU.Build.0 = Release|Any CPU + {9D94CD6C-9451-449A-BED2-1C07D624A8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D94CD6C-9451-449A-BED2-1C07D624A8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D94CD6C-9451-449A-BED2-1C07D624A8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D94CD6C-9451-449A-BED2-1C07D624A8E0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs b/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs index e163a5f..bcf4f07 100644 --- a/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs +++ b/samples/GeekLearning.Storage.BasicSample/Controllers/ValuesController.cs @@ -22,7 +22,7 @@ public ValuesController(TemplatesStore templates) public async Task> Get() { - return new string[] { await templates.Store.ReadAllText("json.json"), "value2" }; + return new string[] { await templates.Store.ReadAllTextAsync("json.json"), "value2" }; } // GET api/values/5 diff --git a/samples/GeekLearning.Storage.BasicSample/Startup.cs b/samples/GeekLearning.Storage.BasicSample/Startup.cs index 055a4e2..8d375c3 100644 --- a/samples/GeekLearning.Storage.BasicSample/Startup.cs +++ b/samples/GeekLearning.Storage.BasicSample/Startup.cs @@ -20,18 +20,25 @@ public Startup(IHostingEnvironment env) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); + HostingEnvironement = env; } public IConfigurationRoot Configuration { get; } + public IHostingEnvironment HostingEnvironement { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); + + services.AddStorage() + .AddAzureStorage() + .AddFileSystemStorage(HostingEnvironement.ContentRootPath); - services.AddStorage().AddAzureStorage().AddFileSystemStorage(); services.Configure(Configuration.GetSection("Storage")); + + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/samples/GeekLearning.Storage.BasicSample/appsettings.json b/samples/GeekLearning.Storage.BasicSample/appsettings.json index fa8ce71..5fa8279 100644 --- a/samples/GeekLearning.Storage.BasicSample/appsettings.json +++ b/samples/GeekLearning.Storage.BasicSample/appsettings.json @@ -6,5 +6,15 @@ "System": "Information", "Microsoft": "Information" } + }, + "Storage": { + "Stores": { + "Templates": { + "Provider": "FileSystem", + "Parameters": { + "Path" : "Templates" + } + } + } } } diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 9b4ef33..9587f66 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -34,79 +34,72 @@ public AzureStore(string connectionString, string containerName) container = new Lazy(() => this.client.Value.GetContainerReference(this.containerName)); } - public Task GetExpirableUri(string uri) + private async Task InternalGetAsync(IPrivateFileReference file) { - throw new NotImplementedException(); + return new Internal.AzureFileReference(file.Path, await this.container.Value.GetBlobReferenceFromServerAsync(file.Path)); } - public async Task ReadInMemory(string path) + public async Task GetAsync(IPrivateFileReference file) { - var memoryStream = new MemoryStream(); - var blockBlob = await GetBlobReference(path); - await blockBlob.DownloadRangeToStreamAsync(memoryStream, null, null); - return memoryStream; + return await InternalGetAsync(file); } - private Task GetBlobReference(string path) + public async Task GetAsync(Uri uri) { - var uri = new Uri(path, UriKind.RelativeOrAbsolute); if (uri.IsAbsoluteUri) { - return this.client.Value.GetBlobReferenceFromServerAsync(uri); + return new Internal.AzureFileReference(await this.client.Value.GetBlobReferenceFromServerAsync(uri)); } else { - return this.container.Value.GetBlobReferenceFromServerAsync(path); + return new Internal.AzureFileReference(await this.container.Value.GetBlobReferenceFromServerAsync(uri.ToString())); } } - public async Task Read(string path) + + public async Task ReadAsync(IPrivateFileReference file) { - return await this.ReadInMemory(path); + var fileReference = await InternalGetAsync(file); + return await fileReference.ReadInMemoryAsync(); } - public async Task ReadAllBytes(string path) + public async Task ReadAllBytesAsync(IPrivateFileReference file) { - var stream = await this.ReadInMemory(path); - return stream.ToArray(); + var fileReference = await InternalGetAsync(file); + return (await fileReference.ReadInMemoryAsync()).ToArray(); } - public async Task ReadAllText(string path) + public async Task ReadAllTextAsync(IPrivateFileReference file) { - var blockBlob = await GetBlobReference(path); - using (var reader = new StreamReader(await blockBlob.OpenReadAsync(AccessCondition.GenerateEmptyCondition(), new BlobRequestOptions(), new OperationContext()))) + var fileReference = await InternalGetAsync(file); + using (var reader = new StreamReader(await fileReference.CloudBlob.OpenReadAsync(AccessCondition.GenerateEmptyCondition(), new BlobRequestOptions(), new OperationContext()))) { return await reader.ReadToEndAsync(); } } - public async Task Save(Stream data, string path, string mimeType) + public async Task SaveAsync(Stream data, IPrivateFileReference file, string mimeType) { - var blockBlob = this.container.Value.GetBlockBlobReference(path); + var blockBlob = this.container.Value.GetBlockBlobReference(file.Path); await blockBlob.UploadFromStreamAsync(data); blockBlob.Properties.ContentType = mimeType; blockBlob.Properties.CacheControl = "max-age=300, must-revalidate"; await blockBlob.SetPropertiesAsync(); - return blockBlob.Uri.ToString(); + return new Internal.AzureFileReference(blockBlob); } - public async Task Save(byte[] data, string path, string mimeType) + public async Task SaveAsync(byte[] data, IPrivateFileReference file, string mimeType) { - var blockBlob = this.container.Value.GetBlockBlobReference(path); + var blockBlob = this.container.Value.GetBlockBlobReference(file.Path); await blockBlob.UploadFromByteArrayAsync(data, 0, data.Length); blockBlob.Properties.ContentType = mimeType; blockBlob.Properties.CacheControl = "max-age=300, must-revalidate"; await blockBlob.SetPropertiesAsync(); - return blockBlob.Uri.ToString(); + return new Internal.AzureFileReference(blockBlob); } - public async Task List(string path) + public async Task ListAsync(string path) { - if (path.EndsWith("*")) - { - path = path.TrimEnd('*'); - } - BlobContinuationToken continuationToken = null; List results = new List(); do @@ -117,13 +110,38 @@ public async Task List(string path) } while (continuationToken != null); - return results.Select(blob => blob.Uri.ToString()).ToArray(); + return results.Select(blob => new Internal.AzureFileReference(blob)).ToArray(); + } + + public async Task ListAsync(string path, string searchPattern) + { + throw new NotSupportedException(); + //var firstWildCard = searchPattern.IndexOf('*'); + //if (firstWildCard >= 0) + //{ + // path += searchPattern.Substring(0, firstWildCard); + // searchPattern = searchPattern.Substring(firstWildCard); + //} + + //Regex.Escape(wildcardExpression).Replace(@"\*", ".*").Replace(@"\?", "."); + + //BlobContinuationToken continuationToken = null; + //List results = new List(); + //do + //{ + // var response = await this.container.Value.ListBlobsSegmentedAsync(path, continuationToken); + // continuationToken = response.ContinuationToken; + // results.AddRange(response.Results); + //} + //while (continuationToken != null); + + //return results.Select(blob => new Internal.AzureFileReference(blob)).ToArray(); } - public async Task Delete(string path) + public async Task DeleteAsync(IPrivateFileReference file) { - var blockBlob = await GetBlobReference(path); - await blockBlob.DeleteAsync(); + var fileReference = await InternalGetAsync(file); + await fileReference.DeleteAsync(); } } } diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs new file mode 100644 index 0000000..a45c5a1 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -0,0 +1,69 @@ +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.Storage.Azure.Internal +{ + public class AzureFileReference : IFileReference + { + private ICloudBlob cloudBlob; + + public AzureFileReference(IListBlobItem blobItem) + : this(blobItem as ICloudBlob) + { + } + + public AzureFileReference(string path, IListBlobItem blobItem) + : this(path, blobItem as ICloudBlob) + { + } + + public AzureFileReference(string path, ICloudBlob cloudBlob) + { + this.Path = path; + this.cloudBlob = cloudBlob; + } + + public AzureFileReference(ICloudBlob cloudBlob) : + this(cloudBlob.Uri.MakeRelativeUri(cloudBlob.Container.Uri).ToString(), cloudBlob) + { + + } + + public string Path { get; } + + public string PublicUrl => cloudBlob.Uri.ToString(); + + public ICloudBlob CloudBlob => this.cloudBlob; + + public Task DeleteAsync() + { + return this.cloudBlob.DeleteAsync(); + } + + public async Task ReadAsync() + { + return await this.ReadInMemoryAsync(); + } + + public async Task ReadInMemoryAsync() + { + var memoryStream = new MemoryStream(); + await this.CloudBlob.DownloadRangeToStreamAsync(memoryStream, null, null); + return memoryStream; + } + + public Task UpdateAsync(Stream stream) + { + return this.cloudBlob.UploadFromStreamAsync(stream); + } + + public Task GetExpirableUriAsync() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj new file mode 100644 index 0000000..c90d136 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem.Server/GeekLearning.Storage.FileSystem.Server.xproj @@ -0,0 +1,21 @@ + + + + 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 new file mode 100644 index 0000000..7395edb --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem.Server/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +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.Server")] +[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("9d94cd6c-9451-449a-bed2-1c07d624a8e0")] diff --git a/src/GeekLearning.Storage.FileSystem.Server/project.json b/src/GeekLearning.Storage.FileSystem.Server/project.json new file mode 100644 index 0000000..e85be12 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem.Server/project.json @@ -0,0 +1,14 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "GeekLearning.Storage.FileSystem": "1.0.0-*", + "NETStandard.Library": "1.6.0" + }, + + "frameworks": { + "netstandard1.6": { + "imports": "dnxcore50" + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs b/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs new file mode 100644 index 0000000..e9d35bb --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/FileSystemOptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.Storage.FileSystem +{ + public class FileSystemOptions + { + public string RootPath { get; set; } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs index 8194332..28d76cd 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStorageProvider.cs @@ -1,15 +1,15 @@ namespace GeekLearning.Storage.FileSystem { using Storage; - using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Options; public class FileSystemStorageProvider : IStorageProvider { - private IHostingEnvironment appEnv; + private IOptions options; - public FileSystemStorageProvider(IHostingEnvironment appEnv) + public FileSystemStorageProvider(IOptions options) { - this.appEnv = appEnv; + this.options = options; } public string Name @@ -22,7 +22,7 @@ public string Name public IStore BuildStore(StorageOptions.StorageStore storeOptions) { - return new FileSystemStore(storeOptions.Parameters["Path"], this.appEnv.ContentRootPath); + return new FileSystemStore(storeOptions.Parameters["Path"], this.options.Value.RootPath); } } } diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index f708d91..35259d3 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -9,7 +9,7 @@ public class FileSystemStore : IStore { private string absolutePath; - public FileSystemStore(string path, string appPath) + public FileSystemStore(string path, string rootPath) { if (string.IsNullOrEmpty(path)) { @@ -22,71 +22,102 @@ public FileSystemStore(string path, string appPath) } else { - this.absolutePath = Path.Combine(appPath, path); + this.absolutePath = Path.Combine(rootPath, path); } } - public Task Delete(string path) + private Internal.FileSystemFileReference InternalGetAsync(IPrivateFileReference file) { - File.Delete(Path.Combine(this.absolutePath, path)); - return Task.FromResult(true); + return new Internal.FileSystemFileReference(Path.Combine(this.absolutePath, file.Path), file.Path); } - public Task GetExpirableUri(string uri) + public async Task GetAsync(IPrivateFileReference file) { - return Task.FromResult(uri); + return InternalGetAsync(file); } - public Task List(string path) + public async Task GetAsync(Uri uri) { - var directoryPath = Path.GetDirectoryName(Path.Combine(this.absolutePath, path)); + throw new NotImplementedException(); + } + + public Task DeleteAsync(IPrivateFileReference file) + { + var fileReference = InternalGetAsync(file); + return fileReference.DeleteAsync(); + } + + public Task ListAsync(string path) + { + var directoryPath = Path.Combine(this.absolutePath, path); + if (!Directory.Exists(directoryPath)) + { + return Task.FromResult(new IFileReference[0]); + } + + return Task.FromResult(Directory.GetFiles(directoryPath) + .Select(fullPath => + (IFileReference)new Internal.FileSystemFileReference(fullPath, fullPath.Replace(this.absolutePath, "") + .Trim('/', '\\'))) + .ToArray()); + } + + public Task ListAsync(string path, string searchPattern) + { + var directoryPath = Path.Combine(this.absolutePath, path); if (!Directory.Exists(directoryPath)) { - return Task.FromResult(new string[0]); + return Task.FromResult(new IFileReference[0]); } - return Task.FromResult(Directory.GetFiles(directoryPath, path) - .Select(x => x.Replace(this.absolutePath, "") - .Trim('/', '\\')) + return Task.FromResult(Directory.GetFiles(directoryPath, searchPattern) + .Select(fullPath => + (IFileReference)new Internal.FileSystemFileReference(fullPath, fullPath.Replace(this.absolutePath, "") + .Trim('/', '\\'))) .ToArray()); } - public Task Read(string path) + public Task ReadAsync(IPrivateFileReference file) { - return Task.FromResult((Stream)File.OpenRead(Path.Combine(this.absolutePath, path))); + var fileReference = InternalGetAsync(file); + return fileReference.ReadAsync(); } - public Task ReadAllBytes(string path) + public Task ReadAllBytesAsync(IPrivateFileReference file) { - return Task.FromResult(File.ReadAllBytes(Path.Combine(this.absolutePath, path))); + var fileReference = InternalGetAsync(file); + return Task.FromResult(File.ReadAllBytes(fileReference.FileSystemPath)); } - public Task ReadAllText(string path) + public Task ReadAllTextAsync(IPrivateFileReference file) { - return Task.FromResult(File.ReadAllText(Path.Combine(this.absolutePath, path))); + var fileReference = InternalGetAsync(file); + return Task.FromResult(File.ReadAllText(fileReference.FileSystemPath)); } - public async Task Save(Stream data, string path, string mimeType) + public async Task SaveAsync(Stream data, IPrivateFileReference file, string mimeType) { - EnsurePathExists(path); - using (var file = File.Open(Path.Combine(this.absolutePath, path), FileMode.Create, FileAccess.Write)) + var fileReference = InternalGetAsync(file); + EnsurePathExists(fileReference.FileSystemPath); + using (var fileStream = File.Open(fileReference.FileSystemPath, FileMode.Create, FileAccess.Write)) { - await data.CopyToAsync(file); + await data.CopyToAsync(fileStream); } - return path; + return fileReference; } - public Task Save(byte[] data, string path, string mimeType) + public Task SaveAsync(byte[] data, IPrivateFileReference file, string mimeType) { - EnsurePathExists(path); - File.WriteAllBytes(Path.Combine(this.absolutePath, path), data); - return Task.FromResult(path); + var fileReference = InternalGetAsync(file); + EnsurePathExists(fileReference.FileSystemPath); + File.WriteAllBytes(fileReference.FileSystemPath, data); + return Task.FromResult((IFileReference)fileReference); } private void EnsurePathExists(string path) { - var directoryPath = Path.GetDirectoryName(Path.Combine(this.absolutePath, path)); + var directoryPath = Path.GetDirectoryName(path); if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); diff --git a/src/GeekLearning.Storage.FileSystem/GeekLearningFileSystemStorageExtensions.cs b/src/GeekLearning.Storage.FileSystem/GeekLearningFileSystemStorageExtensions.cs index 95dbeb7..37665eb 100644 --- a/src/GeekLearning.Storage.FileSystem/GeekLearningFileSystemStorageExtensions.cs +++ b/src/GeekLearning.Storage.FileSystem/GeekLearningFileSystemStorageExtensions.cs @@ -6,8 +6,9 @@ public static class GeekLearningFileSystemStorageExtensions { - public static IServiceCollection AddFileSystemStorage(this IServiceCollection services) + public static IServiceCollection AddFileSystemStorage(this IServiceCollection services, string rootPath) { + services.Configure(options => options.RootPath = rootPath); services.TryAddEnumerable(ServiceDescriptor.Transient()); return services; } diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs new file mode 100644 index 0000000..9688128 --- /dev/null +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.Storage.FileSystem.Internal +{ + public class FileSystemFileReference : IFileReference + { + private string filePath; + private string path; + + public FileSystemFileReference(string filePath, string path) + { + this.filePath = filePath; + this.path = path; + } + + public string FileSystemPath => this.filePath; + + public string Path => this.path; + + + public string PublicUrl + { + get + { + throw new NotImplementedException(); + } + } + + public Task DeleteAsync() + { + File.Delete(this.filePath); + return Task.FromResult(true); + } + + public Task GetExpirableUriAsync() + { + throw new NotImplementedException(); + } + + public async Task ReadAsync() + { + return File.OpenRead(this.filePath); + } + + public async Task UpdateAsync(Stream stream) + { + using (var file = File.Open(this.filePath, FileMode.Truncate, FileAccess.Write)) + { + await stream.CopyToAsync(file); + } + } + } +} diff --git a/src/GeekLearning.Storage.FileSystem/project.json b/src/GeekLearning.Storage.FileSystem/project.json index 50830d8..4e1ebf7 100644 --- a/src/GeekLearning.Storage.FileSystem/project.json +++ b/src/GeekLearning.Storage.FileSystem/project.json @@ -12,7 +12,6 @@ "NETStandard.Library": "1.5.0-rc2-24027", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0-rc2-final", "Microsoft.Extensions.Options": "1.0.0-rc2-final", - "Microsoft.AspNetCore.Hosting.Abstractions": "1.0.0-rc2-final", "GeekLearning.Storage": "*" }, diff --git a/src/GeekLearning.Storage/IFileReference.cs b/src/GeekLearning.Storage/IFileReference.cs new file mode 100644 index 0000000..1b46383 --- /dev/null +++ b/src/GeekLearning.Storage/IFileReference.cs @@ -0,0 +1,18 @@ +namespace GeekLearning.Storage +{ + using System.IO; + using System.Threading.Tasks; + + public interface IFileReference: IPrivateFileReference + { + string PublicUrl { get; } + + Task ReadAsync(); + + Task DeleteAsync(); + + Task UpdateAsync(Stream stream); + + Task GetExpirableUriAsync(); + } +} diff --git a/src/GeekLearning.Storage/IPrivateFileReference.cs b/src/GeekLearning.Storage/IPrivateFileReference.cs new file mode 100644 index 0000000..570d710 --- /dev/null +++ b/src/GeekLearning.Storage/IPrivateFileReference.cs @@ -0,0 +1,7 @@ +namespace GeekLearning.Storage +{ + public interface IPrivateFileReference + { + string Path { get; } + } +} diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index f9debe9..c8f09e7 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -1,24 +1,29 @@ namespace GeekLearning.Storage { + using System; using System.IO; using System.Threading.Tasks; public interface IStore { - Task List(string path); + Task ListAsync(string path); - Task Delete(string path); + Task ListAsync(string path, string searchPattern); - Task Read(string path); + Task GetAsync(IPrivateFileReference file); - Task ReadAllBytes(string path); + Task GetAsync(Uri file); - Task ReadAllText(string path); + Task DeleteAsync(IPrivateFileReference file); - Task Save(byte[] data, string path, string mimeType); + Task ReadAsync(IPrivateFileReference file); - Task Save(Stream data, string path, string mimeType); + Task ReadAllBytesAsync(IPrivateFileReference file); - Task GetExpirableUri(string uri); + Task ReadAllTextAsync(IPrivateFileReference file); + + Task SaveAsync(byte[] data, IPrivateFileReference file, string mimeType); + + Task SaveAsync(Stream data, IPrivateFileReference file, string mimeType); } } diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs new file mode 100644 index 0000000..76892bc --- /dev/null +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -0,0 +1,48 @@ +namespace GeekLearning.Storage +{ + using GeekLearning.Storage; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + + public static class IStoreExtensions + { + public static Task DeleteAsync(this IStore store, string path) + { + return store.DeleteAsync(new Internal.PrivateFileReference(path)); + } + + public static Task GetAsync(this IStore store, string path) + { + return store.GetAsync(new Internal.PrivateFileReference(path)); + } + + public static Task ReadAsync(this IStore store, string path) + { + return store.ReadAsync(new Internal.PrivateFileReference(path)); + } + + public static Task ReadAllBytesAsync(this IStore store, string path) + { + return store.ReadAllBytesAsync(new Internal.PrivateFileReference(path)); + } + + public static Task ReadAllTextAsync(this IStore store, string path) + { + return store.ReadAllTextAsync(new Internal.PrivateFileReference(path)); + } + + public static Task SaveAsync(this IStore store, byte[] data, string path, string mimeType) + { + return store.SaveAsync(data, new Internal.PrivateFileReference(path), mimeType); + } + + public static Task SaveAsync(this IStore store, Stream data, string path, string mimeType) + { + return store.SaveAsync(data, new Internal.PrivateFileReference(path), mimeType); + } + } +} diff --git a/src/GeekLearning.Storage/Internal/PrivateFileReference.cs b/src/GeekLearning.Storage/Internal/PrivateFileReference.cs new file mode 100644 index 0000000..4e0deb4 --- /dev/null +++ b/src/GeekLearning.Storage/Internal/PrivateFileReference.cs @@ -0,0 +1,11 @@ +namespace GeekLearning.Storage.Internal +{ + public class PrivateFileReference : IPrivateFileReference + { + public PrivateFileReference(string path) + { + this.Path = path; + } + public string Path { get; } + } +} From de5e193664d03a1f36827134c94a2f1fff3ca1fa Mon Sep 17 00:00:00 2001 From: Cyprien Autexier Date: Mon, 4 Jul 2016 18:02:53 +0200 Subject: [PATCH 2/2] first integration tests various bugfixes additionnal api tweaks --- src/GeekLearning.Storage.Azure/AzureStore.cs | 87 +++++++++---- .../Internal/AzureFileReference.cs | 3 +- .../Internal/AzureListDirectoryWrapper.cs | 62 ++++++++++ .../Internal/AzureListFileWrapper.cs | 43 +++++++ src/GeekLearning.Storage.Azure/project.json | 3 +- .../FileSystemStore.cs | 21 ++-- .../Internal/FileSystemFileReference.cs | 2 +- .../project.json | 3 +- src/GeekLearning.Storage/IStore.cs | 4 +- src/GeekLearning.Storage/IStoreExtensions.cs | 10 ++ .../GeekLearning.Integration.Test.xproj | 7 +- .../IntegrationCollection.cs | 14 +++ .../ListTests.cs | 97 +++++++++++++++ .../GeekLearning.Integration.Test/Program.cs | 2 + .../ReadTests.cs | 72 +++++++++++ .../SubDirectory/TextFile2.txt | 1 + .../SampleDirectory/TextFile.txt | 1 + .../SampleDirectory/template.hbs | 1 + .../StoresFixture.cs | 116 ++++++++++++++++++ .../appsettings.development.json | 19 +++ .../appsettings.json | 20 +++ .../project.json | 32 ++++- 22 files changed, 575 insertions(+), 45 deletions(-) create mode 100644 src/GeekLearning.Storage.Azure/Internal/AzureListDirectoryWrapper.cs create mode 100644 src/GeekLearning.Storage.Azure/Internal/AzureListFileWrapper.cs create mode 100644 tests/GeekLearning.Integration.Test/IntegrationCollection.cs create mode 100644 tests/GeekLearning.Integration.Test/ListTests.cs create mode 100644 tests/GeekLearning.Integration.Test/ReadTests.cs create mode 100644 tests/GeekLearning.Integration.Test/SampleDirectory/SubDirectory/TextFile2.txt create mode 100644 tests/GeekLearning.Integration.Test/SampleDirectory/TextFile.txt create mode 100644 tests/GeekLearning.Integration.Test/SampleDirectory/template.hbs create mode 100644 tests/GeekLearning.Integration.Test/StoresFixture.cs create mode 100644 tests/GeekLearning.Integration.Test/appsettings.development.json create mode 100644 tests/GeekLearning.Integration.Test/appsettings.json diff --git a/src/GeekLearning.Storage.Azure/AzureStore.cs b/src/GeekLearning.Storage.Azure/AzureStore.cs index 9587f66..c2da086 100644 --- a/src/GeekLearning.Storage.Azure/AzureStore.cs +++ b/src/GeekLearning.Storage.Azure/AzureStore.cs @@ -98,44 +98,83 @@ public async Task SaveAsync(byte[] data, IPrivateFileReference f return new Internal.AzureFileReference(blockBlob); } - public async Task ListAsync(string path) + //public async Task ListAsync(string path) + //{ + // if (string.IsNullOrEmpty(path) || path == "/" || path == "\\") + // { + // path = ""; + // } + + // BlobContinuationToken continuationToken = null; + // List results = new List(); + // do + // { + // var response = await this.container.Value.ListBlobsSegmentedAsync(path, true, BlobListingDetails.None, null, continuationToken, new BlobRequestOptions(), new OperationContext()); + // continuationToken = response.ContinuationToken; + // results.AddRange(response.Results); + // } + // while (continuationToken != null); + + // return results.Select(blob => new Internal.AzureFileReference(blob)).ToArray(); + //} + + public async Task ListAsync(string path, bool recursive) { + if (string.IsNullOrWhiteSpace(path)) + { + path = null; + } + else + { + if (!path.EndsWith("/")) + { + path = path + "/"; + } + } + BlobContinuationToken continuationToken = null; List results = new List(); do { - var response = await this.container.Value.ListBlobsSegmentedAsync(path, continuationToken); + var response = await this.container.Value.ListBlobsSegmentedAsync(path, recursive, BlobListingDetails.None, null, continuationToken, new BlobRequestOptions(), new OperationContext()); continuationToken = response.ContinuationToken; results.AddRange(response.Results); } while (continuationToken != null); - return results.Select(blob => new Internal.AzureFileReference(blob)).ToArray(); + return results.OfType().Select(blob => new Internal.AzureFileReference(blob)).ToArray(); } - public async Task ListAsync(string path, string searchPattern) + public async Task ListAsync(string path, string searchPattern, bool recursive) { + var firstWildCard = searchPattern.IndexOf('*'); + if (firstWildCard >= 0) + { + path += searchPattern.Substring(0, firstWildCard); + searchPattern = searchPattern.Substring(firstWildCard); + } + + Microsoft.Extensions.FileSystemGlobbing.Matcher matcher = new Microsoft.Extensions.FileSystemGlobbing.Matcher(StringComparison.Ordinal); + matcher.AddInclude(searchPattern); + + var operationContext = new OperationContext(); + BlobContinuationToken continuationToken = null; + List results = new List(); + do + { + var response = await this.container.Value.ListBlobsSegmentedAsync(path, recursive, BlobListingDetails.None, null, continuationToken, new BlobRequestOptions(), new OperationContext()); + continuationToken = response.ContinuationToken; + results.AddRange(response.Results); + } + while (continuationToken != null); + + var pathMap = results.Select(blob => new Internal.AzureFileReference(blob)).ToDictionary(x => x.Path); throw new NotSupportedException(); - //var firstWildCard = searchPattern.IndexOf('*'); - //if (firstWildCard >= 0) - //{ - // path += searchPattern.Substring(0, firstWildCard); - // searchPattern = searchPattern.Substring(firstWildCard); - //} - - //Regex.Escape(wildcardExpression).Replace(@"\*", ".*").Replace(@"\?", "."); - - //BlobContinuationToken continuationToken = null; - //List results = new List(); - //do - //{ - // var response = await this.container.Value.ListBlobsSegmentedAsync(path, continuationToken); - // continuationToken = response.ContinuationToken; - // results.AddRange(response.Results); - //} - //while (continuationToken != null); - - //return results.Select(blob => new Internal.AzureFileReference(blob)).ToArray(); + //var filteredResults = matcher.Execute( + // new Internal.AzureListDirectoryWrapper(path, + // pathMap.Values)); + + //return filteredResults.Files.Select(x => pathMap[x.Path]).ToArray(); } public async Task DeleteAsync(IPrivateFileReference file) diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs index a45c5a1..4b257f2 100644 --- a/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs +++ b/src/GeekLearning.Storage.Azure/Internal/AzureFileReference.cs @@ -28,7 +28,7 @@ public AzureFileReference(string path, ICloudBlob cloudBlob) } public AzureFileReference(ICloudBlob cloudBlob) : - this(cloudBlob.Uri.MakeRelativeUri(cloudBlob.Container.Uri).ToString(), cloudBlob) + this(cloudBlob.Name, cloudBlob) { } @@ -53,6 +53,7 @@ public async Task ReadInMemoryAsync() { var memoryStream = new MemoryStream(); await this.CloudBlob.DownloadRangeToStreamAsync(memoryStream, null, null); + memoryStream.Seek(0, SeekOrigin.Begin); return memoryStream; } diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureListDirectoryWrapper.cs b/src/GeekLearning.Storage.Azure/Internal/AzureListDirectoryWrapper.cs new file mode 100644 index 0000000..d6e2369 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Internal/AzureListDirectoryWrapper.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace GeekLearning.Storage.Azure.Internal +{ + public class AzureListDirectoryWrapper : DirectoryInfoBase + { + private string fullName; + + public AzureListDirectoryWrapper(FileSystemInfoBase childrens) + { + this.fullName = "root"; + this.ParentDirectory = null; + } + + public AzureListDirectoryWrapper(CloudBlobDirectory blobDirectory, AzureListDirectoryWrapper parent = null) + { + this.ParentDirectory = parent; + this.fullName = blobDirectory.Prefix; + } + + public override string FullName + { + get + { + return fullName; + } + } + + public override string Name + { + get + { + return fullName; + } + } + + public override DirectoryInfoBase ParentDirectory + { + get; + } + + public override IEnumerable EnumerateFileSystemInfos() + { + throw new NotImplementedException(); + } + + public override DirectoryInfoBase GetDirectory(string path) + { + throw new NotImplementedException(); + } + + public override FileInfoBase GetFile(string path) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GeekLearning.Storage.Azure/Internal/AzureListFileWrapper.cs b/src/GeekLearning.Storage.Azure/Internal/AzureListFileWrapper.cs new file mode 100644 index 0000000..787f705 --- /dev/null +++ b/src/GeekLearning.Storage.Azure/Internal/AzureListFileWrapper.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace GeekLearning.Storage.Azure.Internal +{ + public class AzureListFileWrapper : FileInfoBase + { + private CloudBlob blob; + + public AzureListFileWrapper(CloudBlob blob) + { + this.blob = blob; + } + + public override string FullName + { + get + { + return this.blob.Name; + } + } + + public override string Name + { + get + { + return this.blob.Name; + } + } + + public override DirectoryInfoBase ParentDirectory + { + get + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/GeekLearning.Storage.Azure/project.json b/src/GeekLearning.Storage.Azure/project.json index 8236ff6..e21a9bf 100644 --- a/src/GeekLearning.Storage.Azure/project.json +++ b/src/GeekLearning.Storage.Azure/project.json @@ -13,7 +13,8 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0-rc2-final", "Microsoft.Extensions.Options": "1.0.0-rc2-final", "WindowsAzure.Storage": "7.0.1-preview", - "GeekLearning.Storage": "*" + "GeekLearning.Storage": "*", + "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-final" }, "frameworks": { diff --git a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs index 35259d3..4ca816a 100644 --- a/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs +++ b/src/GeekLearning.Storage.FileSystem/FileSystemStore.cs @@ -47,33 +47,36 @@ public Task DeleteAsync(IPrivateFileReference file) return fileReference.DeleteAsync(); } - public Task ListAsync(string path) + public Task ListAsync(string path, bool recursive) { - var directoryPath = Path.Combine(this.absolutePath, path); + var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.absolutePath : Path.Combine(this.absolutePath, path); if (!Directory.Exists(directoryPath)) { return Task.FromResult(new IFileReference[0]); } return Task.FromResult(Directory.GetFiles(directoryPath) - .Select(fullPath => + .Select(fullPath => (IFileReference)new Internal.FileSystemFileReference(fullPath, fullPath.Replace(this.absolutePath, "") .Trim('/', '\\'))) .ToArray()); } - public Task ListAsync(string path, string searchPattern) + public Task ListAsync(string path, string searchPattern, bool recursive) { - var directoryPath = Path.Combine(this.absolutePath, path); + var directoryPath = (string.IsNullOrEmpty(path) || path == "/" || path == "\\") ? this.absolutePath : Path.Combine(this.absolutePath, path); if (!Directory.Exists(directoryPath)) { return Task.FromResult(new IFileReference[0]); } - return Task.FromResult(Directory.GetFiles(directoryPath, searchPattern) - .Select(fullPath => - (IFileReference)new Internal.FileSystemFileReference(fullPath, fullPath.Replace(this.absolutePath, "") - .Trim('/', '\\'))) + Microsoft.Extensions.FileSystemGlobbing.Matcher matcher = new Microsoft.Extensions.FileSystemGlobbing.Matcher(StringComparison.Ordinal); + matcher.AddInclude(searchPattern); + + var results = matcher.Execute(new Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoWrapper(new DirectoryInfo(path))); + + return Task.FromResult(results.Files + .Select(match => (IFileReference)new Internal.FileSystemFileReference(match.Path, match.Path.Replace(this.absolutePath, "").Trim('/', '\\'))) .ToArray()); } diff --git a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs index 9688128..662e835 100644 --- a/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs +++ b/src/GeekLearning.Storage.FileSystem/Internal/FileSystemFileReference.cs @@ -14,7 +14,7 @@ public class FileSystemFileReference : IFileReference public FileSystemFileReference(string filePath, string path) { this.filePath = filePath; - this.path = path; + this.path = path.Replace('\\', '/'); } public string FileSystemPath => this.filePath; diff --git a/src/GeekLearning.Storage.FileSystem/project.json b/src/GeekLearning.Storage.FileSystem/project.json index 4e1ebf7..8ef4eb3 100644 --- a/src/GeekLearning.Storage.FileSystem/project.json +++ b/src/GeekLearning.Storage.FileSystem/project.json @@ -12,7 +12,8 @@ "NETStandard.Library": "1.5.0-rc2-24027", "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0-rc2-final", "Microsoft.Extensions.Options": "1.0.0-rc2-final", - "GeekLearning.Storage": "*" + "GeekLearning.Storage": "*", + "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-final" }, "frameworks": { diff --git a/src/GeekLearning.Storage/IStore.cs b/src/GeekLearning.Storage/IStore.cs index c8f09e7..a5d6658 100644 --- a/src/GeekLearning.Storage/IStore.cs +++ b/src/GeekLearning.Storage/IStore.cs @@ -6,9 +6,9 @@ public interface IStore { - Task ListAsync(string path); + Task ListAsync(string path, bool recursive); - Task ListAsync(string path, string searchPattern); + Task ListAsync(string path, string searchPattern, bool recursive); Task GetAsync(IPrivateFileReference file); diff --git a/src/GeekLearning.Storage/IStoreExtensions.cs b/src/GeekLearning.Storage/IStoreExtensions.cs index 76892bc..c898252 100644 --- a/src/GeekLearning.Storage/IStoreExtensions.cs +++ b/src/GeekLearning.Storage/IStoreExtensions.cs @@ -10,6 +10,16 @@ public static class IStoreExtensions { + public static Task ListAsync(this IStore store, string path) + { + return store.ListAsync(path, recursive: false); + } + + public static Task ListAsync(this IStore store, string path, string searchPattern) + { + return store.ListAsync(path, searchPattern, recursive: false); + } + public static Task DeleteAsync(this IStore store, string path) { return store.DeleteAsync(new Internal.PrivateFileReference(path)); diff --git a/tests/GeekLearning.Integration.Test/GeekLearning.Integration.Test.xproj b/tests/GeekLearning.Integration.Test/GeekLearning.Integration.Test.xproj index 208aa5b..f22f170 100644 --- a/tests/GeekLearning.Integration.Test/GeekLearning.Integration.Test.xproj +++ b/tests/GeekLearning.Integration.Test/GeekLearning.Integration.Test.xproj @@ -4,7 +4,6 @@ 14.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - 590b21b0-2afa-4329-82ad-ef180c50eb5c @@ -13,9 +12,11 @@ .\bin\ v4.5.2 - 2.0 + + + - + \ No newline at end of file diff --git a/tests/GeekLearning.Integration.Test/IntegrationCollection.cs b/tests/GeekLearning.Integration.Test/IntegrationCollection.cs new file mode 100644 index 0000000..99cf1f7 --- /dev/null +++ b/tests/GeekLearning.Integration.Test/IntegrationCollection.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace GeekLearning.Integration.Test +{ + [CollectionDefinition(nameof(Integration))] + public class Integration: ICollectionFixture + { + + } +} diff --git a/tests/GeekLearning.Integration.Test/ListTests.cs b/tests/GeekLearning.Integration.Test/ListTests.cs new file mode 100644 index 0000000..b5c6b3f --- /dev/null +++ b/tests/GeekLearning.Integration.Test/ListTests.cs @@ -0,0 +1,97 @@ +namespace GeekLearning.Integration.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Xunit; + using GeekLearning.Storage; + using Microsoft.Extensions.DependencyInjection; + + [Collection(nameof(Integration)), Trait("Operation", "List"), Trait("Kind", "Integration")] + public class ListTests + { + StoresFixture storeFixture; + + public ListTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(ListRootFiles)), InlineData("azure"), InlineData("filesystem")] + public async Task ListRootFiles(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expected = new string[] { "TextFile.txt", "template.hbs" }; + + var results = await store.ListAsync(null); + + var missingFiles = expected.Except(results.Select(f => f.Path)).ToArray(); + + var unexpectedFiles = results.Select(f => f.Path).Except(expected).ToArray(); + + Assert.Empty(missingFiles); + Assert.Empty(unexpectedFiles); + } + + [Theory(DisplayName = nameof(ListEmptyPathFiles)), InlineData("azure"), InlineData("filesystem")] + public async Task ListEmptyPathFiles(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expected = new string[] { "TextFile.txt", "template.hbs" }; + + var results = await store.ListAsync(""); + + var missingFiles = expected.Except(results.Select(f => f.Path)).ToArray(); + + var unexpectedFiles = results.Select(f => f.Path).Except(expected).ToArray(); + + Assert.Empty(missingFiles); + Assert.Empty(unexpectedFiles); + } + + [Theory(DisplayName = nameof(ListSubDirectoryFiles)), InlineData("azure"), InlineData("filesystem")] + public async Task ListSubDirectoryFiles(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expected = new string[] { "SubDirectory/TextFile2.txt" }; + + var results = await store.ListAsync("SubDirectory"); + + var missingFiles = expected.Except(results.Select(f => f.Path)).ToArray(); + + var unexpectedFiles = results.Select(f => f.Path).Except(expected).ToArray(); + + Assert.Empty(missingFiles); + Assert.Empty(unexpectedFiles); + } + + [Theory(DisplayName = nameof(ListSubDirectoryFilesWithTrailingSlash)), InlineData("azure"), InlineData("filesystem")] + public async Task ListSubDirectoryFilesWithTrailingSlash(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expected = new string[] { "SubDirectory/TextFile2.txt" }; + + var results = await store.ListAsync("SubDirectory/"); + + var missingFiles = expected.Except(results.Select(f => f.Path)).ToArray(); + + var unexpectedFiles = results.Select(f => f.Path).Except(expected).ToArray(); + + Assert.Empty(missingFiles); + Assert.Empty(unexpectedFiles); + } + } +} diff --git a/tests/GeekLearning.Integration.Test/Program.cs b/tests/GeekLearning.Integration.Test/Program.cs index 27b28c4..fb08d10 100644 --- a/tests/GeekLearning.Integration.Test/Program.cs +++ b/tests/GeekLearning.Integration.Test/Program.cs @@ -7,6 +7,8 @@ namespace GeekLearning.Integration.Test { public class Program { + //public static IConfigurationRoot Configuration { get; private set; } + public static void Main(string[] args) { } diff --git a/tests/GeekLearning.Integration.Test/ReadTests.cs b/tests/GeekLearning.Integration.Test/ReadTests.cs new file mode 100644 index 0000000..ba1f77b --- /dev/null +++ b/tests/GeekLearning.Integration.Test/ReadTests.cs @@ -0,0 +1,72 @@ +namespace GeekLearning.Integration.Test +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Xunit; + using GeekLearning.Storage; + using Microsoft.Extensions.DependencyInjection; + using System.IO; + + [Collection(nameof(Integration)), Trait("Operation", "Read"), Trait("Kind", "Integration")] + public class ReadTests + { + StoresFixture storeFixture; + + public ReadTests(StoresFixture fixture) + { + this.storeFixture = fixture; + } + + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + public async Task ReadAllTextFromRootFile(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expectedText = "42"; + + var actualText = await store.ReadAllTextAsync("TextFile.txt"); + + Assert.Equal(expectedText, actualText); + } + + [Theory(DisplayName = nameof(ReadAllTextFromRootFile)), InlineData("azure"), InlineData("filesystem")] + public async Task ReadAllTextFromSubdirectoryFile(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expectedText = ">42"; + + var actualText = await store.ReadAllTextAsync("SubDirectory/TextFile2.txt"); + + Assert.Equal(expectedText, actualText); + } + + + [Theory(DisplayName = nameof(ReadFileFromSubdirectoryFile)), InlineData("azure"), InlineData("filesystem")] + public async Task ReadFileFromSubdirectoryFile(string storeName) + { + var storageFactory = this.storeFixture.Services.GetRequiredService(); + + var store = storageFactory.GetStore(storeName); + + var expectedText = ">42"; + + var file = await store.GetAsync("SubDirectory/TextFile2.txt"); + + string actualText = null; + + using (var reader = new StreamReader(await file.ReadAsync())) + { + actualText = await reader.ReadToEndAsync(); + } + + Assert.Equal(expectedText, actualText); + } + } +} diff --git a/tests/GeekLearning.Integration.Test/SampleDirectory/SubDirectory/TextFile2.txt b/tests/GeekLearning.Integration.Test/SampleDirectory/SubDirectory/TextFile2.txt new file mode 100644 index 0000000..306a14e --- /dev/null +++ b/tests/GeekLearning.Integration.Test/SampleDirectory/SubDirectory/TextFile2.txt @@ -0,0 +1 @@ +>42 \ No newline at end of file diff --git a/tests/GeekLearning.Integration.Test/SampleDirectory/TextFile.txt b/tests/GeekLearning.Integration.Test/SampleDirectory/TextFile.txt new file mode 100644 index 0000000..7900b74 --- /dev/null +++ b/tests/GeekLearning.Integration.Test/SampleDirectory/TextFile.txt @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/tests/GeekLearning.Integration.Test/SampleDirectory/template.hbs b/tests/GeekLearning.Integration.Test/SampleDirectory/template.hbs new file mode 100644 index 0000000..7b97894 --- /dev/null +++ b/tests/GeekLearning.Integration.Test/SampleDirectory/template.hbs @@ -0,0 +1 @@ +{{.}} \ No newline at end of file diff --git a/tests/GeekLearning.Integration.Test/StoresFixture.cs b/tests/GeekLearning.Integration.Test/StoresFixture.cs new file mode 100644 index 0000000..1f21a5e --- /dev/null +++ b/tests/GeekLearning.Integration.Test/StoresFixture.cs @@ -0,0 +1,116 @@ +namespace GeekLearning.Integration.Test +{ + using Microsoft.Extensions.DependencyInjection; + using Storage; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Xunit; + using Microsoft.Extensions.PlatformAbstractions; + using Microsoft.Extensions.Configuration; + using System.Diagnostics; + using Microsoft.WindowsAzure.Storage; + using Microsoft.WindowsAzure.Storage.Blob; + + public class StoresFixture : IDisposable + { + private CloudStorageAccount cloudStorageAccount; + private CloudBlobContainer container; + + public StoresFixture() + { + this.BasePath = PlatformServices.Default.Application.ApplicationBasePath; + + var builder = new ConfigurationBuilder() + .SetBasePath(BasePath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.development.json", optional: true). + AddInMemoryCollection(new KeyValuePair[] { + new KeyValuePair("Storage:Stores:azure:Parameters:Container", Guid.NewGuid().ToString("N").ToLower()) + }); + + this.Configuration = builder.Build(); + + var services = new ServiceCollection(); + + services.AddOptions(); + + services.AddStorage() + .AddAzureStorage() + .AddFileSystemStorage(BasePath); + + services.Configure(Configuration.GetSection("Storage")); + + this.Services = services.BuildServiceProvider(); + + ResetStores(); + + } + + private void ResetStores() + { + ResetAzureStore(); + ResetFileSystemStore(); + } + + private void ResetFileSystemStore() + { + var directoryName = Configuration["Storage:Stores:filesystem:Parameters:Path"]; + var process = Process.Start(new ProcessStartInfo("robocopy.exe") + { + Arguments = $"\"{System.IO.Path.Combine(BasePath, "SampleDirectory")}\" \"{System.IO.Path.Combine(BasePath, directoryName)}\" /MIR" + }); + + if (process.WaitForExit(30000)) + { + + } + else + { + throw new TimeoutException("File system store was not reset properly"); + } + } + + private void ResetAzureStore() + { + var azCopy = System.IO.Path.Combine( + Environment.ExpandEnvironmentVariables(Configuration["AzCopyPath"]), + "AzCopy.exe"); + + + cloudStorageAccount = Microsoft.WindowsAzure.Storage.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; + + var client = cloudStorageAccount.CreateCloudBlobClient(); + + container = client.GetContainerReference(containerName); + container.CreateAsync().Wait(); + + var process = Process.Start(new ProcessStartInfo(azCopy) + { + Arguments = $"/Source:\"{System.IO.Path.Combine(BasePath, "SampleDirectory")}\" /Dest:\"{dest}\" /DestKey:{key} /S" + }); + + if (process.WaitForExit(30000)) + { + + } + else + { + 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(); + } + } +} diff --git a/tests/GeekLearning.Integration.Test/appsettings.development.json b/tests/GeekLearning.Integration.Test/appsettings.development.json new file mode 100644 index 0000000..282588b --- /dev/null +++ b/tests/GeekLearning.Integration.Test/appsettings.development.json @@ -0,0 +1,19 @@ +{ + "Storage": { + "Stores": { + "filesystem": { + "Provider": "FileSystem", + "Parameters": { + "Path": "Templates" + } + }, + "azure": { + "Provider": "Azure", + "Parameters": { + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=sandordevtest;AccountKey=Fat8BWzlIFDnjoS41WTHW0dT8U0rDDbUOrw9jOduHBVHtu1RkIfApQpBPEalaZPsOBTPBQBFqEbjWpY6QJpiZA==", + "Container": "templates" + } + } + } + } +} diff --git a/tests/GeekLearning.Integration.Test/appsettings.json b/tests/GeekLearning.Integration.Test/appsettings.json new file mode 100644 index 0000000..22a101c --- /dev/null +++ b/tests/GeekLearning.Integration.Test/appsettings.json @@ -0,0 +1,20 @@ +{ + "AzCopyPath": "%ProgramFiles(x86)%\\Microsoft SDKs\\Azure\\AzCopy", + "Storage": { + "Stores": { + "filesystem": { + "Provider": "FileSystem", + "Parameters": { + "Path": "Templates" + } + }, + "azure": { + "Provider": "Azure", + "Parameters": { + "ConnectionString": "YourConnectionString", + "Container": "templates" + } + } + } + } +} diff --git a/tests/GeekLearning.Integration.Test/project.json b/tests/GeekLearning.Integration.Test/project.json index 7747fc2..3ccaab3 100644 --- a/tests/GeekLearning.Integration.Test/project.json +++ b/tests/GeekLearning.Integration.Test/project.json @@ -1,13 +1,32 @@ -{ +{ "version": "1.0.0-*", "buildOptions": { - "emitEntryPoint": true + "emitEntryPoint": true, + "copyToOutput": [ + "SampleDirectory/**/*.*", + "appsettings.json", + "appsettings.*.json" + ] }, + "testRunner": "xunit", + "dependencies": { "GeekLearning.Storage": "*", "GeekLearning.Storage.Azure": "*", - "GeekLearning.Storage.FileSystem": "*" + "GeekLearning.Storage.FileSystem": "*", + "Microsoft.NETCore.Platforms": "1.0.1-rc2-24027", + "dotnet-test-xunit": "1.0.0-rc2-build10025", + "xunit": "2.1.0-rc2-build3176", + "xunit.runner.visualstudio": "2.1.0", + "Microsoft.Extensions.DependencyInjection": "1.0.0-rc2-final", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-final", + "Microsoft.Extensions.Configuration": "1.0.0-rc2-final", + "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final", + "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final", + "Microsoft.Extensions.Options": "1.0.0-rc2-final", + "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0-rc2-final" }, "frameworks": { @@ -19,6 +38,13 @@ "version": "1.0.0-rc2-3002702" } } + }, + "net451": { + "frameworkAssemblies": { + }, + "dependencies": { + "xunit.runner.console": "2.1.0" + } } } }