diff --git a/cmd/humanlog/main.go b/cmd/humanlog/main.go index c3ac341..5253cf5 100644 --- a/cmd/humanlog/main.go +++ b/cmd/humanlog/main.go @@ -11,7 +11,6 @@ import ( "github.com/aybabtme/rgbterm" "github.com/blang/semver" - "github.com/fatih/color" types "github.com/humanlogio/api/go/types/v1" "github.com/humanlogio/humanlog" "github.com/humanlogio/humanlog/internal/pkg/config" @@ -165,11 +164,12 @@ func newApp() *cli.App { app.Usage = "reads structured logs from stdin, makes them pretty on stdout!" var ( - ctx context.Context - cancel context.CancelFunc - cfg *config.Config - statefile *state.State - updateRes <-chan *checkForUpdateRes + ctx context.Context + cancel context.CancelFunc + cfg *config.Config + statefile *state.State + promptedToUpdate *semver.Version + updateRes <-chan *checkForUpdateRes ) app.Before = func(c *cli.Context) error { @@ -205,6 +205,11 @@ func newApp() *cli.App { return fmt.Errorf("reading default config file: %v", err) } + if statefile.LatestKnownVersion != nil && statefile.LatestKnownVersion.GT(semverVersion) { + promptToUpdate(semverVersion, *statefile.LatestKnownVersion) + promptedToUpdate = statefile.LatestKnownVersion + } + if shouldCheckForUpdate(c, cfg, statefile) { req := &checkForUpdateReq{ arch: runtime.GOARCH, @@ -222,14 +227,11 @@ func newApp() *cli.App { if !ok { return nil } - - if res.hasUpdate { - log.Print( - color.YellowString("Update available %s -> %s.", semverVersion, res.sem), - ) - log.Print( - color.YellowString("Run %s to upgrade.", color.New(color.Bold).Sprint("humanlog version update")), - ) + if res.hasUpdate && promptedToUpdate == nil && promptedToUpdate.LT(res.sem) { + alreadyPromptedForSameUpdate := promptedToUpdate != nil && promptedToUpdate.GTE(res.sem) + if !alreadyPromptedForSameUpdate { + promptToUpdate(semverVersion, res.sem) + } } default: } diff --git a/cmd/humanlog/versions.go b/cmd/humanlog/versions.go index 8b6c4e0..07eb7f6 100644 --- a/cmd/humanlog/versions.go +++ b/cmd/humanlog/versions.go @@ -58,7 +58,7 @@ func reqMeta(st *state.State) *types.ReqMeta { return req } -func updateFromResMeta(st *state.State, res *types.ResMeta, lastUpdateCheck *time.Time) error { +func updateFromResMeta(st *state.State, res *types.ResMeta, latestKnownVersion *semver.Version, latestKnownVersionUpdatedAt *time.Time) error { changed := false if st.AccountID == nil || res.AccountId != *st.AccountID { st.AccountID = &res.AccountId @@ -68,8 +68,18 @@ func updateFromResMeta(st *state.State, res *types.ResMeta, lastUpdateCheck *tim st.MachineID = &res.MachineId changed = true } - if st.LastUpdateCheckAt == nil && lastUpdateCheck != nil { - st.LastUpdateCheckAt = lastUpdateCheck + if st.LatestKnownVersion == nil && latestKnownVersion != nil { + st.LatestKnownVersion = latestKnownVersion + changed = true + } else if st.LatestKnownVersion != nil && latestKnownVersion != nil && !st.LatestKnownVersion.EQ(*latestKnownVersion) { + st.LatestKnownVersion = latestKnownVersion + changed = true + } + if st.LastestKnownVersionUpdatedAt == nil && latestKnownVersionUpdatedAt != nil { + st.LastestKnownVersionUpdatedAt = latestKnownVersionUpdatedAt + changed = true + } else if st.LastestKnownVersionUpdatedAt != nil && latestKnownVersionUpdatedAt != nil && !st.LastestKnownVersionUpdatedAt.Equal(*latestKnownVersionUpdatedAt) { + st.LastestKnownVersionUpdatedAt = latestKnownVersionUpdatedAt changed = true } if !changed { @@ -106,12 +116,7 @@ func versionCmd( if err != nil { return fmt.Errorf("invalid semver received: %w", err) } - log.Print( - color.YellowString("Update available %s -> %s.", semverVersion, nextSV), - ) - log.Print( - color.YellowString("Run %s to upgrade.", color.New(color.Bold).Sprint("humanlog version update")), - ) + promptToUpdate(semverVersion, nextSV) log.Printf("- url: %s", nextArtifact.Url) log.Printf("- sha256: %s", nextArtifact.Sha256) log.Printf("- sig: %s", nextArtifact.Signature) @@ -173,14 +178,14 @@ func checkForUpdate(ctx context.Context, cfg *config.Config, state *state.State) msg := res.Msg lastCheckAt := time.Now() - if err := updateFromResMeta(state, msg.Meta, &lastCheckAt); err != nil { - log.Printf("failed to persist internal state: %v", err) - } - nextSV, err := msg.NextVersion.AsSemver() if err != nil { return nil, nil, false, err } + if err := updateFromResMeta(state, msg.Meta, &nextSV, &lastCheckAt); err != nil { + log.Printf("failed to persist internal state: %v", err) + } + return msg.NextVersion, msg.NextArtifact, currentSV.LT(nextSV), nil } @@ -207,3 +212,12 @@ func asyncCheckForUpdate(ctx context.Context, req *checkForUpdateReq, cfg *confi }() return out } + +func promptToUpdate(from, to semver.Version) { + log.Print( + color.YellowString("Update available %s -> %s.", from, to), + ) + log.Print( + color.YellowString("Run `%s` to upgrade.", color.New(color.Bold).Sprint("humanlog version update")), + ) +} diff --git a/internal/pkg/state/state.go b/internal/pkg/state/state.go index 8dad345..653dbe6 100644 --- a/internal/pkg/state/state.go +++ b/internal/pkg/state/state.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" "time" + + "github.com/blang/semver" ) var DefaultState = State{ @@ -93,10 +95,11 @@ func WriteStateFile(path string, state *State) error { } type State struct { - Version int `json:"version"` - AccountID *int64 `json:"account_id"` - MachineID *int64 `json:"machine_id"` - LastUpdateCheckAt *time.Time `json:"last_update_check_at"` + Version int `json:"version"` + AccountID *int64 `json:"account_id"` + MachineID *int64 `json:"machine_id"` + LatestKnownVersion *semver.Version `json:"latest_known_version,omitempty"` + LastestKnownVersionUpdatedAt *time.Time `json:"latest_known_version_updated_at"` // unexported path string