263 lines
5.9 KiB
Go
263 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"archive/zip"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func executeAction(versionDir string, action Action, index int, backupDir string) tea.Cmd {
|
|
total := index + 1
|
|
prefix := fmt.Sprintf("[%d]", total)
|
|
|
|
switch action.Type {
|
|
case "add":
|
|
return executeAdd(versionDir, action, index, backupDir, prefix)
|
|
case "delete":
|
|
return func() tea.Msg {
|
|
absPath := filepath.Join(versionDir, action.Path)
|
|
if err := backupPath(versionDir, action.Path, backupDir); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("backup failed: %w", err)}
|
|
}
|
|
if err := os.RemoveAll(absPath); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("delete failed: %w", err)}
|
|
}
|
|
return actionCompleteMsg{index: index}
|
|
}
|
|
case "copy":
|
|
return func() tea.Msg {
|
|
dst := filepath.Join(versionDir, action.NewPath)
|
|
if _, err := os.Stat(dst); err == nil {
|
|
if err := backupPath(versionDir, action.NewPath, backupDir); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("backup failed: %w", err)}
|
|
}
|
|
}
|
|
src := filepath.Join(versionDir, action.Path)
|
|
if err := copyPath(src, dst); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("copy failed: %w", err)}
|
|
}
|
|
return actionCompleteMsg{index: index}
|
|
}
|
|
case "move":
|
|
return func() tea.Msg {
|
|
src := filepath.Join(versionDir, action.Path)
|
|
dst := filepath.Join(versionDir, action.NewPath)
|
|
if _, err := os.Stat(dst); err == nil {
|
|
if err := backupPath(versionDir, action.NewPath, backupDir); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("backup failed: %w", err)}
|
|
}
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("mkdir failed: %w", err)}
|
|
}
|
|
if err := os.Rename(src, dst); err != nil {
|
|
if err := copyPath(src, dst); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("move failed: %w", err)}
|
|
}
|
|
os.RemoveAll(src)
|
|
}
|
|
return actionCompleteMsg{index: index}
|
|
}
|
|
default:
|
|
return func() tea.Msg {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("unknown action type: %s", action.Type)}
|
|
}
|
|
}
|
|
}
|
|
|
|
func executeAdd(versionDir string, action Action, index int, backupDir, prefix string) tea.Cmd {
|
|
return func() tea.Msg {
|
|
absPath := filepath.Join(versionDir, action.Path)
|
|
|
|
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("mkdir failed: %w", err)}
|
|
}
|
|
|
|
if _, err := os.Stat(absPath); err == nil {
|
|
if err := backupPath(versionDir, action.Path, backupDir); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("backup failed: %w", err)}
|
|
}
|
|
}
|
|
|
|
err := downloadFile(action.URL, absPath)
|
|
if err != nil {
|
|
if len(action.Mirrors) > 0 {
|
|
return mirrorChoiceMsg{index: index, mirrors: action.Mirrors, action: action}
|
|
}
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("download failed: %w", err)}
|
|
}
|
|
|
|
if action.Unzip {
|
|
destDir := filepath.Dir(absPath)
|
|
if err := unzipFile(absPath, destDir); err != nil {
|
|
return actionErrorMsg{index: index, err: fmt.Errorf("unzip failed: %w", err)}
|
|
}
|
|
os.Remove(absPath)
|
|
}
|
|
|
|
return actionCompleteMsg{index: index}
|
|
}
|
|
}
|
|
|
|
func waitForProgress(ch chan float64, index int) tea.Cmd {
|
|
return func() tea.Msg {
|
|
p, ok := <-ch
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return actionProgressMsg{percent: p}
|
|
}
|
|
}
|
|
|
|
func downloadFile(url, destPath string) error {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
|
}
|
|
|
|
tmpPath := destPath + ".tmp"
|
|
f, err := os.Create(tmpPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(f, resp.Body)
|
|
f.Close()
|
|
if err != nil {
|
|
os.Remove(tmpPath)
|
|
return err
|
|
}
|
|
|
|
return os.Rename(tmpPath, destPath)
|
|
}
|
|
|
|
func unzipFile(zipPath, destDir string) error {
|
|
r, err := zip.OpenReader(zipPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer r.Close()
|
|
|
|
for _, f := range r.File {
|
|
target := filepath.Join(destDir, f.Name)
|
|
|
|
if !strings.HasPrefix(filepath.Clean(target), filepath.Clean(destDir)+string(os.PathSeparator)) {
|
|
continue
|
|
}
|
|
|
|
if f.FileInfo().IsDir() {
|
|
os.MkdirAll(target, 0o755)
|
|
continue
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
rc, err := f.Open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
outFile, err := os.Create(target)
|
|
if err != nil {
|
|
rc.Close()
|
|
return err
|
|
}
|
|
|
|
_, err = io.Copy(outFile, rc)
|
|
outFile.Close()
|
|
rc.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func backupPath(versionDir, relativePath, backupDir string) error {
|
|
src := filepath.Join(versionDir, relativePath)
|
|
if _, err := os.Stat(src); os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
dst := filepath.Join(versionDir, backupDir, relativePath)
|
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
info, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return copyDir(src, dst)
|
|
}
|
|
return copyFile(src, dst)
|
|
}
|
|
|
|
func copyPath(src, dst string) error {
|
|
info, err := os.Stat(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return copyDir(src, dst)
|
|
}
|
|
return copyFile(src, dst)
|
|
}
|
|
|
|
func copyFile(src, dst string) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, in)
|
|
return err
|
|
}
|
|
|
|
func copyDir(src, dst string) error {
|
|
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rel, err := filepath.Rel(src, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
target := filepath.Join(dst, rel)
|
|
|
|
if info.IsDir() {
|
|
return os.MkdirAll(target, 0o755)
|
|
}
|
|
return copyFile(path, target)
|
|
})
|
|
}
|