diff --git a/openflare_agent/internal/updater/updater.go b/openflare_agent/internal/updater/updater.go index 170e9b9f..46ab41d1 100644 --- a/openflare_agent/internal/updater/updater.go +++ b/openflare_agent/internal/updater/updater.go @@ -9,9 +9,9 @@ import ( "io" "log/slog" "net/http" + "openflare/utils" "os" "runtime" - "strconv" "strings" "time" @@ -360,146 +360,6 @@ func buildReleaseCheckKey(options agent.UpdateOptions, remoteVersion string) str return channel + ":" + remoteVersion } -type versionInfo struct { - valid bool - isDev bool - numbers []int - prerelease []string -} - -func parseVersionInfo(version string) versionInfo { - normalized := normalizeVersion(version) - if normalized == "" || strings.EqualFold(normalized, "dev") { - return versionInfo{isDev: strings.EqualFold(normalized, "dev")} - } - base := normalized - prerelease := "" - if index := strings.IndexRune(normalized, '-'); index >= 0 { - base = normalized[:index] - prerelease = normalized[index+1:] - } - segments := strings.Split(base, ".") - parts := make([]int, 0, len(segments)) - for _, segment := range segments { - segment = strings.TrimSpace(segment) - if segment == "" { - parts = append(parts, 0) - continue - } - numeric := strings.Builder{} - for _, r := range segment { - if r < '0' || r > '9' { - break - } - numeric.WriteRune(r) - } - if numeric.Len() == 0 { - return versionInfo{} - } - value, err := strconv.Atoi(numeric.String()) - if err != nil { - return versionInfo{} - } - parts = append(parts, value) - } - info := versionInfo{valid: len(parts) > 0, numbers: parts} - if prerelease != "" { - info.prerelease = splitPrereleaseIdentifiers(prerelease) - } - return info -} - -func splitPrereleaseIdentifiers(value string) []string { - parts := strings.FieldsFunc(strings.TrimSpace(value), func(r rune) bool { - return r == '.' || r == '-' - }) - filtered := make([]string, 0, len(parts)) - for _, part := range parts { - part = strings.TrimSpace(part) - if part != "" { - filtered = append(filtered, part) - } - } - return filtered -} - func compareVersions(local string, remote string) int { - left := parseVersionInfo(local) - right := parseVersionInfo(remote) - if left.isDev { - if right.valid { - return -1 - } - return 0 - } - if !left.valid || !right.valid { - return 0 - } - - maxLen := len(left.numbers) - if len(right.numbers) > maxLen { - maxLen = len(right.numbers) - } - for index := 0; index < maxLen; index++ { - leftValue := 0 - rightValue := 0 - if index < len(left.numbers) { - leftValue = left.numbers[index] - } - if index < len(right.numbers) { - rightValue = right.numbers[index] - } - if leftValue < rightValue { - return -1 - } - if leftValue > rightValue { - return 1 - } - } - if len(left.prerelease) == 0 && len(right.prerelease) == 0 { - return 0 - } - if len(left.prerelease) == 0 { - return 1 - } - if len(right.prerelease) == 0 { - return -1 - } - maxLen = len(left.prerelease) - if len(right.prerelease) > maxLen { - maxLen = len(right.prerelease) - } - for index := 0; index < maxLen; index++ { - if index >= len(left.prerelease) { - return -1 - } - if index >= len(right.prerelease) { - return 1 - } - leftPart := left.prerelease[index] - rightPart := right.prerelease[index] - leftNumber, leftErr := strconv.Atoi(leftPart) - rightNumber, rightErr := strconv.Atoi(rightPart) - switch { - case leftErr == nil && rightErr == nil: - if leftNumber < rightNumber { - return -1 - } - if leftNumber > rightNumber { - return 1 - } - case leftErr == nil: - return -1 - case rightErr == nil: - return 1 - default: - if leftPart < rightPart { - return -1 - } - if leftPart > rightPart { - return 1 - } - } - } - return 0 + return utils.CompareVersions(local, remote) } diff --git a/openflare_server/service/update.go b/openflare_server/service/update.go index 0176eb91..1a40b699 100644 --- a/openflare_server/service/update.go +++ b/openflare_server/service/update.go @@ -11,11 +11,11 @@ import ( "log/slog" "net/http" "openflare/common" + "openflare/utils" "os" "os/exec" "path/filepath" "runtime" - "strconv" "strings" "sync" "time" @@ -568,203 +568,7 @@ func (channel ReleaseChannel) String() string { } func isVersionNewer(current string, latest string) bool { - currentInfo := parseVersionInfo(current) - latestInfo := parseVersionInfo(latest) - if currentInfo.IsDev { - return latestInfo.Valid - } - if !currentInfo.Valid || !latestInfo.Valid { - return false - } - return compareVersionInfo(currentInfo, latestInfo) < 0 -} - -type versionInfo struct { - Valid bool - IsDev bool - Numbers []int - Prerelease []string - GitDescribeDistance int - GitDescribeTail []string -} - -func parseVersionInfo(version string) versionInfo { - normalized := strings.TrimSpace(strings.TrimPrefix(version, "v")) - if normalized == "" || normalized == "dev" { - return versionInfo{IsDev: strings.EqualFold(normalized, "dev")} - } - base := normalized - prerelease := "" - if separator := strings.IndexRune(normalized, '-'); separator >= 0 { - base = normalized[:separator] - prerelease = normalized[separator+1:] - } - - segments := strings.Split(base, ".") - parts := make([]int, 0, len(segments)) - for _, segment := range segments { - segment = strings.TrimSpace(segment) - if segment == "" { - parts = append(parts, 0) - continue - } - - numeric := strings.Builder{} - for _, r := range segment { - if r < '0' || r > '9' { - break - } - numeric.WriteRune(r) - } - if numeric.Len() == 0 { - parts = append(parts, 0) - continue - } - value, err := strconv.Atoi(numeric.String()) - if err != nil { - return versionInfo{} - } - parts = append(parts, value) - } - info := versionInfo{Valid: len(parts) > 0, Numbers: parts} - if prerelease != "" { - identifiers := splitPrereleaseIdentifiers(prerelease) - if distance, tail, ok := parseGitDescribeIdentifiers(identifiers); ok { - info.GitDescribeDistance = distance - info.GitDescribeTail = tail - } else { - info.Prerelease = identifiers - } - } - return info -} - -func parseGitDescribeIdentifiers(identifiers []string) (int, []string, bool) { - if len(identifiers) < 2 { - return 0, nil, false - } - distance, err := strconv.Atoi(strings.TrimSpace(identifiers[0])) - if err != nil || distance <= 0 { - return 0, nil, false - } - commitToken := strings.TrimSpace(identifiers[1]) - if commitToken == "" || !strings.HasPrefix(strings.ToLower(commitToken), "g") { - return 0, nil, false - } - return distance, identifiers[1:], true -} - -func splitPrereleaseIdentifiers(value string) []string { - parts := strings.FieldsFunc(strings.TrimSpace(value), func(r rune) bool { - return r == '.' || r == '-' - }) - filtered := make([]string, 0, len(parts)) - for _, part := range parts { - part = strings.TrimSpace(part) - if part != "" { - filtered = append(filtered, part) - } - } - return filtered -} - -func compareVersionInfo(left versionInfo, right versionInfo) int { - maxLen := len(left.Numbers) - if len(right.Numbers) > maxLen { - maxLen = len(right.Numbers) - } - for index := 0; index < maxLen; index++ { - leftValue := 0 - rightValue := 0 - if index < len(left.Numbers) { - leftValue = left.Numbers[index] - } - if index < len(right.Numbers) { - rightValue = right.Numbers[index] - } - if leftValue < rightValue { - return -1 - } - if leftValue > rightValue { - return 1 - } - } - - if left.GitDescribeDistance != right.GitDescribeDistance { - if left.GitDescribeDistance < right.GitDescribeDistance { - return -1 - } - return 1 - } - if left.GitDescribeDistance > 0 || right.GitDescribeDistance > 0 { - maxLen = len(left.GitDescribeTail) - if len(right.GitDescribeTail) > maxLen { - maxLen = len(right.GitDescribeTail) - } - for index := 0; index < maxLen; index++ { - if index >= len(left.GitDescribeTail) { - return -1 - } - if index >= len(right.GitDescribeTail) { - return 1 - } - if left.GitDescribeTail[index] < right.GitDescribeTail[index] { - return -1 - } - if left.GitDescribeTail[index] > right.GitDescribeTail[index] { - return 1 - } - } - return 0 - } - - if len(left.Prerelease) == 0 && len(right.Prerelease) == 0 { - return 0 - } - if len(left.Prerelease) == 0 { - return 1 - } - if len(right.Prerelease) == 0 { - return -1 - } - - maxLen = len(left.Prerelease) - if len(right.Prerelease) > maxLen { - maxLen = len(right.Prerelease) - } - for index := 0; index < maxLen; index++ { - if index >= len(left.Prerelease) { - return -1 - } - if index >= len(right.Prerelease) { - return 1 - } - leftPart := left.Prerelease[index] - rightPart := right.Prerelease[index] - leftNumber, leftErr := strconv.Atoi(leftPart) - rightNumber, rightErr := strconv.Atoi(rightPart) - switch { - case leftErr == nil && rightErr == nil: - if leftNumber < rightNumber { - return -1 - } - if leftNumber > rightNumber { - return 1 - } - case leftErr == nil: - return -1 - case rightErr == nil: - return 1 - default: - if leftPart < rightPart { - return -1 - } - if leftPart > rightPart { - return 1 - } - } - } - return 0 + return utils.CompareVersions(current, latest) < 0 } func buildUploadedServerBinaryView(fileName string, currentVersion string, detectedVersion string, uploadedAt time.Time) *UploadedServerBinary { diff --git a/openflare_server/utils/version.go b/openflare_server/utils/version.go new file mode 100644 index 00000000..a3ea780e --- /dev/null +++ b/openflare_server/utils/version.go @@ -0,0 +1,208 @@ +package utils + +import ( + "strconv" + "strings" +) + +type VersionInfo struct { + Valid bool + IsDev bool + Numbers []int + Prerelease []string + GitDescribeDistance int + GitDescribeTail []string +} + +func ParseVersionInfo(version string) VersionInfo { + normalized := strings.TrimSpace(strings.TrimPrefix(version, "v")) + if normalized == "" || normalized == "dev" { + return VersionInfo{IsDev: strings.EqualFold(normalized, "dev")} + } + base := normalized + prerelease := "" + if separator := strings.IndexRune(normalized, '-'); separator >= 0 { + base = normalized[:separator] + prerelease = normalized[separator+1:] + } + + segments := strings.Split(base, ".") + parts := make([]int, 0, len(segments)) + for _, segment := range segments { + segment = strings.TrimSpace(segment) + if segment == "" { + parts = append(parts, 0) + continue + } + + numeric := strings.Builder{} + for _, r := range segment { + if r < '0' || r > '9' { + break + } + numeric.WriteRune(r) + } + if numeric.Len() == 0 { + parts = append(parts, 0) + continue + } + value, err := strconv.Atoi(numeric.String()) + if err != nil { + return VersionInfo{} + } + parts = append(parts, value) + } + info := VersionInfo{Valid: len(parts) > 0, Numbers: parts} + if prerelease != "" { + identifiers := splitPrereleaseIdentifiers(prerelease) + if distance, tail, ok := parseGitDescribeIdentifiers(identifiers); ok { + info.GitDescribeDistance = distance + info.GitDescribeTail = tail + } else { + info.Prerelease = identifiers + } + } + return info +} + +func parseGitDescribeIdentifiers(identifiers []string) (int, []string, bool) { + if len(identifiers) < 2 { + return 0, nil, false + } + distance, err := strconv.Atoi(strings.TrimSpace(identifiers[0])) + if err != nil || distance <= 0 { + return 0, nil, false + } + commitToken := strings.TrimSpace(identifiers[1]) + if commitToken == "" || !strings.HasPrefix(strings.ToLower(commitToken), "g") { + return 0, nil, false + } + return distance, identifiers[1:], true +} + +func splitPrereleaseIdentifiers(value string) []string { + parts := strings.FieldsFunc(strings.TrimSpace(value), func(r rune) bool { + return r == '.' || r == '-' + }) + filtered := make([]string, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + filtered = append(filtered, part) + } + } + return filtered +} + +// CompareVersions compares two version strings. +// Returns -1 if left < right, 1 if left > right, and 0 if they are equal. +func CompareVersions(local, remote string) int { + left := ParseVersionInfo(local) + right := ParseVersionInfo(remote) + if left.IsDev { + if right.Valid { + return -1 + } + return 0 + } + if !left.Valid || !right.Valid { + return 0 + } + + maxLen := len(left.Numbers) + if len(right.Numbers) > maxLen { + maxLen = len(right.Numbers) + } + for index := 0; index < maxLen; index++ { + leftValue := 0 + rightValue := 0 + if index < len(left.Numbers) { + leftValue = left.Numbers[index] + } + if index < len(right.Numbers) { + rightValue = right.Numbers[index] + } + if leftValue < rightValue { + return -1 + } + if leftValue > rightValue { + return 1 + } + } + + if left.GitDescribeDistance != right.GitDescribeDistance { + if left.GitDescribeDistance < right.GitDescribeDistance { + return -1 + } + return 1 + } + if left.GitDescribeDistance > 0 || right.GitDescribeDistance > 0 { + maxLen = len(left.GitDescribeTail) + if len(right.GitDescribeTail) > maxLen { + maxLen = len(right.GitDescribeTail) + } + for index := 0; index < maxLen; index++ { + if index >= len(left.GitDescribeTail) { + return -1 + } + if index >= len(right.GitDescribeTail) { + return 1 + } + if left.GitDescribeTail[index] < right.GitDescribeTail[index] { + return -1 + } + if left.GitDescribeTail[index] > right.GitDescribeTail[index] { + return 1 + } + } + return 0 + } + + if len(left.Prerelease) == 0 && len(right.Prerelease) == 0 { + return 0 + } + if len(left.Prerelease) == 0 { + return 1 + } + if len(right.Prerelease) == 0 { + return -1 + } + + maxLen = len(left.Prerelease) + if len(right.Prerelease) > maxLen { + maxLen = len(right.Prerelease) + } + for index := 0; index < maxLen; index++ { + if index >= len(left.Prerelease) { + return -1 + } + if index >= len(right.Prerelease) { + return 1 + } + leftPart := left.Prerelease[index] + rightPart := right.Prerelease[index] + leftNumber, leftErr := strconv.Atoi(leftPart) + rightNumber, rightErr := strconv.Atoi(rightPart) + switch { + case leftErr == nil && rightErr == nil: + if leftNumber < rightNumber { + return -1 + } + if leftNumber > rightNumber { + return 1 + } + case leftErr == nil: + return -1 + case rightErr == nil: + return 1 + default: + if leftPart < rightPart { + return -1 + } + if leftPart > rightPart { + return 1 + } + } + } + return 0 +}