mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
269 lines
7.5 KiB
Go
269 lines
7.5 KiB
Go
package version
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
)
|
|
|
|
type VersionInfo struct {
|
|
Current string
|
|
Latest string
|
|
IsGit bool
|
|
IsBranch bool
|
|
IsTag bool
|
|
HasUpdate bool
|
|
}
|
|
|
|
// VersionFetcher is an interface for fetching version information
|
|
type VersionFetcher interface {
|
|
GetCurrentVersion(dmsPath string) (string, error)
|
|
GetLatestVersion(dmsPath string) (string, error)
|
|
}
|
|
|
|
// DefaultVersionFetcher is the default implementation that uses git/curl
|
|
type DefaultVersionFetcher struct{}
|
|
|
|
func (d *DefaultVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
|
|
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
|
originalDir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer func() {
|
|
if err := os.Chdir(originalDir); err != nil {
|
|
log.Warnf("failed to change back to original directory: %v", err)
|
|
}
|
|
}()
|
|
|
|
if err := os.Chdir(dmsPath); err != nil {
|
|
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
|
}
|
|
|
|
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
|
|
if tagOutput, err := tagCmd.Output(); err == nil {
|
|
return strings.TrimSpace(string(tagOutput)), nil
|
|
}
|
|
|
|
branchCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
|
if branchOutput, err := branchCmd.Output(); err == nil {
|
|
branch := strings.TrimSpace(string(branchOutput))
|
|
revCmd := exec.Command("git", "rev-parse", "--short", "HEAD")
|
|
if revOutput, err := revCmd.Output(); err == nil {
|
|
rev := strings.TrimSpace(string(revOutput))
|
|
return fmt.Sprintf("%s@%s", branch, rev), nil
|
|
}
|
|
return branch, nil
|
|
}
|
|
}
|
|
|
|
cmd := exec.Command("dms", "--version")
|
|
if output, err := cmd.Output(); err == nil {
|
|
return strings.TrimSpace(string(output)), nil
|
|
}
|
|
|
|
return "unknown", nil
|
|
}
|
|
|
|
func (d *DefaultVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
|
|
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
|
|
originalDir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer func() {
|
|
if err := os.Chdir(originalDir); err != nil {
|
|
log.Warnf("failed to change back to original directory: %v", err)
|
|
}
|
|
}()
|
|
|
|
if err := os.Chdir(dmsPath); err != nil {
|
|
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
|
|
}
|
|
|
|
currentRefCmd := exec.Command("git", "symbolic-ref", "-q", "HEAD")
|
|
currentRefOutput, _ := currentRefCmd.Output()
|
|
onBranch := len(currentRefOutput) > 0
|
|
|
|
if !onBranch {
|
|
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
|
|
if _, err := tagCmd.Output(); err == nil {
|
|
// Add timeout to git fetch to prevent hanging
|
|
fetchCmd := exec.Command("timeout", "5s", "git", "fetch", "origin", "--tags", "--quiet")
|
|
if err := fetchCmd.Run(); err != nil {
|
|
log.Debugf("git fetch tags failed (continuing with local tags): %v", err)
|
|
}
|
|
|
|
latestTagCmd := exec.Command("git", "tag", "-l", "v*", "--sort=-version:refname")
|
|
latestTagOutput, err := latestTagCmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get latest tag: %w", err)
|
|
}
|
|
|
|
tags := strings.Split(strings.TrimSpace(string(latestTagOutput)), "\n")
|
|
if len(tags) == 0 || tags[0] == "" {
|
|
return "", fmt.Errorf("no version tags found")
|
|
}
|
|
return tags[0], nil
|
|
}
|
|
} else {
|
|
branchCmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
|
|
branchOutput, err := branchCmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get current branch: %w", err)
|
|
}
|
|
currentBranch := strings.TrimSpace(string(branchOutput))
|
|
|
|
// Add timeout to git fetch to prevent hanging
|
|
fetchCmd := exec.Command("timeout", "5s", "git", "fetch", "origin", currentBranch, "--quiet")
|
|
if err := fetchCmd.Run(); err != nil {
|
|
log.Debugf("git fetch branch failed (continuing with local ref): %v", err)
|
|
}
|
|
|
|
remoteRevCmd := exec.Command("git", "rev-parse", "--short", fmt.Sprintf("origin/%s", currentBranch))
|
|
remoteRevOutput, err := remoteRevCmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get remote revision: %w", err)
|
|
}
|
|
remoteRev := strings.TrimSpace(string(remoteRevOutput))
|
|
return fmt.Sprintf("%s@%s", currentBranch, remoteRev), nil
|
|
}
|
|
}
|
|
|
|
// Add timeout to prevent hanging when GitHub is down
|
|
cmd := exec.Command("curl", "-s", "--max-time", "5", "https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to fetch latest release: %w", err)
|
|
}
|
|
|
|
var result struct {
|
|
TagName string `json:"tag_name"`
|
|
}
|
|
if err := json.Unmarshal(output, &result); err != nil {
|
|
for _, line := range strings.Split(string(output), "\n") {
|
|
if strings.Contains(line, "\"tag_name\"") {
|
|
parts := strings.Split(line, "\"")
|
|
if len(parts) >= 4 {
|
|
return parts[3], nil
|
|
}
|
|
}
|
|
}
|
|
return "", fmt.Errorf("failed to parse latest version: %w", err)
|
|
}
|
|
|
|
return result.TagName, nil
|
|
}
|
|
|
|
// defaultFetcher is used by the public functions
|
|
var defaultFetcher VersionFetcher = &DefaultVersionFetcher{}
|
|
|
|
func GetCurrentDMSVersion() (string, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
|
|
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
|
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
|
return "", fmt.Errorf("DMS not installed")
|
|
}
|
|
|
|
return defaultFetcher.GetCurrentVersion(dmsPath)
|
|
}
|
|
|
|
func GetLatestDMSVersion() (string, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
|
|
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
|
return defaultFetcher.GetLatestVersion(dmsPath)
|
|
}
|
|
|
|
func GetDMSVersionInfo() (*VersionInfo, error) {
|
|
return GetDMSVersionInfoWithFetcher(defaultFetcher)
|
|
}
|
|
|
|
func GetDMSVersionInfoWithFetcher(fetcher VersionFetcher) (*VersionInfo, error) {
|
|
homeDir, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get home directory: %w", err)
|
|
}
|
|
|
|
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
|
|
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("DMS not installed")
|
|
}
|
|
|
|
current, err := fetcher.GetCurrentVersion(dmsPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
latest, err := fetcher.GetLatestVersion(dmsPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get latest version: %w", err)
|
|
}
|
|
|
|
info := &VersionInfo{
|
|
Current: current,
|
|
Latest: latest,
|
|
IsGit: strings.Contains(current, "@"),
|
|
IsBranch: strings.Contains(current, "@"),
|
|
IsTag: !strings.Contains(current, "@") && strings.HasPrefix(current, "v"),
|
|
}
|
|
|
|
if info.IsBranch {
|
|
parts := strings.Split(current, "@")
|
|
latestParts := strings.Split(latest, "@")
|
|
if len(parts) == 2 && len(latestParts) == 2 {
|
|
info.HasUpdate = parts[1] != latestParts[1]
|
|
}
|
|
} else if info.IsTag {
|
|
info.HasUpdate = current != latest
|
|
} else {
|
|
info.HasUpdate = false
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func CompareVersions(v1, v2 string) int {
|
|
v1 = strings.TrimPrefix(v1, "v")
|
|
v2 = strings.TrimPrefix(v2, "v")
|
|
|
|
parts1 := strings.Split(v1, ".")
|
|
parts2 := strings.Split(v2, ".")
|
|
|
|
maxLen := len(parts1)
|
|
if len(parts2) > maxLen {
|
|
maxLen = len(parts2)
|
|
}
|
|
|
|
for i := 0; i < maxLen; i++ {
|
|
var p1, p2 int
|
|
if i < len(parts1) {
|
|
fmt.Sscanf(parts1[i], "%d", &p1) //nolint:errcheck
|
|
}
|
|
if i < len(parts2) {
|
|
fmt.Sscanf(parts2[i], "%d", &p2) //nolint:errcheck
|
|
}
|
|
|
|
if p1 < p2 {
|
|
return -1
|
|
}
|
|
if p1 > p2 {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|