Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple with the ABP BLOB storing module #98

Merged
merged 3 commits into from Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions EasyAbp.FileManagement.sln
Expand Up @@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.FileManagement.Blaz
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.FileManagement.Blazor.WebAssembly", "src\EasyAbp.FileManagement.Blazor.WebAssembly\EasyAbp.FileManagement.Blazor.WebAssembly.csproj", "{9D060D93-B97D-4640-9770-6B507F97C295}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyAbp.FileManagement.Domain.Core", "src\EasyAbp.FileManagement.Domain.Core\EasyAbp.FileManagement.Domain.Core.csproj", "{1CB06CFE-6095-43E1-A624-3A42739ED575}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -165,6 +167,10 @@ Global
{9D060D93-B97D-4640-9770-6B507F97C295}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D060D93-B97D-4640-9770-6B507F97C295}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D060D93-B97D-4640-9770-6B507F97C295}.Release|Any CPU.Build.0 = Release|Any CPU
{1CB06CFE-6095-43E1-A624-3A42739ED575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CB06CFE-6095-43E1-A624-3A42739ED575}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CB06CFE-6095-43E1-A624-3A42739ED575}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CB06CFE-6095-43E1-A624-3A42739ED575}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -195,6 +201,7 @@ Global
{FF70CED3-39D6-42BA-83B4-C11478153993} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
{2553DA49-445E-4911-B40E-109CE3815AEB} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
{9D060D93-B97D-4640-9770-6B507F97C295} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
{1CB06CFE-6095-43E1-A624-3A42739ED575} = {649A3FFA-182F-4E56-9717-E6A9A2BEC545}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4324B3B4-B60B-4E3C-91D8-59576B4E26DD}
Expand Down
2 changes: 1 addition & 1 deletion common.props
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Version>3.0.1</Version>
<Version>4.0.0-preview.1</Version>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>EasyAbp Team</Authors>
Expand Down
Expand Up @@ -66,7 +66,7 @@ public class BasicFileOperationAuthorizationHandler : FileOperationAuthorization

await SetSucceedIfUserIsManagerAsync(context, requirement);

var configuration = _configurationProvider.Get(resource.FileContainerName);
var configuration = _configurationProvider.Get<FileContainerConfiguration>(resource.FileContainerName);

await SetFailIfUserIsNotPersonalContainerOwnerAsync(configuration, context, resource);
}
Expand Down

Large diffs are not rendered by default.

@@ -1,15 +1,41 @@
using System;
using JetBrains.Annotations;

namespace EasyAbp.FileManagement.Files
{
public class FileOperationInfoModel
{
public Guid? ParentId { get; set; }


[NotNull]
public string FileContainerName { get; set; }


[CanBeNull]
public string FileName { get; set; }

[CanBeNull]
public string MimeType { get; set; }

public FileType? FileType { get; set; }

public long? ByteSize { get; set; }

public Guid? OwnerUserId { get; set; }


[CanBeNull]
public File File { get; set; }

public FileOperationInfoModel(Guid? parentId, [NotNull] string fileContainerName, [CanBeNull] string fileName,
[CanBeNull] string mimeType, FileType? fileType, long? byteSize, Guid? ownerUserId, [CanBeNull] File file)
{
ParentId = parentId;
FileContainerName = fileContainerName;
FileName = fileName;
MimeType = mimeType;
FileType = fileType;
ByteSize = byteSize;
OwnerUserId = ownerUserId;
File = file;
}
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\common.props" />

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="EasyAbp.FileManagement.Domain" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Volo.Abp.AutoMapper" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<ProjectReference Include="..\EasyAbp.FileManagement.Domain.Shared\EasyAbp.FileManagement.Domain.Shared.csproj" />
</ItemGroup>

</Project>
@@ -0,0 +1,36 @@
using EasyAbp.FileManagement.Files;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Modularity;

namespace EasyAbp.FileManagement
{
[DependsOn(
typeof(FileManagementDomainSharedModule),
typeof(AbpAutoMapperModule),
typeof(AbpDddDomainModule)
)]
public class FileManagementDomainCoreModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<File, FileEto>(typeof(FileManagementDomainCoreModule));
options.AutoEventSelectors.Add<File>();
});
}

