Files
2026-05-28 19:15:14 +03:30

1066 lines
31 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
using xCommons.Constants;
using xCommons.Extensions;
using xExceptions.Constants;
using xModels.Dtos;
using xStorageService.Constants;
using xStorageService.Helpers;
using xStorageService.Interfaces;
using xStorageService.Models;
namespace xStorageService.Providers
{
public partial class XStorageProvider : IXStorageProvider
{
public readonly string BasePath;
private readonly XFileType[] ThumbSupprtedTypes = new XFileType[] {
XFileType.Image, XFileType.CoverImage, XFileType.ProfileImasge
};
public IFileProvider FileProvider { get; }
public XStorageHelper StorageHelper { get; }
public IXThumbnailProvider ThumbnailProvider { get; }
public XStorageConfiguration Configuration { get; }
public XStorageProvider(
IFileProvider fileProvider,
XStorageHelper storageHelper,
IXThumbnailProvider thumbnailProvider,
XStorageConfiguration configuration
)
{
//
FileProvider = fileProvider;
Configuration = configuration;
StorageHelper = storageHelper;
ThumbnailProvider = thumbnailProvider;
//
BasePath = Path
.Combine(Directory
.GetCurrentDirectory(), Configuration.BaseFolder);
}
//
#region Special Folders Path Getter (s) ...
/// <summary>
/// Get Storage Folder Path
/// </summary>
/// <returns></returns>
public string GetStorageFolderPath()
{
return Path.Combine(BasePath, Configuration.Storage);
}
/// <summary>
/// Get Temp Folder Path
/// </summary>
/// <returns></returns>
public string GetTempFolderPath()
{
return Path.Combine(GetStorageFolderPath(), Configuration.Temp);
}
/// <summary>
/// Get Uploads Folder Path
/// </summary>
/// <returns></returns>
public string GetUploadsFolderPath()
{
return Path.Combine(GetStorageFolderPath(), Configuration.Uploads);
}
/// <summary>
/// Get Widgets Folder Path
/// </summary>
/// <returns></returns>
public string GetWidgetsFolderPath()
{
return Path.Combine(GetStorageFolderPath(), Configuration.Widgets);
}
/// <summary>
/// Get Thumb Folder Path
/// </summary>
/// <returns></returns>
public string GetThumbFolderPath()
{
return Path.Combine(GetUploadsFolderPath(), Configuration.Thumb);
}
/// <summary>
/// Get Images Folder Path
/// </summary>
/// <returns></returns>
public string GetImageFolderPath()
{
return Path.Combine(GetUploadsFolderPath(), Configuration.Image);
}
/// <summary>
/// Get Audios Folder Path
/// </summary>
/// <returns></returns>
public string GetAudioFolderPath()
{
return Path.Combine(GetUploadsFolderPath(), Configuration.Audio);
}
/// <summary>
/// Get Videos Folder Path
/// </summary>
/// <returns></returns>
public string GetVideoFolderPath()
{
return Path.Combine(GetUploadsFolderPath(), Configuration.Video);
}
/// <summary>
/// Get Documents Folder Path
/// </summary>
/// <returns></returns>
public string GetDocumentFolderPath()
{
return Path.Combine(GetUploadsFolderPath(), Configuration.Document);
}
/// <summary>
/// Prepare Full Path to Use for Static Files
/// </summary>
/// <param name="fullPath"></param>
/// <returns></returns>
public string FilePathResolver(string fullPath)
{
return fullPath.Replace(BasePath, "");
}
/// <summary>
/// Convert a File Path to Url
/// </summary>
/// <param name="fullPath"></param>
/// <returns></returns>
public string ConvertToProfileImageUrl(string fullPath)
{
//
var authority = !Configuration.IdentityAuthority.IsNullOrEmpty() &&
Configuration.IdentityAuthority.EndsWith("/") ?
Configuration.IdentityAuthority.Substring(0, Configuration.IdentityAuthority.Length - 1) :
Configuration.IdentityAuthority;
//
return $"{authority}{FilePathResolver(fullPath)}";
}
/// <summary>
/// Convert a File Path to Url
/// </summary>
/// <param name="fullPath"></param>
/// <returns></returns>
public string ConvertToFileUrl(string fullPath)
{
//
var authority = !Configuration.Authority.IsNullOrEmpty() &&
Configuration.Authority.EndsWith("/") ?
Configuration.Authority.Substring(0, Configuration.Authority.Length - 1) :
Configuration.Authority;
//
return $"{authority}/{FilePathResolver(fullPath)}";
}
#endregion
//
#region Prepare Storage Structure ...
/// <summary>
/// Check Folder Structure of Storage Area
/// and Create them if not Exists
/// </summary>
public void Prepare()
{
//
try
{
//
// Storage Folder Path Check ...
if (!IsFolderExists(GetStorageFolderPath()))
{
Directory.CreateDirectory(GetStorageFolderPath());
}
//
// Temp Folder Path Check ...
if (!IsFolderExists(GetTempFolderPath()))
{
Directory.CreateDirectory(GetTempFolderPath());
}
//
// Temp Folder Path Check ...
if (!IsFolderExists(GetThumbFolderPath()))
{
Directory.CreateDirectory(GetThumbFolderPath());
}
//
// Widgets Folder Path Chekc ...
if (!IsFolderExists(GetWidgetsFolderPath()))
{
Directory.CreateDirectory(GetWidgetsFolderPath());
}
//
// Uploads Folder Path Chekc ...
if (!IsFolderExists(GetUploadsFolderPath()))
{
Directory.CreateDirectory(GetUploadsFolderPath());
}
//
// Image Folder Path Chekc ...
if (!IsFolderExists(GetImageFolderPath()))
{
Directory.CreateDirectory(GetImageFolderPath());
}
//
// Music Folder Path Chekc ...
if (!IsFolderExists(GetAudioFolderPath()))
{
Directory.CreateDirectory(GetAudioFolderPath());
}
//
// Video Folder Path Chekc ...
if (!IsFolderExists(GetVideoFolderPath()))
{
Directory.CreateDirectory(GetVideoFolderPath());
}
//
// Document Folder Path Chekc ...
if (!IsFolderExists(GetDocumentFolderPath()))
{
Directory.CreateDirectory(GetDocumentFolderPath());
}
}
catch
{
XException.StorageServiceInitialFailed.Throw();
}
//
// Set Temp Folder for Actions ...
ThumbnailProvider.SetTempFolder(GetTempFolderPath());
}
#endregion
//
#region Validators ...
/// <summary>
/// Detrmines a Path Exists or not
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public bool IsFolderExists(string path)
{
return Directory.Exists(path);
}
/// <summary>
/// Check a File Exists or not
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public bool IsFileExists(string filePath)
{
return File.Exists(filePath);
}
/// <summary>
/// Check a File Name Support Thumbnail or not
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public bool IsSupportThumb(string fileName)
{
//
if (!fileName.StartsWith(Configuration.FilePrefix))
{
XException.InavlidFile.Throw();
}
//
var fileType = StorageHelper.GetType(fileName);
return IsSupportThumb(fileType);
}
/// <summary>
/// Check a FileType Support Thumbnail or not
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public bool IsSupportThumb(XFileType type)
{
return ThumbSupprtedTypes.Contains(type);
}
/// <summary>
/// Validate a File Exists or not
/// </summary>
/// <param name="filePath"></param>
public void ValidateFileExists(string filePath)
{
//
if (!IsFileExists(filePath))
{
XException.NotFound.Throw();
}
}
/// <summary>
/// Validate a File Model with it's Related Physical File
/// </summary>
/// <param name="file"></param>
public void ValidateFileModel(XFileDto file)
{
//
// Validate Args ...
if (file == null)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Full File Path ...
var fileFullPath = file.Path;
if (fileFullPath.IsNullOrEmpty())
{
XException.InavlidFile.Throw();
}
//
// Retrieve is File Exists or not ...
var isPhysicalFileExists = IsFileExists(file.Path);
if (!isPhysicalFileExists)
{
XException.NotFound.Throw();
}
}
/// <summary>
/// Validate File for Upload
/// </summary>
/// <param name="file"></param>
public void ValidateFile(IFormFile file)
{
//
// Validate Args ...
if (file == null)
{
XException.InvalidArgs.Throw();
}
//
// Check File Size not Zero ...
if (file.Length == 0)
{
XException.EmptyFile.Throw();
}
//
// Check File Max Size ...
if (file.Length > Configuration.MaxFileSize)
{
XException.MaxFileSizeExceeded.Throw();
}
}
/// <summary>
/// Validate a Collection of IFormFile
/// </summary>
/// <param name="files"></param>
public void ValidateFiles(IFormFileCollection files)
{
//
// Validate Args ...
if (!files.HasChild())
{
XException.InvalidArgs.Throw();
}
//
files.ToList()
.ForEach(f => ValidateFile(f));
}
/// <summary>
/// Validate File for Profile Image
/// </summary>
/// <param name="file"></param>
public void ValidateProfileImage(IFormFile file)
{
//
// Check File Validation ...
// since Args Checked in File Validation, there is no need to
// double check it ...
ValidateFile(file);
//
// Check File Type ...
var fileType = GetFileType(file.FileName);
if (fileType != XFileType.Image)
{
XException.UnsupportedFileType.Throw();
}
}
#endregion
//
#region Generators ...
/// <summary>
/// Generate New File
/// </summary>
/// <returns></returns>
public string NewFileName()
{
return Configuration.FilePrefix + Guid.NewGuid().ToString().ToLower();
}
/// <summary>
/// Retrieve Related Thumnail File
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GenerateThumbName(string fileName)
{
//
if (!IsSupportThumb(fileName))
{
XException.UnsupportedFileType.Throw();
}
//
return fileName.Replace(Configuration.FilePrefix, Configuration.ThumbPrefix);
}
/// <summary>
/// Get a New FileName with Extension based on Given fileName
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GetNewFileName(string fileName)
{
//
// Validate Args ...
if (fileName.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var fileExt = GetFileExtension(fileName);
var newFileNameWithoutExt = NewFileName();
//
// Generate New File Name ...
var newFileName = string.Format("{0}{1}", newFileNameWithoutExt, fileExt);
//
return newFileName;
}
#endregion
//
#region Tools Actions ...
/// <summary>
/// Extract a FileName's Extension
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GetFileExtension(string fileName)
{
//
var result = StorageHelper.GetFileExtension(fileName);
if (result.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
return result;
}
/// <summary>
/// Extract a FileName's Without it's Extension
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GetFileNameWithoutExtension(string fileName)
{
//
var result = StorageHelper.GetFileNameWithoutExtension(fileName);
if (result.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
return result;
}
/// <summary>
/// Get file Extension of Specified File Name
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public FileExtensions GetFileExtensionType(string fileName)
{
//
var fileExt = GetFileExtension(fileName);
if (fileExt.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
var result = StorageHelper.GetExtensionType(fileExt);
if (result == -1)
{
XException.UnsupportedFileType.Throw();
}
//
return (FileExtensions)result;
}
/// <summary>
/// Return File Type of Specific File Name
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public XFileType GetFileType(string fileName)
{
//
var fileExtension = GetFileExtensionType(fileName);
return GetFileType(fileExtension);
}
/// <summary>
/// Return File Type of Specific Extension
/// </summary>
/// <param name="fileExtension"></param>
/// <returns></returns>
public XFileType GetFileType(FileExtensions fileExtension)
{
return StorageHelper.GetType((int)fileExtension);
}
/// <summary>
/// Retrieve Full Path of thumbnail File
/// </summary>
/// <param name="thumFileName"></param>
/// <returns></returns>
public string GetThumbnailFileFullPath(string thumFileName)
{
//
if (!thumFileName.StartsWith(Configuration.ThumbPrefix))
{
XException.InavlidFile.Throw();
}
//
return Path.Combine(GetThumbFolderPath(), thumFileName);
}
/// <summary>
/// Retrieve a File Thumbnail Path if Supported
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GetRelatedThumbnailFullPath(string fileName)
{
//
var thumbName = GenerateThumbName(fileName);
return GetThumbnailFileFullPath(thumbName);
}
/// <summary>
/// Retrieve Special File Type's Folder Path
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public string GetFileTypeFolderPath(XFileType type)
{
//
// Retrieve Folder Path Based on file ...
switch (type)
{
case XFileType.Image:
case XFileType.CoverImage:
case XFileType.ProfileImasge:
return GetImageFolderPath();
case XFileType.Audio:
return GetAudioFolderPath();
case XFileType.Video:
return GetVideoFolderPath();
case XFileType.Document:
default:
return GetDocumentFolderPath();
}
}
/// <summary>
/// Retrieve a File's Folder path
/// </summary>
/// <param name="fileName"></param>
/// <param name="isTemp"></param>
/// <param name="isWidget"></param>
/// <returns></returns>
public string GetFileFolderPath(
string fileName,
bool isTemp = false,
bool isWidget = false
)
{
//
// Validate Args ...
if (fileName.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
var fileType = GetFileType(fileName);
//
var folderPath = "";
//
// if isTemp equals true
// ignore file type and return Temp Folder ...
if (isTemp)
{
folderPath = GetTempFolderPath();
}
//
// if isWidget equals true and isTemp equals false
// ignore file type and return Widget's Folder ...
if (isWidget && !isTemp)
{
folderPath = GetWidgetsFolderPath();
}
//
// if both isTemp and isWidget equals false
// which is the default state, retrieve proper folder path
// based on file type and return it ...
if (!isTemp && !isWidget)
{
folderPath = GetFileTypeFolderPath(fileType);
}
//
// Check folderPath restrict to have Value ...
if (folderPath.IsNullOrEmpty())
{
XException.InvalidData.Throw();
}
//
// return result ...
return folderPath;
}
/// <summary>
/// Retrieve a Full path of a File
/// </summary>
/// <param name="fileName"></param>
/// <param name="isTemp"></param>
/// <param name="isWidget"></param>
/// <returns></returns>
public string GetFileFullPath(
string fileName,
bool isTemp = false,
bool isWidget = false
)
{
//
var folderPath = GetFileFolderPath(
fileName,
isTemp: isTemp,
isWidget: isWidget
);
//
if (folderPath.IsNullOrEmpty())
{
XException.ActionFailed.Throw();
}
//
return Path.Combine(folderPath, fileName);
}
/// <summary>
/// Save a File into Storage Based on it's Type
/// </summary>
/// <param name="file"></param>
/// <param name="isTemp"></param>
/// <param name="isWidget"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<string> SaveFileAsync(
IFormFile file,
bool isTemp = false,
bool isWidget = false,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
ValidateFile(file);
//
// Extract and Generate required Data ...
var fileName = GetNewFileName(file.FileName);
var folderPath = GetFileFolderPath(file.FileName, isTemp, isWidget);
var fileFullPath = Path.Combine(folderPath, fileName);
//
// Save File ...
using (var stream = new FileStream(fileFullPath, FileMode.Create))
{
//
await file.CopyToAsync(
stream,
cancellationToken
);
}
//
// Since all Required data can be Generated based on
// a simple file Name, there is no need to return additional
// data as result ...
return fileName;
}
/// <summary>
/// Delete a Physical File by Validate it's Exists
/// </summary>
/// <param name="fileFullPath"></param>
/// <param name="forceFileExists"></param>
/// <param name="exception"></param>
public void DeleteFile(
string fileFullPath,
bool forceFileExists = true,
Exception exception = null
)
{
//
// Validate Args ...
if (!forceFileExists &&
fileFullPath.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Default Exception ...
if (exception == null)
{
exception = XException.NotFound.ToException();
}
//
// Check File Exists ...
var isFileExists = File.Exists(fileFullPath);
if (forceFileExists &&
!isFileExists)
{
XException.NotFound.Throw();
}
//
// Handle Prevent Error when forceFileExists false
// and File doesn't Exists ...
if (!forceFileExists &&
!isFileExists)
{
return;
}
//
File.Delete(fileFullPath);
}
/// <summary>
/// Remove a List of Physical Files Path
/// and return Un Removable Files ...
/// </summary>
/// <param name="fileFullPaths"></param>
/// <param name="forceFileExists"></param>
/// <param name="exception"></param>
/// <returns></returns>
public ICollection<string> DeleteFiles(
ICollection<string> fileFullPaths,
bool forceFileExists = true,
Exception exception = null
)
{
//
if (exception.IsNull())
{
exception = XException.NotFound.ToException();
}
//
var result = fileFullPaths.Where(f => !IsFileExists(GetFileFullPath(f))).ToList();
if (forceFileExists && result.HasChild())
{
throw exception;
}
//
var existsFile = fileFullPaths.Where(f => IsFileExists(f)).ToList();
existsFile.ForEach(f =>
{
DeleteFile(
f,
forceFileExists: forceFileExists,
exception: exception
);
});
//
return fileFullPaths.Except(existsFile).ToList();
}
/// <summary>
/// Create Thumbnail based on FileName.Ext
/// </summary>
/// <param name="fileName"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task CreateThumbnail(
string fileName,
CancellationToken cancellationToken = default
)
{
//
if (fileName.IsNullOrEmpty())
{
return;
}
//
await Task.Run(() =>
{
//
var fileFullPath = GetFileFullPath(fileName);
if (!IsFileExists(fileFullPath))
{
XException.NotFound.Throw();
}
//
var thumbFileName = GenerateThumbName(fileName);
var thumbFullPath = GetThumbnailFileFullPath(thumbFileName);
//
ThumbnailProvider.ResizeImage(
fileFullPath,
thumbFullPath,
Configuration.ThumbSize,
Configuration.ThumbQuality
);
}, cancellationToken);
}
/// <summary>
/// Create Thumbnail based on XFile model
/// </summary>
/// <param name="file"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task CreateThumbnail(
XFileDto file,
CancellationToken cancellationToken = default
)
{
//
await CreateThumbnail(
file.Name,
cancellationToken
);
}
/// <summary>
/// Save and Create a Thumbnail For Profile Image
/// </summary>
/// <param name="file"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XFileResult> HandleProfileImageSave(
IFormFile file,
CancellationToken cancellationToken = default
)
{
//
ValidateProfileImage(file);
//
var savedFileName = await SaveFileAsync(
file,
isTemp: false,
isWidget: false,
cancellationToken: cancellationToken
);
var thumbFileName = GenerateThumbName(savedFileName);
//
var savedFileFullPath = GetFileFullPath(savedFileName);
var thumbFullPath = GetThumbnailFileFullPath(thumbFileName);
//
await CreateThumbnail(
savedFileName,
cancellationToken
);
//
return new XFileResult
{
Name = file.Name,
FileName = savedFileName,
FilePath = ConvertToProfileImageUrl(savedFileFullPath),
Thmbnail = thumbFileName,
ThmbnailPath = ConvertToProfileImageUrl(thumbFullPath)
};
}
/// <summary>
/// Handle File Upload with Support of Thumbnails ...
/// </summary>
/// <param name="file"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<XFileResult> HandleFileSave(
IFormFile file,
CancellationToken cancellationToken = default
)
{
//
ValidateFile(file);
//
var savedFileName = await SaveFileAsync(file, isTemp: false, isWidget: false);
//
var savedFileFullPath = GetFileFullPath(savedFileName);
//
var thumbFileName = "";
var thumbFullPath = "";
var fileType = GetFileType(savedFileName);
if (fileType.IsImageKind())
{
//
thumbFileName = GenerateThumbName(savedFileName);
thumbFullPath = GetThumbnailFileFullPath(thumbFileName);
//
await CreateThumbnail(savedFileName);
}
//
var result =
new XFileResult
{
Name = file.FileName,
FileName = savedFileName,
FilePath = ConvertToFileUrl(savedFileFullPath),
Thmbnail = thumbFileName,
ThmbnailPath = ConvertToFileUrl(thumbFullPath),
};
//
return result;
}
/// <summary>
/// Handle File Upload with Support of Thumbnails ...
/// </summary>
/// <param name="files"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XFileResult>> HandleFilesSave(
IFormFileCollection files,
CancellationToken cancellationToken = default
)
{
//
ValidateFiles(files);
//
var result = new Collection<XFileResult>();
foreach (var file in files)
{
//
var savedFileName = await SaveFileAsync(
file,
isTemp: false,
isWidget: false,
cancellationToken: cancellationToken
);
//
var savedFileFullPath = GetFileFullPath(savedFileName);
//
var thumbFileName = "";
var thumbFullPath = "";
var fileType = GetFileType(savedFileName);
if (fileType.IsImageKind())
{
//
thumbFileName = GenerateThumbName(savedFileName);
thumbFullPath = GetThumbnailFileFullPath(thumbFileName);
//
await CreateThumbnail(
savedFileName,
cancellationToken
);
}
//
result.Add(
new XFileResult
{
Name = file.FileName,
FileName = savedFileName,
FilePath = ConvertToFileUrl(savedFileFullPath),
Thmbnail = thumbFileName,
ThmbnailPath = ConvertToFileUrl(thumbFullPath),
});
}
//
return result;
}
#endregion
}
}