[AAE-1977] Fix file uploaded after cancelling upload (#5532)

* [AAE-1977] Fix file uploaded after cancelling upload

* Add constants
This commit is contained in:
davidcanonieto
2020-03-03 18:47:29 +00:00
committed by GitHub
parent 45f023c93a
commit 2b1a321baf
4 changed files with 111 additions and 52 deletions

View File

@@ -73,8 +73,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
private uploadService: UploadService, private uploadService: UploadService,
private changeDetector: ChangeDetectorRef, private changeDetector: ChangeDetectorRef,
private userPreferencesService: UserPreferencesService, private userPreferencesService: UserPreferencesService,
private elementRef: ElementRef private elementRef: ElementRef) {
) {
} }
ngOnInit() { ngOnInit() {
@@ -104,9 +103,9 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
}); });
this.counterSubscription = merge( this.counterSubscription = merge(
this.uploadService.fileUploadComplete, this.uploadService.fileUploadComplete,
this.uploadService.fileUploadDeleted this.uploadService.fileUploadDeleted
) )
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(event => { .subscribe(event => {
this.totalCompleted = event.totalComplete; this.totalCompleted = event.totalComplete;
@@ -130,11 +129,11 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe(objId => { .subscribe(objId => {
if (this.filesUploadingList) { if (this.filesUploadingList) {
const file = this.filesUploadingList.find((item) => { const uploadedFile = this.filesUploadingList.find((file) => {
return item.data.entry.id === objId; return file.data ? file.data.entry.id === objId : false;
}); });
if (file) { if (uploadedFile) {
file.status = FileUploadStatus.Cancelled; uploadedFile.status = FileUploadStatus.Cancelled;
this.changeDetector.detectChanges(); this.changeDetector.detectChanges();
} }
} }

View File

@@ -54,8 +54,8 @@ export class FileUploadingListComponent {
constructor( constructor(
private uploadService: UploadService, private uploadService: UploadService,
private nodesApi: NodesApiService, private nodesApi: NodesApiService,
private translateService: TranslationService private translateService: TranslationService) {
) {} }
/** /**
* Cancel file upload * 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 { cancelAllFiles(): void {
this.getUploadingFiles().forEach((file) => const deletedFiles: Observable<FileModel>[] = [];
this.uploadService.cancelUpload(file)
);
const deletedFiles = this.files this.files.forEach((file) => {
.filter((file) => file.status === FileUploadStatus.Complete) if (this.isUploadingFile(file)) {
.map((file) => this.deleteNode(file)); this.uploadService.cancelUpload(file);
} else if (file.status === FileUploadStatus.Complete) {
deletedFiles.push(this.deleteNode(file));
}
});
forkJoin(...deletedFiles).subscribe((files: FileModel[]) => { forkJoin(...deletedFiles).subscribe((files: FileModel[]) => {
const errors = files.filter( const errors = files.filter(
@@ -192,12 +194,9 @@ export class FileUploadingListComponent {
this.error.emit(messageError); this.error.emit(messageError);
} }
private getUploadingFiles(): FileModel[] { private isUploadingFile(file: FileModel): boolean {
return this.files.filter( return file.status === FileUploadStatus.Pending ||
item => file.status === FileUploadStatus.Starting ||
item.status === FileUploadStatus.Pending || file.status === FileUploadStatus.Progress;
item.status === FileUploadStatus.Progress ||
item.status === FileUploadStatus.Starting
);
} }
} }

View File

@@ -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 emitter = new EventEmitter();
const emitterDisposable = emitter.subscribe((e) => { const emitterDisposable = emitter.subscribe((event) => {
expect(e.value).toEqual('File aborted'); expect(event.value).toEqual('File aborted');
emitterDisposable.unsubscribe(); emitterDisposable.unsubscribe();
done(); done();
}); });
const fileFake = new FileModel(<File> { 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(<File> { name: 'fake-name', size: 10 }); const fileFake = new FileModel(<File> { name: 'fake-name', size: 10 });
service.addToQueue(fileFake); service.addToQueue(fileFake);
service.uploadFilesInTheQueue(emitter); service.uploadFilesInTheQueue(emitter);
const file = service.getQueue(); const file = service.getQueue();
service.cancelUpload(...file); 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', () => { it('If newVersion is set, name should be a param', () => {
@@ -196,7 +237,7 @@ describe('UploadService', () => {
done(); done();
}); });
const filesFake = new FileModel( const filesFake = new FileModel(
<File> { name: 'fake-name', size: 10 }, <File> { name: 'fake-file-name', size: 10 },
<FileUploadOptions> { parentId: '123', path: 'fake-dir' } <FileUploadOptions> { parentId: '123', path: 'fake-dir' }
); );
service.addToQueue(filesFake); service.addToQueue(filesFake);

View File

@@ -28,6 +28,9 @@ import {
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model'; import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
const MIN_CANCELLABLE_FILE_SIZE = 1000000;
const MAX_CANCELLABLE_FILE_PERCENTAGE = 50;
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@@ -39,6 +42,7 @@ export class UploadService {
private totalError: number = 0; private totalError: number = 0;
private excludedFileList: string[] = []; private excludedFileList: string[] = [];
private matchingOptions: any = null; private matchingOptions: any = null;
private abortedFile: string;
activeTask: Promise<any> = null; activeTask: Promise<any> = null;
queue: FileModel[] = []; queue: FileModel[] = [];
@@ -113,7 +117,7 @@ export class UploadService {
const promise = this.beginUpload(file, emitter); const promise = this.beginUpload(file, emitter);
this.activeTask = promise; this.activeTask = promise;
this.cache[file.id] = promise; this.cache[file.name] = promise;
const next = () => { const next = () => {
this.activeTask = null; this.activeTask = null;
@@ -132,15 +136,21 @@ export class UploadService {
/** /**
* Cancels uploading of files. * 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 * @param files One or more separate parameters or an array of files specifying uploads to cancel
*/ */
cancelUpload(...files: FileModel[]) { cancelUpload(...files: FileModel[]) {
files.forEach((file) => { files.forEach((file) => {
const promise = this.cache[file.id]; const promise = this.cache[file.name];
if (promise) { if (promise) {
promise.abort(); if (this.isSaveToAbortFile(file)) {
delete this.cache[file.id]; promise.abort();
}
this.abortedFile = file.name;
delete this.cache[file.name];
promise.next();
} else { } else {
const performAction = this.getAction(file); const performAction = this.getAction(file);
performAction(); performAction();
@@ -200,7 +210,6 @@ export class UploadService {
private beginUpload(file: FileModel, emitter: EventEmitter<any>): any { private beginUpload(file: FileModel, emitter: EventEmitter<any>): any {
const promise = this.getUploadPromise(file); const promise = this.getUploadPromise(file);
promise.on('progress', (progress: FileUploadProgress) => { promise.on('progress', (progress: FileUploadProgress) => {
this.onUploadProgress(file, progress); this.onUploadProgress(file, progress);
}) })
@@ -217,12 +226,20 @@ export class UploadService {
} }
}) })
.on('success', (data) => { .on('success', (data) => {
this.onUploadComplete(file, data); if (this.abortedFile === file.name) {
if (emitter) { this.onUploadAborted(file);
emitter.emit({ value: data }); 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; return promise;
} }
@@ -249,13 +266,13 @@ export class UploadService {
private onUploadError(file: FileModel, error: any): void { private onUploadError(file: FileModel, error: any): void {
if (file) { if (file) {
file.errorCode = ( error || {} ).status; file.errorCode = (error || {}).status;
file.status = FileUploadStatus.Error; file.status = FileUploadStatus.Error;
this.totalError++; this.totalError++;
const promise = this.cache[file.id]; const promise = this.cache[file.name];
if (promise) { if (promise) {
delete this.cache[file.id]; delete this.cache[file.name];
} }
const event = new FileUploadErrorEvent(file, error, this.totalError); const event = new FileUploadErrorEvent(file, error, this.totalError);
@@ -269,10 +286,9 @@ export class UploadService {
file.status = FileUploadStatus.Complete; file.status = FileUploadStatus.Complete;
file.data = data; file.data = data;
this.totalComplete++; this.totalComplete++;
const promise = this.cache[file.name];
const promise = this.cache[file.id];
if (promise) { if (promise) {
delete this.cache[file.id]; delete this.cache[file.name];
} }
const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted); const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted);
@@ -286,15 +302,9 @@ export class UploadService {
file.status = FileUploadStatus.Aborted; file.status = FileUploadStatus.Aborted;
this.totalAborted++; this.totalAborted++;
const promise = this.cache[file.id];
if (promise) {
delete this.cache[file.id];
}
const event = new FileUploadEvent(file, FileUploadStatus.Aborted); const event = new FileUploadEvent(file, FileUploadStatus.Aborted);
this.fileUpload.next(event); this.fileUpload.next(event);
this.fileUploadAborted.next(event); this.fileUploadAborted.next(event);
promise.next();
} }
} }
@@ -319,7 +329,7 @@ export class UploadService {
} }
} }
private getAction(file) { private getAction(file: FileModel) {
const actions = { const actions = {
[FileUploadStatus.Pending]: () => this.onUploadCancelled(file), [FileUploadStatus.Pending]: () => this.onUploadCancelled(file),
[FileUploadStatus.Deleted]: () => this.onUploadDeleted(file), [FileUploadStatus.Deleted]: () => this.onUploadDeleted(file),
@@ -328,4 +338,14 @@ export class UploadService {
return actions[file.status]; 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;
}
} }