public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper<FileManagementDomainCoreModule>();

Configure<AbpAutoMapperOptions>(options =>
{
options.AddProfile<FileManagementDomainAutoMapperProfile>(validate: true);
});
}
}
}
@@ -1,24 +1,20 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.MultiTenancy;

namespace EasyAbp.FileManagement.Files
{
public class File : FullAuditedAggregateRoot<Guid>, IMultiTenant
public class File : FullAuditedAggregateRoot<Guid>, IFile, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }

public virtual Guid? ParentId { get; protected set; }

[NotNull]
public virtual string FileContainerName { get; protected set; }

[NotNull]
public virtual string FileName { get; protected set; }

[CanBeNull]
public virtual string MimeType { get; protected set; }

public virtual FileType FileType { get; protected set; }
Expand All @@ -27,15 +23,12 @@ public class File : FullAuditedAggregateRoot<Guid>, IMultiTenant

public virtual long ByteSize { get; protected set; }

[CanBeNull]
public virtual string Hash { get; protected set; }

[CanBeNull]
public virtual string BlobName { get; protected set; }

public virtual Guid? OwnerUserId { get; protected set; }

[CanBeNull]
public virtual string Flag { get; protected set; }

protected File()
Expand Down
@@ -0,0 +1,13 @@
using Volo.Abp;

namespace EasyAbp.FileManagement.Files
{
public class FileContainerConflictException : BusinessException
{
public FileContainerConflictException() : base(
"FileContainerConflict",
$"Multiple file upload requests attempted to save files in the same file container.")
{
}
}
}
Expand Up @@ -2,9 +2,9 @@

