Skip to content

Instantly share code, notes, and snippets.

@bradleypatton
Created September 29, 2020 21:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradleypatton/4705a99b4fc89a8b5fe314eebf695008 to your computer and use it in GitHub Desktop.
Save bradleypatton/4705a99b4fc89a8b5fe314eebf695008 to your computer and use it in GitHub Desktop.
Abstraction for Azure Files and Local Storage
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