feat: improve usability by including a progressbar while installing packages on windows and mac

This commit is contained in:
Patryk Hegenberg 2025-03-20 20:16:22 +01:00
parent 26bdcd9a6a
commit c903fa1803
2 changed files with 267 additions and 229 deletions

View file

@ -13,6 +13,7 @@ import (
"strings" "strings"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"fyne.io/tools" "fyne.io/tools"
@ -97,242 +98,279 @@ func InstallDependency(index int, sudoPassword string, dependencies []Dependency
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
switch index { cmd, err = installWindowsDependencies(index, cmd, &mainWindow)
case 0: // VSCode if err != nil {
cmd = tools.CommandInShell("winget", "install", "-e", "--id", dialog.ShowError(err, mainWindow)
"Microsoft.VisualStudioCode") log.Error(err.Error())
case 1: // Docker Desktop
wslCheckCmd := tools.CommandInShell("wsl", "--status")
err := wslCheckCmd.Run()
if err != nil {
wslInstallCmd := tools.CommandInShell("wsl", "--install")
err = wslInstallCmd.Run()
if err != nil {
errMsg := fmt.Errorf("error: installing WSL: %v", err)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
return
}
dialog.ShowInformation("WSL wird installiert", "WSL wird installiert. Bitte warten Sie, bis die Installation abgeschlossen ist und starten Sie die Anwendung neu.", mainWindow)
return
}
cmd = tools.CommandInShell("winget", "install", "-e", "--id",
"Docker.DockerDesktop")
} }
case "darwin": case "darwin":
brewCheckCmd := tools.CommandInShell("which", "brew") cmd, err = installDarwinDependencies(index, cmd, &mainWindow)
err := brewCheckCmd.Run() dialog.ShowError(err, mainWindow)
if err != nil { log.Error(err.Error())
brewInstallCmd := tools.CommandInShell("bin/bash", "-c", "\"$(curl -fsSl https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
err := brewInstallCmd.Run()
if err != nil {
errMsg := fmt.Errorf("error: installing homebrew falied: %v", err)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
}
}
switch index {
case 0: // VSCode
cmd = tools.CommandInShell("brew", "install", "--cask", "visual-studio-code")
case 1: // Docker
cmd = tools.CommandInShell("brew", "install", "docker")
}
case "linux": case "linux":
osInfo, err := osinfo.GetLinuxDistribution() err = installLinuxDependencies(index, sudoPassword, cmd, dependencies, &mainWindow)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
dialog.ShowError(fmt.Errorf("error getting OS info: %v", err), mainWindow) dialog.ShowError(err, mainWindow)
return
}
var downloadURL, fileName string
switch index {
case 0: // VSCode
switch osInfo.ID {
case "debian", "ubuntu", "linuxmint":
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64"
fileName = "vscode.deb"
case "fedora":
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-rpm-x64"
fileName = "vscode.rpm"
default:
dialog.ShowInformation("Nicht unterstützt", fmt.Sprintf("Automatische Installation für dieses OS %v nicht verfügbar", osInfo), mainWindow)
return
}
go func() {
progressBar := widget.NewProgressBar()
progressDialog := dialog.NewCustomWithoutButtons("Download in progress", progressBar, mainWindow)
progressDialog.Show()
homeDir, _ := os.UserHomeDir()
downloadDir := filepath.Join(homeDir, "Downloads")
filePath := filepath.Join(downloadDir, fileName)
err := download.WithProgressBar(downloadURL, filePath, progressBar)
progressDialog.Hide()
if err != nil {
log.Error(err.Error())
dialog.ShowError(err, mainWindow)
return
}
var installCmd string
switch osInfo.ID {
case "debian", "ubuntu", "linuxmint":
installCmd = fmt.Sprintf("sudo -S dpkg -i %s", filePath)
case "fedora":
installCmd = fmt.Sprintf("sudo -S dnf install -y %s", filePath)
}
cmd := exec.Command("sh", "-c", installCmd)
cmd.Stdin = strings.NewReader(sudoPassword + "\n")
dialog.ShowInformation("Installation gestartet",
"Die Installation von VSCode wurde gestartet.", mainWindow)
output, err := cmd.CombinedOutput()
if err != nil {
errMsg := fmt.Errorf("installation failed:\n%s", output)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
return
} else {
dialog.ShowInformation("Erfolg", "VSCode erfolgreich installiert!", mainWindow)
CheckDependencies(dependencies)
}
}()
case 1: // Docker
go func() {
progressBar := widget.NewProgressBar()
progressDialog := dialog.NewCustomWithoutButtons("Docker Installation läuft...", progressBar, mainWindow)
progressDialog.Show()
osInfo, err := osinfo.GetLinuxDistribution()
if err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("error getting os infos: %v", err)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
return
}
var commands []string
var cleanupCommands []string
var totalSteps int
switch osInfo.ID {
case "ubuntu", "linuxmint", "debian":
// Ubuntu/Debian Commands
distroPath := "ubuntu"
codeName := "$(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\")"
if osInfo.ID == "debian" {
distroPath = "debian"
codeName = "$(. /etc/os-release && echo \"$VERSION_CODENAME\")"
}
arch := "$(dpkg --print-architecture)"
commands = []string{
"apt-get update",
"apt-get install -y wget",
fmt.Sprintf("wget -qO- https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/ | grep -oP 'href=\"\\K[^\"]*(?=.*deb)' | xargs -I{} wget https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/{}",
distroPath, codeName, arch, distroPath, codeName, arch),
"dpkg -i ./containerd.io*.deb docker-ce*.deb docker-ce-cli*.deb docker-buildx-plugin*.deb docker-compose-plugin*.deb",
"apt-get install -f -y",
"service docker start",
}
cleanupCommands = []string{
"rm -rf ./containerd.io*.deb ./docker-ce*.deb ./docker-ce-cli*.deb ./docker-buildx-plugin*.deb ./docker-compose-plugin*.deb",
"apt-get autoremove -y",
"apt-get clean",
}
totalSteps = len(commands) + len(cleanupCommands)
case "fedora":
// Fedora Commands
fedoraVer := "$(rpm -E %fedora)"
commands = []string{
"dnf install -y wget",
fmt.Sprintf("wget https://download.docker.com/linux/fedora/%s/x86_64/stable/Packages/containerd-*.rpm docker-*.rpm docker-ce-*.rpm", fedoraVer),
"dnf install -y ./*.rpm",
"systemctl enable --now docker",
}
cleanupCommands = []string{
"rm -rf ./containerd-*.rpm ./docker-*.rpm ./docker-ce-*.rpm",
"dnf autoremove -y",
"dnf clean all",
}
totalSteps = len(commands) + len(cleanupCommands)
default:
progressDialog.Hide()
dialog.ShowInformation("not supported", "Automatic Docker installation not supported for your OS.", mainWindow)
return
}
progressStep := 1.0 / float64(totalSteps)
currentProgress := 0.0
for _, cmd := range commands {
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
command.Stdin = strings.NewReader(sudoPassword + "\n")
if output, err := command.CombinedOutput(); err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("error at %s:\n%s", cmd, output)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
return
}
currentProgress += progressStep
progressBar.SetValue(currentProgress)
}
for _, cmd := range cleanupCommands {
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
command.Stdin = strings.NewReader(sudoPassword + "\n")
if output, err := command.CombinedOutput(); err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("cleanup error at %s:\n%s", cmd, output)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
return
}
currentProgress += progressStep
progressBar.SetValue(currentProgress)
}
progressDialog.Hide()
dialog.ShowInformation(
"Installation finished",
"Docker was succesfully installed! Please re-start your system to let the changes take effect.",
mainWindow,
)
CheckDependencies(dependencies)
}()
} }
} }
if cmd != nil { if cmd != nil {
err = cmd.Start() showInstallProgressBar(mainWindow, fmt.Sprintf("installing %v", depName), cmd, depName, dependencies)
if err != nil {
errMsg := fmt.Errorf("error starting installation process: %v", err)
dialog.ShowError(errMsg, mainWindow)
log.Error(errMsg.Error())
} else {
dialog.ShowInformation("Installation started",
fmt.Sprintf("Installation of %s was started", depName), mainWindow)
}
} }
} }
func installWindowsDependencies(index int, cmd *exec.Cmd, mainWindow *fyne.Window) (*exec.Cmd, error) {
switch index {
case 0: // VSCode
cmd = tools.CommandInShell("winget", "install", "-e", "--id",
"Microsoft.VisualStudioCode")
case 1: // Docker Desktop
wslCheckCmd := tools.CommandInShell("wsl", "--status")
err := wslCheckCmd.Run()
if err != nil {
wslInstallCmd := tools.CommandInShell("wsl", "--install", "ubuntu")
dialog.ShowInformation("WSL wird installiert", "WSL wird installiert. Bitte warten Sie, bis die Installation abgeschlossen ist und starten Sie die Anwendung neu.", *mainWindow)
err = wslInstallCmd.Run()
if err != nil {
errMsg := fmt.Errorf("error: installing WSL: %v", err)
return nil, errMsg
}
return nil, err
}
cmd = tools.CommandInShell("winget", "install", "-e", "--id",
"Docker.DockerDesktop")
}
return cmd, nil
}
func installDarwinDependencies(index int, cmd *exec.Cmd, mainWindow *fyne.Window) (*exec.Cmd, error) {
brewCheckCmd := tools.CommandInShell("which", "brew")
err := brewCheckCmd.Run()
if err != nil {
brewInstallCmd := tools.CommandInShell("bin/bash", "-c", "\"$(curl -fsSl https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
err := brewInstallCmd.Run()
if err != nil {
errMsg := fmt.Errorf("error: installing homebrew falied: %v", err)
return nil, errMsg
}
}
switch index {
case 0: // VSCode
cmd = tools.CommandInShell("brew", "install", "--cask", "visual-studio-code")
case 1: // Docker
cmd = tools.CommandInShell("brew", "install", "docker")
}
return cmd, nil
}
func installLinuxDependencies(index int, sudoPassword string, cmd *exec.Cmd, dependencies []Dependency, mainWindow *fyne.Window) error {
osInfo, err := osinfo.GetLinuxDistribution()
if err != nil {
errMsg := fmt.Errorf("error getting OS info: %v", err)
return errMsg
}
var downloadURL, fileName string
switch index {
case 0: // VSCode
switch osInfo.ID {
case "debian", "ubuntu", "linuxmint":
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64"
fileName = "vscode.deb"
case "fedora":
downloadURL = "https://code.visualstudio.com/sha/download?build=stable&os=linux-rpm-x64"
fileName = "vscode.rpm"
default:
dialog.ShowInformation("Nicht unterstützt", fmt.Sprintf("Automatische Installation für dieses OS %v nicht verfügbar", osInfo), *mainWindow)
return nil
}
go func() {
progressBar := widget.NewProgressBar()
progressDialog := dialog.NewCustomWithoutButtons("Download in progress", progressBar, *mainWindow)
progressDialog.Show()
homeDir, _ := os.UserHomeDir()
downloadDir := filepath.Join(homeDir, "Downloads")
filePath := filepath.Join(downloadDir, fileName)
err := download.WithProgressBar(downloadURL, filePath, progressBar)
progressDialog.Hide()
if err != nil {
log.Error(err.Error())
dialog.ShowError(err, *mainWindow)
return
}
var installCmd string
switch osInfo.ID {
case "debian", "ubuntu", "linuxmint":
installCmd = fmt.Sprintf("sudo -S dpkg -i %s", filePath)
case "fedora":
installCmd = fmt.Sprintf("sudo -S dnf install -y %s", filePath)
}
cmd := exec.Command("sh", "-c", installCmd)
cmd.Stdin = strings.NewReader(sudoPassword + "\n")
dialog.ShowInformation("Installation started",
"Installation of VSCode started.", *mainWindow)
output, err := cmd.CombinedOutput()
if err != nil {
errMsg := fmt.Errorf("installation failed:\n%s", output)
dialog.ShowError(errMsg, *mainWindow)
log.Error(errMsg.Error())
return
} else {
dialog.ShowInformation("Success", "VSCode succesfully installed!", *mainWindow)
CheckDependencies(dependencies)
}
}()
case 1: // Docker
go func() {
progressBar := widget.NewProgressBar()
progressDialog := dialog.NewCustomWithoutButtons("Docker installation in progress...", progressBar, *mainWindow)
progressDialog.Show()
osInfo, err := osinfo.GetLinuxDistribution()
if err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("error getting os infos: %v", err)
dialog.ShowError(errMsg, *mainWindow)
log.Error(errMsg.Error())
return
}
var commands []string
var cleanupCommands []string
var totalSteps int
switch osInfo.ID {
case "ubuntu", "linuxmint", "debian":
// Ubuntu/Debian Commands
distroPath := "ubuntu"
codeName := "$(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\")"
if osInfo.ID == "debian" {
distroPath = "debian"
codeName = "$(. /etc/os-release && echo \"$VERSION_CODENAME\")"
}
arch := "$(dpkg --print-architecture)"
commands = []string{
"apt-get update",
"apt-get install -y wget",
fmt.Sprintf("wget -qO- https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/ | grep -oP 'href=\"\\K[^\"]*(?=.*deb)' | xargs -I{} wget https://download.docker.com/linux/%s/dists/%s/pool/stable/%s/{}",
distroPath, codeName, arch, distroPath, codeName, arch),
"dpkg -i ./containerd.io*.deb docker-ce*.deb docker-ce-cli*.deb docker-buildx-plugin*.deb docker-compose-plugin*.deb",
"apt-get install -f -y",
"service docker start",
}
cleanupCommands = []string{
"rm -rf ./containerd.io*.deb ./docker-ce*.deb ./docker-ce-cli*.deb ./docker-buildx-plugin*.deb ./docker-compose-plugin*.deb",
"apt-get autoremove -y",
"apt-get clean",
}
totalSteps = len(commands) + len(cleanupCommands)
case "fedora":
fedoraVer := "$(rpm -E %fedora)"
commands = []string{
"dnf install -y wget",
fmt.Sprintf("wget https://download.docker.com/linux/fedora/%s/x86_64/stable/Packages/containerd-*.rpm docker-*.rpm docker-ce-*.rpm", fedoraVer),
"dnf install -y ./*.rpm",
"systemctl enable --now docker",
}
cleanupCommands = []string{
"rm -rf ./containerd-*.rpm ./docker-*.rpm ./docker-ce-*.rpm",
"dnf autoremove -y",
"dnf clean all",
}
totalSteps = len(commands) + len(cleanupCommands)
default:
progressDialog.Hide()
dialog.ShowInformation("not supported", "Automatic Docker installation not supported for your OS.", *mainWindow)
return
}
progressStep := 1.0 / float64(totalSteps)
currentProgress := 0.0
for _, cmd := range commands {
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
command.Stdin = strings.NewReader(sudoPassword + "\n")
if output, err := command.CombinedOutput(); err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("error at %s:\n%s", cmd, output)
dialog.ShowError(errMsg, *mainWindow)
log.Error(errMsg.Error())
return
}
currentProgress += progressStep
progressBar.SetValue(currentProgress)
}
for _, cmd := range cleanupCommands {
command := exec.Command("sudo", "-S", "sh", "-c", cmd)
command.Stdin = strings.NewReader(sudoPassword + "\n")
if output, err := command.CombinedOutput(); err != nil {
progressDialog.Hide()
errMsg := fmt.Errorf("cleanup error at %s:\n%s", cmd, output)
dialog.ShowError(errMsg, *mainWindow)
log.Error(errMsg.Error())
return
}
currentProgress += progressStep
progressBar.SetValue(currentProgress)
}
progressDialog.Hide()
dialog.ShowInformation(
"Installation finished",
"Docker was succesfully installed! Please re-start your system to let the changes take effect.",
*mainWindow,
)
CheckDependencies(dependencies)
}()
}
return nil
}
func showInstallProgressBar(window fyne.Window, message string, cmd *exec.Cmd, depName string, dependencies []Dependency) {
progress := widget.NewProgressBarInfinite()
content := container.NewVBox(
widget.NewLabel(message),
progress,
)
popup := widget.NewModalPopUp(content, window.Canvas())
popup.Show()
go func() {
err := cmd.Run()
popup.Hide()
if err != nil {
errMsg := fmt.Errorf("error during installation of %s: %v", depName, err)
dialog.ShowError(errMsg, window)
log.Error(errMsg.Error())
} else {
dialog.ShowInformation("Installation finished", fmt.Sprintf("%s successfully installed!", depName), window)
CheckDependencies(dependencies)
}
}()
}

View file

@ -64,7 +64,7 @@ func ShowDependencyScreen() {
ShowProjectScreen() ShowProjectScreen()
} else { } else {
dialog.ShowInformation("dependencies missing", dialog.ShowInformation("dependencies missing",
"Bitte installieren Sie alle erforderlichen Abhängigkeiten, bevor Sie fortfahren.", mainWindow) "Please install all required dependencies before proceeding.", mainWindow)
} }
}) })