This commit is contained in:
Peter Maquiran
2024-03-26 12:03:30 +01:00
parent 0b876e516b
commit ca0b968abe
32 changed files with 1016 additions and 794 deletions
@@ -1,9 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class PublicationFormMVService {
constructor() { }
}
@@ -1,13 +1,13 @@
import { TestBed } from '@angular/core/testing';
import { PublicationFormMVService } from './publication-form-mv.service';
import { PublicationFromMvService } from './publication-from-mv.service';
describe('PublicationFormMVService', () => {
let service: PublicationFormMVService;
describe('PublicationFromMvService', () => {
let service: PublicationFromMvService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PublicationFormMVService);
service = TestBed.inject(PublicationFromMvService);
});
it('should be created', () => {
@@ -0,0 +1,356 @@
import { Injectable } from '@angular/core';
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service';
import { PublicationsService } from 'src/app/services/publications.service';
import { ToastService } from 'src/app/services/toast.service';
import { PublicationFolderService } from 'src/app/store/publication-folder.service';
import { Chunks, ChunksBase64, IOUploadError, PublicationAttachmentEntity, PublicationFormModel, UploadFileUseCase } from './upload-streaming.service';
import { ObjectMergeNotification } from 'src/app/services/socket-connection-mcr.service';
import { v4 as uuidv4 } from 'uuid'
import { Result } from 'neverthrow';
import { IPublicationFormModelEntity } from '../new-publication/interface/interface';
import { CMAPIService } from "src/app/shared/repository/CMAPI/cmapi.service"
enum ActionType {
newRapid = "1",
new = "2",
edit = "3"
}
@Injectable({
providedIn: 'any'
})
export class PublicationFromMvService {
id: string = uuidv4()
private UploadFileUseCase = new UploadFileUseCase()
form = new PublicationFormModel()
ObjectMergeNotification = new ObjectMergeNotification()
totalPercentage = 0
publicationType: ActionType
folderId: string
constructor(
private publications: PublicationsService,
private toastService: ToastService,
private httpErroHandle: HttpErrorHandle,
public PublicationFolderService: PublicationFolderService,
private CMAPIService: CMAPIService,
public publicationFolderService: PublicationFolderService
) {}
clear() {
this.id = uuidv4()
this.UploadFileUseCase = new UploadFileUseCase()
this.form = new PublicationFormModel()
this.ObjectMergeNotification = new ObjectMergeNotification()
this.totalPercentage = 0
this.ObjectMergeNotification.connect();
window['upload-header-set-add'](this.id, this.totalPercentage, this.save)
}
cancel() {
window['upload-header-set-remove'](this.id)
}
setFolderId(folderId) {
this.folderId = folderId
}
save = async() => {
if (this.publicationType == ActionType.edit) {
if (this.form.Files.length >= 1) {
// const loader = this.toastService.loading()
this.form.send = true
const upload = await this.uploadVideosFiles()
if(upload) {
this.form.Files = this.form.Files.map((e:PublicationAttachmentEntity) => {
if(e.FileType == 'video' && e.toUpload) {
e.OriginalFileName = e?.chucksManager?.path?.replace(".mp4", "") || e.OriginalFileName
e.FileExtension = e.FileExtension || "mp4"
}
return e
})
}
const publication: any = Object.assign({}, this.form)
publication.Files = publication.Files.map( (e:PublicationAttachmentEntity) => ({
FileBase64: e.url,
FileExtension: e.FileExtension,
OriginalFileName: e.OriginalFileName || 'foto'
}))
try {
const response = await this.publications.UpdatePublication(publication.ProcessId, publication).toPromise()
this.httpErroHandle.httpsSucessMessagge('Editar publicação')
this.publicationFolderService.getPublicationsIds(this.folderId)
// this.goBack();
} catch (error) {
this.httpErroHandle.httpStatusHandle(error)
if (error.status == 404) {
this.PublicationFolderService.deletePost(this.form.ProcessId, this.form.DocumentId)
// this.goBack();
}
} finally {
// loader.remove()
}
} else {
this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo")
}
}
else {
let time = new Date()
if (this.form.Files.length >= 1) {
// const loader = this.toastService.loading()
this.form.send = false
const upload = await this.uploadVideosFiles()
if(upload) {
this.form.Files = this.form.Files.map((e:PublicationAttachmentEntity) => {
if(e.FileType == 'video' && e.toUpload) {
e.OriginalFileName = e.chucksManager.path.replace(".mp4", "")
e.FileExtension = e.FileExtension || "mp4"
e.Base64 = ''
}
if(e.FileType == 'video' && !e.toUpload) {
e.Base64 = e.url
}
return e
})
const publication: any = Object.assign({}, this.form)
publication.Files = publication.Files.map( (e:PublicationAttachmentEntity) => ({
FileBase64: e.Base64,
FileExtension: e.FileExtension,
OriginalFileName: e.OriginalFileName || 'foto'
}))
publication.DocumentId = null;
publication.ProcessId = this.folderId
try {
await this.publications.CreatePublication(publication.ProcessId, publication).toPromise()
if (this.publicationType == '1') {
} else if (this.publicationType == '2') {
this.httpErroHandle.httpsSucessMessagge('Criar publicação')
} else if (this.publicationType == '3') {
this.httpErroHandle.httpsSucessMessagge('Editar publicação')
}
// this.goBackToViewPublications.emit();
window['upload-header-set-remove'](this.id);
this.publicationFolderService.getPublicationsIds(this.folderId)
} catch (error) {
this.httpErroHandle.httpStatusHandle(error)
} finally {
// loader.remove()
}
} else {
this.toastService._badRequest("ocorreu um erro ao enviar o ficheiro")
// loader.remove()
}
} else {
this.toastService._badRequest("É necessário adicionar uma imagem ou vídeo")
}
}
// this.PublicationHolderService.setPublication(this.publicationFormMV)
this.ObjectMergeNotification.close()
}
setDataToFrom(data: IPublicationFormModelEntity) {
this.form.setData(data)
}
private getVideoFiles() {
return this.form.Files.filter( x => x.FileType == 'video')
}
async commit(PublicationAttachmentEntity: PublicationAttachmentEntity) {
PublicationAttachmentEntity.chucksManager.doneChunkUpload()
const mergeRequest = await this.ObjectMergeNotification.socket.commit(PublicationAttachmentEntity.chucksManager.path)
if(mergeRequest.isOk()) {
console.log("commit")
PublicationAttachmentEntity.chucksManager.contentSetReady()
return true
} else {
console.log('no commit')
return false
}
}
private upload(PublicationAttachmentEntity: PublicationAttachmentEntity) {
return new Promise(async (resolve, reject)=> {
if(!PublicationAttachmentEntity.hasChunkManger) {
if(PublicationAttachmentEntity.hasBlob) {
const fileBlob = PublicationAttachmentEntity.blobFile;
const fileChunks = new Chunks({chunkSize: 1000 })
fileChunks.setFile(fileBlob)
PublicationAttachmentEntity.setChunkManger(fileChunks)
} else {
const Base64 = PublicationAttachmentEntity.Base64;
const fileChunks = new ChunksBase64({chunkSize: 1000 })
fileChunks.setFile(Base64)
PublicationAttachmentEntity.setChunkManger(fileChunks)
}
PublicationAttachmentEntity.chucksManager.updateTotalPercentageTrigger = () => {
this.uploadPercentage()
}
}
let attemp = 0;
let result: Result<true, IOUploadError>
if( PublicationAttachmentEntity.chucksManager.isUploading == false && PublicationAttachmentEntity.chucksManager.doneUpload == false) {
do {
attemp++
PublicationAttachmentEntity.chucksManager.clearManualRetry()
PublicationAttachmentEntity.chucksManager.setUploading()
result = await this.UploadFileUseCase.execute(PublicationAttachmentEntity)
PublicationAttachmentEntity.chucksManager.clearUploading()
} while (attemp<3 && result.isErr() && result.error == 'slow')
if(result.isErr()) {
PublicationAttachmentEntity.chucksManager.setManualRetry()
resolve(false)
} else {
return await resolve(this.commit(PublicationAttachmentEntity))
}
} else if ( PublicationAttachmentEntity.chucksManager.contentReady == false) {
console.log("try to send again")
return await resolve(this.commit(PublicationAttachmentEntity))
} else {
console.log('already uploading')
}
})
}
uploadVideosFiles(): Promise<Boolean> {
return new Promise((resolve, reject) => {
// this.ObjectMergeNotification.socket.registerWhenConnected(() => {
const videosFiles = this.getVideoFiles()
window['upload-header-set-percentage'](this.id, 1)
window['upload-header-remove-retry'](this.id);
const videosFilesToUploads = videosFiles.filter( e => e.FileType == "video" && e.toUpload)
const Promises: Promise<any>[] = []
for(const file of videosFilesToUploads) {
const promise = this.upload(file)
Promises.push(promise)
}
// Use Promise.all to wait for all promises to resolve
Promise.all(Promises)
.then((results) => {
// Check if every promise resolved successfully
const allPromisesResolvedSuccessfully = results.every((result) => result == true);
if (allPromisesResolvedSuccessfully) {
console.log('All promises resolved successfully.');
resolve(true)
} else {
window['upload-header-set-retry'](this.id);
resolve(false)
console.log('Some promises failed to resolve successfully.');
}
})
.catch((error) => {
resolve(false)
console.error('An error occurred while resolving promises:', error);
});
//})
})
}
uploadPercentage() {
const videosFiles = this.getVideoFiles()
const percentageArray = videosFiles.map((e) => e.chucksManager.calculatePercentage())
// Check if the array is not empty
if (percentageArray.length === 0) {
window['upload-header-set-percentage'](this.id, this.totalPercentage)
return 0;
} else {
console.log("===============!!==========================================")
let sum = percentageArray.reduce((acc, percentage) => acc + percentage, 0);
// Calculate the average percentage
let averagePercentage = sum / percentageArray.length;
this.totalPercentage = averagePercentage
window['upload-header-set-percentage'](this.id, this.totalPercentage)
return averagePercentage;
}
}
}
@@ -3,7 +3,7 @@ import { ok, err, Result } from 'neverthrow';
import { ObjectMergeNotification } from 'src/app/services/socket-connection-mcr.service';
import { CMAPIService } from "src/app/shared/repository/CMAPI/cmapi.service"
import { DomSanitizer } from '@angular/platform-browser';
import { v4 as uuidv4 } from 'uuid'
export enum UploadError {
@@ -26,7 +26,7 @@ export class UploadStreamingService {
}
}
class UploadFileUseCase {
export class UploadFileUseCase {
CMAPIService: CMAPIService = window["CMAPIAPIRepository"]
constructor() {}
async execute(PublicationAttachmentEntity: PublicationAttachmentEntity): Promise<Result<true, IOUploadError >> {
@@ -168,7 +168,7 @@ export class PublicationAttachmentEntity {
this.toUpload = true
}
setChunkManger (chunks: Chunks) {
setChunkManger (chunks: Chunks | ChunksBase64) {
this.chucksManager = new ChucksManager({chunks})
}
get hasChunkManger() {
@@ -178,6 +178,10 @@ export class PublicationAttachmentEntity {
get hasChunkManager() {
return this.chucksManager != null
}
get hasBlob() {
return this.blobFile
}
}
interface IPublicationFormModelEntity {
@@ -210,6 +214,7 @@ export class PublicationFormModel implements IPublicationFormModelEntity {
Files: PublicationAttachmentEntity[] = []
hasSet = false
send = false
setData(data: IPublicationFormModelEntity) {
if(data.Files) {
@@ -224,12 +229,15 @@ export class PublicationFormModel implements IPublicationFormModelEntity {
export class PublicationFormMV {
readonly id = uuidv4()
private UploadFileUseCase = new UploadFileUseCase()
form = new PublicationFormModel()
ObjectMergeNotification = new ObjectMergeNotification()
totalPercentage = 0
constructor() {
// this.ObjectMergeNotification.connect();
this.ObjectMergeNotification.connect();
window['upload-header-set-add'](this.id, this.totalPercentage)
}
setDataToFrom(data: IPublicationFormModelEntity) {
@@ -245,9 +253,11 @@ export class PublicationFormMV {
const mergeRequest = await this.ObjectMergeNotification.socket.commit(PublicationAttachmentEntity.chucksManager.path)
if(mergeRequest.isOk()) {
console.log("commit")
PublicationAttachmentEntity.chucksManager.contentSetReady()
return true
} else {
console.log('no commit')
return false
}
}
@@ -257,20 +267,31 @@ export class PublicationFormMV {
return new Promise(async (resolve, reject)=> {
if(!PublicationAttachmentEntity.hasChunkManger) {
const fileBlob = PublicationAttachmentEntity.blobFile;
const fileChunks = new Chunks({chunkSize: 50 })
fileChunks.setFile(fileBlob)
PublicationAttachmentEntity.setChunkManger(fileChunks)
if(PublicationAttachmentEntity.hasBlob) {
const fileBlob = PublicationAttachmentEntity.blobFile;
const fileChunks = new Chunks({chunkSize: 50 })
fileChunks.setFile(fileBlob)
PublicationAttachmentEntity.setChunkManger(fileChunks)
} else {
const Base64 = PublicationAttachmentEntity.Base64;
const fileChunks = new ChunksBase64({chunkSize: 50 })
fileChunks.setFile(Base64)
PublicationAttachmentEntity.setChunkManger(fileChunks)
}
PublicationAttachmentEntity.chucksManager.updateTotalPercentageTrigger = () => {
this.uploadPercentage()
}
} else if(PublicationAttachmentEntity.chucksManager.doneUpload) {
return resolve(true)
}
let attemp = 0;
let result: Result<true, IOUploadError>
if( PublicationAttachmentEntity.chucksManager.isUploading == false) {
if( PublicationAttachmentEntity.chucksManager.isUploading == false && PublicationAttachmentEntity.chucksManager.doneUpload == false) {
do {
attemp++
@@ -293,7 +314,7 @@ export class PublicationFormMV {
}
} else if ( PublicationAttachmentEntity.chucksManager.contentReady == false) {
console.log("try to send again")
return await resolve(this.commit(PublicationAttachmentEntity))
} else {
@@ -311,6 +332,8 @@ export class PublicationFormMV {
// this.ObjectMergeNotification.socket.registerWhenConnected(() => {
const videosFiles = this.getVideoFiles()
window['upload-header-set-percentage'](this.id, 1)
const videosFilesToUploads = videosFiles.filter( e => e.FileType == "video" && e.toUpload && e.blobFile)
const Promises: Promise<any>[] = []
@@ -328,8 +351,10 @@ export class PublicationFormMV {
if (allPromisesResolvedSuccessfully) {
console.log('All promises resolved successfully.');
window['upload-header-set-remove'](this.id);
resolve(true)
} else {
window['upload-header-set-remove'](this.id);
resolve(false)
console.log('Some promises failed to resolve successfully.');
}
@@ -344,22 +369,26 @@ export class PublicationFormMV {
}
get uploadPercentage() {
uploadPercentage() {
const videosFiles = this.getVideoFiles()
const percentageArray = videosFiles.map((e) => e.chucksManager.calculatePercentage())
// Check if the array is not empty
if (percentageArray.length === 0) {
return null;
window['upload-header-set-percentage'](this.id, this.totalPercentage)
return 0;
} else {
let sum = percentageArray.reduce((acc, percentage) => acc + percentage, 0);
// Calculate the average percentage
let averagePercentage = sum / percentageArray.length;
this.totalPercentage = averagePercentage
window['upload-header-set-percentage'](this.id, this.totalPercentage)
return averagePercentage;
}
let sum = percentageArray.reduce((acc, percentage) => acc + percentage, 0);
// Calculate the average percentage
let averagePercentage = sum / percentageArray.length;
return averagePercentage;
}
}
@@ -405,6 +434,51 @@ export class Chunks {
}
export class ChunksBase64 {
chunkSize: number
private base64: string
bytes: Uint8Array
constructor({chunkSize}) {
this.chunkSize = chunkSize * 1024
}
get totalChunks () {
return Math.ceil(this.bytes.length / this.chunkSize);
}
setFile(base64: string) {
this.base64 = base64
let utf8Encoder = new TextEncoder();
this.bytes = utf8Encoder.encode(base64);
}
// Function to read a chunk of the file
async readChunk(start: number, end: number) {
// Slice the last 1MB of bytes
let slicedBytes = this.bytes.slice(start, end);
// Convert the sliced bytes back to a string
let text = new TextDecoder().decode(slicedBytes);
return text
}
async getChunks(i: number): Promise<string> {
i--
if(i < this.totalChunks) {
const start = i * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.bytes.length);
const chunk = await this.readChunk(start, end);
return chunk
}
}
}
interface IUploadResponse {
result: Result<any, Error>
attemp: number
@@ -424,6 +498,8 @@ export class ChucksManager {
needToCommit = true
subscribeToUseCaseResponse: Function[] = []
updateTotalPercentageTrigger = () => {}
getUploadPercentage() {
return this.uploadPercentage
}
@@ -483,6 +559,7 @@ export class ChucksManager {
setPercentage() {
const percentage: number = this.calculatePercentage()
console.log({percentage})
this.updateTotalPercentageTrigger()
this.uploadPercentage = percentage.toString()+"%"
}