src/file-sharing/FileUploadManager.js
import {Map} from 'immutable';
import Logs from '../Logs';
import {FileUpload} from './FileUpload';
import {bindMethods} from '../utils/bindMethods';
import {EventEmitter} from '../eventemitter';
import {FileUploadStatus} from './FileUploadStatus';
const log = Logs.instance.getLogger('SippoJS/FileUploadManager');
/**
* FileUploadManager objects allows creating and receiving file transfers
* They are obtained by calling the {@link Session#createFileSharingManager}
* method of a {@link Session} object
* @experimental
*/
export class FileUploadManager {
/** @protected */
static newInstance(fileSharingService) {
const fileSharingManager = new FileUploadManager(fileSharingService);
fileSharingManager.initialize();
return Promise.resolve(fileSharingManager);
}
/** @private */
constructor(fileSharingService) {
/** @private */
this.fileSharingService = fileSharingService;
/** @private */
this.fileUploads = new Map();
/** @type {EventEmitter} */
this.emitter = new EventEmitter({
wildcard: true,
delimiter: '/',
});
bindMethods(this, [
'onStart',
'onStream',
'onComplete',
'onError',
'onAbort',
'onFile',
]);
}
/** @private */
initialize() {
this.fileSharingService.emitter.on('start', this.onStart);
this.fileSharingService.emitter.on('stream', this.onStream);
this.fileSharingService.emitter.on('complete', this.onComplete);
this.fileSharingService.emitter.on('error', this.onError);
this.fileSharingService.emitter.on('abort', this.onAbort);
this.fileSharingService.emitter.on('file', this.onFile);
}
/** @private */
onStart(fileInfo) {
log.log('start', fileInfo);
const id = fileInfo.uploadId;
const status = FileUploadStatus.SENDING;
const fileUpload = this.fileUploads.get(id).with({status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'start'], fileUpload);
}
/** @private */
onStream(fileInfo) {
log.log('stream', fileInfo);
const id = fileInfo.uploadId;
const bytesSent = fileInfo.sent;
const status = FileUploadStatus.SENDING;
const fileUpload = this.fileUploads.get(id).with({bytesSent, status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'progress'], fileUpload);
}
/** @private */
onComplete(fileInfo) {
log.log('complete', fileInfo);
const id = fileInfo.uploadId;
const bytesSent = fileInfo.wrote;
const status = FileUploadStatus.SENDING;
const fileUpload = this.fileUploads.get(id).with({bytesSent, status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'progress'], fileUpload);
}
/** @private */
onError(error, fileInfo) {
log.error('error', error);
const id = fileInfo.uploadId;
const status = FileUploadStatus.ERROR;
const fileUpload = this.fileUploads.get(id).with({status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'error'], fileUpload);
}
/** @private */
onAbort(fileInfo) {
log.log('abort', fileInfo);
const id = fileInfo.uploadId;
const bytesSent = fileInfo.sent;
const status = FileUploadStatus.ABORTED;
const fileUpload = this.fileUploads.get(id).with({bytesSent, status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'abort'], fileUpload);
}
/** @private */
async onFile(event) {
log.log('file', event);
const id = event.uploadId;
if (event.file.size <= 0) {
const fileUpload = this.fileUploads.get(id).with({status: FileUploadStatus.ERROR});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'error'], fileUpload);
} else {
const url = await this.fileSharingService.createSharedFile(event.file.key);
const fileUpload = this.fileUploads.get(id).with({status: FileUploadStatus.COMPLETED, url});
this.fileUploads = this.fileUploads.set(id, fileUpload);
this.emitter.emit([id, 'end'], fileUpload);
}
}
waitForEnd(id) {
let onEnd, onAbort, onError;
const endPromise = new Promise((resolve) => {
onEnd = resolve;
this.emitter.on([id, 'end'], onEnd);
});
const abortPromise = new Promise((resolve) => {
onAbort = resolve;
this.emitter.on([id, 'abort'], onAbort);
});
const errorPromise = new Promise((resolve, reject) => {
onError = reject;
this.emitter.on([id, 'error'], onError);
});
return Promise.race([endPromise, abortPromise, errorPromise]).finally(() => {
this.emitter.off([id, 'end'], onEnd);
this.emitter.off([id, 'abort'], onAbort);
this.emitter.off([id, 'error'], onError);
});
}
/**
* Returns an immutable map with file uploads
* @return {InmutableMap<string, FileUpload}
*/
getFileUploads() {
return this.fileUploads;
}
/**
* Starts uploading a new file
* @param {File} file Local file
* @return {Promise<string>} id of the new file upload
*/
async create(file) {
const id = await this.fileSharingService.upload(file);
const name = file.name;
const type = file.type;
const size = file.size;
const status = FileUploadStatus.LOADING;
const fileUpload = FileUpload.valueOf({id, name, type, size, status});
this.fileUploads = this.fileUploads.set(id, fileUpload);
return id;
}
/**
* Aborts a currently in progress file upload
* @param {FileUpload} fileUpload
* @return {Promise<void>}
*/
abort(fileUpload) {
const id = fileUpload.getId();
return Promise.all([this.fileSharingService.abort(id), this.waitForEnd(id)]);
}
/**
* Removes a file upload from the list
* @param {FileUpload} fileUpload
*/
remove(fileUpload) {
if (!fileUpload.hasStatus(FileUploadStatus.COMPLETED, FileUploadStatus.ABORTED, FileUploadStatus.ERROR)) {
throw new Error(`File Upload in ${fileUpload.getStatus().toString()} status can't be removed`);
}
this.fileUploads = this.fileUploads.delete(fileUpload.getId());
}
}