mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-05 20:55:29 +03:00
refactor(util): move panel version comparison into a shared package
The node-assignment path in the service package needs the same MAJOR.MINOR.PATCH comparison the panel updater uses, but service/panel imports service, so the helper cannot stay there.
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
// Package version compares 3x-ui release versions ("v3.8.0" or "3.8.0").
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compare returns -1, 0 or 1 as a is older than, equal to or newer than b, and
|
||||||
|
// false when either side is not a plain MAJOR.MINOR.PATCH version.
|
||||||
|
func Compare(a, b string) (int, bool) {
|
||||||
|
aParts, okA := parse(a)
|
||||||
|
bParts, okB := parse(b)
|
||||||
|
if !okA || !okB {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
for i := range len(aParts) {
|
||||||
|
if aParts[i] > bParts[i] {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
if aParts[i] < bParts[i] {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize strips surrounding space and a leading "v" from a version tag.
|
||||||
|
func Normalize(v string) string {
|
||||||
|
return strings.TrimPrefix(strings.TrimSpace(v), "v")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(v string) ([3]int, bool) {
|
||||||
|
var result [3]int
|
||||||
|
parts := strings.Split(Normalize(v), ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
for i, part := range parts {
|
||||||
|
n, err := strconv.Atoi(part)
|
||||||
|
if err != nil {
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
result[i] = n
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCompareRejectsUnexpectedFormats(t *testing.T) {
|
||||||
|
if _, ok := Compare("latest", "2.9.3"); ok {
|
||||||
|
t.Fatal("expected non-semver latest tag to be rejected")
|
||||||
|
}
|
||||||
|
if _, ok := Compare("v2.9", "2.9.3"); ok {
|
||||||
|
t.Fatal("expected short version to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||||
|
"github.com/mhsanaei/3x-ui/v3/internal/util/version"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/global"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/global"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
||||||
)
|
)
|
||||||
@@ -522,50 +523,13 @@ func resolveUpdateFolders() (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isNewerVersion(latest string, current string) bool {
|
func isNewerVersion(latest string, current string) bool {
|
||||||
cmp, ok := compareVersionStrings(latest, current)
|
cmp, ok := version.Compare(latest, current)
|
||||||
if !ok {
|
if !ok {
|
||||||
return normalizeVersionTag(latest) != normalizeVersionTag(current)
|
return version.Normalize(latest) != version.Normalize(current)
|
||||||
}
|
}
|
||||||
return cmp > 0
|
return cmp > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareVersionStrings(a string, b string) (int, bool) {
|
|
||||||
aParts, okA := parseVersionParts(a)
|
|
||||||
bParts, okB := parseVersionParts(b)
|
|
||||||
if !okA || !okB {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
for i := range len(aParts) {
|
|
||||||
if aParts[i] > bParts[i] {
|
|
||||||
return 1, true
|
|
||||||
}
|
|
||||||
if aParts[i] < bParts[i] {
|
|
||||||
return -1, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseVersionParts(version string) ([3]int, bool) {
|
|
||||||
var result [3]int
|
|
||||||
parts := strings.Split(normalizeVersionTag(version), ".")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return result, false
|
|
||||||
}
|
|
||||||
for i, part := range parts {
|
|
||||||
n, err := strconv.Atoi(part)
|
|
||||||
if err != nil {
|
|
||||||
return result, false
|
|
||||||
}
|
|
||||||
result[i] = n
|
|
||||||
}
|
|
||||||
return result, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeVersionTag(version string) string {
|
|
||||||
return strings.TrimPrefix(strings.TrimSpace(version), "v")
|
|
||||||
}
|
|
||||||
|
|
||||||
func shellQuote(value string) string {
|
func shellQuote(value string) string {
|
||||||
return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'"
|
return "'" + strings.ReplaceAll(value, "'", "'\\''") + "'"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,6 @@ func TestIsNewerVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompareVersionStringsRejectsUnexpectedFormats(t *testing.T) {
|
|
||||||
if _, ok := compareVersionStrings("latest", "2.9.3"); ok {
|
|
||||||
t.Fatal("expected non-semver latest tag to be rejected")
|
|
||||||
}
|
|
||||||
if _, ok := compareVersionStrings("v2.9", "2.9.3"); ok {
|
|
||||||
t.Fatal("expected short version to be rejected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShellQuote(t *testing.T) {
|
func TestShellQuote(t *testing.T) {
|
||||||
if got := shellQuote("/usr/bin/curl"); got != "'/usr/bin/curl'" {
|
if got := shellQuote("/usr/bin/curl"); got != "'/usr/bin/curl'" {
|
||||||
t.Fatalf("unexpected quote result: %s", got)
|
t.Fatalf("unexpected quote result: %s", got)
|
||||||
|
|||||||
Reference in New Issue
Block a user