blob: d179780dd1606b12b2caa4904cc53a5b2c0ae876 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {Component, OnInit, ViewChild, Inject, OnDestroy} from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ToastrService } from 'ngx-toastr';
import {ApplicationSecurityService, ManageUngitService, StorageService} from '../../core/services';
import {FolderTreeComponent} from './folder-tree/folder-tree.component';
import {BucketBrowserService, TodoItemNode} from '../../core/services/bucket-browser.service';
import {FileUtils} from '../../core/util';
import {BucketDataService} from './bucket-data.service';
import {BucketConfirmationDialogComponent} from './bucket-confirmation-dialog/bucket-confirmation-dialog.component';
import {logger} from 'codelyzer/util/logger';
import {HttpEventType, HttpResponse} from '@angular/common/http';
import {CopyPathUtils} from '../../core/util/copyPathUtils';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
@Component({
selector: 'dlab-bucket-browser',
templateUrl: './bucket-browser.component.html',
styleUrls: ['./bucket-browser.component.scss']
})
export class BucketBrowserComponent implements OnInit, OnDestroy {
private unsubscribe$ = new Subject();
public addedFiles = [];
public folderItems = [];
public originFolderItems = [];
public objectPath;
public path = '';
public pathInsideBucket = '';
public bucketName = '';
public endpoint = '';
public selectedFolder: any;
public selectedFolderForAction: any;
public selected: any[];
public bucketStatus;
public allDisable: boolean;
public isActionsOpen: boolean;
public folders: any[];
public selectedItems;
public searchValue: string;
public isQueueFull: boolean;
public refreshTokenLimit = 1500000;
private isTokenRefreshing = false;
public isSelectionOpened: any;
public isFilterVisible: boolean;
public buckets;
public isFileUploading: boolean;
public uploadingQueueLength: number = 4;
public maxFileSize: number = 4294967296;
public cloud: string;
@ViewChild(FolderTreeComponent, {static: true}) folderTreeComponent;
constructor(
@Inject(MAT_DIALOG_DATA) public data: any,
public toastr: ToastrService,
public dialog: MatDialog,
public dialogRef: MatDialogRef<BucketBrowserComponent>,
private manageUngitService: ManageUngitService,
private _fb: FormBuilder,
private bucketBrowserService: BucketBrowserService,
public bucketDataService: BucketDataService,
private auth: ApplicationSecurityService,
private storage: StorageService,
) {
}
ngOnInit() {
this.bucketName = this.data.bucket;
this.endpoint = this.data.endpoint;
this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
this.bucketStatus = this.data.bucketStatus;
this.buckets = this.data.buckets;
this.cloud = this.getCloud();
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
public getTokenValidTime() {
const token = JSON.parse(atob(this.storage.getToken().split('.')[1]));
return token.exp * 1000 - new Date().getTime();
}
private refreshToken() {
this.isTokenRefreshing = true;
this.auth.refreshToken()
.pipe(
takeUntil(this.unsubscribe$)
)
.subscribe(tokens => {
this.storage.storeTokens(tokens);
this.isTokenRefreshing = false;
this.sendFile();
});
}
public showItem(item) {
const flatItem = this.folderTreeComponent.nestedNodeMap.get(item);
this.folderTreeComponent.showItem(flatItem);
}
public closeUploadWindow() {
this.addedFiles = [];
}
public toggleSelectedFile(file, type) {
type === 'file' ? file.isSelected = !file.isSelected : file.isFolderSelected = !file.isFolderSelected;
this.selected = this.folderItems.filter(item => item.isSelected);
this.selectedFolderForAction = this.folderItems.filter(item => item.isFolderSelected);
this.selectedItems = [...this.selected, ...this.selectedFolderForAction];
this.isActionsOpen = false;
}
filesPicked(files) {
Array.prototype.forEach.call(files, file => {
this.addedFiles.push(file.webkitRelativePath);
});
}
public dissableAll(event) {
this.allDisable = event;
}
public handleFileInput(event) {
const fullFilesList = Object['values'](event.target.files);
if (fullFilesList.length > 0) {
const files = fullFilesList.filter(v => v.size < this.maxFileSize);
const toBigFile = fullFilesList.length !== files.length;
const toMany = files.length > 50;
if (files.length > 50) {
files.length = 50;
}
if (toBigFile || toMany) {
this.dialog.open(BucketConfirmationDialogComponent, {data: {
items: {toBig: toBigFile, toMany: toMany}, type: 'uploading-error'
} , width: '550px'})
.afterClosed().subscribe((res) => {
if (res) {
if (this.refreshTokenLimit > this.getTokenValidTime()) {
this.isTokenRefreshing = true;
this.auth.refreshToken()
.pipe(
takeUntil(this.unsubscribe$)
)
.subscribe(v => {
this.uploadingQueue(files);
this.isTokenRefreshing = false;
});
} else {
this.uploadingQueue(files);
}
}
});
} else {
if (this.refreshTokenLimit > this.getTokenValidTime()) {
this.isTokenRefreshing = true;
this.auth.refreshToken()
.pipe(
takeUntil(this.unsubscribe$)
)
.subscribe(v => {
this.uploadingQueue(files);
this.isTokenRefreshing = false;
});
} else {
this.uploadingQueue(files);
}
}
}
event.target.value = '';
}
private async uploadingQueue(files) {
if (files.length) {
let askForAll = true;
let skipAll = false;
const folderFiles = this.folderItems.filter(v => !v.children).map(v => v.item);
for (const file of files) {
const existFile = folderFiles.filter(v => v === file['name'])[0];
const uploadItem = {
name: file['name'],
file: file,
size: file.size,
path: this.path,
};
if (existFile && askForAll) {
const result = await this.openResolveDialog(existFile);
if (result) {
askForAll = !result.forAll;
if (result.forAll && !result.replaceObject) {
skipAll = true;
}
if (result.replaceObject) {
this.addedFiles.push(uploadItem);
this.uploadNewFile(uploadItem);
}
}
} else if (!existFile || (existFile && !askForAll && !skipAll)) {
this.addedFiles.push(uploadItem);
this.uploadNewFile(uploadItem);
}
}
}
setTimeout(() => {
const element = document.querySelector('#upload-list');
element && element.scrollIntoView({ block: 'end', behavior: 'smooth' });
}, 0);
}
async openResolveDialog(existFile) {
const dialog = this.dialog.open(BucketConfirmationDialogComponent, {
data: {items: existFile, type: 'upload-dublicat'} , width: '550px'
});
return dialog.afterClosed().toPromise().then(result => {
return Promise.resolve(result);
});
}
public onFolderClick(event) {
this.searchValue = '';
this.clearSelection();
this.selectedFolder = event.flatNode;
if (this.isSelectionOpened) {
this.isSelectionOpened = false;
}
this.folderItems = event.element ? event.element.children : event.children;
if (this.folderItems) {
this.folders = this.folderItems.filter(v => v.children);
const files = this.folderItems.filter(v => !v.children).sort((a, b) => a.item > b.item ? 1 : -1);
this.folderItems = [...this.folders, ...files];
this.objectPath = event.pathObject;
this.path = event.path;
this.originFolderItems = this.folderItems.map(v => v);
this.pathInsideBucket = this.path.indexOf('/') !== -1 ? this.path.slice(this.path.indexOf('/') + 1) + '/' : '';
this.folderItems.forEach(item => item.isSelected = false);
}
}
public filterObjects() {
this.folderItems = this.originFolderItems.filter(v => v.item.toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1);
}
private clearSelection() {
this.folderItems.forEach(item => item.isSelected = false);
this.folderItems.forEach(item => item.isFolderSelected = false);
this.selected = this.folderItems.filter(item => item.isSelected);
this.selectedFolderForAction = this.folderItems.filter(item => item.isFolderSelected);
this.selectedItems = [];
}
public deleteAddedFile(file) {
if ( file.subscr && file.request) {
this.dialog.open(BucketConfirmationDialogComponent, {data: {items: file, type: 'cancel-uploading'} , width: '550px'})
.afterClosed().subscribe((res) => {
res && file.subscr.unsubscribe();
res && this.addedFiles.splice(this.addedFiles.indexOf(file), 1);
this.isFileUploading = !!this.addedFiles.filter(v => v.status === 'uploading').length;
this.sendFile();
}, () => {
this.isFileUploading = !!this.addedFiles.filter(v => v.status === 'uploading').length;
this.sendFile();
});
} else {
this.addedFiles.splice(this.addedFiles.indexOf(file), 1);
this.isFileUploading = !!this.addedFiles.filter(v => v.status === 'uploading').length;
this.sendFile();
}
}
private uploadNewFile(file) {
const path = file.path.indexOf('/') !== -1 ? this.path.slice(this.path.indexOf('/') + 1) : '';
const fullPath = path ? `${path}/${file.name}` : file.name;
const formData = new FormData();
formData.append('size', file.file.size);
formData.append('object', fullPath);
formData.append('bucket', this.bucketName);
formData.append('endpoint', this.endpoint);
formData.append('file', file.file);
file.status = 'waiting';
file.request = this.bucketBrowserService.uploadFile(formData);
this.sendFile(file);
}
public sendFile(file?) {
const waitUploading = this.addedFiles.filter(v => v.status === 'waiting');
const uploading = this.addedFiles.filter(v => v.status === 'uploading');
this.isQueueFull = !!waitUploading.length;
this.isFileUploading = !!this.addedFiles.filter(v => v.status === 'uploading').length;
// console.log((this.getTokenValidTime() / 1000 / 60 ).toFixed(0) + ' minutes');
if ((this.refreshTokenLimit > this.getTokenValidTime()) && !this.isTokenRefreshing) {
this.refreshToken();
}
if (waitUploading.length && uploading.length < this.uploadingQueueLength) {
if (!file) {
file = waitUploading[0];
}
file.status = 'uploading';
this.isFileUploading = !!this.addedFiles.filter(v => v.status === 'uploading').length;
this.isQueueFull = !!this.addedFiles.filter(v => v.status === 'waiting').length;
file.subscr = file.request.subscribe((event: any) => {
if (event.type === HttpEventType.UploadProgress) {
file.progress = Math.round(95 * event.loaded / event.total);
if (file.progress === 95 && !file.interval) {
file.interval = setInterval(() => {
if (file.progress < 99) {
return file.progress++;
}
}, file.size < 1094967296 ? 12000 : 20000);
}
} else if (event['type'] === HttpEventType.Response) {
window.clearInterval(file.interval);
file.status = 'uploaded';
delete file.request;
this.sendFile(this.addedFiles.filter(v => v.status === 'waiting')[0]);
this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
}
}, error => {
window.clearInterval(file.interval);
file.status = 'failed';
delete file.request;
this.sendFile(this.addedFiles.filter(v => v.status === 'waiting')[0]);
}
);
}
}
public refreshBucket() {
this.path = '';
this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
this.isSelectionOpened = false;
}
public openBucket($event) {
this.bucketName = $event.name;
this.endpoint = $event.endpoint;
this.path = '';
this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
this.isSelectionOpened = false;
this.cloud = this.getCloud();
}
private getCloud() {
return this.buckets.filter(v => v.children.some(bucket => {
return bucket.name === this.bucketName;
}))[0].cloud.toLowerCase();
}
public createFolder(folder) {
this.allDisable = true;
this.folderTreeComponent.addNewItem(folder, '', false);
}
public fileAction(action) {
const selected = this.folderItems.filter(item => item.isSelected);
const folderSelected = this.folderItems.filter(item => item.isFolderSelected);
if (action === 'download') {
this.clearSelection();
this.isActionsOpen = false;
const path = encodeURIComponent(`${this.pathInsideBucket}${selected[0].item}`);
selected[0]['isDownloading'] = true;
this.folderItems.forEach(item => item.isSelected = false);
this.bucketBrowserService.downloadFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}/download`)
.pipe(
takeUntil(this.unsubscribe$)
)
.subscribe(event => {
if (event['type'] === HttpEventType.DownloadProgress) {
selected[0].progress = Math.round(100 * event['loaded'] / selected[0].object.size);
}
if (event['type'] === HttpEventType.Response) {
FileUtils.downloadBigFiles(event['body'], selected[0].item);
setTimeout(() => {
selected[0]['isDownloading'] = false;
selected[0].progress = 0;
}, 1000);
}
}, error => {
this.toastr.error(error.message || 'File downloading error!', 'Oops!');
selected[0]['isDownloading'] = false;
}
);
}
if (action === 'delete') {
const itemsForDeleting = [...folderSelected, ...selected];
const objects = itemsForDeleting.map(obj => obj.object.object);
let dataForServer = [];
objects.forEach(object => {
dataForServer.push(...this.bucketDataService.serverData.map(v => v.object).filter(v => v.indexOf(object) === 0));
});
dataForServer = [...dataForServer, ...objects].filter((v, i, arr) => i === arr.indexOf(v));
this.dialog.open(BucketConfirmationDialogComponent, {data: {items: itemsForDeleting, type: 'delete'} , width: '550px'})
.afterClosed().subscribe((res) => {
!res && this.clearSelection();
res && this.bucketBrowserService.deleteFile({
bucket: this.bucketName, endpoint: this.endpoint, 'objects': dataForServer
})
.pipe(
takeUntil(this.unsubscribe$)
)
.subscribe(() => {
this.bucketDataService.refreshBucketdata(this.bucketName, this.endpoint);
this.toastr.success('Objects successfully deleted!', 'Success!');
this.clearSelection();
}, error => {
this.toastr.error(error.message || 'Objects deleting error!', 'Oops!');
this.clearSelection();
});
});
}
}
public toogleActions() {
this.isActionsOpen = !this.isActionsOpen;
}
public closeActions() {
this.isActionsOpen = false;
}
public copyPath() {
const selected = this.folderItems.filter(item => item.isSelected || item.isFolderSelected)[0];
CopyPathUtils.copyPath(selected.object.bucket + '/' + selected.object.object);
this.clearSelection();
this.isActionsOpen = false;
this.toastr.success('Object path successfully copied!', 'Success!');
}
public toggleBucketSelection() {
this.isSelectionOpened = !this.isSelectionOpened;
}
public closeFilterInput() {
this.isFilterVisible = false;
this.searchValue = '';
this.filterObjects();
}
public convertDate(date) {
const utcDate = new Date(date);
return new Date(utcDate.setTime( utcDate.getTime() - utcDate.getTimezoneOffset() * 60 * 1000 ));
}
}