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  }