mirror of
https://github.com/Valkyrie00/bold-brew.git
synced 2026-03-17 15:50:12 +01:00
Consolidate all BrewService methods into a single file. Add section comments to organize code into logical groups: - Core info (GetBrewVersion) - Package operations (Update/Install/Remove) - Tap support (InstallTap/IsTapInstalled) - Internal helpers (executeCommand)
208 lines
5.5 KiB
Go
208 lines
5.5 KiB
Go
package services
|
|
|
|
import (
|
|
"bbrew/internal/models"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/rivo/tview"
|
|
)
|
|
|
|
// BrewServiceInterface defines the contract for Homebrew operations.
|
|
// BrewService is a pure executor of brew commands - it does NOT hold data.
|
|
// For data retrieval, use DataProviderInterface.
|
|
type BrewServiceInterface interface {
|
|
// Core info
|
|
GetBrewVersion() (string, error)
|
|
|
|
// Package operations
|
|
UpdateHomebrew() error
|
|
UpdateAllPackages(app *tview.Application, outputView *tview.TextView) error
|
|
UpdatePackage(info models.Package, app *tview.Application, outputView *tview.TextView) error
|
|
RemovePackage(info models.Package, app *tview.Application, outputView *tview.TextView) error
|
|
InstallPackage(info models.Package, app *tview.Application, outputView *tview.TextView) error
|
|
|
|
// Tap support
|
|
InstallTap(tapName string, app *tview.Application, outputView *tview.TextView) error
|
|
IsTapInstalled(tapName string) bool
|
|
}
|
|
|
|
// BrewService provides methods to execute Homebrew commands.
|
|
// It is a pure executor - no data storage. Use DataProvider for data.
|
|
type BrewService struct {
|
|
brewVersion string
|
|
}
|
|
|
|
// NewBrewService creates a new instance of BrewService.
|
|
var NewBrewService = func() BrewServiceInterface {
|
|
return &BrewService{}
|
|
}
|
|
|
|
// GetBrewVersion retrieves the version of Homebrew installed on the system, caching it for future calls.
|
|
func (s *BrewService) GetBrewVersion() (string, error) {
|
|
if s.brewVersion != "" {
|
|
return s.brewVersion, nil
|
|
}
|
|
|
|
cmd := exec.Command("brew", "--version")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
s.brewVersion = strings.TrimSpace(string(output))
|
|
return s.brewVersion, nil
|
|
}
|
|
|
|
// UpdateHomebrew updates the Homebrew package manager by running the `brew update` command.
|
|
func (s *BrewService) UpdateHomebrew() error {
|
|
cmd := exec.Command("brew", "update")
|
|
return cmd.Run()
|
|
}
|
|
|
|
// UpdateAllPackages upgrades all outdated packages.
|
|
func (s *BrewService) UpdateAllPackages(app *tview.Application, outputView *tview.TextView) error {
|
|
cmd := exec.Command("brew", "upgrade") // #nosec G204
|
|
return s.executeCommand(app, cmd, outputView)
|
|
}
|
|
|
|
// UpdatePackage upgrades a specific package.
|
|
func (s *BrewService) UpdatePackage(info models.Package, app *tview.Application, outputView *tview.TextView) error {
|
|
var cmd *exec.Cmd
|
|
if info.Type == models.PackageTypeCask {
|
|
cmd = exec.Command("brew", "upgrade", "--cask", info.Name) // #nosec G204
|
|
} else {
|
|
cmd = exec.Command("brew", "upgrade", info.Name) // #nosec G204
|
|
}
|
|
return s.executeCommand(app, cmd, outputView)
|
|
}
|
|
|
|
// RemovePackage uninstalls a package.
|
|
func (s *BrewService) RemovePackage(info models.Package, app *tview.Application, outputView *tview.TextView) error {
|
|
var cmd *exec.Cmd
|
|
if info.Type == models.PackageTypeCask {
|
|
cmd = exec.Command("brew", "uninstall", "--cask", info.Name) // #nosec G204
|
|
} else {
|
|
cmd = exec.Command("brew", "uninstall", info.Name) // #nosec G204
|
|
}
|
|
return s.executeCommand(app, cmd, outputView)
|
|
}
|
|
|
|
// InstallPackage installs a package.
|
|
func (s *BrewService) InstallPackage(info models.Package, app *tview.Application, outputView *tview.TextView) error {
|
|
var cmd *exec.Cmd
|
|
if info.Type == models.PackageTypeCask {
|
|
cmd = exec.Command("brew", "install", "--cask", info.Name) // #nosec G204
|
|
} else {
|
|
cmd = exec.Command("brew", "install", info.Name) // #nosec G204
|
|
}
|
|
return s.executeCommand(app, cmd, outputView)
|
|
}
|
|
|
|
// InstallTap installs a Homebrew tap.
|
|
func (s *BrewService) InstallTap(tapName string, app *tview.Application, outputView *tview.TextView) error {
|
|
cmd := exec.Command("brew", "tap", tapName) // #nosec G204
|
|
return s.executeCommand(app, cmd, outputView)
|
|
}
|
|
|
|
// IsTapInstalled checks if a tap is already installed.
|
|
func (s *BrewService) IsTapInstalled(tapName string) bool {
|
|
cmd := exec.Command("brew", "tap")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
taps := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
for _, tap := range taps {
|
|
if strings.TrimSpace(tap) == tapName {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// executeCommand runs a command and captures its output, updating the provided TextView.
|
|
func (s *BrewService) executeCommand(
|
|
app *tview.Application,
|
|
cmd *exec.Cmd,
|
|
outputView *tview.TextView,
|
|
) error {
|
|
stdoutPipe, stdoutWriter := io.Pipe()
|
|
stderrPipe, stderrWriter := io.Pipe()
|
|
cmd.Stdout = stdoutWriter
|
|
cmd.Stderr = stderrWriter
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(3)
|
|
|
|
cmdErrCh := make(chan error, 1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
defer stdoutWriter.Close()
|
|
defer stderrWriter.Close()
|
|
cmdErrCh <- cmd.Wait()
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
defer stdoutPipe.Close()
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := stdoutPipe.Read(buf)
|
|
if n > 0 {
|
|
output := make([]byte, n)
|
|
copy(output, buf[:n])
|
|
app.QueueUpdateDraw(func() {
|
|
_, _ = outputView.Write(output) // #nosec G104
|
|
outputView.ScrollToEnd()
|
|
})
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
app.QueueUpdateDraw(func() {
|
|
fmt.Fprintf(outputView, "\nError: %v\n", err)
|
|
})
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
defer stderrPipe.Close()
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := stderrPipe.Read(buf)
|
|
if n > 0 {
|
|
output := make([]byte, n)
|
|
copy(output, buf[:n])
|
|
app.QueueUpdateDraw(func() {
|
|
_, _ = outputView.Write(output) // #nosec G104
|
|
outputView.ScrollToEnd()
|
|
})
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
app.QueueUpdateDraw(func() {
|
|
fmt.Fprintf(outputView, "\nError: %v\n", err)
|
|
})
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
return <-cmdErrCh
|
|
}
|