From cdb3ffe43984a460991a6107cd056ef7cbde4593 Mon Sep 17 00:00:00 2001 From: yusing Date: Fri, 28 Mar 2025 08:08:33 +0800 Subject: [PATCH] refactor: clean up code and enhance utilities with new functions --- internal/utils/io.go | 6 ----- internal/utils/serialization.go | 10 ++++---- internal/utils/strutils/filepath.go | 11 ++++++++ internal/utils/strutils/format.go | 39 +++++++++++++++++++++++++++++ internal/utils/testing/testing.go | 9 +++++++ internal/watcher/events/events.go | 2 ++ internal/watcher/health/config.go | 8 +++--- internal/watcher/health/status.go | 29 +++++++++++++++++++++ internal/watcher/health/types.go | 6 ++--- 9 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 internal/utils/strutils/filepath.go diff --git a/internal/utils/io.go b/internal/utils/io.go index f24da03..2ee52d2 100644 --- a/internal/utils/io.go +++ b/internal/utils/io.go @@ -106,12 +106,6 @@ func (p BidirectionalPipe) Start() gperr.Error { return b.Error() } -var copyBufPool = sync.Pool{ - New: func() any { - return make([]byte, copyBufSize) - }, -} - type httpFlusher interface { Flush() error } diff --git a/internal/utils/serialization.go b/internal/utils/serialization.go index 7f9251e..cebcc73 100644 --- a/internal/utils/serialization.go +++ b/internal/utils/serialization.go @@ -529,10 +529,10 @@ func SaveJSON[T any](path string, src *T, perm os.FileMode) error { return os.WriteFile(path, data, perm) } -func LoadJSONIfExist[T any](path string, dst *T) (exists bool, err error) { - err = loadSerialized(path, dst, json.Unmarshal) - if err != nil && os.IsNotExist(err) { - return false, nil +func LoadJSONIfExist[T any](path string, dst *T) error { + err := loadSerialized(path, dst, json.Unmarshal) + if os.IsNotExist(err) { + return nil } - return true, err + return err } diff --git a/internal/utils/strutils/filepath.go b/internal/utils/strutils/filepath.go new file mode 100644 index 0000000..86afe0e --- /dev/null +++ b/internal/utils/strutils/filepath.go @@ -0,0 +1,11 @@ +package strutils + +import "strings" + +// IsValidFilename checks if a filename is safe and doesn't contain path traversal attempts +// Returns true if the filename is valid, false otherwise +func IsValidFilename(filename string) bool { + return !strings.Contains(filename, "/") && + !strings.Contains(filename, "\\") && + !strings.Contains(filename, "..") +} diff --git a/internal/utils/strutils/format.go b/internal/utils/strutils/format.go index a604e99..626b701 100644 --- a/internal/utils/strutils/format.go +++ b/internal/utils/strutils/format.go @@ -2,6 +2,7 @@ package strutils import ( "fmt" + "math" "strconv" "strings" "time" @@ -65,6 +66,44 @@ func ParseBool(s string) bool { } } +func formatFloat(f float64) string { + f = math.Round(f*100) / 100 + if f == 0 { + return "0" + } + return strconv.FormatFloat(f, 'f', -1, 64) +} + +func FormatByteSize[T ~int64 | ~uint64 | ~float64](size T) (value, unit string) { + const ( + _ = (1 << (10 * iota)) + kb + mb + gb + tb + pb + ) + switch { + case size < kb: + return fmt.Sprintf("%v", size), "B" + case size < mb: + return formatFloat(float64(size) / kb), "KiB" + case size < gb: + return formatFloat(float64(size) / mb), "MiB" + case size < tb: + return formatFloat(float64(size) / gb), "GiB" + case size < pb: + return formatFloat(float64(size/gb) / kb), "TiB" // prevent overflow + default: + return formatFloat(float64(size/tb) / kb), "PiB" // prevent overflow + } +} + +func FormatByteSizeWithUnit[T ~int64 | ~uint64 | ~float64](size T) string { + value, unit := FormatByteSize(size) + return value + " " + unit +} + func PortString(port uint16) string { return strconv.FormatUint(uint64(port), 10) } diff --git a/internal/utils/testing/testing.go b/internal/utils/testing/testing.go index fcd9e2e..5db7283 100644 --- a/internal/utils/testing/testing.go +++ b/internal/utils/testing/testing.go @@ -1,6 +1,7 @@ package utils import ( + "bytes" "errors" "os" "reflect" @@ -102,6 +103,14 @@ func ExpectDeepEqual[T any](t *testing.T, got T, want T) { } } +func ExpectBytesEqual(t *testing.T, got []byte, want []byte) { + t.Helper() + if !bytes.Equal(got, want) { + t.Errorf("expected:\n%v, got\n%v", want, got) + t.FailNow() + } +} + func ExpectTrue(t *testing.T, got bool) { t.Helper() if !got { diff --git a/internal/watcher/events/events.go b/internal/watcher/events/events.go index 069c376..6b851d0 100644 --- a/internal/watcher/events/events.go +++ b/internal/watcher/events/events.go @@ -34,6 +34,8 @@ const ( ActionContainerDie ActionContainerDestroy + ActionForceReload + actionContainerWakeMask = ActionContainerCreate | ActionContainerStart | ActionContainerUnpause actionContainerSleepMask = ActionContainerKill | ActionContainerStop | ActionContainerPause | ActionContainerDie ) diff --git a/internal/watcher/health/config.go b/internal/watcher/health/config.go index f81e118..88169a8 100644 --- a/internal/watcher/health/config.go +++ b/internal/watcher/health/config.go @@ -14,7 +14,9 @@ type HealthCheckConfig struct { Timeout time.Duration `json:"timeout" validate:"omitempty,min=1s"` } -var DefaultHealthConfig = &HealthCheckConfig{ - Interval: common.HealthCheckIntervalDefault, - Timeout: common.HealthCheckTimeoutDefault, +func DefaultHealthConfig() *HealthCheckConfig { + return &HealthCheckConfig{ + Interval: common.HealthCheckIntervalDefault, + Timeout: common.HealthCheckTimeoutDefault, + } } diff --git a/internal/watcher/health/status.go b/internal/watcher/health/status.go index 3202cac..355dd70 100644 --- a/internal/watcher/health/status.go +++ b/internal/watcher/health/status.go @@ -1,5 +1,7 @@ package health +import "encoding/json" + type Status uint8 const ( @@ -13,6 +15,7 @@ const ( NumStatuses int = iota - 1 HealthyMask = StatusHealthy | StatusNapping | StatusStarting + IdlingMask = StatusNapping | StatusStarting ) func (s Status) String() string { @@ -36,6 +39,28 @@ func (s Status) MarshalJSON() ([]byte, error) { return []byte(`"` + s.String() + `"`), nil } +func (s *Status) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return err + } + switch str { + case "healthy": + *s = StatusHealthy + case "unhealthy": + *s = StatusUnhealthy + case "napping": + *s = StatusNapping + case "starting": + *s = StatusStarting + case "error": + *s = StatusError + default: + *s = StatusUnknown + } + return nil +} + func (s Status) Good() bool { return s&HealthyMask != 0 } @@ -43,3 +68,7 @@ func (s Status) Good() bool { func (s Status) Bad() bool { return s&HealthyMask == 0 } + +func (s Status) Idling() bool { + return s&IdlingMask != 0 +} diff --git a/internal/watcher/health/types.go b/internal/watcher/health/types.go index 3ced2c0..4c8c0c5 100644 --- a/internal/watcher/health/types.go +++ b/internal/watcher/health/types.go @@ -11,9 +11,9 @@ import ( type ( HealthCheckResult struct { - Healthy bool - Detail string - Latency time.Duration + Healthy bool `json:"healthy"` + Detail string `json:"detail"` + Latency time.Duration `json:"latency"` } WithHealthInfo interface { Status() Status