Skip to content

Commit

Permalink
Add ability to run commands before trying to connect
Browse files Browse the repository at this point in the history
  • Loading branch information
svanharmelen committed Nov 22, 2024
1 parent ddf5664 commit 7bd76cd
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 48 deletions.
86 changes: 54 additions & 32 deletions components/ConnectionSelection.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package components

import (
"context"
"fmt"
"math/rand"
"strings"

"github.com/gdamore/tcell/v2"
Expand All @@ -11,6 +13,7 @@ import (
"github.com/jorgerojas26/lazysql/commands"
"github.com/jorgerojas26/lazysql/drivers"
"github.com/jorgerojas26/lazysql/helpers"
"github.com/jorgerojas26/lazysql/helpers/logger"
"github.com/jorgerojas26/lazysql/models"
)

Expand Down Expand Up @@ -140,50 +143,69 @@ func NewConnectionSelection(connectionForm *ConnectionForm, connectionPages *mod
return cs
}

func (cs *ConnectionSelection) Connect(connection models.Connection) {
func (cs *ConnectionSelection) Connect(connection models.Connection) *tview.Application {
if MainPages.HasPage(connection.URL) {
MainPages.SwitchToPage(connection.URL)
App.Draw()
} else {
cs.StatusText.SetText("Connecting...").SetTextColor(app.Styles.TertiaryTextColor)
return App.Draw()
}

ctx, cancelFn := context.WithCancel(context.Background())

if len(connection.Commands) > 0 {
cs.StatusText.SetText("Running commands...").SetTextColor(app.Styles.TertiaryTextColor)
App.Draw()

var newDbDriver drivers.Driver
// This is super arbitrary, but allows you to use '${port}' in your commands
// which will be replaced with a random port number between 5000 and 6000 to
// avoid conflicts when connecting to multiple databases from the same machine.
port := 5000 + rand.Intn(1000)

Check failure on line 161 in components/ConnectionSelection.go

View workflow job for this annotation

GitHub Actions / lint

G404: Use of weak random number generator (math/rand or math/rand/v2 instead of crypto/rand) (gosec)
connection.URL = strings.ReplaceAll(connection.URL, "${port}", fmt.Sprintf("%d", port))

switch connection.Provider {
case drivers.DriverMySQL:
newDbDriver = &drivers.MySQL{}
case drivers.DriverPostgres:
newDbDriver = &drivers.Postgres{}
case drivers.DriverSqlite:
newDbDriver = &drivers.SQLite{}
for _, command := range connection.Commands {
command = strings.ReplaceAll(command, "${port}", fmt.Sprintf("%d", port))
if err := helpers.RunCommand(ctx, command); err != nil {
logger.Error("Command failed to start", map[string]any{"error": err.Error()})
}
}
}

err := newDbDriver.Connect(connection.URL)
cs.StatusText.SetText("Connecting...").SetTextColor(app.Styles.TertiaryTextColor)
App.Draw()

if err != nil {
cs.StatusText.SetText(err.Error()).SetTextStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
App.Draw()
} else {
newHome := NewHomePage(connection, newDbDriver)
var newDBDriver drivers.Driver

MainPages.AddAndSwitchToPage(connection.URL, newHome, true)
switch connection.Provider {
case drivers.DriverMySQL:
newDBDriver = &drivers.MySQL{}
case drivers.DriverPostgres:
newDBDriver = &drivers.Postgres{}
case drivers.DriverSqlite:
newDBDriver = &drivers.SQLite{}
}

cs.StatusText.SetText("")
App.Draw()
err := newDBDriver.Connect(connection.URL)
if err != nil {
cs.StatusText.SetText(err.Error()).SetTextStyle(tcell.StyleDefault.Foreground(tcell.ColorRed))
cancelFn()
return App.Draw()
}

selectedRow, selectedCol := ConnectionListTable.GetSelection()
cell := ConnectionListTable.GetCell(selectedRow, selectedCol)
cell.SetText(fmt.Sprintf("[green]* %s", cell.Text))
newHome := NewHomePage(cancelFn, connection, newDBDriver)
MainPages.AddAndSwitchToPage(connection.URL, newHome, true)

ConnectionListTable.SetCell(selectedRow, selectedCol, cell)
cs.StatusText.SetText("")
App.Draw()

MainPages.SwitchToPage(connection.URL)
newHome.Tree.SetCurrentNode(newHome.Tree.GetRoot())
newHome.Tree.Wrapper.SetTitle(fmt.Sprintf("%s (%s)", connection.Name, strings.ToUpper(connection.Provider)))
App.SetFocus(newHome.Tree)
App.Draw()
}
selectedRow, selectedCol := ConnectionListTable.GetSelection()
cell := ConnectionListTable.GetCell(selectedRow, selectedCol)
cell.SetText(fmt.Sprintf("[green]* %s", cell.Text))

}
ConnectionListTable.SetCell(selectedRow, selectedCol, cell)

MainPages.SwitchToPage(connection.URL)
newHome.Tree.SetCurrentNode(newHome.Tree.GetRoot())
newHome.Tree.Wrapper.SetTitle(fmt.Sprintf("%s (%s)", connection.Name, strings.ToUpper(connection.Provider)))

App.SetFocus(newHome.Tree)
return App.Draw()
}
34 changes: 18 additions & 16 deletions components/Home.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package components

import (
"context"
"fmt"

"github.com/gdamore/tcell/v2"
Expand All @@ -14,6 +15,9 @@ import (

type Home struct {
*tview.Flex

stop context.CancelFunc

Tree *Tree
TabbedPane *TabbedPane
LeftWrapper *tview.Flex
Expand All @@ -22,10 +26,10 @@ type Home struct {
HelpModal *HelpModal
DBDriver drivers.Driver
FocusedWrapper string
ListOfDbChanges []models.DbDmlChange
ListOfDBChanges []models.DbDmlChange
}

func NewHomePage(connection models.Connection, dbdriver drivers.Driver) *Home {
func NewHomePage(cancelFn context.CancelFunc, connection models.Connection, dbdriver drivers.Driver) *Home {
tree := NewTree(connection.DBName, dbdriver)
tabbedPane := NewTabbedPane()
leftWrapper := tview.NewFlex()
Expand All @@ -34,15 +38,18 @@ func NewHomePage(connection models.Connection, dbdriver drivers.Driver) *Home {
maincontent := tview.NewFlex()

home := &Home{
Flex: tview.NewFlex().SetDirection(tview.FlexRow),
Flex: tview.NewFlex().SetDirection(tview.FlexRow),

stop: cancelFn,

Tree: tree,
TabbedPane: tabbedPane,
LeftWrapper: leftWrapper,
RightWrapper: rightWrapper,
HelpStatus: NewHelpStatus(),
HelpModal: NewHelpModal(),
ListOfDbChanges: []models.DbDmlChange{},
DBDriver: dbdriver,
ListOfDBChanges: []models.DbDmlChange{},
}

go home.subscribeToTreeChanges()
Expand Down Expand Up @@ -96,7 +103,7 @@ func (home *Home) subscribeToTreeChanges() {
table = tab.Content
home.TabbedPane.SwitchToTabByReference(tab.Reference)
} else {
table = NewResultsTable(&home.ListOfDbChanges, home.Tree, home.DBDriver).WithFilter()
table = NewResultsTable(&home.ListOfDBChanges, home.Tree, home.DBDriver).WithFilter()
table.SetDatabaseName(databaseName)
table.SetTableName(tableName)

Expand Down Expand Up @@ -286,7 +293,7 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey {
home.TabbedPane.SwitchToTabByName(tabNameEditor)
tab.Content.SetIsFiltering(true)
} else {
tableWithEditor := NewResultsTable(&home.ListOfDbChanges, home.Tree, home.DBDriver).WithEditor()
tableWithEditor := NewResultsTable(&home.ListOfDBChanges, home.Tree, home.DBDriver).WithEditor()
home.TabbedPane.AppendTab(tabNameEditor, tableWithEditor, tabNameEditor)
tableWithEditor.SetIsFiltering(true)
}
Expand All @@ -298,17 +305,12 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey {
MainPages.SwitchToPage(pageNameConnections)
}
case commands.Quit:
if tab != nil {
table := tab.Content

if !table.GetIsFiltering() && !table.GetIsEditing() {
App.Stop()
}
} else {
if tab == nil || (!table.GetIsEditing() && !table.GetIsFiltering()) {
home.stop()
App.Stop()
}
case commands.Save:
if (len(home.ListOfDbChanges) > 0) && !table.GetIsEditing() {
if (len(home.ListOfDBChanges) > 0) && !table.GetIsEditing() {
confirmationModal := NewConfirmationModal("")

confirmationModal.SetDoneFunc(func(_ int, buttonLabel string) {
Expand All @@ -317,12 +319,12 @@ func (home *Home) homeInputCapture(event *tcell.EventKey) *tcell.EventKey {

if buttonLabel == "Yes" {

err := home.DBDriver.ExecutePendingChanges(home.ListOfDbChanges)
err := home.DBDriver.ExecutePendingChanges(home.ListOfDBChanges)

if err != nil {
table.SetError(err.Error(), nil)
} else {
home.ListOfDbChanges = []models.DbDmlChange{}
home.ListOfDBChanges = []models.DbDmlChange{}

table.FetchRecords(nil)
home.Tree.ForceRemoveHighlight()
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb h1:GRiLv4rgyqjqzxbhJke65IYUf4NCOOvrPOJbV/sPxkM=
github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
Expand Down
75 changes: 75 additions & 0 deletions helpers/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package helpers

import (
"context"
"errors"
"io"
"os/exec"
"strings"
"time"

"github.com/mitchellh/go-linereader"

"github.com/jorgerojas26/lazysql/helpers/logger"
)

func RunCommand(ctx context.Context, command string) error {
var cmd *exec.Cmd

parts := strings.Fields(command)
if len(parts) == 1 {
cmd = exec.CommandContext(ctx, parts[0])

Check failure on line 21 in helpers/command.go

View workflow job for this annotation

GitHub Actions / lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
} else {
cmd = exec.CommandContext(ctx, parts[0], parts[1:]...)

Check failure on line 23 in helpers/command.go

View workflow job for this annotation

GitHub Actions / lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
}

// Create a pipe to read the output from.
pr, pw := io.Pipe()
startedCh := make(chan struct{})
copyDoneCh := make(chan struct{})
go logOutput(pr, startedCh, copyDoneCh)

// Connect the pipe to stdout and stderr.
cmd.Stderr = pw
cmd.Stdout = pw

if err := cmd.Start(); err != nil {
return err
}

go func() {
if err := cmd.Wait(); err != nil {
logger.Error("Command failed", map[string]any{"error": err.Error()})
}

pw.Close()

Check failure on line 45 in helpers/command.go

View workflow job for this annotation

GitHub Actions / lint

unhandled-error: Unhandled error in call to function io.PipeWriter.Close (revive)
<-copyDoneCh
}()

// Wait for the command to start
select {
case <-ctx.Done():
logger.Error("Command cancelled", map[string]any{"error": ctx.Err()})

Check failure on line 52 in helpers/command.go

View workflow job for this annotation

GitHub Actions / lint

`cancelled` is a misspelling of `canceled` (misspell)
case <-startedCh:
logger.Info("Command started", map[string]any{"command": command})
case <-time.After(5 * time.Second):
return errors.New("command timeout")
}

return nil
}

func logOutput(r io.Reader, started, doneCh chan struct{}) {
defer close(doneCh)
lr := linereader.New(r)

// Wait for the command to start
line := <-lr.Ch
started <- struct{}{}
logger.Debug("Command output", map[string]any{"line": line})

// Log the rest of the output
for line := range lr.Ch {
logger.Debug("Command output", map[string]any{"line": line})
}
}
1 change: 1 addition & 0 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Connection struct {
Provider string
DBName string
URL string
Commands []string
}

type StateChange struct {
Expand Down

0 comments on commit 7bd76cd

Please sign in to comment.