From 1c62f3074c30d365a883ceb9fc2b2e9f580a8630 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Mon, 31 Jul 2017 13:52:32 +0200 Subject: [PATCH] selectable persistence providers --- terminus-terminal/src/api.ts | 4 +++ .../terminalSettingsTab.component.pug | 12 +++++++ .../terminalSettingsTab.component.ts | 8 +++-- terminus-terminal/src/config.ts | 3 ++ terminus-terminal/src/index.ts | 31 ++++--------------- .../screen.ts} | 17 ++++++++-- .../src/{ => persistence}/tmux.ts | 11 ++++--- .../src/services/sessions.service.ts | 30 +++++++++++------- 8 files changed, 70 insertions(+), 46 deletions(-) rename terminus-terminal/src/{persistenceProviders.ts => persistence/screen.ts} (90%) rename terminus-terminal/src/{ => persistence}/tmux.ts (97%) diff --git a/terminus-terminal/src/api.ts b/terminus-terminal/src/api.ts index 2e76ba05..9e238ad8 100644 --- a/terminus-terminal/src/api.ts +++ b/terminus-terminal/src/api.ts @@ -28,6 +28,10 @@ export interface SessionOptions { } export abstract class SessionPersistenceProvider { + abstract id: string + abstract displayName: string + + abstract isAvailable (): boolean abstract async attachSession (recoveryId: any): Promise abstract async startSession (options: SessionOptions): Promise abstract async terminateSession (recoveryId: string): Promise diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.pug b/terminus-terminal/src/components/terminalSettingsTab.component.pug index dc023067..dc450f99 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.pug +++ b/terminus-terminal/src/components/terminalSettingsTab.component.pug @@ -259,3 +259,15 @@ [value]='"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}} diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.ts b/terminus-terminal/src/components/terminalSettingsTab.component.ts index 41c6df14..3e91edcd 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.ts +++ b/terminus-terminal/src/components/terminalSettingsTab.component.ts @@ -5,7 +5,7 @@ const fontManager = require('font-manager') import { Component, Inject } from '@angular/core' import { ConfigService, HostAppService, Platform } from 'terminus-core' -import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider } from '../api' +import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvider, SessionPersistenceProvider } from '../api' @Component({ template: require('./terminalSettingsTab.component.pug'), @@ -14,6 +14,7 @@ import { TerminalColorSchemeProvider, ITerminalColorScheme, IShell, ShellProvide export class TerminalSettingsTabComponent { fonts: string[] = [] shells: IShell[] = [] + persistenceProviders: SessionPersistenceProvider[] colorSchemes: ITerminalColorScheme[] = [] equalComparator = equal editingColorScheme: ITerminalColorScheme @@ -24,7 +25,10 @@ export class TerminalSettingsTabComponent { private hostApp: HostAppService, @Inject(ShellProvider) private shellProviders: ShellProvider[], @Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[], - ) { } + @Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[], + ) { + this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable()) + } async ngOnInit () { if (this.hostApp.platform === Platform.Windows || this.hostApp.platform === Platform.macOS) { diff --git a/terminus-terminal/src/config.ts b/terminus-terminal/src/config.ts index 35e96206..d6518afd 100644 --- a/terminus-terminal/src/config.ts +++ b/terminus-terminal/src/config.ts @@ -43,6 +43,7 @@ export class TerminalConfigProvider extends ConfigProvider { terminal: { font: 'Menlo', shell: '~default-shell~', + persistence: 'screen', }, hotkeys: { 'copy': [ @@ -74,6 +75,7 @@ export class TerminalConfigProvider extends ConfigProvider { terminal: { font: 'Consolas', shell: '~clink~', + persistence: null, }, hotkeys: { 'copy': [ @@ -104,6 +106,7 @@ export class TerminalConfigProvider extends ConfigProvider { terminal: { font: 'Liberation Mono', shell: '~default-shell~', + persistence: 'screen', }, hotkeys: { 'copy': [ diff --git a/terminus-terminal/src/index.ts b/terminus-terminal/src/index.ts index 6982a765..75a0c574 100644 --- a/terminus-terminal/src/index.ts +++ b/terminus-terminal/src/index.ts @@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' 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 { TerminalTabComponent } from './components/terminalTab.component' @@ -12,8 +12,8 @@ import { ColorPickerComponent } from './components/colorPicker.component' import { SessionsService } from './services/sessions.service' -import { ScreenPersistenceProvider } from './persistenceProviders' -import { TMuxPersistenceProvider } from './tmux' +import { ScreenPersistenceProvider } from './persistence/screen' +import { TMuxPersistenceProvider } from './persistence/tmux' import { ButtonProvider } from './buttonProvider' import { RecoveryProvider } from './recoveryProvider' import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api' @@ -42,36 +42,17 @@ import { hterm } from './hterm' ], providers: [ SessionsService, - ScreenPersistenceProvider, - TMuxPersistenceProvider, { provide: ToolbarButtonProvider, useClass: ButtonProvider, 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: ConfigProvider, useClass: TerminalConfigProvider, multi: true }, { provide: HotkeyProvider, useClass: TerminalHotkeyProvider, multi: true }, { provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, 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: MacOSDefaultShellProvider, multi: true }, { provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true }, diff --git a/terminus-terminal/src/persistenceProviders.ts b/terminus-terminal/src/persistence/screen.ts similarity index 90% rename from terminus-terminal/src/persistenceProviders.ts rename to terminus-terminal/src/persistence/screen.ts index ef60fb7b..d8160689 100644 --- a/terminus-terminal/src/persistenceProviders.ts +++ b/terminus-terminal/src/persistence/screen.ts @@ -1,11 +1,11 @@ import * as fs from 'mz/fs' 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 { Injectable } from '@angular/core' import { Logger, LogService } from 'terminus-core' -import { SessionOptions, SessionPersistenceProvider } from './api' +import { SessionOptions, SessionPersistenceProvider } from '../api' declare function delay (ms: number): Promise @@ -29,6 +29,8 @@ async function listProcesses (): Promise { @Injectable() export class ScreenPersistenceProvider extends SessionPersistenceProvider { + id = 'screen' + displayName = 'GNU Screen' private logger: Logger constructor ( @@ -38,9 +40,18 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider { this.logger = log.create('main') } + isAvailable () { + try { + execFileSync('sh', ['-c', 'which screen']) + return true + } catch (_) { + return false + } + } + async attachSession (recoveryId: any): Promise { let lines = await new Promise(resolve => { - execCallback('screen -list', (_err, stdout) => { + execAsync('screen -list', (_err, stdout) => { // returns an error code on macOS resolve(stdout.split('\n')) }) diff --git a/terminus-terminal/src/tmux.ts b/terminus-terminal/src/persistence/tmux.ts similarity index 97% rename from terminus-terminal/src/tmux.ts rename to terminus-terminal/src/persistence/tmux.ts index 3a155db1..181e85d6 100644 --- a/terminus-terminal/src/tmux.ts +++ b/terminus-terminal/src/persistence/tmux.ts @@ -3,7 +3,7 @@ import { execFileSync } from 'child_process' import * as AsyncLock from 'async-lock' import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs' import * as childProcess from 'child_process' -import { SessionOptions, SessionPersistenceProvider } from './api' +import { SessionOptions, SessionPersistenceProvider } from '../api' const TMUX_CONFIG = ` set -g status off @@ -174,10 +174,15 @@ export class TMux { @Injectable() export class TMuxPersistenceProvider extends SessionPersistenceProvider { + id = 'tmux' + displayName = 'Tmux' private tmux: TMux constructor () { super() + if (this.isAvailable()) { + this.tmux = new TMux() + } } isAvailable (): boolean { @@ -189,10 +194,6 @@ export class TMuxPersistenceProvider extends SessionPersistenceProvider { } } - init () { - this.tmux = new TMux() - } - async attachSession (recoveryId: any): Promise { let sessions = await this.tmux.list() if (!sessions.includes(recoveryId)) { diff --git a/terminus-terminal/src/services/sessions.service.ts b/terminus-terminal/src/services/sessions.service.ts index e9145fc5..1a4e3dda 100644 --- a/terminus-terminal/src/services/sessions.service.ts +++ b/terminus-terminal/src/services/sessions.service.ts @@ -3,8 +3,8 @@ const psNode = require('ps-node') let nodePTY import * as fs from 'mz/fs' import { Subject } from 'rxjs' -import { Injectable } from '@angular/core' -import { Logger, LogService, ElectronService } from 'terminus-core' +import { Injectable, Inject } from '@angular/core' +import { Logger, LogService, ElectronService, ConfigService } from 'terminus-core' import { exec } from 'mz/child_process' import { SessionOptions, SessionPersistenceProvider } from '../api' @@ -178,7 +178,8 @@ export class SessionsService { private lastID = 0 constructor ( - private persistence: SessionPersistenceProvider, + @Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[], + private config: ConfigService, electron: ElectronService, log: LogService, ) { @@ -187,9 +188,10 @@ export class SessionsService { } async prepareNewSession (options: SessionOptions): Promise { - if (this.persistence) { - let recoveryId = await this.persistence.startSession(options) - options = await this.persistence.attachSession(recoveryId) + let persistence = this.getPersistence() + if (persistence) { + let recoveryId = await persistence.startSession(options) + options = await persistence.attachSession(recoveryId) } return options } @@ -198,10 +200,11 @@ export class SessionsService { this.lastID++ options.name = `session-${this.lastID}` let session = new Session(options) + let persistence = this.getPersistence() session.destroyed$.first().subscribe(() => { delete this.sessions[session.name] - if (this.persistence) { - this.persistence.terminateSession(session.recoveryId) + if (persistence) { + persistence.terminateSession(session.recoveryId) } }) this.sessions[session.name] = session @@ -209,9 +212,14 @@ export class SessionsService { } async recover (recoveryId: string): Promise { - if (!this.persistence) { - return null + let persistence = this.getPersistence() + 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 } }