mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-08 21:40:03 +00:00
fixed zmodem errors - fixes #6677, fixes #5845, fixes #5243, fixes #5132, fixes #5021, fixes #7511, fixes #7053, fixes #6917, fixes #6639, fixes #6259, fixes #6182, fixes #6122, fixes #5845, fixes #5737, fixes #5701, fixes #5609, fixes #5311, fixes #5243, fixes #5231, fixes #5132
This commit is contained in:
parent
0f0f61f432
commit
9c89bab256
@ -4,7 +4,6 @@ import { ipcMain } from 'electron'
|
|||||||
import { Application } from './app'
|
import { Application } from './app'
|
||||||
import { UTF8Splitter } from './utfSplitter'
|
import { UTF8Splitter } from './utfSplitter'
|
||||||
import { Subject, debounceTime } from 'rxjs'
|
import { Subject, debounceTime } from 'rxjs'
|
||||||
import { StringDecoder } from './stringDecoder'
|
|
||||||
|
|
||||||
class PTYDataQueue {
|
class PTYDataQueue {
|
||||||
private buffers: Buffer[] = []
|
private buffers: Buffer[] = []
|
||||||
@ -91,7 +90,6 @@ class PTYDataQueue {
|
|||||||
export class PTY {
|
export class PTY {
|
||||||
private pty: nodePTY.IPty
|
private pty: nodePTY.IPty
|
||||||
private outputQueue: PTYDataQueue
|
private outputQueue: PTYDataQueue
|
||||||
private decoder = new StringDecoder()
|
|
||||||
exited = false
|
exited = false
|
||||||
|
|
||||||
constructor (private id: string, private app: Application, ...args: any[]) {
|
constructor (private id: string, private app: Application, ...args: any[]) {
|
||||||
@ -101,7 +99,7 @@ export class PTY {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.outputQueue = new PTYDataQueue(this.pty, data => {
|
this.outputQueue = new PTYDataQueue(this.pty, data => {
|
||||||
setImmediate(() => this.emit('data', this.decoder.write(data)))
|
setImmediate(() => this.emit('data', data))
|
||||||
})
|
})
|
||||||
|
|
||||||
this.pty.onData(data => this.outputQueue.push(Buffer.from(data)))
|
this.pty.onData(data => this.outputQueue.push(Buffer.from(data)))
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
// based on Joyent's StringDecoder
|
|
||||||
// https://github.com/nodejs/string_decoder/blob/master/lib/string_decoder.js
|
|
||||||
|
|
||||||
export class StringDecoder {
|
|
||||||
lastNeed: number
|
|
||||||
lastTotal: number
|
|
||||||
lastChar: Buffer
|
|
||||||
|
|
||||||
constructor () {
|
|
||||||
this.lastNeed = 0
|
|
||||||
this.lastTotal = 0
|
|
||||||
this.lastChar = Buffer.allocUnsafe(4)
|
|
||||||
}
|
|
||||||
|
|
||||||
write (buf: Buffer): Buffer {
|
|
||||||
if (buf.length === 0) {
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
let r: Buffer|undefined = undefined
|
|
||||||
let i = 0
|
|
||||||
if (this.lastNeed) {
|
|
||||||
r = this.fillLast(buf)
|
|
||||||
if (r === undefined) {
|
|
||||||
return Buffer.from('')
|
|
||||||
}
|
|
||||||
i = this.lastNeed
|
|
||||||
this.lastNeed = 0
|
|
||||||
}
|
|
||||||
if (i < buf.length) {
|
|
||||||
return r ? Buffer.concat([r, this.text(buf, i)]) : this.text(buf, i)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// For UTF-8, a replacement character is added when ending on a partial
|
|
||||||
// character.
|
|
||||||
end (buf?: Buffer): Buffer {
|
|
||||||
const r = buf?.length ? this.write(buf) : Buffer.from('')
|
|
||||||
if (this.lastNeed) {
|
|
||||||
console.log('end', r)
|
|
||||||
return Buffer.concat([r, Buffer.from('\ufffd')])
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns all complete UTF-8 characters in a Buffer. If the Buffer ended on a
|
|
||||||
// partial character, the character's bytes are buffered until the required
|
|
||||||
// number of bytes are available.
|
|
||||||
private text (buf: Buffer, i: number) {
|
|
||||||
const total = this.utf8CheckIncomplete(buf, i)
|
|
||||||
if (!this.lastNeed) {
|
|
||||||
return buf.slice(i)
|
|
||||||
}
|
|
||||||
this.lastTotal = total
|
|
||||||
const end = buf.length - (total - this.lastNeed)
|
|
||||||
buf.copy(this.lastChar, 0, end)
|
|
||||||
return buf.slice(i, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts to complete a partial non-UTF-8 character using bytes from a Buffer
|
|
||||||
private fillLast (buf: Buffer): Buffer|undefined {
|
|
||||||
if (this.lastNeed <= buf.length) {
|
|
||||||
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, this.lastNeed)
|
|
||||||
return this.lastChar.slice(0, this.lastTotal)
|
|
||||||
}
|
|
||||||
buf.copy(this.lastChar, this.lastTotal - this.lastNeed, 0, buf.length)
|
|
||||||
this.lastNeed -= buf.length
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a
|
|
||||||
// continuation byte. If an invalid byte is detected, -2 is returned.
|
|
||||||
private utf8CheckByte (byte) {
|
|
||||||
if (byte <= 0x7F) {return 0} else if (byte >> 5 === 0x06) {return 2} else if (byte >> 4 === 0x0E) {return 3} else if (byte >> 3 === 0x1E) {return 4}
|
|
||||||
return byte >> 6 === 0x02 ? -1 : -2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks at most 3 bytes at the end of a Buffer in order to detect an
|
|
||||||
// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4)
|
|
||||||
// needed to complete the UTF-8 character (if applicable) are returned.
|
|
||||||
private utf8CheckIncomplete (buf, i) {
|
|
||||||
let j = buf.length - 1
|
|
||||||
if (j < i) {return 0}
|
|
||||||
let nb = this.utf8CheckByte(buf[j])
|
|
||||||
if (nb >= 0) {
|
|
||||||
if (nb > 0) {this.lastNeed = nb - 1}
|
|
||||||
return nb
|
|
||||||
}
|
|
||||||
if (--j < i || nb === -2) {return 0}
|
|
||||||
nb = this.utf8CheckByte(buf[j])
|
|
||||||
if (nb >= 0) {
|
|
||||||
if (nb > 0) {this.lastNeed = nb - 2}
|
|
||||||
return nb
|
|
||||||
}
|
|
||||||
if (--j < i || nb === -2) {return 0}
|
|
||||||
nb = this.utf8CheckByte(buf[j])
|
|
||||||
if (nb >= 0) {
|
|
||||||
if (nb > 0) {
|
|
||||||
if (nb === 2) {nb = 0} else {this.lastNeed = nb - 3}
|
|
||||||
}
|
|
||||||
return nb
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
@ -123,6 +123,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
|
|
||||||
protected logger: Logger
|
protected logger: Logger
|
||||||
protected output = new Subject<string>()
|
protected output = new Subject<string>()
|
||||||
|
protected binaryOutput = new Subject<Buffer>()
|
||||||
protected sessionChanged = new Subject<BaseSession|null>()
|
protected sessionChanged = new Subject<BaseSession|null>()
|
||||||
private bellPlayer: HTMLAudioElement
|
private bellPlayer: HTMLAudioElement
|
||||||
private termContainerSubscriptions = new SubscriptionContainer()
|
private termContainerSubscriptions = new SubscriptionContainer()
|
||||||
@ -153,6 +154,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
get output$ (): Observable<string> { return this.output }
|
get output$ (): Observable<string> { return this.output }
|
||||||
|
get binaryOutput$ (): Observable<Buffer> { return this.binaryOutput }
|
||||||
|
|
||||||
get resize$ (): Observable<ResizeEvent> {
|
get resize$ (): Observable<ResizeEvent> {
|
||||||
if (!this.frontend) {
|
if (!this.frontend) {
|
||||||
@ -369,7 +371,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.configure()
|
this.configure()
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.output.subscribe(() => {
|
this.binaryOutput$.subscribe(() => {
|
||||||
this.displayActivity()
|
this.displayActivity()
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
@ -564,6 +566,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.output.complete()
|
this.output.complete()
|
||||||
|
this.binaryOutput.complete()
|
||||||
this.frontendReady.complete()
|
this.frontendReady.complete()
|
||||||
|
|
||||||
super.destroy()
|
super.destroy()
|
||||||
@ -741,6 +744,12 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.attachSessionHandler(this.session.binaryOutput$, data => {
|
||||||
|
if (this.enablePassthrough) {
|
||||||
|
this.binaryOutput.next(data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (destroyOnSessionClose) {
|
if (destroyOnSessionClose) {
|
||||||
this.attachSessionHandler(this.session.closed$, () => {
|
this.attachSessionHandler(this.session.closed$, () => {
|
||||||
this.destroy()
|
this.destroy()
|
||||||
|
@ -22,7 +22,7 @@ export class SessionMiddleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SesssionMiddlewareStack extends SessionMiddleware {
|
export class SessionMiddlewareStack extends SessionMiddleware {
|
||||||
private stack: SessionMiddleware[] = []
|
private stack: SessionMiddleware[] = []
|
||||||
private subs = new SubscriptionContainer()
|
private subs = new SubscriptionContainer()
|
||||||
|
|
||||||
|
@ -4,13 +4,14 @@ import { Observable, filter, first } from 'rxjs'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { TerminalDecorator } from '../api/decorator'
|
import { TerminalDecorator } from '../api/decorator'
|
||||||
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
|
import { BaseTerminalTabComponent } from '../api/baseTerminalTab.component'
|
||||||
|
import { SessionMiddleware } from '../api/middleware'
|
||||||
import { LogService, Logger, HotkeysService, PlatformService, FileUpload } from 'tabby-core'
|
import { LogService, Logger, HotkeysService, PlatformService, FileUpload } from 'tabby-core'
|
||||||
|
|
||||||
const SPACER = ' '
|
const SPACER = ' '
|
||||||
|
|
||||||
/** @hidden */
|
class ZModemMiddleware extends SessionMiddleware {
|
||||||
@Injectable()
|
private sentry: ZModem.Sentry
|
||||||
export class ZModemDecorator extends TerminalDecorator {
|
private isActive = false
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
private activeSession: any = null
|
private activeSession: any = null
|
||||||
private cancelEvent: Observable<any>
|
private cancelEvent: Observable<any>
|
||||||
@ -21,65 +22,52 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
private platform: PlatformService,
|
private platform: PlatformService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.logger = log.create('zmodem')
|
this.cancelEvent = this.outputToSession$.pipe(filter(x => x.length === 1 && x[0] === 3))
|
||||||
this.cancelEvent = hotkeys.hotkey$.pipe(filter(x => x === 'ctrl-c'))
|
|
||||||
}
|
|
||||||
|
|
||||||
attach (terminal: BaseTerminalTabComponent): void {
|
this.logger = log.create('zmodem')
|
||||||
let isActive = false
|
this.sentry = new ZModem.Sentry({
|
||||||
const sentry = new ZModem.Sentry({
|
|
||||||
to_terminal: data => {
|
to_terminal: data => {
|
||||||
if (isActive) {
|
if (this.isActive) {
|
||||||
terminal.write(data)
|
this.outputToTerminal.next(Buffer.from(data))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sender: data => terminal.session!.feedFromTerminal(Buffer.from(data)),
|
sender: data => this.outputToSession.next(Buffer.from(data)),
|
||||||
on_detect: async detection => {
|
on_detect: async detection => {
|
||||||
try {
|
try {
|
||||||
terminal.enablePassthrough = false
|
this.isActive = true
|
||||||
isActive = true
|
await this.process(detection)
|
||||||
await this.process(terminal, detection)
|
|
||||||
} finally {
|
} finally {
|
||||||
terminal.enablePassthrough = true
|
this.isActive = false
|
||||||
isActive = false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
on_retract: () => {
|
on_retract: () => {
|
||||||
this.showMessage(terminal, 'transfer cancelled')
|
this.showMessage('transfer cancelled')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
|
||||||
this.attachToSession(sentry, terminal)
|
|
||||||
this.subscribeUntilDetached(terminal, terminal.sessionChanged$.subscribe(() => {
|
|
||||||
this.attachToSession(sentry, terminal)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private attachToSession (sentry, terminal) {
|
feedFromSession (data: Buffer): void {
|
||||||
if (!terminal.session) {
|
const chunkSize = 1024
|
||||||
return
|
for (let i = 0; i <= Math.floor(data.length / chunkSize); i++) {
|
||||||
}
|
try {
|
||||||
this.subscribeUntilDetached(terminal, terminal.session.binaryOutput$.subscribe(data => {
|
this.sentry.consume(Buffer.from(data.slice(i * chunkSize, (i + 1) * chunkSize)))
|
||||||
const chunkSize = 1024
|
} catch (e) {
|
||||||
for (let i = 0; i <= Math.floor(data.length / chunkSize); i++) {
|
this.showMessage(colors.bgRed.black(' Error ') + ' ' + e)
|
||||||
try {
|
this.logger.error('protocol error', e)
|
||||||
sentry.consume(Buffer.from(data.slice(i * chunkSize, (i + 1) * chunkSize)))
|
this.activeSession.abort()
|
||||||
} catch (e) {
|
this.activeSession = null
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Error ') + ' ' + e)
|
this.isActive = false
|
||||||
this.logger.error('protocol error', e)
|
return
|
||||||
this.activeSession.abort()
|
|
||||||
this.activeSession = null
|
|
||||||
terminal.enablePassthrough = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
if (!this.isActive) {
|
||||||
|
this.outputToTerminal.next(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async process (terminal, detection): Promise<void> {
|
private async process (detection): Promise<void> {
|
||||||
this.showMessage(terminal, colors.bgBlue.black(' ZMODEM ') + ' Session started')
|
this.showMessage(colors.bgBlue.black(' ZMODEM ') + ' Session started')
|
||||||
this.showMessage(terminal, '------------------------')
|
this.showMessage('------------------------')
|
||||||
|
|
||||||
const zsession = detection.confirm()
|
const zsession = detection.confirm()
|
||||||
this.activeSession = zsession
|
this.activeSession = zsession
|
||||||
@ -90,7 +78,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
let filesRemaining = transfers.length
|
let filesRemaining = transfers.length
|
||||||
let sizeRemaining = transfers.reduce((a, b) => a + b.getSize(), 0)
|
let sizeRemaining = transfers.reduce((a, b) => a + b.getSize(), 0)
|
||||||
for (const transfer of transfers) {
|
for (const transfer of transfers) {
|
||||||
await this.sendFile(terminal, zsession, transfer, filesRemaining, sizeRemaining)
|
await this.sendFile(zsession, transfer, filesRemaining, sizeRemaining)
|
||||||
filesRemaining--
|
filesRemaining--
|
||||||
sizeRemaining -= transfer.getSize()
|
sizeRemaining -= transfer.getSize()
|
||||||
}
|
}
|
||||||
@ -98,7 +86,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
await zsession.close()
|
await zsession.close()
|
||||||
} else {
|
} else {
|
||||||
zsession.on('offer', xfer => {
|
zsession.on('offer', xfer => {
|
||||||
this.receiveFile(terminal, xfer, zsession)
|
this.receiveFile(xfer, zsession)
|
||||||
})
|
})
|
||||||
|
|
||||||
zsession.start()
|
zsession.start()
|
||||||
@ -108,29 +96,27 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async receiveFile (terminal, xfer, zsession) {
|
private async receiveFile (xfer, zsession) {
|
||||||
const details: {
|
const details: {
|
||||||
name: string,
|
name: string,
|
||||||
size: number,
|
size: number,
|
||||||
} = xfer.get_details()
|
} = xfer.get_details()
|
||||||
this.showMessage(terminal, colors.bgYellow.black(' Offered ') + ' ' + details.name, true)
|
this.showMessage(colors.bgYellow.black(' Offered ') + ' ' + details.name, true)
|
||||||
this.logger.info('offered', xfer)
|
this.logger.info('offered', xfer)
|
||||||
|
|
||||||
const transfer = await this.platform.startDownload(details.name, 0o644, details.size)
|
const transfer = await this.platform.startDownload(details.name, 0o644, details.size)
|
||||||
if (!transfer) {
|
if (!transfer) {
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Rejected ') + ' ' + details.name)
|
this.showMessage(colors.bgRed.black(' Rejected ') + ' ' + details.name)
|
||||||
xfer.skip()
|
xfer.skip()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let canceled = false
|
let canceled = false
|
||||||
const cancelSubscription = this.cancelEvent.subscribe(() => {
|
const cancelSubscription = this.cancelEvent.subscribe(() => {
|
||||||
if (terminal.hasFocus) {
|
try {
|
||||||
try {
|
zsession._skip()
|
||||||
zsession._skip()
|
} catch {}
|
||||||
} catch {}
|
canceled = true
|
||||||
canceled = true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -141,7 +127,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
transfer.write(Buffer.from(chunk))
|
transfer.write(Buffer.from(chunk))
|
||||||
this.showMessage(terminal, colors.bgYellow.black(' ' + Math.round(100 * transfer.getCompletedBytes() / details.size).toString().padStart(3, ' ') + '% ') + ' ' + details.name, true)
|
this.showMessage(colors.bgYellow.black(' ' + Math.round(100 * transfer.getCompletedBytes() / details.size).toString().padStart(3, ' ') + '% ') + ' ' + details.name, true)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
this.cancelEvent.pipe(first()).toPromise(),
|
this.cancelEvent.pipe(first()).toPromise(),
|
||||||
@ -150,19 +136,19 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
transfer.cancel()
|
transfer.cancel()
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + details.name)
|
this.showMessage(colors.bgRed.black(' Canceled ') + ' ' + details.name)
|
||||||
} else {
|
} else {
|
||||||
transfer.close()
|
transfer.close()
|
||||||
this.showMessage(terminal, colors.bgGreen.black(' Received ') + ' ' + details.name)
|
this.showMessage(colors.bgGreen.black(' Received ') + ' ' + details.name)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Error ') + ' ' + details.name)
|
this.showMessage(colors.bgRed.black(' Error ') + ' ' + details.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelSubscription.unsubscribe()
|
cancelSubscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendFile (terminal, zsession, transfer: FileUpload, filesRemaining, sizeRemaining) {
|
private async sendFile (zsession, transfer: FileUpload, filesRemaining, sizeRemaining) {
|
||||||
const offer = {
|
const offer = {
|
||||||
name: transfer.getName(),
|
name: transfer.getName(),
|
||||||
size: transfer.getSize(),
|
size: transfer.getSize(),
|
||||||
@ -171,15 +157,13 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
bytes_remaining: sizeRemaining,
|
bytes_remaining: sizeRemaining,
|
||||||
}
|
}
|
||||||
this.logger.info('offering', offer)
|
this.logger.info('offering', offer)
|
||||||
this.showMessage(terminal, colors.bgYellow.black(' Offered ') + ' ' + offer.name, true)
|
this.showMessage(colors.bgYellow.black(' Offered ') + ' ' + offer.name, true)
|
||||||
|
|
||||||
const xfer = await zsession.send_offer(offer)
|
const xfer = await zsession.send_offer(offer)
|
||||||
if (xfer) {
|
if (xfer) {
|
||||||
let canceled = false
|
let canceled = false
|
||||||
const cancelSubscription = this.cancelEvent.subscribe(() => {
|
const cancelSubscription = this.cancelEvent.subscribe(() => {
|
||||||
if (terminal.hasFocus) {
|
canceled = true
|
||||||
canceled = true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -190,7 +174,7 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await xfer.send(chunk)
|
await xfer.send(chunk)
|
||||||
this.showMessage(terminal, colors.bgYellow.black(' ' + Math.round(100 * transfer.getCompletedBytes() / offer.size).toString().padStart(3, ' ') + '% ') + offer.name, true)
|
this.showMessage(colors.bgYellow.black(' ' + Math.round(100 * transfer.getCompletedBytes() / offer.size).toString().padStart(3, ' ') + '% ') + offer.name, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
@ -204,23 +188,51 @@ export class ZModemDecorator extends TerminalDecorator {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (canceled) {
|
if (canceled) {
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Canceled ') + ' ' + offer.name)
|
this.showMessage(colors.bgRed.black(' Canceled ') + ' ' + offer.name)
|
||||||
} else {
|
} else {
|
||||||
this.showMessage(terminal, colors.bgGreen.black(' Sent ') + ' ' + offer.name)
|
this.showMessage(colors.bgGreen.black(' Sent ') + ' ' + offer.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelSubscription.unsubscribe()
|
cancelSubscription.unsubscribe()
|
||||||
} else {
|
} else {
|
||||||
transfer.cancel()
|
transfer.cancel()
|
||||||
this.showMessage(terminal, colors.bgRed.black(' Rejected ') + ' ' + offer.name)
|
this.showMessage(colors.bgRed.black(' Rejected ') + ' ' + offer.name)
|
||||||
this.logger.warn('rejected by the other side')
|
this.logger.warn('rejected by the other side')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showMessage (terminal, msg: string, overwrite = false) {
|
private showMessage (msg: string, overwrite = false) {
|
||||||
terminal.write(Buffer.from(`\r${msg}${SPACER}`))
|
this.outputToTerminal.next(Buffer.from(`\r${msg}${SPACER}`))
|
||||||
if (!overwrite) {
|
if (!overwrite) {
|
||||||
terminal.write(Buffer.from('\r\n'))
|
this.outputToTerminal.next(Buffer.from('\r\n'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class ZModemDecorator extends TerminalDecorator {
|
||||||
|
constructor (
|
||||||
|
private log: LogService,
|
||||||
|
private hotkeys: HotkeysService,
|
||||||
|
private platform: PlatformService,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (terminal: BaseTerminalTabComponent): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.attachToSession(terminal)
|
||||||
|
this.subscribeUntilDetached(terminal, terminal.sessionChanged$.subscribe(() => {
|
||||||
|
this.attachToSession(terminal)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private attachToSession (terminal: BaseTerminalTabComponent) {
|
||||||
|
if (!terminal.session) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
terminal.session.middleware.unshift(new ZModemMiddleware(this.log, this.hotkeys, this.platform))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { Observable, Subject } from 'rxjs'
|
|||||||
import { Logger } from 'tabby-core'
|
import { Logger } from 'tabby-core'
|
||||||
import { LoginScriptProcessor, LoginScriptsOptions } from './middleware/loginScriptProcessing'
|
import { LoginScriptProcessor, LoginScriptsOptions } from './middleware/loginScriptProcessing'
|
||||||
import { OSCProcessor } from './middleware/oscProcessing'
|
import { OSCProcessor } from './middleware/oscProcessing'
|
||||||
import { SesssionMiddlewareStack } from './api/middleware'
|
import { SessionMiddlewareStack } from './api/middleware'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A session object for a [[BaseTerminalTabComponent]]
|
* A session object for a [[BaseTerminalTabComponent]]
|
||||||
@ -11,8 +11,8 @@ import { SesssionMiddlewareStack } from './api/middleware'
|
|||||||
export abstract class BaseSession {
|
export abstract class BaseSession {
|
||||||
open: boolean
|
open: boolean
|
||||||
truePID?: number
|
truePID?: number
|
||||||
oscProcessor = new OSCProcessor()
|
readonly oscProcessor = new OSCProcessor()
|
||||||
protected readonly middleware = new SesssionMiddlewareStack()
|
readonly middleware = new SessionMiddlewareStack()
|
||||||
protected output = new Subject<string>()
|
protected output = new Subject<string>()
|
||||||
protected binaryOutput = new Subject<Buffer>()
|
protected binaryOutput = new Subject<Buffer>()
|
||||||
protected closed = new Subject<void>()
|
protected closed = new Subject<void>()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user