parent
9a2a4201fc
commit
fe91dc2dd1
|
@ -1,5 +1,11 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Forge string
|
||||
|
||||
const (
|
||||
|
@ -18,5 +24,17 @@ type Asset struct {
|
|||
}
|
||||
|
||||
type Forger interface {
|
||||
Name() string
|
||||
Latest() (Release, error)
|
||||
}
|
||||
|
||||
func splitURI(uri string) (string, []string, error) {
|
||||
u, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("could not parse URI: %w", err)
|
||||
}
|
||||
u.Scheme = "https"
|
||||
paths := strings.FieldsFunc(u.Path, func(r rune) bool { return r == '/' })
|
||||
u.Path = ""
|
||||
return u.String(), paths, nil
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ Forger = (*Gitea)(nil)
|
||||
|
@ -23,6 +25,23 @@ type GiteaRelease struct {
|
|||
} `json:"assets"`
|
||||
}
|
||||
|
||||
func NewGitea(uri string) (Gitea, error) {
|
||||
var g Gitea
|
||||
base, paths, err := splitURI(uri)
|
||||
if err != nil {
|
||||
return g, err
|
||||
}
|
||||
|
||||
g.BaseURL = base
|
||||
g.Owner = paths[0]
|
||||
g.Repo = paths[1]
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g Gitea) Name() string {
|
||||
return g.Repo
|
||||
}
|
||||
|
||||
func (g Gitea) Latest() (Release, error) {
|
||||
var release Release
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
)
|
||||
|
||||
type Meta struct {
|
||||
Packages []Package `json:"packages"`
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func Add(pkg Package) error {
|
||||
m, err := Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range m.Packages {
|
||||
if strings.EqualFold(pkg.Name, p.Name) {
|
||||
return errors.New("package already exists locally")
|
||||
}
|
||||
}
|
||||
m.Packages = append(m.Packages, pkg)
|
||||
return save(m)
|
||||
}
|
||||
|
||||
func Read() (Meta, error) {
|
||||
var m Meta
|
||||
fp, err := metaPath()
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("could not get meta file: %w", err)
|
||||
}
|
||||
fi, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return m, fmt.Errorf("could not open meta file: %w", err)
|
||||
}
|
||||
defer fi.Close()
|
||||
|
||||
if err := json.NewDecoder(fi).Decode(&m); err != nil {
|
||||
return m, fmt.Errorf("could not decode meta: %w", err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func Remove(name string) error {
|
||||
m, err := Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for idx, p := range m.Packages {
|
||||
if strings.EqualFold(name, p.Name) {
|
||||
m.Packages = append(m.Packages[:idx], m.Packages[idx+1:]...)
|
||||
return save(m)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not find package to remove for %q", name)
|
||||
}
|
||||
|
||||
func metaPath() (string, error) {
|
||||
return xdg.DataFile("eget/meta.json")
|
||||
}
|
||||
|
||||
func save(m Meta) error {
|
||||
fp, err := metaPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get meta path: %w", err)
|
||||
}
|
||||
fi, err := os.Create(fp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create meta file: %w", err)
|
||||
}
|
||||
defer fi.Close()
|
||||
return json.NewEncoder(fi).Encode(m)
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/mholt/archiver/v3"
|
||||
"go.jolheiser.com/eget/forge"
|
||||
)
|
||||
|
||||
var (
|
||||
amd64Re = regexp.MustCompile(`amd64|x86_64|64-bit`)
|
||||
linuxRe = regexp.MustCompile(`linux`)
|
||||
windowsRe = regexp.MustCompile(`windows`)
|
||||
)
|
||||
|
||||
func Install(f forge.Forger) error {
|
||||
var re *regexp.Regexp
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
re = linuxRe
|
||||
case "windows":
|
||||
re = windowsRe
|
||||
default:
|
||||
return fmt.Errorf("%q is not a supported OS", runtime.GOOS)
|
||||
}
|
||||
|
||||
latest, err := f.Latest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var asset forge.Asset
|
||||
for _, a := range latest.Assets {
|
||||
if amd64Re.MatchString(a.DownloadURL) && re.MatchString(a.DownloadURL) {
|
||||
asset = a
|
||||
}
|
||||
}
|
||||
|
||||
if asset.Name == "" {
|
||||
return errors.New("no release found for this OS")
|
||||
}
|
||||
|
||||
tmp, err := os.MkdirTemp(os.TempDir(), "eget-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
resp, err := http.Get(asset.DownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tmpDest := filepath.Join(tmp, asset.Name)
|
||||
fi, err := os.Create(tmpDest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(fi, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fi.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := unpack(tmpDest, f.Name()); err != nil {
|
||||
return fmt.Errorf("could not unpack download: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectRootDir(path string) bool {
|
||||
infos, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return len(infos) != 1
|
||||
}
|
||||
|
||||
func unpack(src, name string) error {
|
||||
dest := filepath.Join(xdg.DataHome, name)
|
||||
uaIface, err := archiver.ByExtension(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u, ok := uaIface.(archiver.Unarchiver)
|
||||
if !ok {
|
||||
d, ok := uaIface.(archiver.Decompressor)
|
||||
if !ok {
|
||||
return fmt.Errorf("format specified by source filename is not an archive or compression format: %s (%T)", src, uaIface)
|
||||
}
|
||||
var binExt string
|
||||
if runtime.GOOS == "windows" {
|
||||
binExt = ".exe"
|
||||
}
|
||||
dest = filepath.Join(dest, name+binExt)
|
||||
c := archiver.FileCompressor{Decompressor: d}
|
||||
if err := c.DecompressFile(src, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Chmod(dest, 0o755)
|
||||
}
|
||||
return u.Unarchive(src, dest)
|
||||
}
|
Reference in New Issue