import {ChangeDetectorRef, Component, Input, ViewChild} from '@angular/core'; import {interval, Subscriber, Subscription} from "rxjs"; import {HttpClient, HttpHeaders} from "@angular/common/http"; import {AbstractControl, ValidatorFn, Validators} from "@angular/forms"; import {Location} from '@angular/common'; import {COOKIE} from "../../login/utils/helper.class"; import {Router} from "@angular/router"; import {properties} from "../../../../environments/environment"; import {delay, repeat, startWith, switchMap} from "rxjs/operators"; import {StringUtils} from "../string-utils.class"; import {HelperFunctions} from "../HelperFunctions.class"; import {FullScreenModalComponent} from "../modal/full-screen-modal/full-screen-modal.component"; import {AlertModal} from "../modal/alert"; declare var UIkit; @Component({ selector: 'egi-transfer-data', templateUrl:'./transferData.component.html' , styles:[` /*Arrow*/ /*.source:first-child::after { content: ""; font-size: 20px; font-weight: 600; text-align: center; padding-bottom: 5%; position: absolute; background-image: url('/assets/arrow.svg'); right: -16%; top: 33%; width: 20%; background-size: contain; background-repeat: no-repeat; background-position: bottom; }*/ `] }) export class EGIDataTransferComponent { @Input() isMobile: boolean = false; subscriptions = []; statusSub: Subscription[] = []; accessToken = null; @Input() dois; loginURL = properties.eoscDataTransferLoginUrl; sourceUrls = []; selectedSourceUrl = null; destinationUrl = ""; destinationAuthUser = ""; destinationAuthPass = ""; destinationPath = ""; destinationOptions = null;//properties.eoscDataTransferDestinations.map(dest => {return {"label": dest.destination, "value": dest}}); selectedDestination:{ kind: string, destination: string, description: string, authType: 'token' | 'password' | 'keys', protocol: string, canBrowse: boolean, transferWith: string} = null; folders = {}; files = {}; requests: number = 0; downloadElements = null; @Input() isOpen = false; // @Input() selectedDestinationId = "dcache"; @ViewChild('egiTransferModal') egiTransferModal: AlertModal; @ViewChild('egiTransferFsModal') egiTransferFsModal: FullScreenModalComponent; APIURL = properties.eoscDataTransferAPI; // status: "loading" | "success" | "errorParser" | "errorUser" | "errorTransfer" | "init" | "canceled" = "init"; status: "unused" | "staging" | "submitted" | "active" | "succeeded" | "partial" | "failed" | "canceled" | "errorParser" | "errorUser" | "init" = "init"; // unused("unused"), // active("active"), // succeeded("succeeded"), // failed("failed"), // canceled("canceled"); message; doiPrefix = properties.doiURL; pathValidators = [Validators.required, this.pathValidator() /*StringUtils.urlValidator()*/]; // URLValidators = [Validators.required, StringUtils.urlValidator()]; URLValidators = [Validators.required]; jobId = null; statusMessage = null; jobStatus; privacyAccepted: boolean = false; public hostnameRegex = '[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|' + '[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|' + '[a-zA-Z0-9]+\.[^\s]{2,}|' + '[a-zA-Z0-9]+\.[^\s]{2,}' constructor(private http: HttpClient, private location: Location, private _router: Router, private cdr: ChangeDetectorRef) { } ngOnInit() { this.URLValidators = [Validators.required, Validators.pattern(this.hostnameRegex)]; } ngAfterViewInit() { if(this.isOpen && typeof document !== 'undefined'){ this.open(); } } ngOnDestroy() { this.subscriptions.forEach(subscription => { if (subscription instanceof Subscriber) { subscription.unsubscribe(); } }); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); } open(){ this.accessToken = COOKIE.getCookie("EGIAccessToken"); if(this.accessToken) { // if (this.selectedDestinationId) { // for (let option of this.destinationOptions) { // if (this.selectedDestinationId == option.value.destination) { // this.selectedDestination = option.value; // } // } // } else { // this.selectedDestination = this.destinationOptions[0].value; // } let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/storage/types", {headers: headers}).subscribe( (res: Array) => { this.destinationOptions = res.map(dest => {return {"label": dest.description, "value": dest}}); this.selectedDestination = res[0]; } )); for (let doi of this.dois) { this.sourceUrls.push(this.doiPrefix + doi); } try { this.sourceUrls.sort(function (a, b) { return Number(b.split("zenodo.")[1]) - Number(a.split("zenodo.")[1]); }); } catch (e) { } this.selectedSourceUrl = this.sourceUrls[0]; this.parse(); } this.isOpen = true; this.init(); if(!this.isMobile) { this.egiTransferModal.stayOpen = true; // this.egiTransferModal.back = true; this.egiTransferModal.cancelButton = false; this.egiTransferModal.okButton = true; this.egiTransferModal.okButtonText = ">> Transfer"; this.egiTransferModal.alertTitle = "EOSC Data Transfer"; if(typeof document !== 'undefined') { this.egiTransferModal.open(); } } else { this.egiTransferFsModal.back = false; this.egiTransferFsModal.cancelButton = true; this.egiTransferFsModal.okButton = false; // this.egiTransferFsModal.okButtonText = ">> Transfer"; this.egiTransferFsModal.title = "EOSC Data Transfer"; if(typeof document !== 'undefined') { this.egiTransferFsModal.open(); } } } close(){ if(this.isOpen) { this.isOpen = false; if(!this.isMobile) { this.egiTransferModal.cancel(); } else { this.egiTransferFsModal.cancel(); } } // this.downloadElements = []; this.init(); if(this._router.url.indexOf("&egiTransfer")){ this.location.go(this._router.url.split("&egiTransfer")[0]); } } init(){ this.destinationPath = ""; // this.selectedDestination = this.destinationOptions[0].value; this.selectedSourceUrl = this.sourceUrls[0]; this.message = null; this.status = "init"; this.jobId = null; // this.statusMessage = null; this.statusMessage = "primary"; this.requests = 0; this.folders = {}; this.files = {}; this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); } checkin(){ window.location.href = this.loginURL+"?redirect="+ encodeURIComponent(window.location.href + (window.location.href.indexOf("&egiTransfer=t")!=-1?"":"&egiTransfer=t")); } parse(){ this.status = "active"; this.message = null; this.downloadElements = []; let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/parser?doi=" + encodeURIComponent(this.selectedSourceUrl), {headers: headers}).subscribe( res => { // res['elements'].forEach(element => { // if(element.downloadUrl && element.name) { // this.downloadElements.push(element); // } // }) this.downloadElements= res['elements'] // console.log(this.downloadElements) this.status = "init"; }, error => { this.status = "errorParser"; this.message = error.error && error.error.id && error.error.id == 'doiNotSupported'?'DOI not supported.':( error.error && error.error.description && error.error.description? (error.error.description+'.'):'Error parsing information.') ; this.statusMessage = "danger"; this.cdr.detectChanges(); HelperFunctions.scrollToId("transferAlert"); /* UIkit.notification(this.message, { status: 'error', timeout: 3000, pos: 'bottom-right' });*/ } )); } transfer() { // console.log(this.selectedDestination) this.status = "active"; this.message = ""; let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/user/info?dest="+this.selectedDestination.destination, {headers: headers}).subscribe( res => { // console.log(res) let body = { "files": [], "params": { "priority": 0, "overwrite": false, "retry": 3 } }; // console.log(this.selectedDestination) for (let element of this.downloadElements) { let file = { "sources": [element['downloadUrl']], "destinations": [(this.selectedDestination.protocol+"://") + this.destinationUrl + this.destinationPath + (this.destinationPath.endsWith("/") ? "" : "/") + ((element.path && element.path != "/") ? element.path : "") + element.name], }; //TODO priority? checksum? filesize? // "filesize": element['size'] body.files.push(file); } let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); if(this.selectedDestination.authType != "token" && this.destinationAuthPass.length > 0 && this.destinationAuthUser.length > 0){ headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken, 'Authorization-Storage': btoa(this.destinationAuthUser + ':' + this.destinationAuthPass)}); } this.subscriptions.push(this.http.post(this.APIURL + "/transfers" ,body, {headers: headers}).subscribe( res => { // console.log(res) // UIkit.notification('Data transfer has began! ', { // status: 'success', // timeout: 6000, // pos: 'bottom-right' // }); this.jobId = res['jobId']; this.getStatus(); this.status = "active"; this.statusMessage = "primary"; // this.egiTransferModal.okButton = false; this.message = `
Transfer of ` + this.downloadElements.length + ` files to `+this.selectedDestination.description+` has began.`; /*this.message += `
    `; // TODO LATER we can call status for each file and see if the transfer has been complete for(let element of this.downloadElements){ // console.log(element) // this.message += `
  • `+ element.name+ `
  • `; this.message += `
  • `+ element.name+ `
  • `; } this.message += `
`*/ this.message += ` ` this.cdr.detectChanges(); HelperFunctions.scrollToId("transferAlert"); // this.getStatus(true) }, error => { this.status = "failed"; this.message = "Files could not be transfered."; this.statusMessage = "danger"; this.cdr.detectChanges(); HelperFunctions.scrollToId("transferAlert"); // UIkit.notification("Couldn't transfer files", { // status: 'error', // timeout: 6000, // pos: 'bottom-right' // }); } )); }, error => { this.status = "errorUser"; this.message = "User cannot be authenticated."; this.statusMessage = "danger"; this.cdr.detectChanges(); HelperFunctions.scrollToId("transferAlert"); // UIkit.notification("User can't be authenticated!", { // status: 'error', // timeout: 6000, // pos: 'bottom-right' // }); } )); } getStatus(updateTransferMessage:boolean = false){ if(this.jobId){ this.requests = 10; this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); let source = this.http.get(this.APIURL + "/transfer/" +this.jobId , {headers: headers}).pipe(delay(5000)); let source2 = interval(5000) // request status every 5 secs .pipe( startWith(2000), // first call after 2 secs switchMap(() => this.http.get(this.APIURL + "/transfer/" +this.jobId , {headers: headers})) ); this.statusSub.push(source2.subscribe((res: any) => { this.requests--; if(this.status != res.jobState) { this.status = res.jobState; this.jobStatus = res; this.message = `
Transfer of ` + this.downloadElements.length + ` files to ` + this.selectedDestination.description + ` has began.`; /*this.message += `
    `; // TODO LATER we can call status for each file and see if the transfer has been complete for(let element of this.downloadElements){ // console.log(element) // this.message += `
  • `+ element.name+ `
  • `; this.message += `
  • `+ element.name+ `
  • `; } this.message += `
`*/ this.message += ` `; this.message += "
Transfer status: "+res.jobState+".
"; this.statusMessage = "primary"; //this.statusMessage = res['jobState'] + (res['reason'] ? (" :" + res['reason']) : ""); if(this.status == "partial") { this.message = "At least one of the selected files was successfully transfered."; this.statusMessage = "success"; //this.statusSub.unsubscribe(); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); this.requests = 0; } else if(this.status == "succeeded") { this.message = "Transfer successfully completed!"; this.statusMessage = "success"; //this.statusSub.unsubscribe(); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); this.requests = 0; // UIkit.notification('Transfer successfully completed! ', { // status: 'success', // timeout: 6000, // pos: 'bottom-right' // }); } else if(this.status == "failed") { this.message = "Transfer failed."; this.statusMessage = "danger"; //this.statusSub.unsubscribe(); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); this.requests = 0; // UIkit.notification('Transfer failed', { // status: 'danger', // timeout: 6000, // pos: 'bottom-right' // }); } else if(this.requests <= 0 || (this.status != "active" && this.status != "submitted" && this.status != "staging")) { this.message = "Transfer status: "+this.status+"."; this.statusMessage = "warning"; //this.statusSub.unsubscribe(); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); this.requests = 0; // UIkit.notification('Transfer completed with status: '+this.status, { // status: 'warning', // timeout: 6000, // pos: 'bottom-right' // }); } } if(this.requests <= 0) { this.message = "Transfer status: "+this.status+"."; this.statusMessage = "warning"; //this.statusSub.unsubscribe(); this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); } }, error => { this.status = "failed"; this.message = "Status of the transfer could not be retrieved."; this.statusMessage = "danger"; this.statusSub.forEach(sub => { if(sub instanceof Subscriber) { sub.unsubscribe(); } }); this.requests = 0; // UIkit.notification("Couldn't get status", { // status: 'error', // timeout: 6000, // pos: 'bottom-right' // }); } )); } } cancel(){ if(this.jobId){ let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.delete(this.APIURL + "/transfer/" +this.jobId , {headers: headers}).subscribe( res => { this.jobStatus = res; this.statusMessage = res['jobState'] + (res['reason']?(" :" + res['reason']):""); this.jobId = null; this.status = "canceled"; } )); } } hasBrowse(){ let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/storage/info?dest="+this.selectedDestination.destination+"&seUrl="+encodeURIComponent((this.selectedDestination.protocol+'://') + this.destinationUrl + this.destinationPath) , {headers: headers}).subscribe( res => { console.log(res); } )); } getFolder(folderPath){ //TODO is this necessary? let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/storage/folder?dest="+this.selectedDestination.destination+"&seUrl="+encodeURIComponent((this.selectedDestination.protocol + '://') + this.destinationUrl + folderPath) , {headers: headers}).subscribe( res => { this.folders[folderPath]= res; this.folders[folderPath]['isOpen'] = true; } )); } browseFolder(folderPath){ if(this.folders[folderPath]){ this.folders[folderPath].isOpen = !this.folders[folderPath].isOpen; return; } this.getFolder(folderPath); let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.get(this.APIURL + "/storage/folder/list?dest="+this.selectedDestination.destination+"&folderUrl="+encodeURIComponent((this.selectedDestination.protocol + '://') + this.destinationUrl + folderPath) , {headers: headers}).subscribe( res => { this.files[folderPath]= res['elements']; } )); } createFolder(){ let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.post(this.APIURL + "/storage/folder?dest="+this.selectedDestination.destination+"&seUrl="+ encodeURIComponent(this.destinationUrl + this.destinationPath + "test1/") , {headers: headers}).subscribe( res => { console.log(res); } )); } deleteFolder(){ let headers = new HttpHeaders({'Authorization': 'Bearer '+this.accessToken}); this.subscriptions.push(this.http.delete(this.APIURL + "/storage/folder?dest="+this.selectedDestination.destination+"&seUrl="+encodeURIComponent(this.destinationUrl + this.destinationPath + "test1/") , {headers: headers}).subscribe( res => { console.log(res); } )); } private parseFilename(url){ let filename = url.split("/")[url.split("/").length - 1]; return filename.split("?")[0]; } pathValidator(): ValidatorFn { return (control: AbstractControl): { [key: string]: string } | null => { if (!this.validatePath()) { return {'error': 'Path should start with "/"'}; } return null; } } validatePath():boolean { // let exp1 = /^\/([A-z0-9-_+]+\/?)*$/g; // let exp1 = /^\/([A-z0-9-_]+[+\/]?)*$/; return (this.destinationPath.length > 0 && this.destinationPath.startsWith("/")) } validateDestinationUrl():boolean { return (this.destinationUrl.length > 0 && new RegExp(this.hostnameRegex).test(this.destinationUrl)); } // public sourceUrlValidators() { // this.URLValidators = []; // this.URLValidators = [Validators.required, Validators.pattern(this.hostnameRegex)]; // } }