Skip to content

Commit

Permalink
simplify library packages
Browse files Browse the repository at this point in the history
  • Loading branch information
jreisinger committed Apr 12, 2023
1 parent b0944f8 commit 9838e1f
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 166 deletions.
75 changes: 34 additions & 41 deletions asset/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"regexp"
"text/tabwriter"
Expand All @@ -16,19 +14,20 @@ import (

var GitHubApiUrl = "https://api.github.com" // changed during testing

// Asset represents a GitHub API release asset.
// Asset represents a GitHub API release asset with some calculated fields.
type Asset struct {
BrowserDownloadUrl string `json:"browser_download_url"`
Name string `json:"name"` // filename
IsChecksumsFile bool `json:"-"`
IsChecksumFile bool `json:"-"`
Checksum string `json:"-"` // in hex
UpdatedAt time.Time `json:"updated_at"`
Size int `json:"size"`
DownloadCount int `json:"download_count"`
}

// Get queries GitHub API for assets of the latest repo release whose
// (file)names match the shell pattern, if that is not empty. Pattern matching
// does not apply to checksums files. Repo is a GitHub repository in the form
// Get queries GitHub API for assets of the latest repo release whose name
// matches the shell pattern, if the pattern is not empty. Pattern matching does
// not apply to checksum files. Repo is a GitHub repository in the form
// <user>/<repo>.
func Get(repo string, pattern *string) ([]Asset, error) {
url := GitHubApiUrl + "/repos/" + repo + "/releases/latest"
Expand All @@ -55,8 +54,8 @@ func Get(repo string, pattern *string) ([]Asset, error) {
var assets []Asset

for _, a := range api.Assets {
if isChecksumsFile(a.Name) {
a.IsChecksumsFile = true
if isChecksumFile(a.Name) {
a.IsChecksumFile = true
} else if *pattern != "" {
if matched, _ := filepath.Match(*pattern, a.Name); !matched {
continue
Expand All @@ -68,24 +67,10 @@ func Get(repo string, pattern *string) ([]Asset, error) {
return assets, nil
}

// filename extracts filename from the Asset's BrowserDownloadUrl.
func (a Asset) filename() (string, error) {
u, err := url.Parse(a.BrowserDownloadUrl)
if err != nil {
return "", err
}
_, file := path.Split(u.Path)
return file, nil
}

// Download downloads asset from BrowserDownloadUrl to a file who's name is
// extracted from BrowserDownloadUrl. It creates or truncates the file.
func (a Asset) Download() error {
file, err := a.filename()
if err != nil {
return err
}
f, err := os.Create(file)
// Download downloads asset from a.BrowserDownloadUrl to a file named a.Name. It
// creates or truncates the file.
func Download(a Asset) error {
f, err := os.Create(a.Name)
if err != nil {
return err
}
Expand All @@ -106,35 +91,43 @@ func (a Asset) Download() error {
func Print(assets []Asset) {
const format = "%v\t%v\t%v\t%v\t%v\n"
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(tw, format, "Asset", "Checksums file", "Updated", "Size", "Download count")
fmt.Fprintf(tw, format, "-----", "--------------", "-------", "----", "--------------")
fmt.Fprintf(tw, format, "Asset", "Checksum file", "Updated", "Size", "Download count")
fmt.Fprintf(tw, format, "-----", "-------------", "-------", "----", "--------------")
for _, a := range assets {
fmt.Fprintf(tw, format, a.Name, a.IsChecksumsFile, a.UpdatedAt.Format("2006-01-02"), a.Size, a.DownloadCount)
fmt.Fprintf(tw, format, a.Name, a.IsChecksumFile, a.UpdatedAt.Format("2006-01-02"), a.Size, a.DownloadCount)
}
tw.Flush()
}

func Count(assets []Asset) (nFiles, nChecksumsFiles int) {
// Count counts files and checksum files.
func Count(assets []Asset) (nFiles, nChecksumFiles int) {
for _, a := range assets {
switch {
case a.IsChecksumsFile:
nChecksumsFiles++
case a.IsChecksumFile:
nChecksumFiles++
default:
nFiles++
}
}
return
}

var checksumsFilePatterns = []string{
`(?i)check.?sum`, // ghrel_0.3.1_checksums.txt
`\.sha256$`, // brave-browser-nightly-1.47.27-linux-amd64.zip.sha256
}
// isChecksumFile tells whether asset looks like a file containing checksum(s).
func isChecksumFile(filename string) bool {
checksumFiles := []string{
`(?i)check.?sum`, // ghrel_0.3.1_checksums.txt
`\.sha256$`, // brave-browser-nightly-1.47.27-linux-amd64.zip.sha256
}

var checksumFilePatterns []*regexp.Regexp

for _, f := range checksumFiles {
re := regexp.MustCompile(f)
checksumFilePatterns = append(checksumFilePatterns, re)
}

// isChecksumsFile tells whether asset looks like a file containing checksum(s).
func isChecksumsFile(filename string) bool {
for _, c := range checksumsFilePatterns {
if regexp.MustCompile(c).MatchString(filename) {
for _, re := range checksumFilePatterns {
if re.MatchString(filename) {
return true
}
}
Expand Down
20 changes: 0 additions & 20 deletions asset/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,6 @@ import (
"github.com/stretchr/testify/require"
)

func TestFilename(t *testing.T) {
tests := []struct {
asset Asset
want string
}{
{Asset{}, ""},
{Asset{BrowserDownloadUrl: "httpx://wrongurl.com"}, ""},
{Asset{BrowserDownloadUrl: "https://github.com/jgm/pandoc/releases/download/2.18/pandoc-2.18-1-amd64.deb"}, "pandoc-2.18-1-amd64.deb"},
}
for _, test := range tests {
got, err := test.asset.filename()
if err != nil {
t.Error(err)
}
if got != test.want {
t.Errorf("got %s, want %s", got, test.want)
}
}
}

func TestGetDownloadUrls(t *testing.T) {
t.Run("given valid response, download URLs and no error is returned", func(t *testing.T) {
testUrl := startMockApiServer(t)
Expand Down
102 changes: 44 additions & 58 deletions checksum/checksum.go
Original file line number Diff line number Diff line change
@@ -1,86 +1,72 @@
package checksum

import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"strings"

"github.com/jreisinger/ghrel/asset"
)

// Checksum represents a line from a checksums file. The line looks like:
// ba47c83b6038dda089dd1410b9e97d1de7e4adea7620c856f9b74a782048e272 checkip_0.45.1_linux_amd64.tar.gz
type Checksum struct {
Checksum []byte // in hex
Name string // filename
// Sha256 calculates SHA256 checksum of filename. Checksum is in hex format.
func Sha256(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()

hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
sum := hash.Sum(nil)
return fmt.Sprintf("%x", sum), nil
}

// Get extracts checksums and corresponding filenames from the checksums files.
func Get(assets []asset.Asset) ([]Checksum, error) {
var checksums []Checksum
for _, a := range assets {
if !a.IsChecksumsFile {
continue
}
// Pair represents a line from a checksum file. The line looks like:
//
// ba47c83b6038dda089dd1410b9e97d1de7e4adea7620c856f9b74a782048e272 checkip_0.45.1_linux_amd64.tar.gz
type Pair struct {
Checksum string // in hex
Filename string
}

file, err := os.Open(a.Name)
if err != nil {
return nil, err
}
defer file.Close()
// Parse parses a checksum file into checksum/filename pairs.
func Parse(checksumFile string) ([]Pair, error) {
var checksums []Pair

b, err := io.ReadAll(file)
if err != nil {
return nil, err
}
file, err := os.Open(checksumFile)
if err != nil {
return nil, err
}
defer file.Close()

cs, err := parseChecksumsLines(b)
if err != nil {
return nil, err
}
checksums = append(checksums, cs...)
b, err := io.ReadAll(file)
if err != nil {
return nil, err
}

cs := parseChecksumLines(b)
checksums = append(checksums, cs...)

return checksums, nil
}

// parseChecksumsLines parses bytes from a checksums file that look like:
// parseChecksumLines parses bytes from a checksums file. The bytes look like:
// 712f37d14687e10ae0425bf7e5d0faf17c49f9476b8bb6a96f2a3f91b0436db2 checkip_0.45.1_linux_armv6.tar.gz
// ba47c83b6038dda089dd1410b9e97d1de7e4adea7620c856f9b74a782048e272 checkip_0.45.1_linux_amd64.tar.gz
func parseChecksumsLines(b []byte) ([]Checksum, error) {
var checksums []Checksum
func parseChecksumLines(b []byte) []Pair {
var checksums []Pair
for _, line := range strings.Split(string(b), "\n") {
fields := strings.Fields(line)
if len(fields) != 2 {
continue
}
c, err := hex.DecodeString(fields[0])
if err != nil {
return nil, err
}
checksums = append(checksums, Checksum{
Checksum: c,
Name: fields[1],
checksums = append(checksums, Pair{
Checksum: fields[0],
Filename: fields[1],
})
}
return checksums, nil
}

// Verify opens the filename from c, computes its SHA256 checksum and compares it to the checksum from c.
func (c Checksum) Verify() (ok bool, err error) {
file, err := os.Open(c.Name)
if err != nil {
return false, err
}
defer file.Close()

hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return false, err
}
sum := hash.Sum(nil)

return bytes.Equal(c.Checksum, sum), nil
return checksums
}
39 changes: 13 additions & 26 deletions checksum/checksum_test.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
package checksum

import (
"encoding/hex"
"fmt"
"io"
"os"
"testing"
)

// from `sha256sum testdata/file-to-checksum`
const helloWorld = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

func TestVerify(t *testing.T) {
cs, err := hex.DecodeString(helloWorld)
if err != nil {
t.Errorf("decode hex string: %v", err)
}
checksum := Checksum{Checksum: cs, Name: "testdata/file-to-checksum"}
ok, err := checksum.Verify()
if err != nil {
t.Errorf("Verify failed: %v", err)
}
if !ok {
t.Errorf("Verify %v, want true", ok)
}
var test = struct {
fileName string
fileChecksum string // calculated using sha256sum
checksumsFile string // contains checksum of file and file name
}{
fileName: "testdata/file-to-checksum",
fileChecksum: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
checksumsFile: "testdata/checksum-file", // only one line here
}

func TestGetChecksums(t *testing.T) {
file, err := os.Open("testdata/checksum-file")
func TestParseChecksumsLines(t *testing.T) {
file, err := os.Open(test.checksumsFile)
if err != nil {
t.Error(err)
}
Expand All @@ -38,13 +28,10 @@ func TestGetChecksums(t *testing.T) {
t.Error(err)
}

cs, err := parseChecksumsLines(b)
if err != nil {
t.Errorf("get checksums: %v", err)
}
cs := parseChecksumLines(b)
for _, c := range cs {
got := fmt.Sprintf("%x", c.Checksum)
want := helloWorld
got := c.Checksum
want := test.fileChecksum
if got != want {
t.Errorf("wrong checksum: got %s, want %s", got, want)
}
Expand Down
Loading

0 comments on commit 9838e1f

Please sign in to comment.