using System; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; using System.Linq; using System.Security.Principal; using System.Threading.Tasks; using BO.AppServer.Business.Exceptions; using BO.AppServer.Business.Mapper; using BO.AppServer.Business.Services.Base; using BO.AppServer.Data.Entity; using BO.AppServer.Data.Repository; using BO.AppServer.Metadata.Configuration; using BO.AppServer.Metadata.Dto; using BO.AppServer.Metadata.Enums; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Quadarax.Foundation.Core.Data; using Quadarax.Foundation.Core.Data.Interface; using Quadarax.Foundation.Core.Data.Interface.Entity.Dto; namespace BO.AppServer.Business.Services { public class DocumentService : UserContextService { #region *** Private fields *** private IFileSystem _fileSystem; private WorkspaceRepo _repoWorkspace; private MetadocumentRepo _repoDocument; private MimeTypeRepo _repoMType; private ArtifactRepo _repoArtifact; private StorageService _srvStorage; #endregion #region *** Constructors *** public DocumentService( WorkspaceRepo repoWorkspace, MetadocumentRepo repoDocument, MimeTypeRepo repoMType, ArtifactRepo repoArtifact, StorageService srvStorage, IFileSystem fileSystem, IOptions config, UserRepo repoUser, UserManager userManager, IPrincipal currentPrincipal, ILoggerFactory logger) : base(config, repoUser, userManager, currentPrincipal, logger) { _repoWorkspace = repoWorkspace ?? throw new ArgumentNullException(nameof(repoWorkspace)); _repoDocument = repoDocument ?? throw new ArgumentNullException(nameof(repoDocument)); _repoMType = repoMType ?? throw new ArgumentNullException(nameof(repoMType)); _repoArtifact = repoArtifact ?? throw new ArgumentNullException(nameof(repoArtifact)); _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); _srvStorage = srvStorage ?? throw new ArgumentNullException(nameof(srvStorage)); } #endregion #region *** Public Operations *** public async Task> GetDocumentAsync(long workspaceId, long documentId) { var document = CheckAndGetDocument(workspaceId, documentId); return new ResultValueDto(document.Map()); } public async Task> GetDocumentAsync(long workspaceId, IdentificationTypeEnum identificationType, string identificationValue) { var document = identificationType == IdentificationTypeEnum.Id ? _repoDocument.Get(ArgumentAsLong(nameof(identificationValue), identificationValue)) : _repoDocument.GetByName(workspaceId, identificationValue); if (document == null) throw new NoDataException(); return new ResultValueDto(document.Map()); } public async Task> GetAllDocumentsAsync(long workspaceId, DocumentsScopeEnums scope, PagingDto paging) { CheckAndGetWorkspace(workspaceId); IList documents = new List(); switch (scope) { case DocumentsScopeEnums.All: documents = _repoDocument.Query(paging).Where(x => x.Structure.WorkspaceId == workspaceId).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Pending: documents = _repoDocument.Query(paging).Where(x => x.Structure.WorkspaceId == workspaceId && x.Status == (int)DocumentStatusEnum.Pending).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Done: documents = _repoDocument.Query(paging).Where(x => x.Structure.WorkspaceId == workspaceId && x.Status == (int)DocumentStatusEnum.DoneOk).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Working: documents = _repoDocument.Query(paging).Where(x => x.Structure.WorkspaceId == workspaceId && x.Status == (int)DocumentStatusEnum.Processing).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Failed: documents = _repoDocument.Query(paging).Where(x => x.Structure.WorkspaceId == workspaceId && x.Status == (int)DocumentStatusEnum.DoneFail).OrderBy(x => x.Name).ToList(); break; } return new ResultsValueDto(documents.MapList()); } public async Task> GetDocumentByNameAsync(long workspaceId, string name) { CheckAndGetWorkspace(workspaceId); IList documents = new List(); var document = _repoDocument.GetByName(workspaceId, name); if (document == null) throw new NoDataException(); return new ResultValueDto(document.Map()); } public async Task> GetAllDocumentsAsync(DocumentsScopeEnums scope, PagingDto paging) { IList documents = new List(); switch (scope) { case DocumentsScopeEnums.All: documents = _repoDocument.Query(paging).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Pending: documents = _repoDocument.Query(paging).Where(x => x.Status == (int)DocumentStatusEnum.Pending).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Done: documents = _repoDocument.Query(paging).Where(x => x.Status == (int)DocumentStatusEnum.DoneOk).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Working: documents = _repoDocument.Query(paging).Where(x => x.Status == (int)DocumentStatusEnum.Processing).OrderBy(x => x.Name).ToList(); break; case DocumentsScopeEnums.Failed: documents = _repoDocument.Query(paging).Where(x => x.Status == (int)DocumentStatusEnum.DoneFail).OrderBy(x => x.Name).ToList(); break; } return new ResultsValueDto(documents.MapList()); } public async Task> CreateDocumentAsync(long workspaceId, DocumentCDto document) { var workspace = CheckAndGetWorkspace(workspaceId); var exists = _repoDocument.Query(new Paging()) .Any(x => x.Structure.WorkspaceId == workspaceId && x.Name == document.Name); if (exists) { var exc = new EntityAlreadyExistsException(typeof(Metadocument), document.Name); Log.LogWarning(exc.Message); throw exc; } var newDocument = _repoDocument.New(); document.CopyToDto(newDocument); newDocument.Created = DateTime.Now; newDocument.CreatedByUser = GetCurrentUser(); newDocument.Status = (int)DocumentStatusEnum.New; newDocument.StructureId = workspace.Structures.First().Id; _repoDocument.Set(newDocument); _repoDocument.Commit(); return new ResultValueDto(newDocument.Map()); } public async Task> ClearDocumentArtifactsAsync(long workspaceId, long documentId, bool force) { var document = CheckAndGetDocument(workspaceId, documentId); CheckDocumentStatusForUpdate(document, force); var artifacts = _repoArtifact.GetArtifacts(documentId).ToArray(); // must be to array foreach (var artifact in artifacts) { await _srvStorage.DeleteContent(artifact.Id); _repoArtifact.Remove(artifact.Id); Log.LogInformation($"Document '{document.Name}' ({documentId}) artifact '{artifact.Name + artifact.Extension}' ({artifact.Id}) removed."); } document.Modified = DateTime.Now; document.ModifiedByUser = GetCurrentUser(); _repoDocument.Set(document); _repoArtifact.Commit(); _repoDocument.Commit(); return new ResultsValueDto(artifacts.MapList()); } public async Task> AppendArtifactAsync(long workspaceId, long documentId, ArtifactCDto artifact,bool replace, ArtifactTypeEnum type, Stream content) { //TODO: Rewrite better way - mainly rollback scenario var document = CheckAndGetDocument(workspaceId, documentId); var existingArtifact = _repoArtifact.GetArtifactByName(documentId, artifact.Name, artifact.Extension); if (existingArtifact!=null && !replace) { var exc = new EntityAlreadyExistsException(typeof(Artifact), artifact.Name + "." + artifact.Extension); Log.LogWarning(exc.Message); throw exc; } var mtype = _repoMType.GetByMimeType(artifact.MimeType); if (mtype == null) { var exc = new EntityNotExistsException(typeof(MimeType), artifact.MimeType, 0); Log.LogWarning(exc.Message); throw exc; } var newArtifact = (existingArtifact != null && replace) ? existingArtifact : _repoArtifact.New(); newArtifact.Name = artifact.Name; newArtifact.Extension = artifact.Extension; newArtifact.Length = content.Length; newArtifact.Metadocument = document; newArtifact.MimeType = mtype; newArtifact.Created = DateTime.Now; newArtifact.CreatedByUser = GetCurrentUser(); newArtifact.Type = (int)type; _repoArtifact.Set(newArtifact); document = _repoDocument.Get(documentId); document.Modified = DateTime.Now; document.ModifiedByUser = GetCurrentUser(); _repoDocument.Set(document); _repoArtifact.Commit(); _repoDocument.Commit(); await _srvStorage.PushContentAsync(newArtifact.Id, content); return new ResultValueDto(newArtifact.Map()); } public async Task> GetArtifactAsync(long workspaceId, long documentId, long artifactId) { var artifact = CheckAndGetArtifact(workspaceId, documentId, artifactId); var result = await _srvStorage.PopContentAsync(artifact.Id); return new Tuple(result.Item1, artifact.MimeType.Mimetype1); } public ArtifactCDto CreateArtifactDto(string fileName, string contentType) { var result = new ArtifactCDto(); result.Extension = _fileSystem.Path.GetExtension(fileName); result.MimeType = contentType; result.Name = _fileSystem.Path.GetFileNameWithoutExtension(fileName); return result; } #endregion #region *** Private Operations *** private Workspace CheckAndGetWorkspace(long workspaceId) { var workspace = _repoWorkspace.Get(workspaceId); if (workspace == null) { var exc = new EntityNotExistsException(typeof(Workspace), nameof(Workspace.Id), workspaceId); Log.LogWarning(exc.Message); throw exc; } return workspace; } private Metadocument CheckAndGetDocument(long workspaceId, long documentId) { CheckAndGetWorkspace(workspaceId); var document = _repoDocument.Get(workspaceId ,documentId); if (document == null) { var exc = new EntityNotExistsException(typeof(Metadocument), nameof(Metadocument.Id), documentId); Log.LogWarning(exc.Message); throw exc; } return document; } private void CheckDocumentStatusForUpdate(Metadocument document, bool force) { if (document == null) throw new ArgumentNullException(nameof(document)); var currentStatus = (DocumentStatusEnum)document.Status; var allowedStatus = new DocumentStatusEnum[] { DocumentStatusEnum.New, DocumentStatusEnum.Pending, }; if (!allowedStatus.Contains(currentStatus)) { var message = $"Document '{document.Name}' [{document.Id}] has not allowed status '{currentStatus}' for operation UPDATE. Allowed statuses are {string.Join(",", allowedStatus)}."; Log.LogWarning(message + (force ? " Skip due Force flag." : string.Empty)); if (!force) throw new InvalidDocumentStateException(document.Name, document.Id, currentStatus, "update"); } } private Artifact CheckAndGetArtifact(long workspaceId, long documentId, long artifactId) { CheckAndGetWorkspace(workspaceId); CheckAndGetDocument(workspaceId, documentId); var artifact = _repoArtifact.GetArtifact(documentId, artifactId); if (artifact == null) { var exc = new EntityNotExistsException(typeof(Artifact), nameof(Artifact.Id), artifactId); Log.LogWarning(exc.Message); throw exc; } return artifact; } #endregion } }