import { Injectable } from '@angular/core'; 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" @Injectable({ providedIn: 'root' }) export class UploadStreamingService { constructor( private CMAPIService: CMAPIService,) { } } class UploadFileUseCase { CMAPIService: CMAPIService = window["CMAPIAPIRepository"] constructor() {} async execute(PublicationAttachmentEntity: PublicationAttachmentEntity): Promise> { return new Promise(async (resolve, reject) => { PublicationAttachmentEntity.chucksManager.clearManualRetry() let path: string; const length = PublicationAttachmentEntity.chucksManager.chunks.totalChunks.toString() const readAndUploadChunk = async(index: number) => { const chunk = await PublicationAttachmentEntity.chucksManager.chunks.getChunks(index) const blob = new Blob([chunk]); const blobFile = new File([blob], "test.mp4", { type: blob.type }); return this.CMAPIService.FileContent({length, path: PublicationAttachmentEntity.chucksManager.path, index, blobFile}) } if(!PublicationAttachmentEntity.chucksManager.hasPath()) { const initIndex = 1 const uploadRequest = await readAndUploadChunk(initIndex) if(uploadRequest.isOk()) { path = uploadRequest.value.data PublicationAttachmentEntity.chucksManager.setPath(path) PublicationAttachmentEntity.chucksManager.setResponse(initIndex, uploadRequest) } else { PublicationAttachmentEntity.chucksManager.setManualRetry() return reject(err(PublicationAttachmentEntity)) } } const allRequest: Promise[] = [] let connection = true for (let index = 2; ( (index <= PublicationAttachmentEntity.chucksManager.chunks.totalChunks -1) && connection ); index++) { const needUpload = PublicationAttachmentEntity.chucksManager.needToUploadChunkIndex(index) if(needUpload) { const request = readAndUploadChunk(index).then(async(uploadRequest) => { if(uploadRequest.isErr()) { const pingRequest = await this.CMAPIService.ping() if( pingRequest.isErr()) { connection = false PublicationAttachmentEntity.chucksManager.setManualRetry() return reject(err(PublicationAttachmentEntity)) } } else { PublicationAttachmentEntity.chucksManager.setResponse(index, uploadRequest) } }) allRequest.push(request) // const request = readAndUploadChunk(index) // const uploadRequest = await request // allRequest.push(request) // if(uploadRequest.isErr()) { // const pingRequest = await this.CMAPIService.ping() // if( pingRequest.isErr()) { // reject(err(PublicationAttachmentEntity)) // } // } else { // PublicationAttachmentEntity.chucksManager.setResponse(index, uploadRequest) // } } } if(!connection) { PublicationAttachmentEntity.chucksManager.setManualRetry() return reject(err(PublicationAttachmentEntity)) } else { await Promise.all(allRequest) const uploadRequest = await readAndUploadChunk(PublicationAttachmentEntity.chucksManager.chunks.totalChunks) if(uploadRequest.isErr()) { const pingRequest = await this.CMAPIService.ping() if( pingRequest.isErr()) { PublicationAttachmentEntity.chucksManager.setManualRetry() return reject(err(PublicationAttachmentEntity)) } } else { PublicationAttachmentEntity.chucksManager.setResponse(PublicationAttachmentEntity.chucksManager.chunks.totalChunks, uploadRequest) } PublicationAttachmentEntity.chucksManager.doneChunkUpload() resolve(ok(PublicationAttachmentEntity)) } }) } } export class PublicationAttachmentEntity { url: string FileExtension: string FileType: 'image' | 'video' OriginalFileName: string blobFile?: File toUpload = false; chucksManager : ChucksManager Base64: string constructor({base64, extension, blobFile, OriginalFileName, FileType}:PublicationAttachmentEntityParams) { this.Base64 = base64; this.FileExtension = extension; this.blobFile = blobFile this.OriginalFileName = OriginalFileName this.FileType = FileType this.fixFileBase64(); } fixFileBase64() { if(this.FileType == 'image' ) { if(!this.Base64.startsWith('data:')) { this.url = 'data:image/jpg;base64,' + this.Base64 } else { this.url = this.Base64 } } else if (this.FileType == 'video' ) { if(!this.Base64.startsWith('data:') && !this.Base64.startsWith('http')) { this.url = 'data:video/mp4;base64,' + this.Base64 } else { this.url = this.Base64 } } } needUpload() { this.toUpload = true } setChunkManger (chunks: Chunks) { this.chucksManager = new ChucksManager({chunks}) } get hasChunkManger() { return this.chucksManager?.chunks } get hasChunkManager() { return this.chucksManager != null } } interface IPublicationFormModelEntity { DateIndex: any DocumentId: any ProcessId: any Title: any Message: any DatePublication: any Files: PublicationAttachmentEntity[] } interface PublicationAttachmentEntityParams { base64: string, blobFile?: File extension: string, OriginalFileName: string, FileType: 'image' | 'video' } export class PublicationFormModel implements IPublicationFormModelEntity { constructor() {} DateIndex: any; DocumentId: any; ProcessId: any; Title: any; Message: any; DatePublication: any; OriginalFileName: string; Files: PublicationAttachmentEntity[]; hasSet = false setData(data: IPublicationFormModelEntity) { if(!this.hasSet) { Object.assign(this, data) } this.hasSet = true } } export class PublicationFormMV { private UploadFileUseCase = new UploadFileUseCase() private form = new PublicationFormModel() ObjectMergeNotification = new ObjectMergeNotification() setDataToFrom(data: IPublicationFormModelEntity) { this.form.setData(data) } private getVideoFiles() { return this.form.Files.filter( x => x.FileType == 'video') } private upload(PublicationAttachmentEntity: PublicationAttachmentEntity) { return new Promise(async (resolve, reject)=> { if(!PublicationAttachmentEntity.hasChunkManger) { const fileBlob = PublicationAttachmentEntity.blobFile; const fileChunks = new Chunks({chunkSize: 500 }) fileChunks.setFile(fileBlob) PublicationAttachmentEntity.setChunkManger(fileChunks) PublicationAttachmentEntity.chucksManager.registerOnLastChunk(()=> { const guid = PublicationAttachmentEntity.chucksManager.path this.ObjectMergeNotification.subscribe(guid, (data) => { // console.log("data", data) PublicationAttachmentEntity resolve(true) }) }) } else { if(PublicationAttachmentEntity.chucksManager.doneUpload) { return resolve(true) } } if( PublicationAttachmentEntity.chucksManager.isUploading == false) { PublicationAttachmentEntity.chucksManager.setUploading() const result = await this.UploadFileUseCase.execute(PublicationAttachmentEntity) PublicationAttachmentEntity.chucksManager.clearUploading() if(result.isErr()) { reject(false) } } }) } uploadVideosFiles(): Promise { return new Promise((resolve, reject)=> { const videosFiles = this.getVideoFiles() const videosFilesToUploads = videosFiles.filter( e => e.FileType == "video") const Promises: Promise[] = [] 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 { reject(false) console.log('Some promises failed to resolve successfully.'); } }) .catch((error) => { reject(false) console.error('An error occurred while resolving promises:', error); }); }) } } export class Chunks { chunkSize: number private file: File constructor({chunkSize}) { this.chunkSize = chunkSize * 1024 } get totalChunks () { return Math.ceil(this.file.size / this.chunkSize); } // Function to read a chunk of the file readChunk(start: number, end: number): Promise { const file = this.file return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { if (reader.result instanceof ArrayBuffer) { resolve(reader.result); } else { reject(new Error("Failed to read chunk")); } }; reader.readAsArrayBuffer(file.slice(start, end)); }); } setFile(file: File) { this.file = file } async getChunks(i: number) { i-- if(i < this.totalChunks) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = await this.readChunk(start, end); return chunk } } } interface IUploadResponse { result: Result attemp: number } export class ChucksManager { chunks: Chunks uploads: {[key: string]: IUploadResponse } = {} path: string = undefined uploadPercentage: string = "0%" merging = false onSetPath: Function[] = [] onSetLastChunk: Function[] = [] contentReady = false manualRetry = false isUploading = false subscribeToUseCaseResponse: Function[] = [] getUploadPercentage() { return this.uploadPercentage } get uploadsCount() { return Object.entries(this.uploads).length } get uploadWithSuccessCount() { const uploadWithSuccess = Object.entries(this.uploads).filter(([index, data])=> data.result.isOk()) return uploadWithSuccess.length } get doneUpload() { return this.chunks.totalChunks == this.uploadWithSuccessCount } uploadFunc: Function constructor({chunks}) { this.chunks = chunks } calculatePercentage(): number { /** * Calculate the percentage based on the total and current values. * * @param total - The total value. * @param current - The current value. * @returns The percentage calculated as (current / total) * 100. */ const total = this.chunks.totalChunks const current = this.uploadWithSuccessCount if (total === 0) { return 0; // To avoid division by zero error } const percentage: number = (current / total) * 100; return percentage; } setManualRetry() { this.manualRetry = true } clearManualRetry() { this.manualRetry = false } setUploading() { this.isUploading = true } clearUploading() { this.isUploading = false } setPercentage() { const percentage: number = this.calculatePercentage() console.log({percentage}) this.uploadPercentage = percentage.toString()+"%" } setPath(path: string) { this.path = path this.onSetPath.forEach(callback => callback()); } registerOnSetPath(a: Function) { this.onSetPath.push(a) } registerOnLastChunk(a: Function) { this.onSetPath.push(a) } registerToUseCaseResponse(a: Function) { this.subscribeToUseCaseResponse.push(a) } hasPath() { return this.path != undefined } isIndexRegistered(index) { if(!this.uploads[index]) { return false } return true } needToUploadChunkIndex(index) { return !this.uploads?.[index]?.result?.isOk() } setResponse(index, UploadResponse) { if(!this.isIndexRegistered(index)) { this.uploads[index] = { attemp: 1, result: UploadResponse } console.log({UploadResponse}) } else { this.uploads[index].attemp++; this.uploads[index].result = UploadResponse } this.setPercentage() } doneChunkUpload() { this.merging = true this.onSetLastChunk.forEach(callback => callback()); } contentSetReady() { this.merging = false this.contentReady = true } }