first
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var preservePaths = map[string]bool{
|
||||
"config/settings.json": true,
|
||||
"data": true,
|
||||
}
|
||||
|
||||
func shouldPreserve(rel string) bool {
|
||||
rel = filepath.ToSlash(rel)
|
||||
for p := range preservePaths {
|
||||
if rel == p || strings.HasPrefix(rel, p+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isProcessRunning(pid int) bool {
|
||||
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(out), fmt.Sprintf("%d", pid))
|
||||
}
|
||||
|
||||
func main() {
|
||||
pid := flag.Int("pid", 0, "PID of main app to wait for")
|
||||
zipPath := flag.String("zip", "", "Path to update zip")
|
||||
target := flag.String("target", "", "Install directory")
|
||||
launch := flag.String("launch", "", "Executable to launch after update")
|
||||
flag.Parse()
|
||||
|
||||
if *zipPath == "" || *target == "" {
|
||||
fmt.Println("Usage: updater --pid PID --zip FILE --target DIR --launch EXE")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logsDir := filepath.Join(*target, "logs")
|
||||
os.MkdirAll(logsDir, 0755)
|
||||
logFile, _ := os.Create(filepath.Join(logsDir, "updater.log"))
|
||||
log := func(msg string, args ...interface{}) {
|
||||
line := fmt.Sprintf("[%s] %s\n", time.Now().Format("15:04:05"), fmt.Sprintf(msg, args...))
|
||||
fmt.Print(line)
|
||||
if logFile != nil {
|
||||
logFile.WriteString(line)
|
||||
}
|
||||
}
|
||||
|
||||
if *pid > 0 {
|
||||
log("Waiting for process %d to exit...", *pid)
|
||||
for i := 0; i < 30; i++ {
|
||||
if !isProcessRunning(*pid) {
|
||||
log("Process exited")
|
||||
break
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
log("Extracting %s", *zipPath)
|
||||
tmpDir := *zipPath + "_extract"
|
||||
os.RemoveAll(tmpDir)
|
||||
if err := extractZip(*zipPath, tmpDir); err != nil {
|
||||
log("Extract failed: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Auto-detect nested top-level directory and strip it
|
||||
entries, _ := os.ReadDir(tmpDir)
|
||||
if len(entries) == 1 && entries[0].IsDir() {
|
||||
nested := filepath.Join(tmpDir, entries[0].Name())
|
||||
log("Detected nested directory: %s, stripping", entries[0].Name())
|
||||
tmpDir = nested
|
||||
}
|
||||
|
||||
log("Installing to %s", *target)
|
||||
count := 0
|
||||
skipped := 0
|
||||
err := filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return err
|
||||
}
|
||||
rel, _ := filepath.Rel(tmpDir, path)
|
||||
if shouldPreserve(rel) {
|
||||
log(" Skip: %s", rel)
|
||||
skipped++
|
||||
return nil
|
||||
}
|
||||
dst := filepath.Join(*target, rel)
|
||||
os.MkdirAll(filepath.Dir(dst), 0755)
|
||||
|
||||
src, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
log(" Failed to write: %s (%v)", rel, err)
|
||||
return nil
|
||||
}
|
||||
defer out.Close()
|
||||
io.Copy(out, src)
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log("Install error: %v", err)
|
||||
}
|
||||
log("Updated %d files, skipped %d", count, skipped)
|
||||
|
||||
os.RemoveAll(tmpDir)
|
||||
os.Remove(*zipPath)
|
||||
log("Cleanup done")
|
||||
|
||||
if *launch != "" {
|
||||
exe := filepath.Join(*target, *launch)
|
||||
log("Launching %s", exe)
|
||||
cmd := exec.Command(exe)
|
||||
cmd.Dir = *target
|
||||
cmd.Start()
|
||||
}
|
||||
|
||||
log("Update complete")
|
||||
if logFile != nil {
|
||||
logFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func extractZip(src, dst string) error {
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
path := filepath.Join(dst, f.Name)
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, 0755)
|
||||
continue
|
||||
}
|
||||
os.MkdirAll(filepath.Dir(path), 0755)
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return err
|
||||
}
|
||||
io.Copy(out, rc)
|
||||
out.Close()
|
||||
rc.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user