author | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-10-09 18:08:24 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-10-10 00:32:55 UTC |
parent | cf36003e3af73b18ec3d1abf4820fff5caa61b52 |
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) + } +}