diff --git a/lib/content-services/src/lib/upload/components/file-uploading-dialog.component.ts b/lib/content-services/src/lib/upload/components/file-uploading-dialog.component.ts index d3c1717852..1aaef4cc94 100644 --- a/lib/content-services/src/lib/upload/components/file-uploading-dialog.component.ts +++ b/lib/content-services/src/lib/upload/components/file-uploading-dialog.component.ts @@ -73,8 +73,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { private uploadService: UploadService, private changeDetector: ChangeDetectorRef, private userPreferencesService: UserPreferencesService, - private elementRef: ElementRef - ) { + private elementRef: ElementRef) { } ngOnInit() { @@ -104,9 +103,9 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { }); this.counterSubscription = merge( - this.uploadService.fileUploadComplete, - this.uploadService.fileUploadDeleted - ) + this.uploadService.fileUploadComplete, + this.uploadService.fileUploadDeleted + ) .pipe(takeUntil(this.onDestroy$)) .subscribe(event => { this.totalCompleted = event.totalComplete; @@ -130,11 +129,11 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.onDestroy$)) .subscribe(objId => { if (this.filesUploadingList) { - const file = this.filesUploadingList.find((item) => { - return item.data.entry.id === objId; + const uploadedFile = this.filesUploadingList.find((file) => { + return file.data ? file.data.entry.id === objId : false; }); - if (file) { - file.status = FileUploadStatus.Cancelled; + if (uploadedFile) { + uploadedFile.status = FileUploadStatus.Cancelled; this.changeDetector.detectChanges(); } } diff --git a/lib/content-services/src/lib/upload/components/file-uploading-list.component.ts b/lib/content-services/src/lib/upload/components/file-uploading-list.component.ts index f2dc751e26..406f1e1982 100644 --- a/lib/content-services/src/lib/upload/components/file-uploading-list.component.ts +++ b/lib/content-services/src/lib/upload/components/file-uploading-list.component.ts @@ -54,8 +54,8 @@ export class FileUploadingListComponent { constructor( private uploadService: UploadService, private nodesApi: NodesApiService, - private translateService: TranslationService - ) {} + private translateService: TranslationService) { + } /** * Cancel file upload @@ -91,16 +91,18 @@ export class FileUploadingListComponent { } /** - * Call the appropriate method for each file, depending on state + * Calls the appropriate methods for each file, depending on state */ cancelAllFiles(): void { - this.getUploadingFiles().forEach((file) => - this.uploadService.cancelUpload(file) - ); + const deletedFiles: Observable[] = []; - const deletedFiles = this.files - .filter((file) => file.status === FileUploadStatus.Complete) - .map((file) => this.deleteNode(file)); + this.files.forEach((file) => { + if (this.isUploadingFile(file)) { + this.uploadService.cancelUpload(file); + } else if (file.status === FileUploadStatus.Complete) { + deletedFiles.push(this.deleteNode(file)); + } + }); forkJoin(...deletedFiles).subscribe((files: FileModel[]) => { const errors = files.filter( @@ -192,12 +194,9 @@ export class FileUploadingListComponent { this.error.emit(messageError); } - private getUploadingFiles(): FileModel[] { - return this.files.filter( - item => - item.status === FileUploadStatus.Pending || - item.status === FileUploadStatus.Progress || - item.status === FileUploadStatus.Starting - ); + private isUploadingFile(file: FileModel): boolean { + return file.status === FileUploadStatus.Pending || + file.status === FileUploadStatus.Starting || + file.status === FileUploadStatus.Progress; } } diff --git a/lib/core/services/upload.service.spec.ts b/lib/core/services/upload.service.spec.ts index c4b3efba9f..20fe603119 100644 --- a/lib/core/services/upload.service.spec.ts +++ b/lib/core/services/upload.service.spec.ts @@ -146,21 +146,62 @@ describe('UploadService', () => { }); }); - it('should make XHR abort request after the xhr abort is called', (done) => { + it('should abort file only if it\'s safe to abort', (done) => { const emitter = new EventEmitter(); - const emitterDisposable = emitter.subscribe((e) => { - expect(e.value).toEqual('File aborted'); + const emitterDisposable = emitter.subscribe((event) => { + expect(event.value).toEqual('File aborted'); emitterDisposable.unsubscribe(); done(); }); + const fileFake = new FileModel( { name: 'fake-name', size: 10000000 }); + service.addToQueue(fileFake); + service.uploadFilesInTheQueue(emitter); + + const file = service.getQueue(); + service.cancelUpload(...file); + }); + + it('should let file complete and then delete node if it\'s not safe to abort', (done) => { + const emitter = new EventEmitter(); + + const emitterDisposable = emitter.subscribe((event) => { + expect(event.value).toEqual('File deleted'); + emitterDisposable.unsubscribe(); + + const deleteRequest = jasmine.Ajax.requests.mostRecent(); + expect(deleteRequest.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/myNodeId?permanent=true'); + expect(deleteRequest.method).toBe('DELETE'); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'text/plain', + responseText: 'File deleted' + }); + done(); + }); + const fileFake = new FileModel( { name: 'fake-name', size: 10 }); service.addToQueue(fileFake); service.uploadFilesInTheQueue(emitter); const file = service.getQueue(); service.cancelUpload(...file); + + const request = jasmine.Ajax.requests.mostRecent(); + expect(request.url).toBe('http://localhost:9876/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true&include=allowableOperations'); + expect(request.method).toBe('POST'); + + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'json', + responseText: { + entry: { + id: 'myNodeId' + } + } + }); }); it('If newVersion is set, name should be a param', () => { @@ -196,7 +237,7 @@ describe('UploadService', () => { done(); }); const filesFake = new FileModel( - { name: 'fake-name', size: 10 }, + { name: 'fake-file-name', size: 10 }, { parentId: '123', path: 'fake-dir' } ); service.addToQueue(filesFake); diff --git a/lib/core/services/upload.service.ts b/lib/core/services/upload.service.ts index 9ec13e12f3..17a1c9edd5 100644 --- a/lib/core/services/upload.service.ts +++ b/lib/core/services/upload.service.ts @@ -28,6 +28,9 @@ import { import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model'; import { AlfrescoApiService } from './alfresco-api.service'; +const MIN_CANCELLABLE_FILE_SIZE = 1000000; +const MAX_CANCELLABLE_FILE_PERCENTAGE = 50; + @Injectable({ providedIn: 'root' }) @@ -39,6 +42,7 @@ export class UploadService { private totalError: number = 0; private excludedFileList: string[] = []; private matchingOptions: any = null; + private abortedFile: string; activeTask: Promise = null; queue: FileModel[] = []; @@ -113,7 +117,7 @@ export class UploadService { const promise = this.beginUpload(file, emitter); this.activeTask = promise; - this.cache[file.id] = promise; + this.cache[file.name] = promise; const next = () => { this.activeTask = null; @@ -132,15 +136,21 @@ export class UploadService { /** * Cancels uploading of files. + * If the file is smaller than 1 MB the file will be uploaded and then the node deleted + * to prevent having files that were aborted but still uploaded. * @param files One or more separate parameters or an array of files specifying uploads to cancel */ cancelUpload(...files: FileModel[]) { files.forEach((file) => { - const promise = this.cache[file.id]; - + const promise = this.cache[file.name]; if (promise) { - promise.abort(); - delete this.cache[file.id]; + if (this.isSaveToAbortFile(file)) { + promise.abort(); + } + this.abortedFile = file.name; + delete this.cache[file.name]; + promise.next(); + } else { const performAction = this.getAction(file); performAction(); @@ -200,7 +210,6 @@ export class UploadService { private beginUpload(file: FileModel, emitter: EventEmitter): any { const promise = this.getUploadPromise(file); - promise.on('progress', (progress: FileUploadProgress) => { this.onUploadProgress(file, progress); }) @@ -217,12 +226,20 @@ export class UploadService { } }) .on('success', (data) => { - this.onUploadComplete(file, data); - if (emitter) { - emitter.emit({ value: data }); + if (this.abortedFile === file.name) { + this.onUploadAborted(file); + this.deleteAbortedNode(data.entry.id); + if (emitter) { + emitter.emit({ value: 'File deleted' }); + } + } else { + this.onUploadComplete(file, data); + if (emitter) { + emitter.emit({ value: data }); + } } }) - .catch(() => {}); + .catch(() => { }); return promise; } @@ -249,13 +266,13 @@ export class UploadService { private onUploadError(file: FileModel, error: any): void { if (file) { - file.errorCode = ( error || {} ).status; + file.errorCode = (error || {}).status; file.status = FileUploadStatus.Error; this.totalError++; - const promise = this.cache[file.id]; + const promise = this.cache[file.name]; if (promise) { - delete this.cache[file.id]; + delete this.cache[file.name]; } const event = new FileUploadErrorEvent(file, error, this.totalError); @@ -269,10 +286,9 @@ export class UploadService { file.status = FileUploadStatus.Complete; file.data = data; this.totalComplete++; - - const promise = this.cache[file.id]; + const promise = this.cache[file.name]; if (promise) { - delete this.cache[file.id]; + delete this.cache[file.name]; } const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted); @@ -286,15 +302,9 @@ export class UploadService { file.status = FileUploadStatus.Aborted; this.totalAborted++; - const promise = this.cache[file.id]; - if (promise) { - delete this.cache[file.id]; - } - const event = new FileUploadEvent(file, FileUploadStatus.Aborted); this.fileUpload.next(event); this.fileUploadAborted.next(event); - promise.next(); } } @@ -319,7 +329,7 @@ export class UploadService { } } - private getAction(file) { + private getAction(file: FileModel) { const actions = { [FileUploadStatus.Pending]: () => this.onUploadCancelled(file), [FileUploadStatus.Deleted]: () => this.onUploadDeleted(file), @@ -328,4 +338,14 @@ export class UploadService { return actions[file.status]; } + + private deleteAbortedNode(nodeId: string) { + this.apiService.getInstance().core.nodesApi.deleteNode(nodeId, { permanent: true }) + .then(() => this.abortedFile = undefined); + + } + + private isSaveToAbortFile(file: FileModel): boolean { + return file.size > MIN_CANCELLABLE_FILE_SIZE && file.progress.percent < MAX_CANCELLABLE_FILE_PERCENTAGE; + } }