github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/launchpad/api.go (about) 1 /* 2 * Copyright (C) 2022 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package launchpad 19 20 import ( 21 "encoding/json" 22 "net/http" 23 "strings" 24 "sync" 25 "time" 26 ) 27 28 const versionNA = "N/A" 29 30 // ArchiveEntry launchpad archive entry 31 type ArchiveEntry struct { 32 BinaryPackageVersion string `json:"binary_package_version"` 33 } 34 35 // ArchiveResponse launchpad archive response 36 type ArchiveResponse struct { 37 Start int `json:"start"` 38 TotalSize int `json:"total_size"` 39 Entries []ArchiveEntry `json:"entries"` 40 } 41 42 type launchpadHTTP interface { 43 Do(req *http.Request) (*http.Response, error) 44 } 45 46 // API - struct 47 type API struct { 48 http launchpadHTTP 49 cache *cache 50 lock sync.Mutex 51 } 52 53 // New launchpad API instance 54 func New() *API { 55 return &API{ 56 http: &http.Client{Timeout: 20 * time.Second}, 57 cache: &cache{}, 58 } 59 } 60 61 // LatestPublishedReleaseVersion attempts to retrieve latest released binary semantic version 62 func (a *API) LatestPublishedReleaseVersion() (string, error) { 63 response, err := a.latestPPABinaryReleases() 64 if err != nil { 65 return versionNA, err 66 } 67 68 if len(response.Entries) == 0 { 69 return versionNA, err 70 } 71 72 latestPackageVersion := response.Entries[0].BinaryPackageVersion 73 if len(latestPackageVersion) == 0 { 74 return versionNA, err 75 } 76 77 split := strings.Split(latestPackageVersion, "+") 78 if len(split) != 3 { 79 return versionNA, err 80 } 81 82 return split[0], nil 83 } 84 func (a *API) latestPPABinaryReleases() (ArchiveResponse, error) { 85 a.lock.Lock() 86 defer a.lock.Unlock() 87 88 if !a.cache.expired() { 89 return a.cache.get(), nil 90 } 91 92 req, err := http.NewRequest("GET", "https://api.launchpad.net/1.0/~mysteriumnetwork/+archive/ubuntu/node", nil) 93 if err != nil { 94 return ArchiveResponse{}, err 95 } 96 97 q := req.URL.Query() 98 q.Add("ws.op", "getPublishedBinaries") 99 q.Add("status", "Published") 100 req.URL.RawQuery = q.Encode() 101 req.Header.Add("Content-Type", "application/json") 102 resp, err := a.http.Do(req) 103 if err != nil { 104 return ArchiveResponse{}, err 105 } 106 107 cr := ArchiveResponse{} 108 err = json.NewDecoder(resp.Body).Decode(&cr) 109 a.cache.put(cr) 110 return cr, nil 111 } 112 113 type cache struct { 114 lock sync.Mutex 115 expiresAt time.Time 116 response ArchiveResponse 117 } 118 119 func (c *cache) get() ArchiveResponse { 120 c.lock.Lock() 121 defer c.lock.Unlock() 122 return c.response 123 } 124 125 func (c *cache) put(response ArchiveResponse) { 126 c.lock.Lock() 127 defer c.lock.Unlock() 128 c.expiresAt = time.Now().Add(20 * time.Minute) 129 c.response = response 130 } 131 132 func (c *cache) expired() bool { 133 c.lock.Lock() 134 defer c.lock.Unlock() 135 return c.expiresAt.Before(time.Now()) 136 }