Browse Source

Added the ability to iterate over routes in the store.

Kelly Norton 8 năm trước cách đây
mục cha
commit
859aee1362
4 tập tin đã thay đổi với 197 bổ sung2 xóa
  1. 13 2
      context/context.go
  2. 108 0
      context/context_test.go
  3. 68 0
      context/iter.go
  4. 8 0
      web/web.go

+ 13 - 2
context/context.go

@@ -12,6 +12,7 @@ import (
 
 	"github.com/syndtr/goleveldb/leveldb"
 	"github.com/syndtr/goleveldb/leveldb/opt"
+	"github.com/syndtr/goleveldb/leveldb/util"
 )
 
 const (
@@ -159,8 +160,18 @@ func (c *Context) Del(key string) error {
 	return c.db.Delete([]byte(key), &opt.WriteOptions{Sync: true})
 }
 
-// get everything in the db to dump it out for backup purposes
-func (c *Context) GetAll() (map [string]Route, error) {
+// List all routes in an iterator, starting with the key prefix of start (which can also be nil).
+func (c *Context) List(start []byte) *Iter {
+	return &Iter{
+		it: c.db.NewIterator(&util.Range{
+			Start: start,
+			Limit: nil,
+		}, nil),
+	}
+}
+
+// GetAll gets everything in the db to dump it out for backup purposes
+func (c *Context) GetAll() (map[string]Route, error) {
 	golinks := map[string]Route{}
 	iter := c.db.NewIterator(nil, nil)
 	defer iter.Release()

+ 108 - 0
context/context_test.go

@@ -1,6 +1,7 @@
 package context
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -77,3 +78,110 @@ func TestNextID(t *testing.T) {
 		e++
 	}
 }
+
+func TestEmptyList(t *testing.T) {
+	tmp, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	ctx, err := Open(filepath.Join(tmp, "data"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	it := ctx.List(nil)
+	defer it.Release()
+
+	if it.Valid() {
+		t.Fatal("Expected iterator to be invalid at start")
+	}
+
+	if it.Next() {
+		t.Fatal("Expected there to be no next")
+	}
+
+	if err := it.Error(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func putRoutes(ctx *Context, names ...string) error {
+	for _, name := range names {
+		if err := ctx.Put(name, &Route{
+			URL:  fmt.Sprintf("http://%s/", name),
+			Time: time.Unix(0, 420),
+		}); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func mustBeIterOf(t *testing.T, iter *Iter, names ...string) {
+	defer iter.Release()
+
+	if iter.Valid() {
+		t.Fatal("expected Iter to be invalid at start")
+	}
+
+	if err := iter.Error(); err != nil {
+		t.Fatal("expected Iter not to begin with error")
+	}
+
+	if iter.Name() != "" {
+		t.Fatalf("expected empty name but got \"%s\"", iter.Name())
+	}
+
+	if iter.Route() != nil {
+		t.Fatalf("expected empty route but got %v", iter.Route())
+	}
+
+	for i, name := range names {
+		if !iter.Next() {
+			t.Fatalf("at item %d, expected more items", i)
+		}
+
+		if !iter.Valid() {
+			t.Fatalf("on item %d, expected a valid iterator", i)
+		}
+
+		if iter.Name() != name {
+			t.Fatalf("expected name of %s, got %s", name, iter.Name())
+		}
+
+		if iter.Route().URL != fmt.Sprintf("http://%s/", name) {
+			t.Fatalf("expected route to have URL of http://%s/ got %s", name, iter.Route().URL)
+		}
+	}
+
+	if iter.Next() {
+		t.Fatal("iterator has too many items")
+	}
+
+	if iter.Valid() {
+		t.Fatal("iterator should not be valid at end")
+	}
+}
+
+func TestList(t *testing.T) {
+	tmp, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmp)
+
+	ctx, err := Open(filepath.Join(tmp, "data"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := putRoutes(ctx, "a", "c", "d"); err != nil {
+		t.Fatal(err)
+	}
+
+	mustBeIterOf(t, ctx.List(nil), "a", "c", "d")
+	mustBeIterOf(t, ctx.List([]byte{'b'}), "c", "d")
+	mustBeIterOf(t, ctx.List([]byte{'z'}))
+}

+ 68 - 0
context/iter.go

@@ -0,0 +1,68 @@
+package context
+
+import (
+	"bytes"
+
+	"github.com/syndtr/goleveldb/leveldb/iterator"
+)
+
+// Iter allows iteration of the named routes in the store.
+type Iter struct {
+	it   iterator.Iterator
+	name string
+	rt   *Route
+	err  error
+}
+
+// Valid indicates whether the current values of the iterator are valid.
+func (i *Iter) Valid() bool {
+	return i.it.Valid() && i.err == nil
+}
+
+// Next advances the iterator to the next value.
+func (i *Iter) Next() bool {
+	it := i.it
+
+	i.name = ""
+	i.rt = nil
+
+	if !it.Next() {
+		return false
+	}
+
+	rt := &Route{}
+
+	if err := rt.read(bytes.NewBuffer(it.Value())); err != nil {
+		i.err = err
+		return false
+	}
+
+	i.name = string(i.it.Key())
+	i.rt = rt
+
+	return true
+}
+
+// Error returns any active error that has stopped the iterator.
+func (i *Iter) Error() error {
+	if err := i.it.Error(); err != nil {
+		return err
+	}
+
+	return i.err
+}
+
+// Name is the name of the current route.
+func (i *Iter) Name() string {
+	return i.name
+}
+
+// Route is the current route.
+func (i *Iter) Route() *Route {
+	return i.rt
+}
+
+// Release disposes of the resources in the iterator.
+func (i *Iter) Release() {
+	i.it.Release()
+}

+ 8 - 0
web/web.go

@@ -95,6 +95,14 @@ func ListenAndServe(addr string, admin bool, version string, ctx *context.Contex
 		fmt.Fprintln(w, "OK")
 	})
 
+	mux.HandleFunc("/debug/", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/plain")
+		it := ctx.List([]byte{':' + 1})
+		for it.Next() {
+			fmt.Fprintf(w, "%s %s\n", it.Name(), it.Route().URL)
+		}
+	})
+
 	if admin {
 		mux.Handle("/admin/", &adminHandler{ctx})
 	}