gehgassi_backend/gehGassi.External/Services/AzureBlobFileService.cs

411 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using gehGassi.Core.Interfaces;
using gehGassi.Core.Services;
namespace gehGassi.External.Services
{
/// <summary>
/// Service der die Verwaltung von Dateien mittels Azure ermöglicht.
/// Verwendet das neue Azure-Interface
/// </summary>
public class AzureBlobFileService : IFileService
{
internal BlobServiceClient BlobServiceClient;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="connectionString">Connectionstring für Azure-Storage</param>
public AzureBlobFileService(string connectionString)
{
BlobServiceClient = new BlobServiceClient(connectionString);
InitContainers();
}
/// <summary>
/// Initialisieren der Container
/// </summary>
/// <returns>Task</returns>
private void InitContainers()
{
var container = BlobServiceClient.GetBlobContainerClient(FileServiceHelper.DocumentContainer);
container.CreateIfNotExists();
container = BlobServiceClient.GetBlobContainerClient(FileServiceHelper.TempContainer);
container.CreateIfNotExists();
}
/// <summary>
/// Speichern einer Datei in einen Container
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
/// <param name="file">Datei als Byte-Array</param>
/// <param name="overwrite">Überschreiben vorhandener Daten</param>
/// <returns>Absoluter Dateiname</returns>
public async Task<string> StoreAsync(string containerName, string filename, byte[] file, bool overwrite = true)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
await using var stream = new MemoryStream(file);
await blob.UploadAsync(stream);
return blob.Name;
}
/// <summary>
/// Speichern einer Datei in einen Container
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
/// <param name="fileStream">Datei als Stream</param>
/// <param name="overwrite">Überschreiben vorhandener Daten</param>
/// <returns>Absoluter Dateiname</returns>
public async Task<string> StoreAsync(string containerName, string filename, Stream fileStream, bool overwrite = true)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
await blob.UploadAsync(fileStream);
return blob.Name;
}
/// <summary>
/// Umbenennen einer Datei in einem Container
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
/// <param name="newFilename">Neuer Dateiname</param>
public async Task RenameAsync(string containerName, string filename, string newFilename)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
var newBlob = container.GetBlockBlobClient(newFilename);
await newBlob.StartCopyFromUriAsync(blob.Uri);
await blob.DeleteIfExistsAsync();
}
/// <summary>
/// Gibt eine Datei zurück
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
/// <returns>Datei als Byte-Array</returns>
public async Task<byte[]> GetAsync(string containerName, string filename)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
// Save blob contents to a file.
byte[] result = null;
if (await blob.ExistsAsync())
{
await using var memoryStream = new MemoryStream();
await blob.DownloadToAsync(memoryStream);
result = memoryStream.ToArray();
}
return result;
}
/// <summary>
/// Gibt eine Datei zurück
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
/// <returns>Datei als Stream</returns>
public async Task<Stream> GetAsStreamAsync(string containerName, string filename)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
var memoryStream = new MemoryStream();
await blob.DownloadToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
/// <summary>
/// Löschen einer Datei in einem Container
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="filename">Dateiname (inkl. Pfad, wenn gewünscht)</param>
public async Task DeleteAsync(string containerName, string filename)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
var blob = container.GetBlockBlobClient(filename);
await blob.DeleteIfExistsAsync();
}
/// <summary>
/// Gibt eine Liste aller Dateien eines Containers zurück die sich in einem Directory befinden
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="directory">Verzeichnis.</param>
/// <param name="fileNamePart">Teil eines Dateinamens für die suche</param>
/// <returns>Liste von Dateien</returns>
public async Task<List<string>> ListFilesAsync(string containerName, string directory = "", string fileNamePart = "")
{
var result = new List<string>();
Wildcard wildCard = null;
if (!string.IsNullOrWhiteSpace(fileNamePart))
wildCard = new Wildcard(fileNamePart, RegexOptions.IgnoreCase);
var container = BlobServiceClient.GetBlobContainerClient(containerName);
await foreach (var blobPage in container.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, prefix: directory).AsPages())
{
// A hierarchical listing may return both virtual directories and blobs.
foreach (var item in blobPage.Values)
{
if (item.IsPrefix)
continue;
if (!item.IsBlob)
continue;
if (wildCard != null)
{
if (wildCard.IsMatch(GetFilename(item.Blob.Name)))
result.Add(item.Blob.Name);
}
else
result.Add(item.Blob.Name);
}
}
return result;
}
/// <summary>
/// Gibt eine Liste aller Verzeichnise zurück die sich im Container befindne
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="root">wenn angegeben, wird von dem Verzeihnis als root ausgegangen</param>
/// <returns></returns>
public async Task<List<string>> ListDirectoriesAsync(string containerName, string root = "")
{
var result = new List<string>();
var container = BlobServiceClient.GetBlobContainerClient(containerName);
await foreach (var blobPage in container.GetBlobsByHierarchyAsync(delimiter: "/", prefix: root).AsPages())
{
result.AddRange(from item in blobPage.Values where item.IsPrefix select item.Prefix);
}
return result;
}
/// <summary>
/// Koperien von Dateinen eines Containers in einen anderen
/// </summary>
/// <param name="sourceContainer">Quellcontainer</param>
/// <param name="sourceDirectory">Quellpfad</param>
/// <param name="targetContainer">ZielContainer</param>
/// <param name="targetDirectory">ZielPfad</param>
public async Task CopyFilesAsync(string sourceContainer, string sourceDirectory, string targetContainer, string targetDirectory)
{
var cloudSourceContainer = BlobServiceClient.GetBlobContainerClient(sourceContainer);
var cloudTargetContainer = BlobServiceClient.GetBlobContainerClient(targetContainer);
var blobs = new List<BlobItem>();
await foreach (var blobPage in cloudSourceContainer.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, prefix: sourceDirectory).AsPages())
{
blobs.AddRange(from item in blobPage.Values where item.IsBlob select item.Blob);
}
foreach (var blobItem in blobs)
{
var sourceClient = cloudSourceContainer.GetBlockBlobClient(blobItem.Name);
var copyClient = cloudTargetContainer.GetBlockBlobClient(blobItem.Name.Replace(sourceDirectory, targetDirectory));
await copyClient.StartCopyFromUriAsync(sourceClient.Uri);
}
}
/// <summary>
/// Koperien von Dateien eines Pfades in einen anderen
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="sourceDirectory">Quellpfad</param>
/// <param name="targetDirectory">ZielPfad</param>
public async Task CopyFilesAsync(string containerName, string sourceDirectory, string targetDirectory)
{
await CopyFilesAsync(containerName, sourceDirectory, containerName, targetDirectory);
}
/// <summary>
/// Verschieben von Dateien eines Containers in einen anderen
/// </summary>
/// <param name="sourceContainer">Quellcontainer</param>
/// <param name="sourceDirectory">Quellpfad</param>
/// <param name="targetContainer">ZielContainer</param>
/// <param name="targetDirectory">ZielPfad</param>
public async Task MoveFilesAsync(string sourceContainer, string sourceDirectory, string targetContainer, string targetDirectory)
{
var cloudSourceContainer = BlobServiceClient.GetBlobContainerClient(sourceContainer);
var cloudTargetContainer = BlobServiceClient.GetBlobContainerClient(targetContainer);
var blobs = new List<BlobItem>();
await foreach (var blobPage in cloudSourceContainer.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, prefix: sourceDirectory).AsPages())
{
blobs.AddRange(from item in blobPage.Values where item.IsBlob select item.Blob);
}
foreach (var blobItem in blobs)
{
var sourceClient = cloudSourceContainer.GetBlockBlobClient(blobItem.Name);
var copyClient = cloudTargetContainer.GetBlockBlobClient(blobItem.Name.Replace(sourceDirectory, targetDirectory));
await copyClient.StartCopyFromUriAsync(sourceClient.Uri);
await sourceClient.DeleteAsync();
}
}
/// <summary>
/// Verschieben von Dateien eines Pfades in einen anderen
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="sourceDirectory">Quellpfad</param>
/// <param name="targetDirectory">ZielPfad</param>
public async Task MoveFilesAsync(string containerName, string sourceDirectory, string targetDirectory)
{
await MoveFilesAsync(containerName, sourceDirectory, containerName, targetDirectory);
}
/// <summary>
/// Löschen des Inhalts eines Verzeichnises
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="directory">Verzeichnis</param>
/// <param name="fileNamePart">Optional: Dateinamen Wildcard</param>
public async Task ClearDirectoryAsync(string containerName, string directory, string fileNamePart = "")
{
Wildcard wildCard = null;
if (!string.IsNullOrWhiteSpace(fileNamePart))
wildCard = new Wildcard(fileNamePart, RegexOptions.IgnoreCase);
var container = BlobServiceClient.GetBlobContainerClient(containerName);
await foreach (var blobPage in container.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, prefix: directory).AsPages())
{
// A hierarchical listing may return both virtual directories and blobs.
foreach (var item in blobPage.Values)
{
if (item.IsPrefix)
continue;
if (!item.IsBlob)
continue;
if (wildCard != null)
{
if (wildCard.IsMatch(GetFilename(item.Blob.Name)))
{
var client = container.GetBlockBlobClient(item.Blob.Name);
await client.DeleteAsync();
}
}
else
{
var client = container.GetBlockBlobClient(item.Blob.Name);
await client.DeleteAsync();
}
}
}
}
/// <summary>
/// Löscht einen Container und den Inhalt
/// </summary>
/// <param name="containerName">Name des Containers</param>
public async Task DeleteContainerAsync(string containerName)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
await container.DeleteAsync();
}
/// <summary>
/// Löscht Dateien aus einem Ordner auf welche
/// vor dem Datum das letzte mal zugegriffen wurde
/// </summary>
/// <param name="containerName">Name des Containers</param>
/// <param name="directory">Verzeichnis</param>
/// <param name="date">Datum</param>
public async Task DeleteOldFilesAsync(string containerName, string directory, DateTimeOffset date)
{
var container = BlobServiceClient.GetBlobContainerClient(containerName);
await foreach (var blobPage in container.GetBlobsByHierarchyAsync(BlobTraits.None, BlobStates.None, prefix: directory).AsPages())
{
foreach (var item in blobPage.Values)
{
if (item.IsPrefix)
continue;
if (!item.IsBlob)
continue;
var client = container.GetBlockBlobClient(item.Blob.Name);
if (item.Blob.Properties.LastModified < date)
await client.DeleteAsync();
}
}
}
/// <summary>
/// Gibt den reinen Dateinamen eines Files zurück (Ohne Directories usw)
/// </summary>
/// <param name="fileName">Dateiname</param>
/// <returns>Reiner Dateiname</returns>
public string GetFlatFilename(string fileName)
{
return GetFilename(fileName);
}
#region private
/// <summary>
/// Gibt den Dateinamen einer Datei ohne lokale Pfadangaben zurück
/// </summary>
/// <param name="path">Pfad + Dateinamen</param>
/// <returns>Dateiname ohne Pfadangaben</returns>
private string GetFilename(string path)
{
var fileName = path.Split(new[] { "/" }, StringSplitOptions.None).Last();
return fileName;
}
#endregion
}
/// <summary>
/// Hilfsklasse für das Finden von Dateien mittels Wildcards
/// </summary>
[ExcludeFromCodeCoverage]
internal class Wildcard : Regex
{
/// <summary>
/// Erstellt eine instanz
/// </summary>
/// <param name="pattern">Wildcard-Ausdruck</param>
public Wildcard(string pattern) : base(ToRegex(pattern))
{
}
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="pattern">Wildcard-Ausdruck</param>
/// <param name="options">Optionen</param>
public Wildcard(string pattern, RegexOptions options) : base(ToRegex(pattern), options)
{
}
/// <summary>
/// Erstellt einen regulären Ausdruck aus dem Wildcard-Ausdruck
/// </summary>
/// <param name="pattern">Wildcard-Ausdruck</param>
/// <returns>Wildcard-Ausdruck in Regex konvertiert</returns>
public static string ToRegex(string pattern)
{
return "^" + Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$";
}
}
}