git » chasquid » commit aa0486b

protoio: Add a generic protobuf store

author Alberto Bertogli
2016-10-09 18:08:24 UTC
committer Alberto Bertogli
2016-10-10 00:32:55 UTC
parent cf36003e3af73b18ec3d1abf4820fff5caa61b52

protoio: Add a generic protobuf store

This patch adds a generic protobuf store, where one can put and retreive
protobufs, indexed by a string.

This will be used in subsequent patches.

internal/protoio/protoio.go +61 -0
internal/protoio/protoio_test.go +38 -0

diff --git a/internal/protoio/protoio.go b/internal/protoio/protoio.go
index d17a98a..4f5ac10 100644
--- a/internal/protoio/protoio.go
+++ b/internal/protoio/protoio.go
@@ -3,7 +3,9 @@ package protoio
 
 import (
 	"io/ioutil"
+	"net/url"
 	"os"
+	"strings"
 
 	"blitiri.com.ar/go/chasquid/internal/safeio"
 
@@ -46,3 +48,62 @@ func WriteTextMessage(fname string, pb proto.Message, perm os.FileMode) error {
 	out := proto.MarshalTextString(pb)
 	return safeio.WriteFile(fname, []byte(out), perm)
 }
+
+///////////////////////////////////////////////////////////////
+
+// Store represents a persistent protocol buffer message store.
+type Store struct {
+	// Directory where the store is.
+	dir string
+}
+
+// NewStore returns a new Store instance.  It will create dir if needed.
+func NewStore(dir string) (*Store, error) {
+	s := &Store{dir}
+	err := os.MkdirAll(dir, 0770)
+	return s, err
+}
+
+const storeIDPrefix = "s:"
+
+// idToFname takes a generic id and returns the corresponding file for it
+// (which may or may not exist).
+func (s *Store) idToFname(id string) string {
+	return s.dir + "/" + storeIDPrefix + url.QueryEscape(id)
+}
+
+func (s *Store) Put(id string, m proto.Message) error {
+	return WriteTextMessage(s.idToFname(id), m, 0660)
+}
+
+func (s *Store) Get(id string, m proto.Message) (bool, error) {
+	err := ReadTextMessage(s.idToFname(id), m)
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return err == nil, err
+}
+
+func (s *Store) ListIDs() ([]string, error) {
+	ids := []string{}
+
+	entries, err := ioutil.ReadDir(s.dir)
+	if err != nil {
+		return nil, err
+	}
+	for _, e := range entries {
+		if !strings.HasPrefix(e.Name(), storeIDPrefix) {
+			continue
+		}
+
+		id := e.Name()[len(storeIDPrefix):]
+		id, err = url.QueryUnescape(id)
+		if err != nil {
+			continue
+		}
+
+		ids = append(ids, id)
+	}
+
+	return ids, nil
+}
diff --git a/internal/protoio/protoio_test.go b/internal/protoio/protoio_test.go
index 7260a00..e8ad037 100644
--- a/internal/protoio/protoio_test.go
+++ b/internal/protoio/protoio_test.go
@@ -65,3 +65,41 @@ func TestText(t *testing.T) {
 		os.RemoveAll(dir)
 	}
 }
+
+func TestStore(t *testing.T) {
+	dir := mustTempDir(t)
+	st, err := NewStore(dir + "/store")
+	if err != nil {
+		t.Fatalf("failed to create store: %v", err)
+	}
+
+	if ids, err := st.ListIDs(); len(ids) != 0 || err != nil {
+		t.Errorf("expected no ids, got %v - %v", ids, err)
+	}
+
+	pb := &testpb.M{"hola"}
+
+	if err := st.Put("f", pb); err != nil {
+		t.Error(err)
+	}
+
+	pb2 := &testpb.M{}
+	if ok, err := st.Get("f", pb2); err != nil || !ok {
+		t.Errorf("Get(f): %v - %v", ok, err)
+	}
+	if pb.Content != pb2.Content {
+		t.Errorf("content mismatch, got %q, expected %q", pb2.Content, pb.Content)
+	}
+
+	if ok, err := st.Get("notexists", pb2); err != nil || ok {
+		t.Errorf("Get(notexists): %v - %v", ok, err)
+	}
+
+	if ids, err := st.ListIDs(); len(ids) != 1 || ids[0] != "f" || err != nil {
+		t.Errorf("expected [f], got %v - %v", ids, err)
+	}
+
+	if !t.Failed() {
+		os.RemoveAll(dir)
+	}
+}