Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions GeekLearning.Storage.sln
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{520AB1D3-C50
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}"
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -61,6 +63,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
{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -72,5 +78,6 @@ Global
{63416AEA-DA51-4D62-B566-DB7D9BC800DC} = {FBAC4C17-D755-49A9-959D-18FD6B95B543}
{9D94CD6C-9451-449A-BED2-1C07D624A8E0} = {520AB1D3-C501-40FD-ACEB-7CC0D1F00B90}
{590B21B0-2AFA-4329-82AD-EF180C50EB5C} = {2DAF5EF9-8F8E-4C51-BE2D-8D63CA143360}
{8C02EBBE-9EC8-4F47-9464-5A94BDE25A8F} = {520AB1D3-C501-40FD-ACEB-7CC0D1F00B90}
EndGlobalSection
EndGlobal
204 changes: 111 additions & 93 deletions src/GeekLearning.Storage.Azure/AzureStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Core;
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -10,10 +11,8 @@

public class AzureStore : IStore
{
private string connectionString;
private Lazy<CloudBlobContainer> container;
private Lazy<CloudBlobClient> client;
private string containerName;
private Lazy<CloudBlobContainer> container;

public AzureStore(string storeName, string connectionString, string containerName)
{
Expand All @@ -29,93 +28,12 @@ public AzureStore(string storeName, string connectionString, string containerNam
throw new ArgumentNullException("containerName");
}

this.connectionString = connectionString;
this.containerName = containerName;

client = new Lazy<CloudBlobClient>(() => CloudStorageAccount.Parse(this.connectionString).CreateCloudBlobClient());
container = new Lazy<CloudBlobContainer>(() => this.client.Value.GetContainerReference(this.containerName));
this.client = new Lazy<CloudBlobClient>(() => CloudStorageAccount.Parse(connectionString).CreateCloudBlobClient());
this.container = new Lazy<CloudBlobContainer>(() => this.client.Value.GetContainerReference(containerName));
}

public string Name { get; }

private async Task<Internal.AzureFileReference> InternalGetAsync(IPrivateFileReference file, bool withMetadata)
{
var azureFile = file as Internal.AzureFileReference;
if (azureFile != null)
{
return azureFile;
}

try
{
var blob = await this.container.Value.GetBlobReferenceFromServerAsync(file.Path);
return new Internal.AzureFileReference(file.Path, blob);
}
catch (StorageException storageException)
{
if (storageException.RequestInformation.HttpStatusCode == 404)
{
return null;
}
throw;
}
}

public async Task<IFileReference> GetAsync(IPrivateFileReference file, bool withMetadata)
{
return await InternalGetAsync(file, withMetadata);
}

public async Task<IFileReference> GetAsync(Uri uri, bool withMetadata)
{
if (uri.IsAbsoluteUri)
{
return new Internal.AzureFileReference(await this.client.Value.GetBlobReferenceFromServerAsync(uri));
}
else
{
return new Internal.AzureFileReference(await this.container.Value.GetBlobReferenceFromServerAsync(uri.ToString()));
}
}

public async Task<Stream> ReadAsync(IPrivateFileReference file)
{
var fileReference = await InternalGetAsync(file, false);
return await fileReference.ReadInMemoryAsync();
}

public async Task<byte[]> ReadAllBytesAsync(IPrivateFileReference file)
{
var fileReference = await InternalGetAsync(file, false);
return await fileReference.ReadAllBytesAsync();
}

public async Task<string> ReadAllTextAsync(IPrivateFileReference file)
{
var fileReference = await InternalGetAsync(file, false);
return await fileReference.ReadAllTextAsync();
}

public async Task<IFileReference> SaveAsync(Stream data, IPrivateFileReference file, string contentType)
{
var blockBlob = this.container.Value.GetBlockBlobReference(file.Path);
await blockBlob.UploadFromStreamAsync(data);
blockBlob.Properties.ContentType = contentType;
blockBlob.Properties.CacheControl = "max-age=300, must-revalidate";
await blockBlob.SetPropertiesAsync();
return new Internal.AzureFileReference(blockBlob);
}

public async Task<IFileReference> SaveAsync(byte[] data, IPrivateFileReference file, string contentType)
{
var blockBlob = this.container.Value.GetBlockBlobReference(file.Path);
await blockBlob.UploadFromByteArrayAsync(data, 0, data.Length);
blockBlob.Properties.ContentType = contentType;
blockBlob.Properties.CacheControl = "max-age=300, must-revalidate";
await blockBlob.SetPropertiesAsync();
return new Internal.AzureFileReference(blockBlob);
}

public async Task<IFileReference[]> ListAsync(string path, bool recursive, bool withMetadata)
{
if (string.IsNullOrWhiteSpace(path))
Expand All @@ -141,7 +59,7 @@ public async Task<IFileReference[]> ListAsync(string path, bool recursive, bool
}
while (continuationToken != null);

return results.OfType<ICloudBlob>().Select(blob => new Internal.AzureFileReference(blob)).ToArray();
return results.OfType<ICloudBlob>().Select(blob => new Internal.AzureFileReference(blob, withMetadata: withMetadata)).ToArray();
}

public async Task<IFileReference[]> ListAsync(string path, string searchPattern, bool recursive, bool withMetadata)
Expand Down Expand Up @@ -181,7 +99,7 @@ public async Task<IFileReference[]> ListAsync(string path, string searchPattern,
}
while (continuationToken != null);

var pathMap = results.OfType<ICloudBlob>().Select(blob => new Internal.AzureFileReference(blob)).ToDictionary(x => x.Path);
var pathMap = results.OfType<ICloudBlob>().Select(blob => new Internal.AzureFileReference(blob, withMetadata: withMetadata)).ToDictionary(x => x.Path);

var filteredResults = matcher.Execute(
new Internal.AzureListDirectoryWrapper(path,
Expand All @@ -190,19 +108,119 @@ public async Task<IFileReference[]> ListAsync(string path, string searchPattern,
return filteredResults.Files.Select(x => pathMap[path + x.Path]).ToArray();
}

public async Task<IFileReference> GetAsync(IPrivateFileReference file, bool withMetadata)
{
return await this.InternalGetAsync(file, withMetadata);
}

public async Task<IFileReference> GetAsync(Uri uri, bool withMetadata)
{
return await this.InternalGetAsync(uri, withMetadata);
}

public async Task DeleteAsync(IPrivateFileReference file)
{
var fileReference = await InternalGetAsync(file, false);
var fileReference = await this.InternalGetAsync(file);
await fileReference.DeleteAsync();
}

public async Task<IFileReference> AddMetadataAsync(IPrivateFileReference file, IDictionary<string, string> metadata)
public async Task<Stream> ReadAsync(IPrivateFileReference file)
{
var fileReference = await InternalGetAsync(file, false);
var fileReference = await this.InternalGetAsync(file);
return await fileReference.ReadInMemoryAsync();
}

public async Task<byte[]> ReadAllBytesAsync(IPrivateFileReference file)
{
var fileReference = await this.InternalGetAsync(file);
return await fileReference.ReadAllBytesAsync();
}

public async Task<string> ReadAllTextAsync(IPrivateFileReference file)
{
var fileReference = await this.InternalGetAsync(file);
return await fileReference.ReadAllTextAsync();
}

public async Task<IFileReference> SaveAsync(byte[] data, IPrivateFileReference file, string contentType)
{
using (var stream = new SyncMemoryStream(data, 0, data.Length))
{
return await this.SaveAsync(stream, file, contentType);
}
}

public async Task<IFileReference> SaveAsync(Stream data, IPrivateFileReference file, string contentType)
{
var blockBlob = this.container.Value.GetBlockBlobReference(file.Path);

if (await blockBlob.ExistsAsync())
{
await blockBlob.FetchAttributesAsync();
}

await blockBlob.UploadFromStreamAsync(data);

await fileReference.AddMetadataAsync(metadata);
var reference = new Internal.AzureFileReference(blockBlob, withMetadata: true);

return fileReference;
reference.Properties.ContentType = contentType;
await reference.SavePropertiesAsync();

return reference;
}

private async Task<Internal.AzureFileReference> InternalGetAsync(IPrivateFileReference file, bool withMetadata = false)
{
var azureFile = file as Internal.AzureFileReference;
if (azureFile != null)
{
return azureFile;
}

return await this.InternalGetAsync(new Uri(file.Path, UriKind.Relative), withMetadata);
}

private async Task<Internal.AzureFileReference> InternalGetAsync(Uri uri, bool withMetadata)
{
try
{
ICloudBlob blob;

if (uri.IsAbsoluteUri)
{
// When the URI is absolute, we cannot get a simple reference to the blob, so the
// properties and metadata are fetched, even if it was not asked.

blob = await this.client.Value.GetBlobReferenceFromServerAsync(uri);
withMetadata = true;
}
else
{
if (withMetadata)
{
blob = await this.container.Value.GetBlobReferenceFromServerAsync(uri.ToString());
}
else
{
blob = this.container.Value.GetBlockBlobReference(uri.ToString());
if (!(await blob.ExistsAsync()))
{
return null;
}
}
}

return new Internal.AzureFileReference(blob, withMetadata);
}
catch (StorageException storageException)
{
if (storageException.RequestInformation.HttpStatusCode == 404)
{
return null;
}

throw;
}
}
}
}
41 changes: 41 additions & 0 deletions src/GeekLearning.Storage.Azure/Internal/AzureFileProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace GeekLearning.Storage.Azure.Internal
{
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;

public class AzureFileProperties : IFileProperties
{
private const string DefaultCacheControl = "max-age=300, must-revalidate";
private readonly ICloudBlob cloudBlob;

public AzureFileProperties(ICloudBlob cloudBlob)
{
this.cloudBlob = cloudBlob;
if (string.IsNullOrEmpty(this.cloudBlob.Properties.CacheControl))
{
this.cloudBlob.Properties.CacheControl = DefaultCacheControl;
}
}

public DateTimeOffset? LastModified => this.cloudBlob.Properties.LastModified;

public long Length => this.cloudBlob.Properties.Length;

public string ContentType
{
get { return this.cloudBlob.Properties.ContentType; }
set { this.cloudBlob.Properties.ContentType = value; }
}

public string ETag => this.cloudBlob.Properties.ETag;

public string CacheControl
{
get { return this.cloudBlob.Properties.CacheControl; }
set { this.cloudBlob.Properties.CacheControl = value; }
}

public IDictionary<string, string> Metadata => this.cloudBlob.Metadata;
}
}
Loading