mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-21 20:09:53 +00:00
sftp wip
This commit is contained in:
parent
c8d5b7ab61
commit
1f2bf12ed7
@ -1,12 +1,9 @@
|
|||||||
import * as C from 'constants'
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { posix as posixPath } from 'path'
|
import { posix as posixPath } from 'path'
|
||||||
import { Injector, NgZone } from '@angular/core'
|
import { Injector } from '@angular/core'
|
||||||
import { FileDownload, FileUpload, Logger, LogService, wrapPromise } from 'tabby-core'
|
import { FileDownload, FileUpload, Logger, LogService } from 'tabby-core'
|
||||||
import { SFTPWrapper } from 'ssh2'
|
import * as russh from 'russh'
|
||||||
import { promisify } from 'util'
|
|
||||||
|
|
||||||
import type { FileEntry, Stats } from 'ssh2-streams'
|
|
||||||
|
|
||||||
export interface SFTPFile {
|
export interface SFTPFile {
|
||||||
name: string
|
name: string
|
||||||
@ -21,64 +18,65 @@ export interface SFTPFile {
|
|||||||
export class SFTPFileHandle {
|
export class SFTPFileHandle {
|
||||||
position = 0
|
position = 0
|
||||||
|
|
||||||
constructor (
|
// constructor (
|
||||||
private sftp: SFTPWrapper,
|
// private sftp: russh.SFTP,
|
||||||
private handle: Buffer,
|
// private handle: Buffer,
|
||||||
private zone: NgZone,
|
// private zone: NgZone,
|
||||||
) { }
|
// ) { }
|
||||||
|
|
||||||
read (): Promise<Buffer> {
|
read (): Promise<Buffer> {
|
||||||
const buffer = Buffer.alloc(256 * 1024)
|
throw new Error('Not implemented')
|
||||||
return wrapPromise(this.zone, new Promise((resolve, reject) => {
|
// const buffer = Buffer.alloc(256 * 1024)
|
||||||
while (true) {
|
// return wrapPromise(this.zone, new Promise((resolve, reject) => {
|
||||||
const wait = this.sftp.read(this.handle, buffer, 0, buffer.length, this.position, (err, read) => {
|
// while (true) {
|
||||||
if (err) {
|
// const wait = this.sftp.read(this.handle, buffer, 0, buffer.length, this.position, (err, read) => {
|
||||||
reject(err)
|
// if (err) {
|
||||||
return
|
// reject(err)
|
||||||
}
|
// return
|
||||||
this.position += read
|
// }
|
||||||
resolve(buffer.slice(0, read))
|
// this.position += read
|
||||||
})
|
// resolve(buffer.slice(0, read))
|
||||||
if (!wait) {
|
// })
|
||||||
break
|
// if (!wait) {
|
||||||
}
|
// break
|
||||||
}
|
// }
|
||||||
}))
|
// }
|
||||||
|
// }))
|
||||||
}
|
}
|
||||||
|
|
||||||
write (chunk: Buffer): Promise<void> {
|
write (chunk: Buffer): Promise<void> {
|
||||||
return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
|
throw new Error('Not implemented')
|
||||||
while (true) {
|
// return wrapPromise(this.zone, new Promise<void>((resolve, reject) => {
|
||||||
const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
|
// while (true) {
|
||||||
if (err) {
|
// const wait = this.sftp.write(this.handle, chunk, 0, chunk.length, this.position, err => {
|
||||||
reject(err)
|
// if (err) {
|
||||||
return
|
// reject(err)
|
||||||
}
|
// return
|
||||||
this.position += chunk.length
|
// }
|
||||||
resolve()
|
// this.position += chunk.length
|
||||||
})
|
// resolve()
|
||||||
if (!wait) {
|
// })
|
||||||
break
|
// if (!wait) {
|
||||||
}
|
// break
|
||||||
}
|
// }
|
||||||
}))
|
// }
|
||||||
|
// }))
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): Promise<void> {
|
close (): Promise<void> {
|
||||||
return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
|
throw new Error('Not implemented')
|
||||||
|
// return wrapPromise(this.zone, promisify(this.sftp.close.bind(this.sftp))(this.handle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SFTPSession {
|
export class SFTPSession {
|
||||||
get closed$ (): Observable<void> { return this.closed }
|
get closed$ (): Observable<void> { return this.closed }
|
||||||
private closed = new Subject<void>()
|
private closed = new Subject<void>()
|
||||||
private zone: NgZone
|
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor (private sftp: SFTPWrapper, injector: Injector) {
|
constructor (private sftp: russh.SFTP, injector: Injector) {
|
||||||
this.zone = injector.get(NgZone)
|
|
||||||
this.logger = injector.get(LogService).create('sftp')
|
this.logger = injector.get(LogService).create('sftp')
|
||||||
sftp.on('close', () => {
|
sftp.closed$.subscribe(() => {
|
||||||
this.closed.next()
|
this.closed.next()
|
||||||
this.closed.complete()
|
this.closed.complete()
|
||||||
})
|
})
|
||||||
@ -86,115 +84,119 @@ export class SFTPSession {
|
|||||||
|
|
||||||
async readdir (p: string): Promise<SFTPFile[]> {
|
async readdir (p: string): Promise<SFTPFile[]> {
|
||||||
this.logger.debug('readdir', p)
|
this.logger.debug('readdir', p)
|
||||||
const entries = await wrapPromise(this.zone, promisify<FileEntry[]>(f => this.sftp.readdir(p, f))())
|
const entries = await this.sftp.readDirectory(p)
|
||||||
return entries.map(entry => this._makeFile(
|
return entries.map(entry => this._makeFile(
|
||||||
posixPath.join(p, entry.filename), entry,
|
posixPath.join(p, entry.name), entry,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
readlink (p: string): Promise<string> {
|
readlink (p: string): Promise<string> {
|
||||||
this.logger.debug('readlink', p)
|
throw new Error('Not implemented')
|
||||||
return wrapPromise(this.zone, promisify<string>(f => this.sftp.readlink(p, f))())
|
// this.logger.debug('readlink', p)
|
||||||
|
// return wrapPromise(this.zone, promisify<string>(f => this.sftp.readlink(p, f))())
|
||||||
}
|
}
|
||||||
|
|
||||||
async stat (p: string): Promise<SFTPFile> {
|
async stat (p: string): Promise<SFTPFile> {
|
||||||
this.logger.debug('stat', p)
|
throw new Error('Not implemented')
|
||||||
const stats = await wrapPromise(this.zone, promisify<Stats>(f => this.sftp.stat(p, f))())
|
// this.logger.debug('stat', p)
|
||||||
return {
|
// const stats = await wrapPromise(this.zone, promisify<Stats>(f => this.sftp.stat(p, f))())
|
||||||
name: posixPath.basename(p),
|
// return {
|
||||||
fullPath: p,
|
// name: posixPath.basename(p),
|
||||||
isDirectory: stats.isDirectory(),
|
// fullPath: p,
|
||||||
isSymlink: stats.isSymbolicLink(),
|
// isDirectory: stats.isDirectory(),
|
||||||
mode: stats.mode,
|
// isSymlink: stats.isSymbolicLink(),
|
||||||
size: stats.size,
|
// mode: stats.mode,
|
||||||
modified: new Date(stats.mtime * 1000),
|
// size: stats.size,
|
||||||
}
|
// modified: new Date(stats.mtime * 1000),
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async open (p: string, mode: string): Promise<SFTPFileHandle> {
|
async open (p: string, mode: string): Promise<SFTPFileHandle> {
|
||||||
this.logger.debug('open', p)
|
throw new Error('Not implemented')
|
||||||
const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
|
// this.logger.debug('open', p)
|
||||||
return new SFTPFileHandle(this.sftp, handle, this.zone)
|
// const handle = await wrapPromise(this.zone, promisify<Buffer>(f => this.sftp.open(p, mode, f))())
|
||||||
|
// return new SFTPFileHandle(this.sftp, handle, this.zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
async rmdir (p: string): Promise<void> {
|
async rmdir (p: string): Promise<void> {
|
||||||
this.logger.debug('rmdir', p)
|
await this.sftp.removeDirectory(p)
|
||||||
await promisify((f: any) => this.sftp.rmdir(p, f))()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async mkdir (p: string): Promise<void> {
|
async mkdir (p: string): Promise<void> {
|
||||||
this.logger.debug('mkdir', p)
|
await this.sftp.createDirectory(p)
|
||||||
await promisify((f: any) => this.sftp.mkdir(p, f))()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rename (oldPath: string, newPath: string): Promise<void> {
|
async rename (oldPath: string, newPath: string): Promise<void> {
|
||||||
this.logger.debug('rename', oldPath, newPath)
|
throw new Error('Not implemented')
|
||||||
await promisify((f: any) => this.sftp.rename(oldPath, newPath, f))()
|
// this.logger.debug('rename', oldPath, newPath)
|
||||||
|
// await promisify((f: any) => this.sftp.rename(oldPath, newPath, f))()
|
||||||
}
|
}
|
||||||
|
|
||||||
async unlink (p: string): Promise<void> {
|
async unlink (p: string): Promise<void> {
|
||||||
this.logger.debug('unlink', p)
|
await this.sftp.removeFile(p)
|
||||||
await promisify((f: any) => this.sftp.unlink(p, f))()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async chmod (p: string, mode: string|number): Promise<void> {
|
async chmod (p: string, mode: string|number): Promise<void> {
|
||||||
this.logger.debug('chmod', p, mode)
|
throw new Error('Not implemented')
|
||||||
await promisify((f: any) => this.sftp.chmod(p, mode, f))()
|
// this.logger.debug('chmod', p, mode)
|
||||||
|
// await promisify((f: any) => this.sftp.chmod(p, mode, f))()
|
||||||
}
|
}
|
||||||
|
|
||||||
async upload (path: string, transfer: FileUpload): Promise<void> {
|
async upload (path: string, transfer: FileUpload): Promise<void> {
|
||||||
this.logger.info('Uploading into', path)
|
throw new Error('Not implemented')
|
||||||
const tempPath = path + '.tabby-upload'
|
// this.logger.info('Uploading into', path)
|
||||||
try {
|
// const tempPath = path + '.tabby-upload'
|
||||||
const handle = await this.open(tempPath, 'w')
|
// try {
|
||||||
while (true) {
|
// const handle = await this.open(tempPath, 'w')
|
||||||
const chunk = await transfer.read()
|
// while (true) {
|
||||||
if (!chunk.length) {
|
// const chunk = await transfer.read()
|
||||||
break
|
// if (!chunk.length) {
|
||||||
}
|
// break
|
||||||
await handle.write(chunk)
|
// }
|
||||||
}
|
// await handle.write(chunk)
|
||||||
handle.close()
|
// }
|
||||||
try {
|
// handle.close()
|
||||||
await this.unlink(path)
|
// try {
|
||||||
} catch { }
|
// await this.unlink(path)
|
||||||
await this.rename(tempPath, path)
|
// } catch { }
|
||||||
transfer.close()
|
// await this.rename(tempPath, path)
|
||||||
} catch (e) {
|
// transfer.close()
|
||||||
transfer.cancel()
|
// } catch (e) {
|
||||||
this.unlink(tempPath)
|
// transfer.cancel()
|
||||||
throw e
|
// this.unlink(tempPath)
|
||||||
}
|
// throw e
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async download (path: string, transfer: FileDownload): Promise<void> {
|
async download (path: string, transfer: FileDownload): Promise<void> {
|
||||||
this.logger.info('Downloading', path)
|
throw new Error('Not implemented')
|
||||||
try {
|
// this.logger.info('Downloading', path)
|
||||||
const handle = await this.open(path, 'r')
|
// try {
|
||||||
while (true) {
|
// const handle = await this.open(path, 'r')
|
||||||
const chunk = await handle.read()
|
// while (true) {
|
||||||
if (!chunk.length) {
|
// const chunk = await handle.read()
|
||||||
break
|
// if (!chunk.length) {
|
||||||
}
|
// break
|
||||||
await transfer.write(chunk)
|
// }
|
||||||
}
|
// await transfer.write(chunk)
|
||||||
transfer.close()
|
// }
|
||||||
handle.close()
|
// transfer.close()
|
||||||
} catch (e) {
|
// handle.close()
|
||||||
transfer.cancel()
|
// } catch (e) {
|
||||||
throw e
|
// transfer.cancel()
|
||||||
}
|
// throw e
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private _makeFile (p: string, entry: FileEntry): SFTPFile {
|
private _makeFile (p: string, entry: russh.SFTPDirectoryEntry): SFTPFile {
|
||||||
return {
|
return {
|
||||||
fullPath: p,
|
fullPath: p,
|
||||||
name: posixPath.basename(p),
|
name: posixPath.basename(p),
|
||||||
isDirectory: (entry.attrs.mode & C.S_IFDIR) === C.S_IFDIR,
|
isDirectory: entry.type === russh.SFTPFileType.Directory,
|
||||||
isSymlink: (entry.attrs.mode & C.S_IFLNK) === C.S_IFLNK,
|
isSymlink: entry.type === russh.SFTPFileType.Symlink,
|
||||||
mode: entry.attrs.mode,
|
mode: entry.permissions ?? 0,
|
||||||
size: entry.attrs.size,
|
size: entry.objectSize,
|
||||||
modified: new Date(entry.attrs.mtime * 1000),
|
modified: new Date((entry.mtime ?? 0) * 1000),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ export class KeyboardInteractivePrompt {
|
|||||||
export class SSHSession {
|
export class SSHSession {
|
||||||
shell?: russh.Channel
|
shell?: russh.Channel
|
||||||
ssh: russh.SSHClient|russh.AuthenticatedSSHClient
|
ssh: russh.SSHClient|russh.AuthenticatedSSHClient
|
||||||
// sftp?: SFTPWrapper
|
sftp?: russh.SFTP
|
||||||
forwardedPorts: ForwardedPort[] = []
|
forwardedPorts: ForwardedPort[] = []
|
||||||
jumpStream: any
|
jumpStream: any
|
||||||
proxyCommandStream: SSHProxyStream|null = null
|
proxyCommandStream: SSHProxyStream|null = null
|
||||||
@ -100,7 +100,7 @@ export class SSHSession {
|
|||||||
private privateKeyImporters: AutoPrivateKeyLocator[]
|
private privateKeyImporters: AutoPrivateKeyLocator[]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
injector: Injector,
|
private injector: Injector,
|
||||||
public profile: SSHProfile,
|
public profile: SSHProfile,
|
||||||
) {
|
) {
|
||||||
this.logger = injector.get(LogService).create(`ssh-${profile.options.host}-${profile.options.port}`)
|
this.logger = injector.get(LogService).create(`ssh-${profile.options.host}-${profile.options.port}`)
|
||||||
@ -189,11 +189,13 @@ export class SSHSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openSFTP (): Promise<SFTPSession> {
|
async openSFTP (): Promise<SFTPSession> {
|
||||||
throw new Error('Not implemented')
|
if (!(this.ssh instanceof russh.AuthenticatedSSHClient)) {
|
||||||
// if (!this.sftp) {
|
throw new Error('Cannot open SFTP session before auth')
|
||||||
// this.sftp = await wrapPromise(this.zone, promisify<SFTPWrapper>(f => this.ssh.sftp(f))())
|
}
|
||||||
// }
|
if (!this.sftp) {
|
||||||
// return new SFTPSession(this.sftp, this.injector)
|
this.sftp = await this.ssh.openSFTPChannel()
|
||||||
|
}
|
||||||
|
return new SFTPSession(this.sftp, this.injector)
|
||||||
}
|
}
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user