import * as C from 'constants' import { posix as path } from 'path' import { Component, Input, Output, EventEmitter, Inject, Optional } from '@angular/core' import { FileUpload, MenuItemOptions, NotificationsService, PlatformService } from 'tabby-core' import { SFTPSession, SFTPFile } from '../session/sftp' import { SSHSession } from '../session/ssh' import { SFTPContextMenuItemProvider } from '../api' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { SFTPCreateDirectoryModalComponent } from './sftpCreateDirectoryModal.component' interface PathSegment { name: string path: string } @Component({ selector: 'sftp-panel', template: require('./sftpPanel.component.pug'), styles: [require('./sftpPanel.component.scss')], }) export class SFTPPanelComponent { @Input() session: SSHSession @Output() closed = new EventEmitter() sftp: SFTPSession fileList: SFTPFile[]|null = null @Input() path = '/' @Output() pathChange = new EventEmitter() pathSegments: PathSegment[] = [] @Input() cwdDetectionAvailable = false constructor ( private ngbModal: NgbModal, private notifications: NotificationsService, public platform: PlatformService, @Optional() @Inject(SFTPContextMenuItemProvider) protected contextMenuProviders: SFTPContextMenuItemProvider[], ) { this.contextMenuProviders.sort((a, b) => a.weight - b.weight) } async ngOnInit (): Promise { this.sftp = await this.session.openSFTP() try { await this.navigate(this.path) } catch (error) { console.warn('Could not navigate to', this.path, ':', error) this.notifications.error(error.message) await this.navigate('/') } } async navigate (newPath: string, fallbackOnError = true): Promise { const previousPath = this.path this.path = newPath this.pathChange.next(this.path) let p = newPath this.pathSegments = [] while (p !== '/') { this.pathSegments.unshift({ name: path.basename(p), path: p, }) p = path.dirname(p) } this.fileList = null try { this.fileList = await this.sftp.readdir(this.path) } catch (error) { this.notifications.error(error.message) if (previousPath && fallbackOnError) { this.navigate(previousPath, false) } return } const dirKey = a => a.isDirectory ? 1 : 0 this.fileList.sort((a, b) => dirKey(b) - dirKey(a) || a.name.localeCompare(b.name)) } getIcon (item: SFTPFile): string { if (item.isDirectory) { return 'fas fa-folder text-info' } if (item.isSymlink) { return 'fas fa-link text-warning' } return 'fas fa-file' } goUp (): void { this.navigate(path.dirname(this.path)) } async open (item: SFTPFile): Promise { if (item.isDirectory) { await this.navigate(item.fullPath) } else if (item.isSymlink) { const target = path.resolve(this.path, await this.sftp.readlink(item.fullPath)) const stat = await this.sftp.stat(target) if (stat.isDirectory) { await this.navigate(item.fullPath) } else { await this.download(item.fullPath, stat.mode, stat.size) } } else { await this.download(item.fullPath, item.mode, item.size) } } async openCreateDirectoryModal (): Promise { const modal = this.ngbModal.open(SFTPCreateDirectoryModalComponent) const directoryName = await modal.result if (directoryName !== '') { this.sftp.mkdir(path.join(this.path, directoryName)).then(() => { this.notifications.notice('The directory was created successfully') this.navigate(path.join(this.path, directoryName)) }).catch(() => { this.notifications.error('The directory could not be created') }) } } async upload (): Promise { const transfers = await this.platform.startUpload({ multiple: true }) await Promise.all(transfers.map(t => this.uploadOne(t))) } async uploadOne (transfer: FileUpload): Promise { const savedPath = this.path await this.sftp.upload(path.join(this.path, transfer.getName()), transfer) if (this.path === savedPath) { await this.navigate(this.path) } } async download (itemPath: string, mode: number, size: number): Promise { const transfer = await this.platform.startDownload(path.basename(itemPath), mode, size) if (!transfer) { return } this.sftp.download(itemPath, transfer) } getModeString (item: SFTPFile): string { const s = 'SGdrwxrwxrwx' const e = ' ---------' const c = [ 0o4000, 0o2000, C.S_IFDIR, C.S_IRUSR, C.S_IWUSR, C.S_IXUSR, C.S_IRGRP, C.S_IWGRP, C.S_IXGRP, C.S_IROTH, C.S_IWOTH, C.S_IXOTH, ] let result = '' for (let i = 0; i < c.length; i++) { result += item.mode & c[i] ? s[i] : e[i] } return result } async buildContextMenu (item: SFTPFile): Promise { let items: MenuItemOptions[] = [] for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(item, this)))) { items.push({ type: 'separator' }) items = items.concat(section) } return items.slice(1) } async showContextMenu (item: SFTPFile, event: MouseEvent): Promise { event.preventDefault() this.platform.popupContextMenu(await this.buildContextMenu(item), event) } get shouldShowCWDTip (): boolean { return !window.localStorage.sshCWDTipDismissed } dismissCWDTip (): void { window.localStorage.sshCWDTipDismissed = 'true' } close (): void { this.closed.emit() } }