Files
doneit-web/src/app/shared/publication/upload/upload-streaming.service.ts
T

465 lines
11 KiB
TypeScript
Raw Normal View History

2024-01-31 17:12:01 +01:00
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"
2024-02-09 09:10:50 +01:00
import { DomSanitizer } from '@angular/platform-browser';
2024-03-26 12:03:30 +01:00
import { v4 as uuidv4 } from 'uuid'
2024-02-09 10:33:53 +01:00
2024-02-07 16:45:53 +01:00
export enum UploadError {
noConnection = 'noConnection',
slow = 'slow'
}
export type IOUploadError = "noConnection" | "slow"
2024-01-31 17:12:01 +01:00
@Injectable({
providedIn: 'root'
})
export class UploadStreamingService {
2024-02-09 09:10:50 +01:00
constructor(
private CMAPIService: CMAPIService,
private sanitizer: DomSanitizer
) {
2024-02-28 09:04:11 +01:00
this.CMAPIService
2024-02-09 09:10:50 +01:00
window["sanitizer"] = this.sanitizer
}
2024-01-31 17:12:01 +01:00
}
2024-03-26 12:03:30 +01:00
export class UploadFileUseCase {
2024-01-31 17:12:01 +01:00
CMAPIService: CMAPIService = window["CMAPIAPIRepository"]
constructor() {}
2024-02-07 16:45:53 +01:00
async execute(PublicationAttachmentEntity: PublicationAttachmentEntity): Promise<Result<true, IOUploadError >> {
2024-01-31 17:12:01 +01:00
return new Promise(async (resolve, reject) => {
let path: string;
const length = PublicationAttachmentEntity.chucksManager.chunks.totalChunks.toString()
const readAndUploadChunk = async(index: number) => {
2024-02-07 16:45:53 +01:00
const base64 = await PublicationAttachmentEntity.chucksManager.chunks.getChunks(index)
2024-01-31 17:12:01 +01:00
2024-02-08 10:14:21 +01:00
const uploadRequest = this.CMAPIService.FileContent({length, path: PublicationAttachmentEntity.chucksManager.path, index, base64})
2024-02-07 16:45:53 +01:00
uploadRequest.then((uploadRequest) => {
if(uploadRequest.isOk()) {
PublicationAttachmentEntity.chucksManager.setResponse(index, uploadRequest)
}
})
return uploadRequest;
2024-01-31 17:12:01 +01:00
}
2024-02-08 10:14:21 +01:00
if(!PublicationAttachmentEntity.chucksManager.hasPath()) {
const guidRequest = await this.CMAPIService.RequestUpload()
if(guidRequest.isOk()) {
path = guidRequest.value+".mp4"
PublicationAttachmentEntity.chucksManager.setPath(path)
} else {
const pingRequest = await this.CMAPIService.ping()
if( pingRequest.isErr()) {
return resolve(err(UploadError.noConnection))
} else {
return resolve(err(UploadError.slow))
}
}
}
2024-02-07 16:45:53 +01:00
2024-01-31 17:12:01 +01:00
const allRequest: Promise<any>[] = []
let connection = true
2024-02-07 16:45:53 +01:00
let errorMessage: UploadError.noConnection | UploadError.slow
2024-01-31 17:12:01 +01:00
for (let index = 1; ( (index <= PublicationAttachmentEntity.chucksManager.chunks.totalChunks) && connection ); index++) {
2024-01-31 17:12:01 +01:00
const needUpload = PublicationAttachmentEntity.chucksManager.needToUploadChunkIndex(index)
if(needUpload) {
2024-02-28 09:04:11 +01:00
// // upload every chunk at onces
// const request = readAndUploadChunk(index).then(async(uploadRequest) => {
2024-02-28 09:04:11 +01:00
// if(uploadRequest.isErr()) {
2024-02-28 09:04:11 +01:00
// if(connection) {
// connection = false
// const pingRequest = await this.CMAPIService.ping()
// if( pingRequest.isErr()) {
// errorMessage = UploadError.noConnection
// } else {
// errorMessage = UploadError.slow
// }
// }
2024-02-23 13:17:45 +01:00
// }
2024-02-28 09:04:11 +01:00
// })
// allRequest.push(request)
// one by one chunk upload
const request = readAndUploadChunk(index)
allRequest.push(request)
const uploadRequest = await request
if(uploadRequest.isErr()) {
const pingRequest = await this.CMAPIService.ping()
if( pingRequest.isErr()) {
return resolve(err(UploadError.noConnection))
} else {
return resolve(err(UploadError.slow))
}
}
2024-01-31 17:12:01 +01:00
}
}
2024-02-07 16:45:53 +01:00
await Promise.all(allRequest)
2024-01-31 17:12:01 +01:00
2024-02-07 16:45:53 +01:00
if(!connection) {
return resolve(err(errorMessage))
2024-02-02 10:50:20 +01:00
} else {
2024-02-07 16:45:53 +01:00
return resolve(ok(true))
2024-01-31 17:12:01 +01:00
}
})
}
}
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() {
2024-02-09 10:57:41 +01:00
const sanitizer : DomSanitizer = window["sanitizer"]
2024-02-09 09:10:50 +01:00
2024-01-31 17:12:01 +01:00
if(this.FileType == 'image' ) {
if(!this.Base64.includes('data:')) {
2024-01-31 17:12:01 +01:00
this.url = 'data:image/jpg;base64,' + this.Base64
2024-02-09 10:57:41 +01:00
// this.url = sanitizer.bypassSecurityTrustUrl('data:image/jpg;base64,' + this.Base64) as any
2024-01-31 17:12:01 +01:00
} else {
this.url = this.Base64
}
} else if (this.FileType == 'video' ) {
if(!this.Base64.includes('data:') && !this.Base64.startsWith('http')) {
2024-01-31 17:12:01 +01:00
this.url = 'data:video/mp4;base64,' + this.Base64
2024-02-09 10:57:41 +01:00
// this.url = sanitizer.bypassSecurityTrustUrl('data:video/mp4;base64,' + this.Base64) as any
2024-01-31 17:12:01 +01:00
} else {
this.url = this.Base64
}
}
}
needUpload() {
this.toUpload = true
}
2024-03-26 12:03:30 +01:00
setChunkManger (chunks: Chunks | ChunksBase64) {
2024-01-31 17:12:01 +01:00
this.chucksManager = new ChucksManager({chunks})
}
get hasChunkManger() {
return this.chucksManager?.chunks
}
get hasChunkManager() {
return this.chucksManager != null
}
2024-03-26 12:03:30 +01:00
get hasBlob() {
return this.blobFile
}
2024-01-31 17:12:01 +01:00
}
interface IPublicationFormModelEntity {
DateIndex: any
DocumentId: any
ProcessId: any
Title: any
Message: any
DatePublication: any
2024-02-28 09:04:11 +01:00
Files?: PublicationAttachmentEntity[]
2024-01-31 17:12:01 +01:00
}
interface PublicationAttachmentEntityParams {
base64: string,
blobFile?: File
extension: string,
OriginalFileName: string,
FileType: 'image' | 'video'
}
export class PublicationFormModel implements IPublicationFormModelEntity {
constructor() {}
2024-02-28 09:04:11 +01:00
DateIndex: any = new Date()
DocumentId: any = null
ProcessId: any = null
2024-01-31 17:12:01 +01:00
Title: any;
Message: any;
2024-02-28 09:04:11 +01:00
DatePublication = new Date()
2024-01-31 17:12:01 +01:00
OriginalFileName: string;
2024-02-28 09:04:11 +01:00
Files: PublicationAttachmentEntity[] = []
2024-01-31 17:12:01 +01:00
hasSet = false
2024-03-26 12:03:30 +01:00
send = false
2024-01-31 17:12:01 +01:00
setData(data: IPublicationFormModelEntity) {
2024-02-28 09:04:11 +01:00
if(data.Files) {
data.Files = []
}
2024-01-31 17:12:01 +01:00
if(!this.hasSet) {
Object.assign(this, data)
}
this.hasSet = true
}
}
export class Chunks {
chunkSize: number
private file: File
constructor({chunkSize}) {
this.chunkSize = chunkSize * 1024
}
get totalChunks () {
return Math.ceil(this.file.size / this.chunkSize);
}
2024-02-07 16:45:53 +01:00
setFile(file: File) {
this.file = file
}
2024-01-31 17:12:01 +01:00
2024-02-07 16:45:53 +01:00
// Function to read a chunk of the file
readChunk(start: number, end: number): any {
2024-01-31 17:12:01 +01:00
return new Promise((resolve, reject) => {
const reader = new FileReader();
2024-02-07 16:45:53 +01:00
reader.onload = (event: any) => resolve(event.target.result.split(',')[1]);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(this.file.slice(start, end));
2024-01-31 17:12:01 +01:00
});
}
2024-02-07 16:45:53 +01:00
async getChunks(i: number): Promise<string> {
2024-01-31 17:12:01 +01:00
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
}
}
}
2024-03-26 12:03:30 +01:00
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
}
}
}
2024-01-31 17:12:01 +01:00
interface IUploadResponse {
result: Result<any, Error>
attemp: number
}
export class ChucksManager {
chunks: Chunks
uploads: {[key: string]: IUploadResponse } = {}
path: string = undefined
2024-02-01 11:44:56 +01:00
uploadPercentage: string = "1%"
2024-01-31 17:12:01 +01:00
merging = false
onSetPath: Function[] = []
onSetLastChunk: Function[] = []
contentReady = false
manualRetry = false
isUploading = false
2024-03-13 11:52:27 +01:00
needToCommit = true
2024-01-31 17:12:01 +01:00
subscribeToUseCaseResponse: Function[] = []
2024-03-26 12:03:30 +01:00
updateTotalPercentageTrigger = () => {}
2024-01-31 17:12:01 +01:00
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})
2024-03-26 12:03:30 +01:00
this.updateTotalPercentageTrigger()
2024-01-31 17:12:01 +01:00
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) {
2024-02-09 10:33:53 +01:00
this.onSetLastChunk.push(a)
2024-01-31 17:12:01 +01:00
}
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());
}
2024-03-13 11:52:27 +01:00
doneCommit() {
this.needToCommit = false
}
2024-01-31 17:12:01 +01:00
contentSetReady() {
this.merging = false
this.contentReady = true
}
}
2024-02-07 16:45:53 +01:00