1/* 2 * Copyright © 2019-2020 A Bunch Tell LLC. 3 * 4 * This file is part of WriteFreely. 5 * 6 * WriteFreely is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU Affero General Public License, included 8 * in the LICENSE file in this source code package. 9 */ 10 11package writefreely 12 13import ( 14 "github.com/writeas/web-core/log" 15 "io/ioutil" 16 "net/http" 17 "strings" 18 "sync" 19 "time" 20) 21 22// updatesCacheTime is the default interval between cache updates for new 23// software versions 24const defaultUpdatesCacheTime = 12 * time.Hour 25 26// updatesCache holds data about current and new releases of the writefreely 27// software 28type updatesCache struct { 29 mu sync.Mutex 30 frequency time.Duration 31 lastCheck time.Time 32 latestVersion string 33 currentVersion string 34 checkError error 35} 36 37// CheckNow asks for the latest released version of writefreely and updates 38// the cache last checked time. If the version postdates the current 'latest' 39// the version value is replaced. 40func (uc *updatesCache) CheckNow() error { 41 if debugging { 42 log.Info("[update check] Checking for update now.") 43 } 44 uc.mu.Lock() 45 defer uc.mu.Unlock() 46 uc.lastCheck = time.Now() 47 latestRemote, err := newVersionCheck() 48 if err != nil { 49 log.Error("[update check] Failed: %v", err) 50 uc.checkError = err 51 return err 52 } 53 if CompareSemver(latestRemote, uc.latestVersion) == 1 { 54 uc.latestVersion = latestRemote 55 } 56 return nil 57} 58 59// AreAvailable updates the cache if the frequency duration has passed 60// then returns if the latest release is newer than the current running version. 61func (uc updatesCache) AreAvailable() bool { 62 if time.Since(uc.lastCheck) > uc.frequency { 63 uc.CheckNow() 64 } 65 return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 66} 67 68// AreAvailableNoCheck returns if the latest release is newer than the current 69// running version. 70func (uc updatesCache) AreAvailableNoCheck() bool { 71 return CompareSemver(uc.latestVersion, uc.currentVersion) == 1 72} 73 74// LatestVersion returns the latest stored version available. 75func (uc updatesCache) LatestVersion() string { 76 return uc.latestVersion 77} 78 79func (uc updatesCache) ReleaseURL() string { 80 return "https://writefreely.org/releases/" + uc.latestVersion 81} 82 83// ReleaseNotesURL returns the full URL to the blog.writefreely.org release notes 84// for the latest version as stored in the cache. 85func (uc updatesCache) ReleaseNotesURL() string { 86 return wfReleaseNotesURL(uc.latestVersion) 87} 88 89func wfReleaseNotesURL(v string) string { 90 ver := strings.TrimPrefix(v, "v") 91 ver = strings.TrimSuffix(ver, ".0") 92 // hack until go 1.12 in build/travis 93 seg := strings.Split(ver, ".") 94 return "https://blog.writefreely.org/version-" + strings.Join(seg, "-") 95} 96 97// newUpdatesCache returns an initialized updates cache 98func newUpdatesCache(expiry time.Duration) *updatesCache { 99 cache := updatesCache{ 100 frequency: expiry, 101 currentVersion: "v" + softwareVer, 102 } 103 go cache.CheckNow() 104 return &cache 105} 106 107// InitUpdates initializes the updates cache, if the config value is set 108// It uses the defaultUpdatesCacheTime for the cache expiry 109func (app *App) InitUpdates() { 110 if app.cfg.App.UpdateChecks { 111 app.updates = newUpdatesCache(defaultUpdatesCacheTime) 112 } 113} 114 115func newVersionCheck() (string, error) { 116 res, err := http.Get("https://version.writefreely.org") 117 if debugging { 118 log.Info("[update check] GET https://version.writefreely.org") 119 } 120 // TODO: return error if statusCode != OK 121 if err == nil && res.StatusCode == http.StatusOK { 122 defer res.Body.Close() 123 124 body, err := ioutil.ReadAll(res.Body) 125 if err != nil { 126 return "", err 127 } 128 return string(body), nil 129 } 130 return "", err 131} 132