improve subprocess approach

pull/372/head
Jesse Duffield 2 years ago
parent c6607686eb
commit fb7b28243b

@ -56,8 +56,7 @@ func NewApp(config *config.AppConfig) (*App, error) {
}
func (app *App) Run() error {
err := app.Gui.RunWithSubprocesses()
return err
return app.Gui.Run()
}
func (app *App) Close() error {

@ -492,8 +492,7 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.SubProcess = c
return gui.Errors.ErrSubProcess
return gui.runSubprocess(c)
}
func (gui *Gui) handlePruneContainers() error {
@ -537,8 +536,7 @@ func (gui *Gui) containerExecShell(container *commands.Container) error {
resolvedCommand := utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject)
// attach and return the subprocess error
cmd := gui.OSCommand.ExecutableFromString(resolvedCommand)
gui.SubProcess = cmd
return gui.Errors.ErrSubProcess
return gui.runSubprocess(cmd)
}
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {

@ -53,8 +53,7 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command
// if we have a command for attaching, we attach and return the subprocess error
if option.customCommand.Attach {
cmd := gui.OSCommand.ExecutableFromString(option.command)
gui.SubProcess = cmd
return gui.Errors.ErrSubProcess
return gui.runSubprocess(cmd)
}
return gui.WithWaitingStatus(waitingStatus, func() error {

@ -2,20 +2,14 @@ package gui
import (
"context"
"os/exec"
"strings"
"sync"
"time"
"github.com/docker/docker/api/types"
// "io"
// "io/ioutil"
"github.com/go-errors/errors"
// "strings"
throttle "github.com/boz/go-throttle"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
@ -31,7 +25,6 @@ var OverlappingEdges = false
// SentinelErrors are the errors that have special meaning and need to be checked
// by calling functions. The less of these, the better
type SentinelErrors struct {
ErrSubProcess error
ErrNoContainers error
ErrNoImages error
ErrNoVolumes error
@ -49,7 +42,6 @@ type SentinelErrors struct {
// localising things in the code.
func (gui *Gui) GenerateSentinelErrors() {
gui.Errors = SentinelErrors{
ErrSubProcess: errors.New(gui.Tr.RunningSubprocess),
ErrNoContainers: errors.New(gui.Tr.NoContainers),
ErrNoImages: errors.New(gui.Tr.NoImages),
ErrNoVolumes: errors.New(gui.Tr.NoVolumes),
@ -62,7 +54,6 @@ type Gui struct {
Log *logrus.Entry
DockerCommand *commands.DockerCommand
OSCommand *commands.OSCommand
SubProcess *exec.Cmd
State guiState
Config *config.AppConfig
Tr *i18n.TranslationSet
@ -73,8 +64,20 @@ type Gui struct {
ErrorChan chan error
CyclableViews []string
Views Views
// returns true if our views have been created and assigned to gui.Views.
// Views are setup only once, upon application start.
ViewsSetup bool
// if we've suspended the gui (e.g. because we've switched to a subprocess)
// we typically want to pause some things that are running like background
// file refreshes
PauseBackgroundThreads bool
Mutexes
}
type Mutexes struct {
SubprocessMutex sync.Mutex
}
type servicePanelState struct {
@ -130,11 +133,6 @@ type guiState struct {
SubProcessOutput string
Stats map[string]commands.ContainerStats
// SessionIndex tells us how many times we've come back from a subprocess.
// We increment it each time we switch to a new subprocess
// Every time we go to a subprocess we need to close a few goroutines so this index is used for that purpose
SessionIndex int
ScreenMode WindowMaximisation
}
@ -165,8 +163,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
},
Project: &projectState{ContextIndex: 0},
},
SessionIndex: 0,
ViewStack: []string{},
ViewStack: []string{},
}
cyclableViews := []string{"project", "containers", "images", "volumes"}
@ -193,13 +190,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
return gui, nil
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func (gui *Gui) renderGlobalOptions() error {
return gui.renderOptionsMap(map[string]string{
"PgUp/PgDn": gui.Tr.Scroll,
@ -211,15 +201,15 @@ func (gui *Gui) renderGlobalOptions() error {
}
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
currentSessionIndex := gui.State.SessionIndex
_ = function() // time.Tick doesn't run immediately so we'll do that here // TODO: maybe change
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
if gui.State.SessionIndex > currentSessionIndex {
if gui.PauseBackgroundThreads {
return
}
_ = function()
}
}()
@ -289,6 +279,9 @@ func (gui *Gui) Run() error {
}
err = g.MainLoop()
if err == gocui.ErrQuit {
return nil
}
return err
}
@ -411,8 +404,12 @@ func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) editFile(filename string) error {
_, err := gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
return err
cmd, err := gui.OSCommand.EditFile(filename)
if err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
return gui.runSubprocess(cmd)
}
func (gui *Gui) openFile(filename string) error {
@ -422,28 +419,10 @@ func (gui *Gui) openFile(filename string) error {
return nil
}
// runSyncOrAsyncCommand takes the output of a command that may have returned
// either no error, an error, or a subprocess to execute, and if a subprocess
// needs to be set on the gui object, it does so, and then returns the error
// the bool returned tells us whether the calling code should continue
func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) {
if err != nil {
if err != gui.Errors.ErrSubProcess {
return false, gui.createErrorPanel(gui.g, err.Error())
}
}
if sub != nil {
gui.SubProcess = sub
return false, gui.Errors.ErrSubProcess
}
return true, nil
}
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(g, v, gui.Tr.CustomCommandTitle, func(g *gocui.Gui, v *gocui.View) error {
command := gui.trimmedContent(v)
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
return gui.Errors.ErrSubProcess
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(command))
})
}

@ -224,6 +224,5 @@ func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.SubProcess = c
return gui.Errors.ErrSubProcess
return gui.runSubprocess(c)
}

@ -293,8 +293,7 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.SubProcess = c
return gui.Errors.ErrSubProcess
return gui.runSubprocess(c)
}
func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error {
@ -308,8 +307,7 @@ func (gui *Gui) handleServiceRenderLogsToMain(g *gocui.Gui, v *gocui.View) error
return gui.createErrorPanel(gui.g, err.Error())
}
gui.SubProcess = c
return gui.Errors.ErrSubProcess
return gui.runSubprocess(c)
}
func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
@ -366,8 +364,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
),
f: func() error {
gui.SubProcess = gui.OSCommand.RunCustomCommand(rebuildCommand)
return gui.Errors.ErrSubProcess
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(rebuildCommand))
},
},
{

@ -4,48 +4,43 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"strings"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration
// if the error returned from a run is a ErrSubProcess, it runs the subprocess
// otherwise it handles the error, possibly by quitting the application
func (gui *Gui) RunWithSubprocesses() error {
for {
if err := gui.Run(); err != nil {
if err == gocui.ErrQuit {
break
} else if err == gui.Errors.ErrSubProcess {
// preparing the state for when we return
gui.pushView(gui.currentViewName())
// giving goEvery goroutines time to finish
gui.State.SessionIndex++
if err := gui.runCommand(); err != nil {
return err
}
// pop here so we don't stack up view names
gui.popView()
// ensuring we render e.g. the logs of the currently selected item upon return
gui.State.Panels.Main.ObjectKey = ""
} else {
return err
}
}
func (gui *Gui) runSubprocess(cmd *exec.Cmd) error {
gui.Mutexes.SubprocessMutex.Lock()
defer gui.Mutexes.SubprocessMutex.Unlock()
if err := gui.g.Suspend(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.PauseBackgroundThreads = true
cmdErr := gui.runSubprocess(cmd)
if err := gui.g.Resume(); err != nil {
return gui.createErrorPanel(gui.g, err.Error())
}
gui.PauseBackgroundThreads = false
if cmdErr != nil {
return gui.createErrorPanel(gui.g, cmdErr.Error())
}
return nil
}
func (gui *Gui) runCommand() error {
gui.SubProcess.Stdout = os.Stdout
gui.SubProcess.Stderr = os.Stdout
gui.SubProcess.Stdin = os.Stdin
func (gui *Gui) runCommand(cmd *exec.Cmd) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stdout
cmd.Stdin = os.Stdin
stop := make(chan os.Signal, 1)
defer signal.Stop(stop)
@ -54,23 +49,22 @@ func (gui *Gui) runCommand() error {
signal.Notify(stop, os.Interrupt)
<-stop
if err := gui.OSCommand.Kill(gui.SubProcess); err != nil {
if err := gui.OSCommand.Kill(cmd); err != nil {
gui.Log.Error(err)
}
}()
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue))
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(cmd.Args, " "), color.FgBlue))
if err := gui.SubProcess.Run(); err != nil {
if err := cmd.Run(); err != nil {
// not handling the error explicitly because usually we're going to see it
// in the output anyway
gui.Log.Error(err)
}
gui.SubProcess.Stdin = nil
gui.SubProcess.Stdout = ioutil.Discard
gui.SubProcess.Stderr = ioutil.Discard
gui.SubProcess = nil
cmd.Stdin = nil
cmd.Stdout = ioutil.Discard
cmd.Stderr = ioutil.Discard
gui.promptToReturn()

@ -8,7 +8,6 @@ func dutchSet() TranslationSet {
StoppingStatus: "stoppen",
RunningCustomCommandStatus: "Aangepast commando draaien",
RunningSubprocess: "subprocess draaien",
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
ErrorOccurred: "Er is iets fout gegaan! Zou je hier een issue aan willen maken: https://github.com/jesseduffield/lazydocker/issues",

@ -13,7 +13,6 @@ type TranslationSet struct {
Scroll string
Close string
ErrorTitle string
RunningSubprocess string
NoViewMachingNewLineFocusedSwitchStatement string
OpenConfig string
EditConfig string
@ -121,7 +120,6 @@ func englishSet() TranslationSet {
RunningCustomCommandStatus: "running custom command",
RunningBulkCommandStatus: "running bulk command",
RunningSubprocess: "running subprocess",
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
ErrorOccurred: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazydocker/issues",

@ -8,7 +8,6 @@ func germanSet() TranslationSet {
StoppingStatus: "anhalten",
RunningCustomCommandStatus: "führt benutzerdefinierten Befehl aus",
RunningSubprocess: "Unterprozess ausführen",
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
ErrorOccurred: "Es ist ein Fehler aufgetreten! Bitte erstelle ein Issue hier: https://github.com/jesseduffield/lazydocker/issues",

@ -8,7 +8,6 @@ func polishSet() TranslationSet {
StoppingStatus: "zatrzymywanie",
RunningCustomCommandStatus: "uruchamianie własnej komendty",
RunningSubprocess: "uruchamianie podprocesu",
NoViewMachingNewLineFocusedSwitchStatement: "Żaden widok nie odpowiada instrukcji przełączenia newLineFocused",
ErrorOccurred: "Wystąpił błąd! Proszę go zgłosić na https://github.com/jesseduffield/lazydocker/issues",

@ -8,7 +8,6 @@ func turkishSet() TranslationSet {
StoppingStatus: "durduruluyor",
RunningCustomCommandStatus: "özel komut çalıştır",
RunningSubprocess: "İkincil işlem yürütülüyor",
NoViewMachingNewLineFocusedSwitchStatement: "NewLineFocused anahtar deyimi ile eşleşen görünüm yok",
ErrorOccurred: "Bir hata oluştu! Lütfen https://github.com/jesseduffield/lazydocker/issues adresinden bir hataya ilişkin konu oluşturun",

Loading…
Cancel
Save