192 lines
4.1 KiB
Go
192 lines
4.1 KiB
Go
|
package config
|
||
|
|
||
|
import (
|
||
|
"github.com/ulikunitz/xz"
|
||
|
|
||
|
"archive/tar"
|
||
|
"archive/zip"
|
||
|
"compress/gzip"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"io/fs"
|
||
|
"mime"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"runtime"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrUnsupportedOSAndArch = errors.New("no download available for your operating system and hardware architecture")
|
||
|
ErrFileNotFoundInArchive = errors.New("file not found in archive")
|
||
|
ErrUnsupportedArchive = errors.New("unsupported archive format (supported are .tar, .tar.gz, .tar.xz and .zip")
|
||
|
)
|
||
|
|
||
|
func download(executable bool, urlsByOS map[string]map[string]string, progCallback func(progress float32)) (filename string, err error) {
|
||
|
// Find appropriate URL
|
||
|
var url string
|
||
|
var urlByArch map[string]string
|
||
|
var ok bool
|
||
|
if urlByArch, ok = urlsByOS[runtime.GOOS]; !ok {
|
||
|
urlByArch, ok = urlsByOS["any"]
|
||
|
}
|
||
|
if ok {
|
||
|
if url, ok = urlByArch[runtime.GOARCH]; !ok {
|
||
|
url, ok = urlByArch["any"]
|
||
|
}
|
||
|
}
|
||
|
if !ok {
|
||
|
return "", ErrUnsupportedOSAndArch
|
||
|
}
|
||
|
|
||
|
// Initiate request
|
||
|
lastPath := url
|
||
|
resp, err := http.Get(url)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
// Get the filename to save the downloaded file to
|
||
|
var savePath string
|
||
|
if v := resp.Header.Get("Content-Disposition"); v != "" {
|
||
|
disposition, params, err := mime.ParseMediaType(v)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
if disposition == "attachment" {
|
||
|
lastPath = params["filename"]
|
||
|
}
|
||
|
}
|
||
|
if savePath == "" {
|
||
|
sp := strings.Split(lastPath, "/")
|
||
|
savePath = sp[len(sp)-1]
|
||
|
}
|
||
|
|
||
|
// Download resource
|
||
|
size, _ := strconv.Atoi(resp.Header.Get("content-length"))
|
||
|
var perms uint32 = 0666
|
||
|
if executable {
|
||
|
perms = 0777
|
||
|
}
|
||
|
file, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fs.FileMode(perms))
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
for i := 0; ; i += 100_000 {
|
||
|
_, err := io.CopyN(file, resp.Body, 100_000)
|
||
|
if err != nil {
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
} else {
|
||
|
return "", err
|
||
|
}
|
||
|
}
|
||
|
if progCallback != nil && size != 0 {
|
||
|
progCallback(float32(i)/float32(size))
|
||
|
}
|
||
|
}
|
||
|
return savePath, nil
|
||
|
}
|
||
|
|
||
|
func unarchiveSingleFile(archive, target string) error {
|
||
|
unzip := func() error {
|
||
|
ar, err := zip.OpenReader(archive)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer ar.Close()
|
||
|
|
||
|
found := false
|
||
|
for _, file := range ar.File {
|
||
|
if !file.FileInfo().IsDir() && filepath.Base(file.Name) == target {
|
||
|
found = true
|
||
|
dstFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer dstFile.Close()
|
||
|
fileReader, err := file.Open()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer fileReader.Close()
|
||
|
if _, err := io.Copy(dstFile, fileReader); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if !found {
|
||
|
return ErrFileNotFoundInArchive
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
untar := func(rd io.Reader) error {
|
||
|
ar := tar.NewReader(rd)
|
||
|
for {
|
||
|
hdr, err := ar.Next()
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
}
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if hdr.Typeflag == tar.TypeReg && filepath.Base(hdr.Name) == target {
|
||
|
dstFile, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fs.FileMode(hdr.Mode))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer dstFile.Close()
|
||
|
if _, err := io.Copy(dstFile, ar); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
return ErrFileNotFoundInArchive
|
||
|
}
|
||
|
match := func(name string, patterns ...string) bool {
|
||
|
for _, pattern := range patterns {
|
||
|
matches, err := filepath.Match(pattern, archive)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
if matches {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
if match(archive, "*.zip") {
|
||
|
return unzip()
|
||
|
} else if match(archive, "*.tar", "*.tar.[gx]z") {
|
||
|
file, err := os.Open(archive)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer file.Close()
|
||
|
var uncompressedFile io.Reader
|
||
|
if match(archive, "*.tar") {
|
||
|
uncompressedFile = file
|
||
|
} else if match(archive, "*.tar.gz") {
|
||
|
gz, err := gzip.NewReader(file)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer gz.Close()
|
||
|
uncompressedFile = gz
|
||
|
} else if match(archive, "*.tar.xz") {
|
||
|
uncompressedFile, err = xz.NewReader(file)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return untar(uncompressedFile)
|
||
|
} else {
|
||
|
return ErrUnsupportedArchive
|
||
|
}
|
||
|
return nil
|
||
|
}
|