mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-04 18:39:54 +00:00
zone-ified sftp promises
This commit is contained in:
parent
a2ed674b10
commit
6ce76af9be
@ -5,6 +5,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { AsyncSubject, Subject, Observable } from 'rxjs'
|
||||
import { UnlockVaultModalComponent } from '../components/unlockVaultModal.component'
|
||||
import { NotificationsService } from '../services/notifications.service'
|
||||
import { wrapPromise } from 'utils'
|
||||
|
||||
const PBKDF_ITERATIONS = 100000
|
||||
const PBKDF_DIGEST = 'sha512'
|
||||
@ -120,7 +121,7 @@ export class VaultService {
|
||||
passphrase = await this.getPassphrase()
|
||||
}
|
||||
try {
|
||||
return await this.wrapPromise(decryptVault(storage, passphrase))
|
||||
return await wrapPromise(this.zone, decryptVault(storage, passphrase))
|
||||
} catch (e) {
|
||||
_rememberedPassphrase = null
|
||||
if (e.toString().includes('BAD_DECRYPT')) {
|
||||
@ -144,7 +145,7 @@ export class VaultService {
|
||||
if (_rememberedPassphrase) {
|
||||
_rememberedPassphrase = passphrase
|
||||
}
|
||||
return this.wrapPromise(encryptVault(vault, passphrase))
|
||||
return wrapPromise(this.zone, encryptVault(vault, passphrase))
|
||||
}
|
||||
|
||||
async save (vault: Vault, passphrase?: string): Promise<void> {
|
||||
@ -210,14 +211,4 @@ export class VaultService {
|
||||
isEnabled (): boolean {
|
||||
return !!this.store
|
||||
}
|
||||
|
||||
private wrapPromise <T> (promise: Promise<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
promise.then(result => {
|
||||
this.zone.run(() => resolve(result))
|
||||
}).catch(error => {
|
||||
this.zone.run(() => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as os from 'os'
|
||||
import { NgZone } from '@angular/core'
|
||||
|
||||
export const WIN_BUILD_CONPTY_SUPPORTED = 17692
|
||||
export const WIN_BUILD_CONPTY_STABLE = 18309
|
||||
@ -20,3 +21,13 @@ export function getCSSFontFamily (config: any): string {
|
||||
fonts = fonts.map(x => `"${x}"`)
|
||||
return fonts.join(', ')
|
||||
}
|
||||
|
||||
export function wrapPromise <T> (zone: NgZone, promise: Promise<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
promise.then(result => {
|
||||
zone.run(() => resolve(result))
|
||||
}).catch(error => {
|
||||
zone.run(() => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import * as os from 'os'
|
||||
import promiseIpc from 'electron-promise-ipc'
|
||||
import { execFile } from 'mz/child_process'
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { PlatformService, ClipboardContent, HostAppService, Platform, ElectronService, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions } from 'terminus-core'
|
||||
import { PlatformService, ClipboardContent, HostAppService, Platform, ElectronService, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise } from 'terminus-core'
|
||||
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
||||
|
||||
/* eslint-disable block-scoped-var */
|
||||
@ -181,7 +181,7 @@ export class ElectronPlatformService extends PlatformService {
|
||||
|
||||
return Promise.all(result.filePaths.map(async p => {
|
||||
const transfer = new ElectronFileUpload(p)
|
||||
await this.wrapPromise(transfer.open())
|
||||
await wrapPromise(this.zone, transfer.open())
|
||||
this.fileTransferStarted.next(transfer)
|
||||
return transfer
|
||||
}))
|
||||
@ -198,20 +198,10 @@ export class ElectronPlatformService extends PlatformService {
|
||||
return null
|
||||
}
|
||||
const transfer = new ElectronFileDownload(result.filePath, size)
|
||||
await this.wrapPromise(transfer.open())
|
||||
await wrapPromise(this.zone, transfer.open())
|
||||
this.fileTransferStarted.next(transfer)
|
||||
return transfer
|
||||
}
|
||||
|
||||
private wrapPromise <T> (promise: Promise<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
promise.then(result => {
|
||||
this.zone.run(() => resolve(result))
|
||||
}).catch(error => {
|
||||
this.zone.run(() => reject(error))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ElectronFileUpload extends FileUpload {
|
||||
|
@ -5,12 +5,13 @@ import * as sshpk from 'sshpk'
|
||||
import colors from 'ansi-colors'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
import socksv5 from 'socksv5'
|
||||
import { Injector } from '@angular/core'
|
||||
import { Injector, NgZone } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HostAppService, Logger, NotificationsService, Platform, PlatformService } from 'terminus-core'
|
||||
import { HostAppService, Logger, NotificationsService, Platform, PlatformService, wrapPromise } from 'terminus-core'
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
import { Server, Socket, createServer, createConnection } from 'net'
|
||||
import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
|
||||
import type { FileEntry, Stats } from 'ssh2-streams'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { ProxyCommandStream } from './services/ssh.service'
|
||||
import { PasswordStorageService } from './services/passwordStorage.service'
|
||||
@ -137,6 +138,77 @@ interface AuthMethod {
|
||||
path?: string
|
||||
}
|
||||
|
||||
export class SFTPFileHandle {
|
||||
position = 0
|
||||
|
||||
constructor (
|
||||
private sftp: SFTPWrapper,
|
||||
private handle: Buffer,
|
||||
private zone: NgZone,
|
||||
) { }
|
||||
|
||||
read (): Promise<Buffer> {
|
||||
const buffer = Buffer.alloc(256 * 1024)
|
||||
return wrapPromise(this.zone, new Promise((resolve, reject) => {
|
||||
while (true) {
|
||||
const wait = this.sftp.read(this.handle, buffer, 0, buffer.length, this.position, (err, read) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
this.position += read
|
||||
resolve(buffer.slice(0, read))
|
||||
})
|
||||
if (!wait) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
write (chunk: Buffer): Promise<void> {
|
||||
return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
|
||||
while (true) {
|
||||
const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
this.position += chunk.length
|
||||
resolve()
|
||||
})
|
||||
if (!wait) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
close (): Promise<void> {
|
||||
return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
|
||||
}
|
||||
}
|
||||
|
||||
export class SFTPSession {
|
||||
constructor (private sftp: SFTPWrapper, private zone: NgZone) { }
|
||||
|
||||
readdir (p: string): Promise<FileEntry[]> {
|
||||
return wrapPromise(this.zone, promisify<FileEntry[]>(f => this.sftp.readdir(p, f))())
|
||||
}
|
||||
|
||||
readlink (p: string): Promise<string> {
|
||||
return wrapPromise(this.zone, promisify<string>(f => this.sftp.readlink(p, f))())
|
||||
}
|
||||
|
||||
stat (p: string): Promise<Stats> {
|
||||
return wrapPromise(this.zone, promisify<Stats>(f => this.sftp.stat(p, f))())
|
||||
}
|
||||
|
||||
async open (p: string, mode: string): Promise<SFTPFileHandle> {
|
||||
const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
|
||||
return new SFTPFileHandle(this.sftp, handle, this.zone)
|
||||
}
|
||||
}
|
||||
|
||||
export class SSHSession extends BaseSession {
|
||||
scripts?: LoginScript[]
|
||||
shell?: ClientChannel
|
||||
@ -161,6 +233,7 @@ export class SSHSession extends BaseSession {
|
||||
private hostApp: HostAppService
|
||||
private platform: PlatformService
|
||||
private notifications: NotificationsService
|
||||
private zone: NgZone
|
||||
|
||||
constructor (
|
||||
injector: Injector,
|
||||
@ -172,6 +245,7 @@ export class SSHSession extends BaseSession {
|
||||
this.hostApp = injector.get(HostAppService)
|
||||
this.platform = injector.get(PlatformService)
|
||||
this.notifications = injector.get(NotificationsService)
|
||||
this.zone = injector.get(NgZone)
|
||||
|
||||
this.scripts = connection.scripts ?? []
|
||||
this.destroyed$.subscribe(() => {
|
||||
@ -223,11 +297,11 @@ export class SSHSession extends BaseSession {
|
||||
this.remainingAuthMethods.push({ type: 'hostbased' })
|
||||
}
|
||||
|
||||
async openSFTP (): Promise<SFTPWrapper> {
|
||||
async openSFTP (): Promise<SFTPSession> {
|
||||
if (!this.sftp) {
|
||||
this.sftp = await promisify<SFTPWrapper>(f => this.ssh.sftp(f))()
|
||||
this.sftp = await wrapPromise(this.zone, promisify<SFTPWrapper>(f => this.ssh.sftp(f))())
|
||||
}
|
||||
return this.sftp
|
||||
return new SFTPSession(this.sftp, this.zone)
|
||||
}
|
||||
|
||||
async start (): Promise<void> {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import { SFTPWrapper } from 'ssh2'
|
||||
import type { FileEntry, Stats } from 'ssh2-streams'
|
||||
import { promisify } from 'util'
|
||||
import { SSHSession } from '../api'
|
||||
import type { FileEntry } from 'ssh2-streams'
|
||||
import { SSHSession, SFTPSession } from '../api'
|
||||
import * as path from 'path'
|
||||
import * as C from 'constants'
|
||||
import { FileUpload, PlatformService } from 'terminus-core'
|
||||
@ -21,7 +19,7 @@ interface PathSegment {
|
||||
export class SFTPPanelComponent {
|
||||
@Input() session: SSHSession
|
||||
@Output() closed = new EventEmitter<void>()
|
||||
sftp: SFTPWrapper
|
||||
sftp: SFTPSession
|
||||
fileList: FileEntry[]|null = null
|
||||
path = '/'
|
||||
pathSegments: PathSegment[] = []
|
||||
@ -49,8 +47,7 @@ export class SFTPPanelComponent {
|
||||
}
|
||||
|
||||
this.fileList = null
|
||||
this.fileList = await promisify<FileEntry[]>(f => this.sftp.readdir(this.path, f))()
|
||||
console.log(this.fileList)
|
||||
this.fileList = await this.sftp.readdir(this.path)
|
||||
|
||||
const dirKey = a => (a.attrs.mode & C.S_IFDIR) === C.S_IFDIR ? 1 : 0
|
||||
this.fileList.sort((a, b) =>
|
||||
@ -77,8 +74,8 @@ export class SFTPPanelComponent {
|
||||
if ((item.attrs.mode & C.S_IFDIR) === C.S_IFDIR) {
|
||||
this.navigate(path.join(this.path, item.filename))
|
||||
} else if ((item.attrs.mode & C.S_IFLNK) === C.S_IFLNK) {
|
||||
const target = await promisify<string>(f => this.sftp.readlink(itemPath, f))()
|
||||
const stat = await promisify<Stats>(f => this.sftp.stat(target, f))()
|
||||
const target = await this.sftp.readlink(itemPath)
|
||||
const stat = await this.sftp.stat(target)
|
||||
if (stat.isDirectory()) {
|
||||
this.navigate(itemPath)
|
||||
} else {
|
||||
@ -104,30 +101,15 @@ export class SFTPPanelComponent {
|
||||
async uploadOne (transfer: FileUpload): Promise<void> {
|
||||
const itemPath = path.join(this.path, transfer.getName())
|
||||
try {
|
||||
const handle = await promisify<Buffer>(f => this.sftp.open(itemPath, 'w', f))()
|
||||
let position = 0
|
||||
const handle = await this.sftp.open(itemPath, 'w')
|
||||
while (true) {
|
||||
const chunk = await transfer.read()
|
||||
if (!chunk.length) {
|
||||
break
|
||||
}
|
||||
const p = position
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
while (true) {
|
||||
const wait = this.sftp.write(handle, chunk, 0, chunk.length, p, err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
await handle.write(chunk)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
if (!wait) {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
position += chunk.length
|
||||
}
|
||||
this.sftp.close(handle, () => null)
|
||||
handle.close()
|
||||
transfer.close()
|
||||
} catch (e) {
|
||||
transfer.cancel()
|
||||
@ -141,33 +123,16 @@ export class SFTPPanelComponent {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const handle = await promisify<Buffer>(f => this.sftp.open(itemPath, 'r', f))()
|
||||
const buffer = Buffer.alloc(256 * 1024)
|
||||
let position = 0
|
||||
const handle = await this.sftp.open(itemPath, 'r')
|
||||
while (true) {
|
||||
const p = position
|
||||
const chunk: Buffer = await new Promise((resolve, reject) => {
|
||||
while (true) {
|
||||
const wait = this.sftp.read(handle, buffer, 0, buffer.length, p, (err, read) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
resolve(buffer.slice(0, read))
|
||||
})
|
||||
if (!wait) {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
const chunk = await handle.read()
|
||||
if (!chunk.length) {
|
||||
break
|
||||
}
|
||||
await transfer.write(chunk)
|
||||
position += chunk.length
|
||||
}
|
||||
transfer.close()
|
||||
this.sftp.close(handle, () => null)
|
||||
handle.close()
|
||||
} catch (e) {
|
||||
transfer.cancel()
|
||||
throw e
|
||||
|
Loading…
x
Reference in New Issue
Block a user