package update import ( "fmt" "io" "log" "os" "os/exec" "path/filepath" "runtime" "vrc_osc_go/internal/buildinfo" "vrc_osc_go/internal/common" ) type Manager struct { Client *Client OwnerRepo string AssetName string CurrentLabel string } func (m *Manager) CheckAndUpdate() (bool, error) { if m.Client == nil || m.OwnerRepo == "" || m.AssetName == "" { return false, nil } latest, err := m.Client.LatestRelease(m.OwnerRepo) if err != nil { return false, err } if latest.TagName == "" || latest.TagName == m.CurrentLabel { return false, nil } assetURL := "" for _, a := range latest.Assets { if a.Name == m.AssetName { assetURL = a.URL break } } if assetURL == "" { return false, fmt.Errorf("asset %q not found in release %s", m.AssetName, latest.TagName) } base := common.RootDir() tmpDir := filepath.Join(base, "update", latest.TagName) if err := os.MkdirAll(tmpDir, 0o755); err != nil { return false, err } zipPath := filepath.Join(tmpDir, m.AssetName) log.Printf("update available: %s -> %s", m.CurrentLabel, latest.TagName) if err := m.Client.DownloadAsset(assetURL, zipPath); err != nil { return false, err } if err := ExtractZip(zipPath, tmpDir); err != nil { return false, err } exePath := filepath.Join(base, CurrentExeName()) newExe := filepath.Join(tmpDir, CurrentExeName()) if _, err := os.Stat(newExe); err != nil { return false, err } backup := exePath + ".bak" _ = os.Remove(backup) if err := os.Rename(exePath, backup); err != nil { return false, err } if err := CopyFile(newExe, exePath); err != nil { _ = os.Rename(backup, exePath) return false, err } _ = os.Remove(backup) return true, nil } func CopyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { return err } out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) return err } func Launch(exe string, args []string) error { cmd := exec.Command(exe, args...) cmd.Dir = filepath.Dir(exe) return cmd.Start() } func Version() string { return buildinfo.Version } func CurrentExeName() string { if runtime.GOOS == "windows" { return "vrc_osc.exe" } return "vrc_osc" }