Rearrange some code, adds pm av
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"mcquay.me/fs"
|
||||
"mcquay.me/pm"
|
||||
)
|
||||
|
||||
const an = "var/lib/pm/available.json"
|
||||
|
||||
// Pull updates the available package database.
|
||||
func Pull(root string) error {
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading db")
|
||||
}
|
||||
|
||||
o := pm.Available{}
|
||||
|
||||
// Order here is important: the guarantee made is that any packages that
|
||||
// exist in multiple remotes will be fetched by the first configured
|
||||
// remote, which is why we traverse the database in reverse.
|
||||
//
|
||||
// TODO (sm): make this concurrent
|
||||
for i := range db {
|
||||
u := db[len(db)-i-1]
|
||||
resp, err := http.Get(u.String() + "/available.json")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "http get")
|
||||
}
|
||||
|
||||
a := pm.Available{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&a); err != nil {
|
||||
return errors.Wrap(err, "decode remote available")
|
||||
}
|
||||
a.SetRemote(u)
|
||||
o.Update(a)
|
||||
}
|
||||
if err := saveAvailable(root, o); err != nil {
|
||||
return errors.Wrap(err, "saving available db")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAvailable prints all installable packages
|
||||
func ListAvailable(root string, w io.Writer) error {
|
||||
db, err := loadAvailable(root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading")
|
||||
}
|
||||
names := pm.Names{}
|
||||
nvs := map[pm.Name]pm.Versions{}
|
||||
for n, vers := range db {
|
||||
names = append(names, n)
|
||||
for v := range vers {
|
||||
nvs[n] = append(nvs[n], v)
|
||||
}
|
||||
sort.Sort(nvs[n])
|
||||
}
|
||||
sort.Sort(names)
|
||||
|
||||
for _, n := range names {
|
||||
for _, v := range nvs[n] {
|
||||
m := db[n][v]
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\n", m.Name, m.Version, m.Remote.String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadAvailable(root string) (pm.Available, error) {
|
||||
r := pm.Available{}
|
||||
dbn := filepath.Join(root, rn)
|
||||
|
||||
if !fs.Exists(dbn) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Join(root, an))
|
||||
if err != nil {
|
||||
return r, errors.Wrap(err, "open")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&r); err != nil {
|
||||
return r, errors.Wrap(err, "decoding db")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func saveAvailable(root string, db pm.Available) error {
|
||||
f, err := os.Create(filepath.Join(root, an))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create")
|
||||
}
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", "\t")
|
||||
if err := enc.Encode(&db); err != nil {
|
||||
return errors.Wrap(err, "decoding db")
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return errors.Wrap(err, "close db")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"mcquay.me/fs"
|
||||
)
|
||||
|
||||
// DB is a slice of available URI
|
||||
type DB []url.URL
|
||||
|
||||
const rn = "var/lib/pm/remotes.json"
|
||||
|
||||
// AddRemotes appends the provided uri to the list of configured remotes.
|
||||
func AddRemotes(root string, uris []string) error {
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading")
|
||||
}
|
||||
|
||||
dbm := map[string]bool{}
|
||||
for _, u := range db {
|
||||
dbm[u.String()] = true
|
||||
}
|
||||
|
||||
for _, uri := range uris {
|
||||
pu, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "url parse")
|
||||
}
|
||||
|
||||
u := strip(*pu)
|
||||
|
||||
if _, ok := dbm[u.String()]; ok {
|
||||
return fmt.Errorf("%q already in db", u.String())
|
||||
}
|
||||
db = append(db, u)
|
||||
}
|
||||
|
||||
return save(root, db)
|
||||
}
|
||||
|
||||
// RemoveRemotes removes the given uri from the list of configured remotes.
|
||||
func RemoveRemotes(root string, uris []string) error {
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading")
|
||||
}
|
||||
|
||||
rms := map[string]bool{}
|
||||
for _, uri := range uris {
|
||||
pu, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "url parse")
|
||||
}
|
||||
|
||||
u := strip(*pu)
|
||||
|
||||
rms[u.String()] = true
|
||||
}
|
||||
|
||||
o := DB{}
|
||||
for _, d := range db {
|
||||
if _, ok := rms[d.String()]; !ok {
|
||||
o = append(o, d)
|
||||
}
|
||||
}
|
||||
|
||||
if len(o) == len(db) {
|
||||
return errors.New("found no matching remotes")
|
||||
}
|
||||
|
||||
return save(root, o)
|
||||
}
|
||||
|
||||
// ListRemotes prints all configured remotes to w.
|
||||
func ListRemotes(root string, w io.Writer) error {
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "loading")
|
||||
}
|
||||
for _, u := range db {
|
||||
fmt.Fprintf(w, "%s\n", u.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func load(root string) (DB, error) {
|
||||
r := DB{}
|
||||
dbn := filepath.Join(root, rn)
|
||||
|
||||
if !fs.Exists(dbn) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Join(root, rn))
|
||||
if err != nil {
|
||||
return r, errors.Wrap(err, "open")
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(f).Decode(&r); err != nil {
|
||||
return r, errors.Wrap(err, "decoding db")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func save(root string, db DB) error {
|
||||
f, err := os.Create(filepath.Join(root, rn))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create")
|
||||
}
|
||||
enc := json.NewEncoder(f)
|
||||
enc.SetIndent("", "\t")
|
||||
|
||||
if err := enc.Encode(&db); err != nil {
|
||||
return errors.Wrap(err, "decoding db")
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return errors.Wrap(err, "close db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// strip removes all fields we don't currently need.
|
||||
func strip(u url.URL) url.URL {
|
||||
return url.URL{
|
||||
Scheme: u.Scheme,
|
||||
Host: u.Host,
|
||||
Path: u.Path,
|
||||
}
|
||||
}
|
||||
|
||||
func mkdirs(root string) error {
|
||||
d, _ := filepath.Split(filepath.Join(root, rn))
|
||||
if !fs.Exists(d) {
|
||||
if err := os.MkdirAll(d, 0700); err != nil {
|
||||
return errors.Wrap(err, "mk pm dir")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TODO (sm): add more tests, including
|
||||
// - empty add
|
||||
// - removing db to empty
|
||||
// - bad uris
|
||||
|
||||
func dirMe(t *testing.T) (string, func()) {
|
||||
root, err := ioutil.TempDir("", "pm-tests-")
|
||||
if err != nil {
|
||||
t.Fatalf("tmpdir: %v", err)
|
||||
}
|
||||
if err := mkdirs(root); err != nil {
|
||||
t.Fatalf("making pm dirs: %v", err)
|
||||
}
|
||||
return root, func() {
|
||||
if err := os.RemoveAll(root); err != nil {
|
||||
t.Fatalf("cleanup: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
root, del := dirMe(t)
|
||||
defer del()
|
||||
|
||||
{
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if got, want := len(db), 0; got != want {
|
||||
t.Fatalf("empty db not empty: got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
bad := []string{
|
||||
"http\ns://\nFoo|n",
|
||||
}
|
||||
|
||||
if err := AddRemotes(root, bad); err == nil {
|
||||
t.Fatalf("didn't detect bad url")
|
||||
}
|
||||
|
||||
uris := []string{
|
||||
"https://pm.mcquay.me/darwin/amd64",
|
||||
}
|
||||
|
||||
if err := AddRemotes(root, uris); err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
|
||||
if got, want := len(db), len(uris); got != want {
|
||||
t.Fatalf("unepected number of uris; got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
for _, u := range uris {
|
||||
found := false
|
||||
for _, d := range db {
|
||||
if d.String() == u {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("did not find %v in the db", u)
|
||||
}
|
||||
}
|
||||
|
||||
if err := Add(root, uris); err == nil {
|
||||
t.Fatalf("did not detect duplicate, and should have")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
root, del := dirMe(t)
|
||||
defer del()
|
||||
|
||||
if err := Remove(root, nil); err == nil {
|
||||
t.Fatalf("should have returned error on empty db")
|
||||
}
|
||||
|
||||
uris := []string{
|
||||
"https://pm.mcquay.me/foo",
|
||||
"https://pm.mcquay.me/bar",
|
||||
"https://pm.mcquay.me/baz",
|
||||
}
|
||||
|
||||
if err := Remove(root, uris); err == nil {
|
||||
t.Fatalf("should have returned error asking to remove many uri on empty db")
|
||||
}
|
||||
|
||||
if err := Add(root, uris); err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
if err := Remove(root, uris[1:2]); err != nil {
|
||||
t.Fatalf("remove: %v", err)
|
||||
}
|
||||
db, err := load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load: %v", err)
|
||||
}
|
||||
if got, want := len(db), len(uris)-1; got != want {
|
||||
t.Fatalf("unepected number of uris; got %v, want %v", got, want)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, d := range db {
|
||||
if d.String() == uris[1] {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if found {
|
||||
for _, v := range db {
|
||||
t.Logf("%v", v.String())
|
||||
}
|
||||
t.Fatalf("failed to remove %v", uris[1:2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
root, del := dirMe(t)
|
||||
defer del()
|
||||
uris := []string{
|
||||
"https://pm.mcquay.me/foo",
|
||||
"https://pm.mcquay.me/bar",
|
||||
"https://pm.mcquay.me/baz",
|
||||
}
|
||||
|
||||
if err := Add(root, uris); err != nil {
|
||||
t.Fatalf("add: %v", err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if err := List(root, buf); err != nil {
|
||||
t.Fatalf("list: %v", err)
|
||||
}
|
||||
|
||||
for _, u := range uris {
|
||||
if !strings.Contains(buf.String(), u) {
|
||||
t.Fatalf("could not find %q in output\n%v", u, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user