Files
arinera-minecraft-tool/actions.go
2026-05-28 20:13:57 +08:00

285 lines
6.6 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}
}
case "new":
return func() tea.Msg {
absPath := filepath.Join(versionDir, action.Path)
if _, err := os.Stat(absPath); err == nil {
return actionCompleteMsg{index: index}
}
if action.IsDir {
if err := os.MkdirAll(absPath, 0o755); err != nil {
return actionErrorMsg{index: index, err: fmt.Errorf("create dir failed: %w", err)}
}
} else {
if err := os.MkdirAll(filepath.Dir(absPath), 0o755); err != nil {
return actionErrorMsg{index: index, err: fmt.Errorf("mkdir failed: %w", err)}
}
f, err := os.Create(absPath)
if err != nil {
return actionErrorMsg{index: index, err: fmt.Errorf("create file failed: %w", err)}
}
f.Close()
}
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)
})
}