commit d26a1c49fd062f545152a7096d463f0d26ab6ec1 Author: Hadi Khazaee Asl Date: Thu Jan 25 04:49:52 2024 +0330 Initial Commit ... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bd9d0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# +# DotNet ... +bin +obj + +# +# Natural Docs ... +Documentation/* diff --git a/Configurations/.gitkeep b/Configurations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Constants/ConfigurationNodeNames.cs b/Constants/ConfigurationNodeNames.cs new file mode 100644 index 0000000..b6a5233 --- /dev/null +++ b/Constants/ConfigurationNodeNames.cs @@ -0,0 +1,6 @@ +namespace xStorageService.Constants { + public partial struct ConfigurationNodeNames { + public const string STORAGE_SERVICE_NODE_NAME = "StorageConfiguration"; + } + +} \ No newline at end of file diff --git a/Constants/FileExtensions.cs b/Constants/FileExtensions.cs new file mode 100644 index 0000000..6b2df94 --- /dev/null +++ b/Constants/FileExtensions.cs @@ -0,0 +1,140 @@ +using xExceptions.Attributes; + +namespace xStorageService.Constants { + public enum FileExtensions { + // + // Document + [StringValue (".txt")] + TEXT = 100, + + [StringValue (".pdf")] + PDF = 101, + + [StringValue (".ppt")] + PPT = 102, + + [StringValue (".pptx")] + PPTX = 103, + + [StringValue (".odp")] + ODP = 104, + + [StringValue (".pps")] + PPS = 105, + + [StringValue (".ods")] + ODS = 106, + + [StringValue (".xlr")] + XLR = 107, + + [StringValue (".xls")] + XLS = 108, + + [StringValue (".xlsx")] + XLSX = 109, + + [StringValue (".xml")] + XML = 110, + + [StringValue (".doc")] + DOC = 111, + + [StringValue (".docx")] + DOCX = 112, + + [StringValue (".odt")] + ODT = 113, + + [StringValue (".rtf")] + RTF = 114, + + [StringValue (".tex")] + TEX = 115, + + [StringValue (".wpd")] + WPD = 116, + + [StringValue (".md")] + MD = 117, + + // + // Archive + [StringValue (".zip")] + ZIP = 200, + + // + // Internal Supports + [StringValue (".ttf")] + TTF = 300, + + [StringValue (".js")] + JAVASCRIPT = 301, + + [StringValue (".css")] + CSS = 302, + + [StringValue (".htm")] + HTM = 303, + + [StringValue (".html")] + HTML = 304, + + [StringValue (".sdw")] + SAHER_DASHBOARD_WIDGET = 305, + + [StringValue (".sdwmanifest")] + SAHER_DASHBOARD_WIDGET_MANIFEST = 306, + + // + // Video + [StringValue (".mp4")] + MP4 = 400, + + [StringValue (".avi")] + AVI = 401, + + [StringValue (".mpg")] + MPG = 402, + + [StringValue (".mpeg")] + MPEG = 403, + + [StringValue (".wmv")] + WMV = 404, + + // + // Audio + [StringValue (".mp3")] + MP3 = 500, + + [StringValue (".ogg")] + OGG = 501, + + [StringValue (".wav")] + WAV = 502, + + [StringValue (".wma")] + WMA = 503, + + // + // Image + [StringValue (".png")] + PNG = 600, + + [StringValue (".jpeg")] + JPEG = 601, + + [StringValue (".jpg")] + JPG = 602, + + // [StringValue (".ico")] + // ICON = 603, + + [StringValue (".gif")] + GIF = 604, + + [StringValue (".bmp")] + BITMAP = 605, + } +} \ No newline at end of file diff --git a/DI/XDIHelperExtension.cs b/DI/XDIHelperExtension.cs new file mode 100644 index 0000000..de1a27c --- /dev/null +++ b/DI/XDIHelperExtension.cs @@ -0,0 +1,115 @@ +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using xCommons.Extensions; +using xExceptions.Constants; +using xStorageService.Constants; +using xStorageService.Helpers; +using xStorageService.Interfaces; +using xStorageService.Models; +using xStorageService.Providers; + +namespace xStorageService.DI { + public static partial class XDIHelperExtension { + /// + /// Retrieve XStorage Configuration from AppSettings + /// + /// + /// + public static XStorageConfiguration GetXStorageConfiguration (this IConfiguration configuration) { + // + var storageConfigurationSection = configuration.GetSection (ConfigurationNodeNames.STORAGE_SERVICE_NODE_NAME); + return storageConfigurationSection.Get (); + } + + /// + /// Register XStorage Configuration + /// + /// + /// + public static void AddXStorageConfiguration ( + this IServiceCollection services, + IConfiguration configuration + ) { + // + // Check wwwroot Folder ... + if (!Directory.Exists ("wwwroot")) { + Directory.CreateDirectory ("wwwroot"); + } + + // + // Retrieve and Register XStorage Configurations ... + var xStorageConfiguration = configuration.GetXStorageConfiguration (); + if (xStorageConfiguration.IsNull ()) { + XException.InvalidConfiguration.Throw (); + } + + // + xStorageConfiguration.BaseFolder = Path.Combine (Directory.GetCurrentDirectory (), "wwwroot"); + xStorageConfiguration.RootFolder = Directory.GetDirectoryRoot (xStorageConfiguration.BaseFolder); + + // + services.AddSingleton (xStorageConfiguration); + } + + /// + /// Register XStorage Service + /// + /// + /// + public static void AddXStorageService ( + this IServiceCollection services, + IConfiguration configuration + ) { + // + // Register and Retrieve XStorage Configuration + services.AddXStorageConfiguration (configuration); + var xStorageConfiguration = services.GetRegisteredService (); + + // + // Add File Provider ... + services.AddSingleton ( + new PhysicalFileProvider (xStorageConfiguration.RootFolder) + ); + + // + // Register XStorageHelper Service ... + services.AddSingleton (); + + // + // Register ThumbnailHelper ... + services.AddSingleton (); + + // + // Register Storage Provider ... + services.AddScoped (); + + // + // StorageService Preperation... + var xStorageService = services.GetRegisteredService (); + xStorageService.Prepare (); + } + + /// + /// Use XStorage Service + /// + /// + public static void UseXStorageService (this IApplicationBuilder app) { + // + // Get Instance of Scope and Service and Prepare Folder Structure ... + using (var scope = app.ApplicationServices.CreateScope ()) { + // + var storageProvider = scope.ServiceProvider.GetService (); + + // + storageProvider.Prepare (); + } + + // + // available Static Files ... + app.UseStaticFiles (); + } + } +} \ No newline at end of file diff --git a/Extensions/.gitkeep b/Extensions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Helpers/XStorageHelper.cs b/Helpers/XStorageHelper.cs new file mode 100644 index 0000000..743e887 --- /dev/null +++ b/Helpers/XStorageHelper.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using System.Linq; +using xCommons.Constants; +using xCommons.Extensions; +using xExceptions.Constants; +using xStorageService.Constants; + +namespace xStorageService.Helpers { + public partial class XStorageHelper { + + private int[] DocumentTypeThreshold = new int[] { 99, 307 }; + private int[] ImageTypeThreshold = new int[] { 599, 606 }; + private int[] AudioTypeThreshold = new int[] { 499, 504 }; + private int[] VideoTypeThreshold = new int[] { 399, 405 }; + + public XStorageHelper () { } + + /// + /// Retrieve FileType + /// + /// + /// + public XFileType GetType (int fileExtension) { + // + // Compare File Extension Integer Value and + // Find Propper Type for it ... + if (fileExtension > DocumentTypeThreshold[0] && fileExtension < DocumentTypeThreshold[1]) { + return XFileType.Document; + } else if (fileExtension > VideoTypeThreshold[0] && fileExtension < VideoTypeThreshold[1]) { + return XFileType.Video; + } else if (fileExtension > AudioTypeThreshold[0] && fileExtension < AudioTypeThreshold[1]) { + return XFileType.Audio; + } else if (fileExtension > ImageTypeThreshold[0] && fileExtension < ImageTypeThreshold[1]) { + return XFileType.Image; + } else { + // + // if not in case return Document Type + return XFileType.Document; + } + } + + /// + /// Retrieve a File Type + /// + /// + /// + public XFileType GetType (string fileName) { + // + if (fileName.IsNullOrEmpty ()) { + XException.InavlidFile.Throw (); + } + + // + var ext = GetFileExtension (fileName); + var extType = GetExtensionType (ext); + return GetType (extType); + } + + /// + /// used for validating Extension in Widget Upload ... + /// + /// an string array + public string[] GetAllowedExtensionsForWidgetUpload () { + return new [] { + FileExtensions.SAHER_DASHBOARD_WIDGET.GetStringValue () + }; + } + + /// + /// Get a Files Extension string + /// + /// + /// + public string GetFileExtension (string fileName) { + // + // Validate Args ... + if (fileName.IsNullOrEmpty ()) { + return null; + } + + // + var fileExt = ""; + try { + fileExt = Path.GetExtension (fileName); + } catch { } + + // + return fileExt; + } + + /// + /// Get a File Name Without it's Extension + /// + /// + /// + public string GetFileNameWithoutExtension (string fileName) { + // + // Validate Args ... + if (fileName.IsNullOrEmpty ()) { + return null; + } + + // + var fileNameWithoutExt = ""; + try { + fileNameWithoutExt = Path + .GetFileNameWithoutExtension (fileName); + } catch { } + + // + return fileNameWithoutExt; + } + + /// + /// Get File Extension Type + /// + /// + /// + public int GetExtensionType (string fileExtension) { + // + foreach (var extItem in Enum + .GetValues (typeof (FileExtensions)) + .Cast ()) { + if (extItem.GetStringValue () + .Contains (fileExtension.ToLower ())) { + return (int) extItem; + } + } + + // + return -1; + } + } +} \ No newline at end of file diff --git a/Interfaces/IXStorageProvider.cs b/Interfaces/IXStorageProvider.cs new file mode 100644 index 0000000..72b89c1 --- /dev/null +++ b/Interfaces/IXStorageProvider.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; +using xCommons.Constants; +using xModels.Dtos; +using xStorageService.Constants; +using xStorageService.Helpers; +using xStorageService.Models; + +namespace xStorageService.Interfaces +{ + public partial interface IXStorageProvider { + XStorageConfiguration Configuration { get; } + XStorageHelper StorageHelper { get; } + IFileProvider FileProvider { get; } + IXThumbnailProvider ThumbnailProvider { get; } + + // + #region Special Folders Path Getter (s) ... + string GetStorageFolderPath (); + string GetTempFolderPath (); + string GetThumbFolderPath (); + string GetUploadsFolderPath (); + string GetWidgetsFolderPath (); + string GetImageFolderPath (); + string GetAudioFolderPath (); + string GetVideoFolderPath (); + string GetDocumentFolderPath (); + string FilePathResolver (string fullPath); + #endregion + + // + #region Prepare Storage Structure ... + void Prepare (); + #endregion + + // + #region Validators ... + bool IsFolderExists (string path); + bool IsFileExists (string filePath); + bool IsSupportThumb (string fileName); + bool IsSupportThumb (XFileType type); + void ValidateFileExists (string filePath); + void ValidateFileModel (XFileDto file); + void ValidateFile (IFormFile file); + void ValidateProfileImage (IFormFile file); + #endregion + + // + #region Generators ... + string NewFileName (); + string GenerateThumbName (string fileName); + string GetNewFileName (string fileName); + #endregion + + // + #region Tools Actions ... + + string GetFileExtension (string fileName); + string GetFileNameWithoutExtension (string fileName); + FileExtensions GetFileExtensionType (string fileName); + XFileType GetFileType (string fileName); + XFileType GetFileType (FileExtensions fileExtension); + string GetThumbnailFileFullPath (string thumFileName); + string GetRelatedThumbnailFullPath (string fileName); + string GetFileTypeFolderPath (XFileType type); + string GetFileFolderPath ( + string fileName, + bool isTemp = false, + bool isWidget = false + ); + string GetFileFullPath ( + string fileName, + bool isTemp = false, + bool isWidget = false + ); + Task SaveFileAsync ( + IFormFile file, + bool isTemp = false, + bool isWidget = false + ); + void DeleteFile ( + string fileFullPath, + bool forceFileExists = true, + Exception exception = null + ); + ICollection DeleteFiles ( + ICollection fileFullPaths, + bool forceFileExists = true, + Exception exception = null + ); + // Task CreateThumbnail (XFile file); + Task CreateThumbnail (XFileDto file); + Task CreateThumbnail (string fileName); + Task HandleProfileImageSave (IFormFile file); + Task HandleFileSave (IFormFile file); + Task> HandleFilesSave (IFormFileCollection files); + #endregion + } +} \ No newline at end of file diff --git a/Interfaces/IXThumbnailProvider.cs b/Interfaces/IXThumbnailProvider.cs new file mode 100644 index 0000000..dccc7ed --- /dev/null +++ b/Interfaces/IXThumbnailProvider.cs @@ -0,0 +1,14 @@ +using xStorageService.Models; + +namespace xStorageService.Interfaces { + public partial interface IXThumbnailProvider { + void SetTempFolder (string folder); + XImageInfo GetImageInfo (string filePath); + void ResizeImage ( + string filePath, + string resizedFilePath, + int size, + int quality + ); + } +} \ No newline at end of file diff --git a/Models/XFileResult.cs b/Models/XFileResult.cs new file mode 100644 index 0000000..6dea2c4 --- /dev/null +++ b/Models/XFileResult.cs @@ -0,0 +1,9 @@ +namespace xStorageService.Models { + public partial class XFileResult { + public string Name { get; set; } + public string FileName { get; set; } + public string FilePath { get; set; } + public string Thmbnail { get; set; } + public string ThmbnailPath { get; set; } + } +} \ No newline at end of file diff --git a/Models/XImageInfo.cs b/Models/XImageInfo.cs new file mode 100644 index 0000000..ad17c30 --- /dev/null +++ b/Models/XImageInfo.cs @@ -0,0 +1,15 @@ +using ImageMagick; + +namespace xStorageService.Models { + public partial class XImageInfo { + public Interlace Interlace { get; } + public int Height { get; } + public MagickFormat Format { get; } + public string FileName { get; } + public Density Density { get; } + public CompressionMethod Compression { get; } + public ColorSpace ColorSpace { get; } + public int Width { get; } + public int Quality { get; } + } +} \ No newline at end of file diff --git a/Models/XStorageConfiguration.cs b/Models/XStorageConfiguration.cs new file mode 100644 index 0000000..af11b15 --- /dev/null +++ b/Models/XStorageConfiguration.cs @@ -0,0 +1,111 @@ +namespace xStorageService.Models { + public partial class XStorageConfiguration { + /// + /// Base Path of Identity Host Server + /// + /// + public string IdentityAuthority { get; set; } + + /// + /// Base Path of Host Server + /// + /// + public string Authority { get; set; } + + /// + /// Root Directory Of File Storage + /// + /// + public string RootFolder { get; set; } + + /// + /// Base Root of Usable Folder for Files + /// + /// + public string BaseFolder { get; set; } + + /// + /// Storage Folder Label + /// + /// + public string Storage { get; set; } + + /// + /// Temp Folder Label + /// + /// + public string Temp { get; set; } + + /// + /// Thumb Folder Label + /// + /// + public string Thumb { get; set; } + + /// + /// Uploads Folder Label + /// + /// + public string Uploads { get; set; } + + /// + /// Widgets Folder Label + /// + /// + public string Widgets { get; set; } + + /// + /// Images Folder Label + /// + /// + public string Image { get; set; } + + /// + /// Audios Folder Label + /// + /// + public string Audio { get; set; } + + /// + /// Videos Folder Label + /// + /// + public string Video { get; set; } + + /// + /// Documents Folder Label + /// + /// + public string Document { get; set; } + + /// + /// new Files Prefix + /// + /// + public string FilePrefix { get; set; } + + /// + /// Thumb Files Prefix + /// + /// + public string ThumbPrefix { get; set; } + + /// + /// Thumbnail File Sizes + /// + /// + public int ThumbSize { get; set; } + + /// + /// Thumbnail File Quality + /// + /// + public int ThumbQuality { get; set; } + + /// + /// Max File Size + /// + /// + public long MaxFileSize { get; set; } + } +} \ No newline at end of file diff --git a/Providers/XStorageProvider.cs b/Providers/XStorageProvider.cs new file mode 100644 index 0000000..826897e --- /dev/null +++ b/Providers/XStorageProvider.cs @@ -0,0 +1,924 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +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) ... + /// + /// Get Storage Folder Path + /// + /// + public string GetStorageFolderPath () { + return Path.Combine (BasePath, Configuration.Storage); + } + + /// + /// Get Temp Folder Path + /// + /// + public string GetTempFolderPath () { + return Path.Combine (GetStorageFolderPath (), Configuration.Temp); + } + + /// + /// Get Uploads Folder Path + /// + /// + public string GetUploadsFolderPath () { + return Path.Combine (GetStorageFolderPath (), Configuration.Uploads); + } + + /// + /// Get Widgets Folder Path + /// + /// + public string GetWidgetsFolderPath () { + return Path.Combine (GetStorageFolderPath (), Configuration.Widgets); + } + + /// + /// Get Thumb Folder Path + /// + /// + public string GetThumbFolderPath () { + return Path.Combine (GetUploadsFolderPath (), Configuration.Thumb); + } + + /// + /// Get Images Folder Path + /// + /// + public string GetImageFolderPath () { + return Path.Combine (GetUploadsFolderPath (), Configuration.Image); + } + + /// + /// Get Audios Folder Path + /// + /// + public string GetAudioFolderPath () { + return Path.Combine (GetUploadsFolderPath (), Configuration.Audio); + } + + /// + /// Get Videos Folder Path + /// + /// + public string GetVideoFolderPath () { + return Path.Combine (GetUploadsFolderPath (), Configuration.Video); + } + + /// + /// Get Documents Folder Path + /// + /// + public string GetDocumentFolderPath () { + return Path.Combine (GetUploadsFolderPath (), Configuration.Document); + } + + /// + /// Prepare Full Path to Use for Static Files + /// + /// + /// + public string FilePathResolver (string fullPath) { + return fullPath.Replace (BasePath, ""); + } + + /// + /// Convert a File Path to Url + /// + /// + /// + 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)}"; + } + + /// + /// Convert a File Path to Url + /// + /// + /// + 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 ... + /// + /// Check Folder Structure of Storage Area + /// and Create them if not Exists + /// + 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 ... + /// + /// Detrmines a Path Exists or not + /// + /// + /// + public bool IsFolderExists (string path) { + return Directory.Exists (path); + } + + /// + /// Check a File Exists or not + /// + /// + /// + public bool IsFileExists (string filePath) { + return File.Exists (filePath); + } + + /// + /// Check a File Name Support Thumbnail or not + /// + /// + /// + public bool IsSupportThumb (string fileName) { + // + if (!fileName.StartsWith (Configuration.FilePrefix)) { + XException.InavlidFile.Throw (); + } + + // + var fileType = StorageHelper.GetType (fileName); + return IsSupportThumb (fileType); + } + + /// + /// Check a FileType Support Thumbnail or not + /// + /// + /// + public bool IsSupportThumb (XFileType type) { + return ThumbSupprtedTypes.Contains (type); + } + + /// + /// Validate a File Exists or not + /// + /// + public void ValidateFileExists (string filePath) { + // + if (!IsFileExists (filePath)) { + XException.NotFound.Throw (); + } + } + + /// + /// Validate a File Model with it's Related Physical File + /// + /// + 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 (); + } + } + + /// + /// Validate File for Upload + /// + /// + 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 (); + } + } + + /// + /// Validate a Collection of IFormFile + /// + /// + public void ValidateFiles (IFormFileCollection files) { + // + // Validate Args ... + if (!files.HasChild ()) { + XException.InvalidArgs.Throw (); + } + + // + files.ToList () + .ForEach (f => ValidateFile (f)); + } + + /// + /// Validate File for Profile Image + /// + /// + 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 ... + /// + /// Generate New File + /// + /// + public string NewFileName () { + return Configuration.FilePrefix + Guid.NewGuid ().ToString ().ToLower (); + } + + /// + /// Retrieve Related Thumnail File + /// + /// + /// + public string GenerateThumbName (string fileName) { + // + if (!IsSupportThumb (fileName)) { + XException.UnsupportedFileType.Throw (); + } + + // + return fileName.Replace (Configuration.FilePrefix, Configuration.ThumbPrefix); + } + + /// + /// Get a New FileName with Extension based on Given fileName + /// + /// + /// + 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 ... + /// + /// Extract a FileName's Extension + /// + /// + /// + public string GetFileExtension (string fileName) { + // + var result = StorageHelper.GetFileExtension (fileName); + if (result.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + + // + return result; + } + + /// + /// Extract a FileName's Without it's Extension + /// + /// + /// + public string GetFileNameWithoutExtension (string fileName) { + // + var result = StorageHelper.GetFileNameWithoutExtension (fileName); + if (result.IsNullOrEmpty ()) { + XException.InvalidArgs.Throw (); + } + + // + return result; + } + + /// + /// Get file Extension of Specified File Name + /// + /// + /// + 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; + } + + /// + /// Return File Type of Specific File Name + /// + /// + /// + public XFileType GetFileType (string fileName) { + // + var fileExtension = GetFileExtensionType (fileName); + return GetFileType (fileExtension); + } + + /// + /// Return File Type of Specific Extension + /// + /// + /// + public XFileType GetFileType (FileExtensions fileExtension) { + return StorageHelper.GetType ((int) fileExtension); + } + + /// + /// Retrieve Full Path of thumbnail File + /// + /// + /// + public string GetThumbnailFileFullPath (string thumFileName) { + // + if (!thumFileName.StartsWith (Configuration.ThumbPrefix)) { + XException.InavlidFile.Throw (); + } + + // + return Path.Combine (GetThumbFolderPath (), thumFileName); + } + + /// + /// Retrieve a File Thumbnail Path if Supported + /// + /// + /// + public string GetRelatedThumbnailFullPath (string fileName) { + // + var thumbName = GenerateThumbName (fileName); + return GetThumbnailFileFullPath (thumbName); + } + + /// + /// Retrieve Special File Type's Folder Path + /// + /// + /// + 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 (); + } + } + + /// + /// Retrieve a File's Folder path + /// + /// + /// + /// + /// + 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; + } + + /// + /// Retrieve a Full path of a File + /// + /// + /// + /// + /// + 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); + } + + /// + /// Save a File into Storage Based on it's Type + /// + /// + /// + public async Task SaveFileAsync ( + IFormFile file, + bool isTemp = false, + bool isWidget = false) { + // + // 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); + } + + // + // 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; + } + + /// + /// Delete a Physical File by Validate it's Exists + /// + /// + 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); + } + + /// + /// Remove a List of Physical Files Path + /// and return Un Removable Files ... + /// + /// + /// + /// + /// + public ICollection DeleteFiles ( + ICollection 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 (); + } + + /// + /// Create Thumbnail based on FileName.Ext + /// + /// + /// + public async Task CreateThumbnail (string fileName) { + // + 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 + ); + }); + } + + /// + /// Create Thumbnail based on XFile model + /// + /// + /// + public async Task CreateThumbnail (XFileDto file) { + // + await CreateThumbnail (file.Name); + } + + // /// + // /// Create Thumbnail based on XFile model + // /// + // /// + // /// + // public async Task CreateThumbnail (XFile file) { + // // + // await CreateThumbnail (file.Name); + // } + + /// + /// Save and Create a Thumbnail For Profile Image + /// + /// + /// + public async Task HandleProfileImageSave (IFormFile file) { + // + ValidateProfileImage (file); + + // + var savedFileName = await SaveFileAsync (file, isTemp : false, isWidget : false); + var thumbFileName = GenerateThumbName (savedFileName); + + // + var savedFileFullPath = GetFileFullPath (savedFileName); + var thumbFullPath = GetThumbnailFileFullPath (thumbFileName); + + // + await CreateThumbnail (savedFileName); + + // + return new XFileResult { + Name = file.Name, + FileName = savedFileName, + FilePath = ConvertToProfileImageUrl (savedFileFullPath), + Thmbnail = thumbFileName, + ThmbnailPath = ConvertToProfileImageUrl (thumbFullPath) + }; + } + + /// + /// Handle File Upload with Support of Thumbnails ... + /// + /// + /// + public async Task HandleFileSave (IFormFile file) { + // + 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; + } + + /// + /// Handle File Upload with Support of Thumbnails ... + /// + /// + /// + public async Task> HandleFilesSave (IFormFileCollection files) { + // + ValidateFiles (files); + + // + var result = new Collection (); + foreach (var file in files) { + // + 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); + } + + // + result.Add ( + new XFileResult { + Name = file.FileName, + FileName = savedFileName, + FilePath = ConvertToFileUrl (savedFileFullPath), + Thmbnail = thumbFileName, + ThmbnailPath = ConvertToFileUrl (thumbFullPath), + }); + } + + // + return result; + } + #endregion + } +} \ No newline at end of file diff --git a/Providers/XThumbnailProvider.cs b/Providers/XThumbnailProvider.cs new file mode 100644 index 0000000..790475b --- /dev/null +++ b/Providers/XThumbnailProvider.cs @@ -0,0 +1,34 @@ +using ImageMagick; +using xCommons.Extensions; +using xStorageService.Interfaces; +using xStorageService.Models; + +namespace xStorageService.Providers { + public partial class XThumbnailProvider : IXThumbnailProvider { + public void SetTempFolder (string folder) { + MagickNET.SetTempDirectory (folder); + } + + public XImageInfo GetImageInfo (string filePath) { + // + MagickImageInfo info = new MagickImageInfo (filePath); + return info.MapConvert (); + } + + public void ResizeImage ( + string filePath, + string resizedFilePath, + int size, + int quality + ) { + // + using (var image = new MagickImage (filePath)) { + // + image.Resize (size, size); + image.Strip (); + image.Quality = quality; + image.Write (resizedFilePath); + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3a4a80 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# xStorageService + +it is a Part of xDashboard Project on SaherElm IT Center which provides: + +- Files Storage. +- Files Validations. +- Files Handling. +- etc. + +this module has following dependencies : + +- xCommons +- xModels + +for configure and use this Module refer to DI.XDIHelperExtension.cs file. + +## Maintainer + +Hadi Khazaee asl + +[https://www.saherelm.ir](https://www.saherelm.ir) + +[hadi_khazaee_asl@yahoo.com](mailto:hadi_khazaee_asl@yahoo.com) diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..87b6eb0 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/xStorageService.csproj b/xStorageService.csproj new file mode 100644 index 0000000..31c9ef0 --- /dev/null +++ b/xStorageService.csproj @@ -0,0 +1,35 @@ + + + + + netstandard2.0 + xDashboard.xStorageService + 1.0.0 + Hadi Khazaee Asl + SaherElm IT Center + + provide File Storage/Validating/Handling and etc tools to xDashboard project. + + + + icon.png + + + + + + + + + + + + + + + + + + + + \ No newline at end of file