diff --git a/updates.go b/updates.go index 63b2378..c33247b 100644 --- a/updates.go +++ b/updates.go @@ -1,105 +1,106 @@ /* * Copyright © 2018-2019 A Bunch Tell LLC. * * This file is part of WriteFreely. * * WriteFreely is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, included * in the LICENSE file in this source code package. */ package writefreely import ( "io/ioutil" "net/http" "strings" "sync" "time" ) // updatesCacheTime is the default interval between cache updates for new // software versions -const updatesCacheTime = 12 * time.Hour +const defaultUpdatesCacheTime = 12 * time.Hour // updatesCache holds data about current and new releases of the writefreely // software type updatesCache struct { mu sync.Mutex frequency time.Duration lastCheck time.Time latestVersion string currentVersion string } // CheckNow asks for the latest released version of writefreely and updates // the cache last checked time. If the version postdates the current 'latest' // the version value is replaced. func (uc *updatesCache) CheckNow() error { uc.mu.Lock() defer uc.mu.Unlock() - latestRemote, err := newVersionCheck(uc.currentVersion) + latestRemote, err := newVersionCheck() if err != nil { return err } uc.lastCheck = time.Now() if CompareSemver(latestRemote, uc.latestVersion) == 1 { uc.latestVersion = latestRemote } return nil } // AreAvailable updates the cache if the frequency duration has passed // then returns if the latest release is newer than the current running version. func (uc updatesCache) AreAvailable() bool { if time.Since(uc.lastCheck) > uc.frequency { uc.CheckNow() } return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 } // LatestVersion returns the latest stored version available. func (uc updatesCache) LatestVersion() string { return uc.latestVersion } // ReleaseURL returns the full URL to the blog.writefreely.org release notes // for the latest version as stored in the cache. func (uc updatesCache) ReleaseURL() string { ver := strings.TrimPrefix(uc.latestVersion, "v") ver = strings.TrimSuffix(ver, ".0") // hack until go 1.12 in build/travis seg := strings.Split(ver, ".") return "https://blog.writefreely.org/version-" + strings.Join(seg, "-") } // newUpdatesCache returns an initialized updates cache -func newUpdatesCache() *updatesCache { +func newUpdatesCache(expiry time.Duration) *updatesCache { cache := updatesCache{ - frequency: updatesCacheTime, + frequency: expiry, currentVersion: "v" + softwareVer, } cache.CheckNow() return &cache } // InitUpdates initializes the updates cache, if the config value is set +// It uses the defaultUpdatesCacheTime for the cache expiry func (app *App) InitUpdates() { if app.cfg.App.UpdateChecks { - app.updates = newUpdatesCache() + app.updates = newUpdatesCache(defaultUpdatesCacheTime) } } -func newVersionCheck(serverVersion string) (string, error) { +func newVersionCheck() (string, error) { res, err := http.Get("https://version.writefreely.org") if err == nil && res.StatusCode == http.StatusOK { defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } return string(body), nil } return "", err } diff --git a/updates_test.go b/updates_test.go new file mode 100644 index 0000000..2cb9f92 --- /dev/null +++ b/updates_test.go @@ -0,0 +1,82 @@ +package writefreely + +import ( + "regexp" + "testing" + "time" +) + +func TestUpdatesRoundTrip(t *testing.T) { + cache := newUpdatesCache(defaultUpdatesCacheTime) + t.Run("New Updates Cache", func(t *testing.T) { + + if cache == nil { + t.Fatal("Returned nil cache") + } + + if cache.frequency != defaultUpdatesCacheTime { + t.Fatalf("Got cache expiry frequency: %s but expected: %s", cache.frequency, defaultUpdatesCacheTime) + } + + if cache.currentVersion != "v"+softwareVer { + t.Fatalf("Got current version: %s but expected: %s", cache.currentVersion, "v"+softwareVer) + } + }) + + t.Run("Release URL", func(t *testing.T) { + url := cache.ReleaseURL() + + reg, err := regexp.Compile(`^https:\/\/blog.writefreely.org\/version(-\d+){1,}$`) + if err != nil { + t.Fatalf("Test Case Error: Failed to compile regex: %v", err) + } + match := reg.MatchString(url) + + if !match { + t.Fatalf("Malformed Release URL: %s", url) + } + }) + + t.Run("Check Now", func(t *testing.T) { + // ensure time between init and next check + time.Sleep(1 * time.Second) + + prevLastCheck := cache.lastCheck + + // force to known older version for latest and current + prevLatestVer := "v0.8.1" + cache.latestVersion = prevLatestVer + cache.currentVersion = "v0.8.0" + + err := cache.CheckNow() + if err != nil { + t.Fatalf("Error should be nil, got: %v", err) + } + + if prevLastCheck == cache.lastCheck { + t.Fatal("Expected lastCheck to update") + } + + if cache.lastCheck.Before(prevLastCheck) { + t.Fatal("Last check should be newer than previous") + } + + if prevLatestVer == cache.latestVersion { + t.Fatal("expected latestVersion to update") + } + + }) + + t.Run("Are Available", func(t *testing.T) { + if !cache.AreAvailable() { + t.Fatalf("Cache reports not updates but Current is %s and Latest is %s", cache.currentVersion, cache.latestVersion) + } + }) + + t.Run("Latest Version", func(t *testing.T) { + gotLatest := cache.LatestVersion() + if gotLatest != cache.latestVersion { + t.Fatalf("Malformed latest version. Expected: %s but got: %s", cache.latestVersion, gotLatest) + } + }) +}