selectable persistence providers

This commit is contained in:
Eugene Pankov 2017-07-31 13:52:32 +02:00
parent 514fdbfb6a
commit 1c62f3074c
8 changed files with 70 additions and 46 deletions

View File

@ -28,6 +28,10 @@ export interface SessionOptions {
} }
export abstract class SessionPersistenceProvider { export abstract class SessionPersistenceProvider {
abstract id: string
abstract displayName: string
abstract isAvailable (): boolean
abstract async attachSession (recoveryId: any): Promise<SessionOptions> abstract async attachSession (recoveryId: any): Promise<SessionOptions>
abstract async startSession (options: SessionOptions): Promise<any> abstract async startSession (options: SessionOptions): Promise<any>
abstract async terminateSession (recoveryId: string): Promise<void> abstract async terminateSession (recoveryId: string): Promise<void>

View File

@ -259,3 +259,15 @@
[value]='"audible"' [value]='"audible"'
) )
| Audible | Audible
.form-group
label Session persistence
select.form-control(
'[(ngModel)]'='config.store.terminal.persistence',
(ngModelChange)='config.save()',
)
option([ngValue]='null') Off
option(
*ngFor='let provider of persistenceProviders',
[ngValue]='provider.id'
) {{provider.displayName}}

View File

@ -5,7 +5,7 @@ const fontManager = require('font-manager')
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ConfigService, HostAppService, Platform } from 'terminus-core' import { ConfigService, HostAppService, Platform } from 'terminus-core'
import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider } from '../api' import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider, SessionPersistenceProvider } from '../api'
@Component({ @Component({
template: require('./terminalSettingsTab.component.pug'), template: require('./terminalSettingsTab.component.pug'),
@ -14,6 +14,7 @@ import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvide
export class TerminalSettingsTabComponent { export class TerminalSettingsTabComponent {
fonts: string[] = [] fonts: string[] = []
shells: IShell[] = [] shells: IShell[] = []
persistenceProviders: SessionPersistenceProvider[]
colorSchemes: ITerminalColorScheme[] = [] colorSchemes: ITerminalColorScheme[] = []
equalComparator = equal equalComparator = equal
editingColorScheme: ITerminalColorScheme editingColorScheme: ITerminalColorScheme
@ -24,7 +25,10 @@ export class TerminalSettingsTabComponent {
private hostApp: HostAppService, private hostApp: HostAppService,
@Inject(ShellProvider) private shellProviders: ShellProvider[], @Inject(ShellProvider) private shellProviders: ShellProvider[],
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[], @Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
) { } @Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
) {
this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable())
}
async ngOnInit () { async ngOnInit () {
if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) { if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) {

View File

@ -43,6 +43,7 @@ export class TerminalConfigProvider extends ConfigProvider {
terminal: { terminal: {
font: 'Menlo', font: 'Menlo',
shell: '~default-shell~', shell: '~default-shell~',
persistence: 'screen',
}, },
hotkeys: { hotkeys: {
'copy': [ 'copy': [
@ -74,6 +75,7 @@ export class TerminalConfigProvider extends ConfigProvider {
terminal: { terminal: {
font: 'Consolas', font: 'Consolas',
shell: '~clink~', shell: '~clink~',
persistence: null,
}, },
hotkeys: { hotkeys: {
'copy': [ 'copy': [
@ -104,6 +106,7 @@ export class TerminalConfigProvider extends ConfigProvider {
terminal: { terminal: {
font: 'Liberation Mono', font: 'Liberation Mono',
shell: '~default-shell~', shell: '~default-shell~',
persistence: 'screen',
}, },
hotkeys: { hotkeys: {
'copy': [ 'copy': [

View File

@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HostAppService, Platform, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core' import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider } from 'terminus-core'
import { SettingsTabProvider } from 'terminus-settings' import { SettingsTabProvider } from 'terminus-settings'
import { TerminalTabComponent } from './components/terminalTab.component' import { TerminalTabComponent } from './components/terminalTab.component'
@ -12,8 +12,8 @@ import { ColorPickerComponent } from './components/colorPicker.component'
import { SessionsService } from './services/sessions.service' import { SessionsService } from './services/sessions.service'
import { ScreenPersistenceProvider } from './persistenceProviders' import { ScreenPersistenceProvider } from './persistence/screen'
import { TMuxPersistenceProvider } from './tmux' import { TMuxPersistenceProvider } from './persistence/tmux'
import { ButtonProvider } from './buttonProvider' import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider' import { RecoveryProvider } from './recoveryProvider'
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api' import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api'
@ -42,36 +42,17 @@ import { hterm } from './hterm'
], ],
providers: [ providers: [
SessionsService, SessionsService,
ScreenPersistenceProvider,
TMuxPersistenceProvider,
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
{
provide: SessionPersistenceProvider,
useFactory: (
hostApp: HostAppService,
screen: ScreenPersistenceProvider,
tmux: TMuxPersistenceProvider,
) => {
if (hostApp.platform === Platform.Windows) {
return null
} else {
if (tmux.isAvailable()) {
tmux.init()
return tmux
} else {
return screen
}
}
},
deps: [HostAppService, ScreenPersistenceProvider, TMuxPersistenceProvider],
},
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true }, { provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true }, { provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true },
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true }, { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true }, { provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true }, { provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true }, { provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true }, { provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },

View File

@ -1,11 +1,11 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { exec, spawn } from 'mz/child_process' import { exec, spawn } from 'mz/child_process'
import { exec as execCallback } from 'child_process' import { exec as execAsync, execFileSync } from 'child_process'
import { AsyncSubject } from 'rxjs' import { AsyncSubject } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Logger, LogService } from 'terminus-core' import { Logger, LogService } from 'terminus-core'
import { SessionOptions, SessionPersistenceProvider } from './api' import { SessionOptions, SessionPersistenceProvider } from '../api'
declare function delay (ms: number): Promise<void> declare function delay (ms: number): Promise<void>
@ -29,6 +29,8 @@ async function listProcesses (): Promise<IChildProcess[]> {
@Injectable() @Injectable()
export class ScreenPersistenceProvider extends SessionPersistenceProvider { export class ScreenPersistenceProvider extends SessionPersistenceProvider {
id = 'screen'
displayName = 'GNU Screen'
private logger: Logger private logger: Logger
constructor ( constructor (
@ -38,9 +40,18 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
this.logger = log.create('main') this.logger = log.create('main')
} }
isAvailable () {
try {
execFileSync('sh', ['-c', 'which screen'])
return true
} catch (_) {
return false
}
}
async attachSession (recoveryId: any): Promise<SessionOptions> { async attachSession (recoveryId: any): Promise<SessionOptions> {
let lines = await new Promise<string[]>(resolve => { let lines = await new Promise<string[]>(resolve => {
execCallback('screen -list', (_err, stdout) => { execAsync('screen -list', (_err, stdout) => {
// returns an error code on macOS // returns an error code on macOS
resolve(stdout.split('\n')) resolve(stdout.split('\n'))
}) })

View File

@ -3,7 +3,7 @@ import { execFileSync } from 'child_process'
import * as AsyncLock from 'async-lock' import * as AsyncLock from 'async-lock'
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs' import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
import * as childProcess from 'child_process' import * as childProcess from 'child_process'
import { SessionOptions, SessionPersistenceProvider } from './api' import { SessionOptions, SessionPersistenceProvider } from '../api'
const TMUX_CONFIG = ` const TMUX_CONFIG = `
set -g status off set -g status off
@ -174,10 +174,15 @@ export class TMux {
@Injectable() @Injectable()
export class TMuxPersistenceProvider extends SessionPersistenceProvider { export class TMuxPersistenceProvider extends SessionPersistenceProvider {
id = 'tmux'
displayName = 'Tmux'
private tmux: TMux private tmux: TMux
constructor () { constructor () {
super() super()
if (this.isAvailable()) {
this.tmux = new TMux()
}
} }
isAvailable (): boolean { isAvailable (): boolean {
@ -189,10 +194,6 @@ export class TMuxPersistenceProvider extends SessionPersistenceProvider {
} }
} }
init () {
this.tmux = new TMux()
}
async attachSession (recoveryId: any): Promise<SessionOptions> { async attachSession (recoveryId: any): Promise<SessionOptions> {
let sessions = await this.tmux.list() let sessions = await this.tmux.list()
if (!sessions.includes(recoveryId)) { if (!sessions.includes(recoveryId)) {

View File

@ -3,8 +3,8 @@ const psNode = require('ps-node')
let nodePTY let nodePTY
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { Logger, LogService, ElectronService } from 'terminus-core' import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core'
import { exec } from 'mz/child_process' import { exec } from 'mz/child_process'
import { SessionOptions, SessionPersistenceProvider } from '../api' import { SessionOptions, SessionPersistenceProvider } from '../api'
@ -178,7 +178,8 @@ export class SessionsService {
private lastID = 0 private lastID = 0
constructor ( constructor (
private persistence: SessionPersistenceProvider, @Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
private config: ConfigService,
electron: ElectronService, electron: ElectronService,
log: LogService, log: LogService,
) { ) {
@ -187,9 +188,10 @@ export class SessionsService {
} }
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> { async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
if (this.persistence) { let persistence = this.getPersistence()
let recoveryId = await this.persistence.startSession(options) if (persistence) {
options = await this.persistence.attachSession(recoveryId) let recoveryId = await persistence.startSession(options)
options = await persistence.attachSession(recoveryId)
} }
return options return options
} }
@ -198,10 +200,11 @@ export class SessionsService {
this.lastID++ this.lastID++
options.name = `session-${this.lastID}` options.name = `session-${this.lastID}`
let session = new Session(options) let session = new Session(options)
let persistence = this.getPersistence()
session.destroyed$.first().subscribe(() => { session.destroyed$.first().subscribe(() => {
delete this.sessions[session.name] delete this.sessions[session.name]
if (this.persistence) { if (persistence) {
this.persistence.terminateSession(session.recoveryId) persistence.terminateSession(session.recoveryId)
} }
}) })
this.sessions[session.name] = session this.sessions[session.name] = session
@ -209,9 +212,14 @@ export class SessionsService {
} }
async recover (recoveryId: string): Promise<SessionOptions> { async recover (recoveryId: string): Promise<SessionOptions> {
if (!this.persistence) { let persistence = this.getPersistence()
return null if (persistence) {
return await persistence.attachSession(recoveryId)
} }
return await this.persistence.attachSession(recoveryId) return null
}
private getPersistence (): SessionPersistenceProvider {
return this.persistenceProviders.find(x => x.id === this.config.store.terminal.persistence) || null
} }
} }