github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/internal/update/update.go (about)

     1  package update
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/abdfnx/gh-api/api"
    12  	"github.com/abdfnx/gh-api/internal/ghinstance"
    13  	"github.com/hashicorp/go-version"
    14  	"gopkg.in/yaml.v3"
    15  )
    16  
    17  var gitDescribeSuffixRE = regexp.MustCompile(`\d+-\d+-g[a-f0-9]{8}$`)
    18  
    19  // ReleaseInfo stores information about a release
    20  type ReleaseInfo struct {
    21  	Version     string    `json:"tag_name"`
    22  	URL         string    `json:"html_url"`
    23  	PublishedAt time.Time `json:"published_at"`
    24  }
    25  
    26  type StateEntry struct {
    27  	CheckedForUpdateAt time.Time   `yaml:"checked_for_update_at"`
    28  	LatestRelease      ReleaseInfo `yaml:"latest_release"`
    29  }
    30  
    31  // CheckForUpdate checks whether this software has had a newer release on GitHub
    32  func CheckForUpdate(client *api.Client, stateFilePath, repo, currentVersion string) (*ReleaseInfo, error) {
    33  	stateEntry, _ := getStateEntry(stateFilePath)
    34  	if stateEntry != nil && time.Since(stateEntry.CheckedForUpdateAt).Hours() < 24 {
    35  		return nil, nil
    36  	}
    37  
    38  	releaseInfo, err := getLatestReleaseInfo(client, repo)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	err = setStateEntry(stateFilePath, time.Now(), *releaseInfo)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	if versionGreaterThan(releaseInfo.Version, currentVersion) {
    49  		return releaseInfo, nil
    50  	}
    51  
    52  	return nil, nil
    53  }
    54  
    55  func getLatestReleaseInfo(client *api.Client, repo string) (*ReleaseInfo, error) {
    56  	var latestRelease ReleaseInfo
    57  	err := client.REST(ghinstance.Default(), "GET", fmt.Sprintf("repos/%s/releases/latest", repo), nil, &latestRelease)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return &latestRelease, nil
    63  }
    64  
    65  func getStateEntry(stateFilePath string) (*StateEntry, error) {
    66  	content, err := ioutil.ReadFile(stateFilePath)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	var stateEntry StateEntry
    72  	err = yaml.Unmarshal(content, &stateEntry)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return &stateEntry, nil
    78  }
    79  
    80  func setStateEntry(stateFilePath string, t time.Time, r ReleaseInfo) error {
    81  	data := StateEntry{CheckedForUpdateAt: t, LatestRelease: r}
    82  	content, err := yaml.Marshal(data)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	_ = ioutil.WriteFile(stateFilePath, content, 0600)
    87  
    88  	return nil
    89  }
    90  
    91  func versionGreaterThan(v, w string) bool {
    92  	w = gitDescribeSuffixRE.ReplaceAllStringFunc(w, func(m string) string {
    93  		idx := strings.IndexRune(m, '-')
    94  		n, _ := strconv.Atoi(m[0:idx])
    95  		return fmt.Sprintf("%d-pre.0", n+1)
    96  	})
    97  
    98  	vv, ve := version.NewVersion(v)
    99  	vw, we := version.NewVersion(w)
   100  
   101  	return ve == nil && we == nil && vv.GreaterThan(vw)
   102  }