-
-
Save bradleypatton/4705a99b4fc89a8b5fe314eebf695008 to your computer and use it in GitHub Desktop.
Abstraction for Azure Files and Local Storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Azure; | |
using Azure.Storage.Files.Shares; | |
using Azure.Storage.Files.Shares.Models; | |
using Neptune.ApplicationSupport; | |
using Neptune.Core; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
namespace Neptune.Web { | |
public interface IStorageClient { | |
public IStorageFolder GetFolder(string folderName); | |
public bool Exists(string folderName); | |
} | |
public interface IStorageFolder { | |
public Task<IStorageFolder> CreateSubfolderAsync(string folderName); | |
public IStorageFolder GetSubfolder(string folderName, bool createIfNotExists = false); | |
/// <summary> | |
/// WARNING: Delete the folder and all contents (recursively) | |
/// </summary> | |
public Task DeleteAllAsync(); | |
/// <summary> | |
/// Delete the specified filename in the current folder. | |
/// </summary> | |
/// <param name="filename"></param> | |
public Task DeleteFileAsync(string filename); | |
/// <summary> | |
/// Return a list of files that have the matching file extensions. | |
/// </summary> | |
/// <param name="exts">Variable number of string parameters</param> | |
/// <remarks>Called like: GetFiles("log", "json")</remarks> | |
public IEnumerable<string> GetFiles(params string[] exts); | |
/// <summary> | |
/// Return a list of files whose names match the supplied regex pattern. | |
/// </summary> | |
/// <param name="regexPattern">Valid regex pattern</param> | |
public IEnumerable<string> GetFiles(string regexPattern = ""); | |
/// <summary> | |
/// Move file from the source folder to the current folder. Delete in source once move is complete. | |
/// </summary> | |
/// <param name="sourceFolder"></param> | |
/// <param name="filename"></param> | |
public async Task MoveToAsync(IStorageFolder sourceFolder, string filename) { | |
//copy file | |
using var stream = sourceFolder.OpenRead(filename); | |
await WriteFileAsync(filename, stream); | |
stream.Close(); | |
// delete source file | |
await sourceFolder.DeleteFileAsync(filename); | |
} | |
/// <summary> | |
/// Return an open stream on the specified file. Caller must close stream. | |
/// </summary> | |
public Stream OpenRead(string filename); | |
/// <summary> | |
/// Return the file contents as a string. | |
/// </summary> | |
/// <param name="filename"></param> | |
/// <returns>string</returns> | |
public Task<string> ReadFileAsync(string filename); | |
/// <summary> | |
/// Write the string contents to the specifed file. | |
/// </summary> | |
public Task WriteFileAsync(string filename, string contents); | |
/// <summary> | |
/// Write the open stream to the specifed file. | |
/// </summary> | |
public Task WriteFileAsync(string filename, Stream stream); | |
} | |
public class AzureStorageClient : IStorageClient { | |
private readonly ShareClient _shareClient; | |
public AzureStorageClient(ShareClient shareClient) { | |
_shareClient = shareClient; | |
} | |
public bool Exists(string folderName) { | |
var directoryClient = _shareClient.GetDirectoryClient(folderName); | |
return directoryClient.Exists(); | |
} | |
public IStorageFolder GetFolder(string folderName) { | |
var directoryClient = _shareClient.GetDirectoryClient(folderName); | |
return new AzureFolder(directoryClient); | |
} | |
} | |
public class AzureFolder : IStorageFolder { | |
private readonly ShareDirectoryClient _dirClient; | |
public AzureFolder(ShareDirectoryClient directoryClient) { | |
_dirClient = directoryClient; | |
} | |
public async Task<IStorageFolder> CreateSubfolderAsync(string folderName) { | |
var directoryClient = await _dirClient.CreateSubdirectoryAsync(folderName); | |
return await Task.FromResult(new AzureFolder(directoryClient.Value)); | |
} | |
/// <summary> | |
/// WARNING: Delete the folder and all contents (recursively) | |
/// </summary> | |
public async Task DeleteAllAsync() { | |
var remaining = new Queue<ShareDirectoryClient>(); | |
remaining.Enqueue(_dirClient); | |
while (remaining.Count > 0) { | |
ShareDirectoryClient dir = remaining.Dequeue(); | |
await foreach (ShareFileItem item in dir.GetFilesAndDirectoriesAsync()) { | |
if (item.IsDirectory) { | |
var subDir = dir.GetSubdirectoryClient(item.Name); | |
await subDir.DeleteAllAsync(); | |
} else { | |
await dir.DeleteFileAsync(item.Name); | |
} | |
} | |
await dir.DeleteAsync(); | |
} | |
} | |
public async Task DeleteFileAsync(string filename) { | |
await _dirClient.DeleteFileAsync(filename); | |
} | |
public IEnumerable<string> GetFiles(string regexPattern = "") { | |
Regex regex = null; | |
if (!string.IsNullOrEmpty(regexPattern)) regex = new Regex(regexPattern); | |
// Return a list of filenames matching an optional regex pattern | |
return _dirClient.GetFilesAndDirectories() | |
.Where(item => !item.IsDirectory && regex != null && regex.IsMatch(item.Name)) | |
.Select(item => item.Name); | |
} | |
public IEnumerable<string> GetFiles(params string[] exts) { | |
var files = new List<string>(); | |
foreach (var item in _dirClient.GetFilesAndDirectories()) { | |
if (!item.IsDirectory && MatchPattern(item, exts)) { | |
files.Add(item.Name); | |
} | |
} | |
return files; | |
} | |
private bool MatchPattern(ShareFileItem item, string[] exts) { | |
throw new NotImplementedException(); | |
} | |
public IStorageFolder GetSubfolder(string folderName, bool createIfNotExists = false) { | |
var directoryClient = _dirClient.GetSubdirectoryClient(folderName); | |
// If set to true have the directory client create the directory if needed. | |
if (createIfNotExists) directoryClient.CreateIfNotExists(); | |
return new AzureFolder(directoryClient); | |
} | |
public Stream OpenRead(string filename) { | |
var fileClient = _dirClient.GetFileClient(filename); | |
return fileClient.OpenRead(); | |
} | |
public async Task<string> ReadFileAsync(string filename) { | |
using var reader = new StreamReader(OpenRead(filename)); | |
string contents = await reader.ReadToEndAsync(); | |
return await Task.FromResult(contents); | |
} | |
public async Task WriteFileAsync(string filename, string contents) { | |
using var stream = contents.ToStream(); | |
await WriteFileAsync(filename, stream); | |
} | |
public async Task WriteFileAsync(string filename, Stream stream) { | |
// Azure allows for 4MB max chunks (4 x 1024 x 1024 = 4194304) | |
const int uploadLimit = 4194304; | |
stream.Seek(0, SeekOrigin.Begin); // ensure stream is at the beginning | |
var fileClient = await _dirClient.CreateFileAsync(filename, stream.Length); | |
// If stream is below the limit upload directly | |
if (stream.Length <= uploadLimit) { | |
await fileClient.Value.UploadRangeAsync(new HttpRange(0, stream.Length), stream); | |
return; | |
} | |
int bytesRead; | |
long index = 0; | |
byte[] buffer = new byte[uploadLimit]; | |
// Stream is larger than the limit so we need to upload in chunks | |
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { | |
// Create a memory stream for the buffer to upload | |
using MemoryStream ms = new MemoryStream(buffer, 0, bytesRead); | |
await fileClient.Value.UploadRangeAsync(new HttpRange(index, ms.Length), ms); | |
index += ms.Length; | |
} | |
} | |
} | |
public class LocalStorageClient : IStorageClient { | |
private readonly string _dirRoot; | |
public LocalStorageClient(string dirRoot) { | |
_dirRoot = dirRoot; | |
} | |
public bool Exists(string folderName) { | |
return Directory.Exists(folderName); | |
} | |
public IStorageFolder GetFolder(string folderName) { | |
var path = Path.Combine(_dirRoot, folderName); | |
var info = new DirectoryInfo(path); | |
return new LocalFolder(info); | |
} | |
} | |
public class LocalFolder : IStorageFolder { | |
private readonly DirectoryInfo _dirInfo; | |
public LocalFolder(DirectoryInfo dirInfo) { | |
_dirInfo = dirInfo; | |
} | |
public async Task<IStorageFolder> CreateSubfolderAsync(string folderName) { | |
var path = Path.Combine(_dirInfo.FullName, folderName); | |
await Task.Run(() => Directory.CreateDirectory(path)); | |
return new LocalFolder(new DirectoryInfo(path)); | |
} | |
/// <summary> | |
/// WARNING: Delete the folder and all contents (recursively) | |
/// </summary> | |
public async Task DeleteAllAsync() { | |
await Task.Run(() => _dirInfo.Delete(true)); | |
} | |
public async Task DeleteFileAsync(string filename) { | |
var fullpath = Path.Combine(_dirInfo.FullName, filename); | |
await Task.Run(() => File.Delete(fullpath)); | |
} | |
public IEnumerable<string> GetFiles(params string[] exts) { | |
return FileUtils.GetFiles(_dirInfo.FullName, exts); | |
} | |
public IEnumerable<string> GetFiles(string regexPattern = "") { | |
Regex regex = null; | |
if (!string.IsNullOrEmpty(regexPattern)) regex = new Regex(regexPattern); | |
// Return a list of filenames matching an optional regex pattern | |
return _dirInfo.GetFiles() | |
.Where(item => regex != null && regex.IsMatch(item.Name)) | |
.Select(item => item.Name); | |
} | |
public IStorageFolder GetSubfolder(string folderName, bool createIfNotExists = false) { | |
throw new NotImplementedException(); | |
} | |
public Stream OpenRead(string filename) { | |
var fullpath = Path.Combine(_dirInfo.FullName, filename); | |
return File.OpenRead(fullpath); | |
} | |
public async Task<string> ReadFileAsync(string filename) { | |
var fullpath = Path.Combine(_dirInfo.FullName, filename); | |
return await File.ReadAllTextAsync(fullpath); | |
} | |
public async Task WriteFileAsync(string filename, string contents) { | |
var fullpath = Path.Combine(_dirInfo.FullName, filename); | |
await File.WriteAllTextAsync(fullpath, contents); | |
} | |
public async Task WriteFileAsync(string filename, Stream stream) { | |
var fullpath = Path.Combine(_dirInfo.FullName, filename); | |
var fs = File.OpenWrite(fullpath); | |
// Ensure stream is at the beginning and then copy to the file stream. | |
stream.Seek(0, SeekOrigin.Begin); | |
await stream.CopyToAsync(fs); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment