From b99f0de912fcaa6d36d578b0368259cfe04b9334 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Thu, 28 May 2026 19:15:31 +0330 Subject: [PATCH] last ... --- Controllers/XFileProviderControllerBase.cs | 213 ++++++++ Interfaces/IXFileProvider.cs | 107 +++- Interfaces/IXFileProviderController.cs | 85 +++- Providers/XFileProvider.cs | 566 ++++++++++++++++++++- 4 files changed, 968 insertions(+), 3 deletions(-) diff --git a/Controllers/XFileProviderControllerBase.cs b/Controllers/XFileProviderControllerBase.cs index d97ab21..37fade7 100644 --- a/Controllers/XFileProviderControllerBase.cs +++ b/Controllers/XFileProviderControllerBase.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Logging; @@ -47,6 +48,218 @@ namespace xFileService.Controllers this.provider = provider; } + // + #region Tools ... + /// + /// Stream Specified File ... + /// + /// + /// + /// + [HttpGet("{id}/Stream/ById")] + public virtual async Task Stream( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var result = await provider.Stream( + id: id, + cancellationToken: cancellationToken + ); + + // + return result; + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Stream Specified File ... + /// + /// + /// + /// + [HttpGet("{name}/Stream/ByName")] + public virtual async Task Stream( + [FromRoute] string name, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var result = await provider.Stream( + name: name, + cancellationToken: cancellationToken + ); + + // + return result; + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Download Specified File ... + /// + /// + /// + /// + [HttpGet("{id}/Download/ById")] + public virtual async Task Download( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var result = await provider.Download( + id: id, + cancellationToken: cancellationToken + ); + + // + return result; + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Download Specified File ... + /// + /// + /// + /// + [HttpGet("{name}/Download/ByName")] + public virtual async Task Download( + string name, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + var result = await provider.Download( + name: name, + cancellationToken: cancellationToken + ); + + // + return result; + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Upload Files ... + /// + /// + /// + /// + [HttpPost("Upload")] + [RequestSizeLimit(966_367_641)] + public virtual async Task>> Upload( + [FromForm] IFormFileCollection files, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Retrieve User Info ... + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + var result = await provider.Upload( + files: files, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + + /// + /// Remove Specified Files ... + /// + /// + /// + /// + /// + /// + [HttpDelete("")] + public virtual async Task>> Remove( + [FromQuery] string ids, + CancellationToken cancellationToken = default + ) + { + // + try + { + // + // Retrieve User Info ... + var userInfo = await GetUserInfo(); + var connectionId = GetConnectionId(); + + // + var result = await provider.Remove( + ids: ids, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + #endregion + // #region Reference ... /// diff --git a/Interfaces/IXFileProvider.cs b/Interfaces/IXFileProvider.cs index 4a9ca48..c931068 100644 --- a/Interfaces/IXFileProvider.cs +++ b/Interfaces/IXFileProvider.cs @@ -1,12 +1,117 @@ using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using xFileService.Models.Dtos; using xFileService.Models.Entities; using xFileService.Providers.Dtos; +using xIdentityModels.Models; using xIdentityService.Interfaces; using xTagService.Interfaces; namespace xFileService.Interfaces { public interface IXFileProvider : IXBaseProvidedHasTag, IXBaseReferenced - { } + { + // + #region Tools ... + /// + /// Stream Specified File ... + /// + /// + /// + /// + Task Stream( + Guid id, + CancellationToken cancellationToken = default + ); + + /// + /// Stream Specified File ... + /// + /// + /// + /// + Task Stream( + string name, + CancellationToken cancellationToken = default + ); + + /// + /// Download Specified File ... + /// + /// + /// + /// + Task Download( + Guid id, + CancellationToken cancellationToken = default + ); + + /// + /// Download Specified File ... + /// + /// + /// + /// + Task Download( + string name, + CancellationToken cancellationToken = default + ); + + /// + /// Upload Files ... + /// + /// + /// + /// + /// + /// + Task> Upload( + IFormFileCollection files, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Files ... + /// + /// + /// + /// + /// + /// + Task> Remove( + string ids, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Specified File Streaming Info ... + /// + /// + /// + /// + Task GetFileDescriptor( + Guid id, + CancellationToken cancellationToken = default + ); + + /// + /// Retrieve Specified File Streaming Info ... + /// + /// + /// + /// + Task GetFileDescriptor( + string fileName, + CancellationToken cancellationToken = default + ); + #endregion + } } \ No newline at end of file diff --git a/Interfaces/IXFileProviderController.cs b/Interfaces/IXFileProviderController.cs index 32a2052..268081e 100644 --- a/Interfaces/IXFileProviderController.cs +++ b/Interfaces/IXFileProviderController.cs @@ -1,4 +1,9 @@ using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using xFileService.Models.Dtos; using xFileService.Models.Entities; using xFileService.Providers.Dtos; @@ -8,5 +13,83 @@ using xTagService.Interfaces; namespace xFileService.Interfaces { public interface IXFileProviderController : IXBaseProvidedHasTagController, IXBaseReferencedController - { } + { + // + #region Tools ... + /// + /// Stream Specified File ... + /// + /// + /// + /// + [HttpGet("{id}/Stream/ById")] + Task Stream( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ); + + /// + /// Stream Specified File ... + /// + /// + /// + /// + [HttpGet("{name}/Stream/ByName")] + Task Stream( + [FromRoute] string name, + CancellationToken cancellationToken = default + ); + + /// + /// Download Specified File ... + /// + /// + /// + /// + [HttpGet("{id}/Download/ById")] + Task Download( + [FromRoute] Guid id, + CancellationToken cancellationToken = default + ); + + /// + /// Download Specified File ... + /// + /// + /// + /// + [HttpGet("{name}/Download/ByName")] + Task Download( + string name, + CancellationToken cancellationToken = default + ); + + /// + /// Upload Files ... + /// + /// + /// + /// + [HttpPost("Upload")] + [RequestSizeLimit(966_367_641)] + Task>> Upload( + [FromForm] IFormFileCollection files, + CancellationToken cancellationToken = default + ); + + /// + /// Remove Specified Files ... + /// + /// + /// + /// + /// + /// + [HttpDelete("")] + Task>> Remove( + [FromQuery] string ids, + CancellationToken cancellationToken = default + ); + #endregion + } } \ No newline at end of file diff --git a/Providers/XFileProvider.cs b/Providers/XFileProvider.cs index f7f9a90..46d89c8 100644 --- a/Providers/XFileProvider.cs +++ b/Providers/XFileProvider.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.FileProviders; using xCommons.Extensions; using xDataService.Models; using xExceptions.Constants; @@ -13,6 +18,7 @@ using xFileService.Models.Dtos; using xFileService.Models.Entities; using xFileService.Providers.Dtos; using xIdentityModels.Models; +using xStorageService.Interfaces; using xTagService.Providers; namespace xFileService.Providers @@ -20,10 +26,12 @@ namespace xFileService.Providers public class XFileProvider : XBaseProvidedHasTag, IXFileProvider { private readonly IXFileServiceProvider provider; + private readonly IXStorageProvider storageProvider; public XFileProvider( IXFileTagProvider tagProvider, - IXFileServiceProvider provider + IXFileServiceProvider provider, + IXStorageProvider storageProvider ) : base( provider: provider, tagProvider: tagProvider, @@ -31,6 +39,7 @@ namespace xFileService.Providers ) { this.provider = provider; + this.storageProvider = storageProvider; } public override bool IsOwned( @@ -65,6 +74,561 @@ namespace xFileService.Providers return result; } + // + #region Tools ... + /// + /// Stream Specified File ... + /// + /// + /// + /// + public virtual async Task Stream( + Guid id, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !id.IsNull() && + !id.IsDefaultGuid(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Generate Stream Info and Validate it ... + var stream = await GetFileDescriptor( + id: id, + cancellationToken: cancellationToken + ); + if (stream.IsNull()) + { + XException.NotFound.Throw(); + } + + // + // Generate Result Model ... + var result = new FileStreamResult( + stream.Stream, + stream.MIMEType + ) + { + FileDownloadName = stream.Name + }; + + // + return result; + } + + /// + /// Stream Specified File ... + /// + /// + /// + /// + public virtual async Task Stream( + string name, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !name.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Generate Stream Info and Validate it ... + var stream = await GetFileDescriptor( + fileName: name, + cancellationToken: cancellationToken + ); + if (stream.IsNull()) + { + XException.NotFound.Throw(); + } + + // + // Generate Result Model ... + var result = new FileStreamResult( + stream.Stream, + stream.MIMEType + ) + { + FileDownloadName = stream.Name + }; + + // + return result; + } + + /// + /// Download Specified File ... + /// + /// + /// + /// + public virtual async Task Download( + Guid id, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !id.IsNull() && + !id.IsDefaultGuid(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Generate Stream Info and Validate it ... + var stream = await GetFileDescriptor( + id: id, + cancellationToken: cancellationToken + ); + if (stream.IsNull()) + { + XException.NotFound.Throw(); + } + + // + // Generate Result Model ... + var result = new PhysicalFileResult( + stream.Path, + stream.MIMEType + ) + { + FileDownloadName = stream.Name + }; + + // + return result; + } + + /// + /// Download Specified File ... + /// + /// + /// + /// + public virtual async Task Download( + string name, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !name.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Generate Stream Info and Validate it ... + var stream = await GetFileDescriptor( + fileName: name, + cancellationToken: cancellationToken + ); + if (stream.IsNull()) + { + XException.NotFound.Throw(); + } + + // + // Generate Result Model ... + var result = new PhysicalFileResult( + stream.Path, + stream.MIMEType + ) + { + FileDownloadName = stream.Name + }; + + // + return result; + } + + /// + /// Upload Files ... + /// + /// + /// + /// + /// + /// + public virtual async Task> Upload( + IFormFileCollection files, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !files.IsNull() && + files.HasChild() && + !userInfo.IsNullOrDefault(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Save Files On Server Side Physically ... + // and Validate it ... + var uploadResults = await storageProvider + .HandleFilesSave(files); + isValid = uploadResults.HasChild(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Prepare Result ... + var result = new List(); + foreach (var ur in uploadResults) + { + // + var fileType = storageProvider.GetFileType(ur.FileName); + + // + // Create Item Model ... + var item = new XFileDto + { + Type = fileType, + FileName = ur.Name, + Name = ur.FileName, + Path = ur.FilePath, + Thumb = ur.Thmbnail, + OwnerId = userInfo.UserId, + ThumbPath = ur.ThmbnailPath, + UploadedOn = DateTime.UtcNow, + }; + + // + try + { + // + // Add Entity Model to DB ... + item = await Add( + item: item, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + + // + result.Add(item); + } + catch { } + } + + // + return result; + } + + /// + /// Remove Specified Files ... + /// + /// + /// + /// + /// + /// + public virtual async Task> Remove( + string ids, + XUserClaimsInfoDto userInfo = null, + string connectionId = null, + CancellationToken cancellationToken = default + ) + { + // + // Validate Args ... + var isValid = !ids.IsNullOrEmpty() && + !userInfo.IsNullOrDefault(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Parse ID(s) List ... + var idsList = ids.ParseListGuid(); + isValid = !idsList.IsNull() && + idsList.HasChild(); + if (!isValid) + { + XException.ActionFailed.Throw(); + } + + // + // Loop Through Parsed Ids and Try to Remove ... + var result = new List(); + foreach (var id in idsList) + { + // + // Retrieve XFileDto and Validate it ... + var model = await Get(id); + isValid = !model.IsNull() && !model.Id.IsNull() && !model.Id.IsDefaultGuid(); + if (!isValid) + { + continue; + } + + // + // Check Physical File Exists or not ... + var fileFullPath = storageProvider.GetFileFullPath(model.Name); + var thumbFullPath = model.Thumb.IsNullOrEmpty() + ? "" + : storageProvider.GetThumbnailFileFullPath(model.Thumb); + var isFileExists = storageProvider.IsFileExists(fileFullPath); + var isThumbExists = model.Thumb.IsNullOrEmpty() + ? false + : storageProvider.IsFileExists(thumbFullPath); + + // + // Remove Physical Thumbnail File ... + if (isThumbExists) + { + // + try + { + // + // Removed Stored Thumbnail ... + storageProvider.DeleteFile( + fileFullPath: thumbFullPath, + forceFileExists: false + ); + } + catch { } + } + + // + // Remove Pgysical File ... + if (isFileExists) + { + // + try + { + // + // Remove Stored File ... + storageProvider.DeleteFile( + fileFullPath: fileFullPath, + forceFileExists: false + ); + + // + // Remove Tags ... + await DetachTags( + id: id, + userInfo: userInfo, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + } + catch { } + } + + // + // Remove File Entty ... + var item = await provider.RemoveAsync( + id: model.Id, + softDelete: false, + saveChanges: true, + connectionId: connectionId, + cancellationToken: cancellationToken + ); + result.Add(model.Id); + } + + // + return result; + } + + /// + /// Retrieve Specified File Streaming Info ... + /// + /// + /// + /// + public virtual async Task GetFileDescriptor( + Guid id, + CancellationToken cancellationToken = default + ) + { + // + // Check File Model Exists or not ... + var isExists = await IsExists( + id: id, + cancellationToken: cancellationToken + ); + if (!isExists) + { + XException.NotFound.Throw(); + } + + // + // Retrieve Item and Validate it ... + var item = await Get( + id: id, + includeBuilder: null, + cancellationToken: cancellationToken + ); + if (item.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Generate Physical File Path and Validate it ... + var fileFullPath = storageProvider.GetFileFullPath(item.Name); + isExists = storageProvider.IsFileExists(fileFullPath); + var filePath = storageProvider.FilePathResolver(fileFullPath); + filePath = Path.Combine("wwwroot", filePath); + if (!isExists) + { + XException.NotFound.Throw(); + } + + // + // Generate Physical File Info ... + IFileInfo fileInfo = storageProvider.FileProvider.GetFileInfo(filePath); + + // + // Prepare Default Streaming Requirements ... + var mimeType = "application/octetstream"; + var readStream = fileInfo.CreateReadStream(); + var fileMimeProvider = new FileExtensionContentTypeProvider(); + var fileName = item.Name; + + // + // Try to Find Physical File's Content Type ... + fileMimeProvider.TryGetContentType( + filePath, + out mimeType + ); + + // + // Prepare Result Model ... + var result = new XFileStreamDescriptorDto + { + Stream = readStream, + MIMEType = mimeType, + Name = fileName, + Path = fileFullPath + }; + + // + return result; + } + + /// + /// Retrieve Specified File Streaming Info ... + /// + /// + /// + /// + public virtual async Task GetFileDescriptor( + string fileName, + CancellationToken cancellationToken = default + ) + { + // + if (fileName.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + var item = await FindOne( + includeBuilder: null, + predicate: e => + e.Name.Contains(fileName) || + e.Path.Contains(fileName) || + e.Thumb.Contains(fileName) || + e.FileName.Contains(fileName) || + e.ThumbPath.Contains(fileName), + cancellationToken: cancellationToken + ); + var isExists = !item.IsNullOrDefault(); + if (!isExists) + { + XException.NotFound.Throw(); + } + + // + // Extract File Name ... + fileName = Path.GetFileName(fileName); + + // + // Check File is Thumbnail or File ... + var isThumb = fileName.StartsWith(storageProvider.Configuration.ThumbPrefix); + var isFile = fileName.StartsWith(storageProvider.Configuration.FilePrefix); + + // + // Check File Type is Valid ... + isExists = isThumb || isFile; + if (!isExists) + { + XException.UnsupportedFileType.Throw(); + } + + // + // Extract File Full Path ... + var fileFullPath = ""; + if (isThumb) + { + fileFullPath = storageProvider.GetThumbnailFileFullPath(fileName); + } + else + { + fileFullPath = storageProvider.GetFileFullPath(fileName); + } + + // + // Check Physical File Exists or Not ... + isExists = storageProvider.IsFileExists(fileFullPath); + if (!isExists) + { + XException.NotFound.Throw(); + } + + // + // Generate Physical File Info ... + var filePath = storageProvider.FilePathResolver(fileFullPath); + filePath = Path.Combine("wwwroot", filePath); + IFileInfo fileInfo = storageProvider.FileProvider.GetFileInfo(filePath); + + // + // Prepare Default Streaming Requirements ... + var mimeType = "application/octetstream"; + var readStream = fileInfo.CreateReadStream(); + var fileMimeProvider = new FileExtensionContentTypeProvider(); + + // + // Try to Find Physical File's Content Type ... + fileMimeProvider.TryGetContentType(filePath, out mimeType); + + // + // Prepare Result Model ... + var result = new XFileStreamDescriptorDto + { + Stream = readStream, + MIMEType = mimeType, + Name = fileName, + Path = fileFullPath + }; + + // + return result; + } + #endregion + // #region Referenced Actions ... ///