namespace EasyAbp.FileManagement.Files
{
public class FileIsMovedToSubDirectoryException : BusinessException
public class FileIsMovingToSubDirectoryException : BusinessException
{
public FileIsMovedToSubDirectoryException() : base(
public FileIsMovingToSubDirectoryException() : base(
message: "A directory cannot be moved from a directory to one of its sub directories.")
{
}
Expand Down
@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EasyAbp.FileManagement.Containers;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Abp.Domain.Services;
using Volo.Abp.Uow;

namespace EasyAbp.FileManagement.Files;

public abstract class FileManagerBase : DomainService, IFileManager
{
protected IFileRepository FileRepository => LazyServiceProvider.LazyGetRequiredService<IFileRepository>();

public abstract Task<File> CreateAsync(CreateFileModel model, CancellationToken cancellationToken = default);

public abstract Task<File> CreateAsync(CreateFileWithStreamModel model,
CancellationToken cancellationToken = default);

public abstract Task<List<File>> CreateManyAsync(List<CreateFileModel> models,
CancellationToken cancellationToken = default);

public abstract Task<List<File>> CreateManyAsync(List<CreateFileWithStreamModel> models,
CancellationToken cancellationToken = default);

public abstract Task<File> UpdateAsync(File file, string newFileName, File oldParent, File newParent,
CancellationToken cancellationToken = default);

public abstract Task<File> UpdateAsync(File file, UpdateFileModel model,
CancellationToken cancellationToken = default);

public abstract Task<File> UpdateAsync(File file, UpdateFileWithStreamModel model,
CancellationToken cancellationToken = default);

public abstract Task DeleteAsync(File file, CancellationToken cancellationToken = default);

protected abstract IFileDownloadProvider GetFileDownloadProvider(File file);

public virtual async Task<FileDownloadInfoModel> GetDownloadInfoAsync(File file)
{
if (file.FileType != FileType.RegularFile)
{
throw new UnexpectedFileTypeException(file.Id, file.FileType, FileType.RegularFile);
}

var provider = GetFileDownloadProvider(file);

return await provider.CreateDownloadInfoAsync(file);
}

protected virtual async Task<File> TryGetFileByNullableIdAsync(Guid? fileId)
{
return fileId.HasValue ? await FileRepository.GetAsync(fileId.Value) : null;
}

protected virtual void CheckDirectoryHasNoFileContent(FileType fileType, byte[] fileContent)
{
if (fileType == FileType.Directory && !fileContent.IsNullOrEmpty())
{
throw new DirectoryFileContentIsNotEmptyException();
}
}

protected virtual async Task CheckNotMovingDirectoryToSubDirectoryAsync([NotNull] File file,
[CanBeNull] File targetParent)
{
if (file.FileType != FileType.Directory)
{
return;
}

var parent = targetParent;

while (parent != null)
{
if (parent.Id == file.Id)
{
throw new FileIsMovingToSubDirectoryException();
}

parent = parent.ParentId.HasValue ? await FileRepository.GetAsync(parent.ParentId.Value) : null;
}
}

protected virtual void CheckFileName(string fileName, IFileContainerConfiguration configuration)
{
Check.NotNullOrWhiteSpace(fileName, nameof(File.FileName));

if (fileName.Contains(FileManagementConsts.DirectorySeparator))
{
throw new FileNameContainsSeparatorException(fileName, FileManagementConsts.DirectorySeparator);
}
}

[UnitOfWork]
protected virtual async Task<bool> IsFileExistAsync(string fileName, Guid? parentId, string fileContainerName,
Guid? ownerUserId)
{
return await FileRepository.FindAsync(fileName, parentId, fileContainerName, ownerUserId) != null;
}

protected virtual async Task CheckFileNotExistAsync(string fileName, Guid? parentId, string fileContainerName,
Guid? ownerUserId)
{
if (await IsFileExistAsync(fileName, parentId, fileContainerName, ownerUserId))
{
throw new FileAlreadyExistsException(fileName, parentId);
}
}

protected virtual void CheckFileQuantity(int count, IFileContainerConfiguration configuration)
{
if (count > configuration.MaxFileQuantityForEachUpload)
{
throw new UploadQuantityExceededLimitException(count, configuration.MaxFileQuantityForEachUpload);
}
}

protected virtual void CheckFileSize(Dictionary<string, long> fileNameByteSizeMapping,
IFileContainerConfiguration configuration)
{
foreach (var pair in fileNameByteSizeMapping.Where(
pair => pair.Value > configuration.MaxByteSizeForEachFile))
{
throw new FileSizeExceededLimitException(pair.Key, pair.Value, configuration.MaxByteSizeForEachFile);
}

var totalByteSize = fileNameByteSizeMapping.Values.Sum();

if (totalByteSize > configuration.MaxByteSizeForEachUpload)
{
throw new UploadSizeExceededLimitException(totalByteSize, configuration.MaxByteSizeForEachUpload);
}
}

protected virtual void CheckFileExtension(IEnumerable<string> fileNames, IFileContainerConfiguration configuration)
{
foreach (var fileName in fileNames.Where(fileName => !IsFileExtensionAllowed(fileName, configuration)))
{
throw new FileExtensionIsNotAllowedException(fileName);
}
}

protected virtual bool IsFileExtensionAllowed(string fileName, IFileContainerConfiguration configuration)
{
var lowerFileName = fileName.ToLowerInvariant();

foreach (var pair in configuration.FileExtensionsConfiguration.Where(x =>
lowerFileName.EndsWith(x.Key.ToLowerInvariant())))
{
return pair.Value;
}

return !configuration.AllowOnlyConfiguredFileExtensions;
}
}
@@ -1,9 +1,11 @@
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace EasyAbp.FileManagement.Files
{
public interface IFileBlobNameGenerator
{
Task<string> CreateAsync(FileType fileType, string fileName, File parent, string mimeType, string directorySeparator);
Task<string> CreateAsync(FileType fileType, string fileName, [CanBeNull] IFile parent, string mimeType,
string directorySeparator);
}
}