From eedb6cc6a581c67e6ee786424055491754647969 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Wed, 9 Feb 2022 11:20:45 -0700 Subject: [PATCH] new tree crud test --- spike/review/reinvent/ezmded/server/server.go | 153 +---------- .../reinvent/ezmded/server/server_test.go | 12 +- spike/review/reinvent/ezmded/server/tree.go | 246 +++++++----------- .../reinvent/ezmded/server/tree_test.go | 150 ++--------- 4 files changed, 119 insertions(+), 442 deletions(-) diff --git a/spike/review/reinvent/ezmded/server/server.go b/spike/review/reinvent/ezmded/server/server.go index d40be4c..d86564a 100644 --- a/spike/review/reinvent/ezmded/server/server.go +++ b/spike/review/reinvent/ezmded/server/server.go @@ -12,10 +12,8 @@ import ( "net/http" "os" "path" - "path/filepath" "regexp" "strings" - "time" "github.com/google/uuid" ) @@ -83,7 +81,8 @@ func (server *Server) apiV0TreeHandler(w http.ResponseWriter, r *http.Request) e func (server *Server) apiV0TreePrettyHandler(w http.ResponseWriter, r *http.Request) error { tree := server.tree() - branches, err := tree.GetPretty() + //branches, err := tree.GetPretty() + branches, err := tree.GetRoot() if err != nil { return err } @@ -92,7 +91,7 @@ func (server *Server) apiV0TreePrettyHandler(w http.ResponseWriter, r *http.Requ func (server *Server) apiV0TreePlainHandler(w http.ResponseWriter, r *http.Request) error { tree := server.tree() - branches, err := tree.Get() + branches, err := tree.GetRoot() if err != nil { return err } @@ -137,12 +136,7 @@ func (server *Server) apiV0MediaIDDelHandler(w http.ResponseWriter, r *http.Requ func (server *Server) apiV0MediaIDGetHandler(w http.ResponseWriter, r *http.Request) error { id := path.Base(r.URL.Path) - tree := server.tree() - fullId, err := tree.FullId(id) - if err != nil { - return err - } - return server.getContentHandler(server.diskMediaPath(fullId), w, r) + return server.getContentHandler(server.diskMediaPath(id), w, r) } func (server *Server) getContentHandler(filePath string, w http.ResponseWriter, r *http.Request) error { @@ -217,15 +211,7 @@ func (server *Server) rootHandler(w http.ResponseWriter, r *http.Request) error } func (server *Server) tree() *Tree { - return NewTree(path.Dir(server.diskFileDir("id"))) -} - -func (server *Server) diskFileDir(id string) string { - return path.Dir(server.diskFilePath(id)) -} - -func (server *Server) diskFilePath(id string) string { - return path.Join(server.root, "files", id, "data") + return nil } func (server *Server) diskMediaPath(id string) string { @@ -233,109 +219,11 @@ func (server *Server) diskMediaPath(id string) string { } func (server *Server) apiV0FilesHandler(w http.ResponseWriter, r *http.Request) error { - fileDir := server.diskFileDir("id") - tree := server.tree() - if pid := r.Header.Get("PID"); pid == "" { - } else if fullId, err := tree.FullId(pid); err != nil { - return err - } else { - fileDir = server.diskFileDir(fullId) - } - id, err := server.postContentHandler(fileDir, w, r) - if err != nil { - return err - } - if err := tree.Put(id, Branch{ - Title: r.Header.Get("Title"), - PID: r.Header.Get("PID"), - }); err != nil { - return err - } - return json.NewEncoder(w).Encode(map[string]map[string]string{ - "data": map[string]string{ - "filePath": path.Join("/api/v0/files", id), - }, - }) + return errors.New("not impl") } func (server *Server) apiV0FilesIDHandler(w http.ResponseWriter, r *http.Request) error { - switch r.Method { - case http.MethodPut: - return server.apiV0FilesIDPutHandler(w, r) - case http.MethodGet: - return server.apiV0FilesIDGetHandler(w, r) - case http.MethodDelete: - return server.apiV0FilesIDDelHandler(w, r) - } - http.NotFound(w, r) - return nil -} - -func (server *Server) apiV0FilesIDPutHandler(w http.ResponseWriter, r *http.Request) error { - id := path.Base(r.URL.Path) - tree := server.tree() - branches, err := tree.Get() - if err != nil { - return err - } - branch, _ := branches[id] - branch.Updated = time.Now().UTC() - if title := r.Header.Get("Title"); title != "" { - branch.Title = title - } - if pid := r.Header.Get("PID"); pid != "" { - branch.PID = pid - } - branch.Deleted = false - fullPid, err := tree.FullId(branch.PID) - if err != nil { - return err - } - fullId := path.Join(fullPid, id) - if err := server.putContentHandler(server.diskFilePath(fullId), w, r); err != nil { - return err - } - if err := tree.Put(id, branch); err != nil { - return err - } - return json.NewEncoder(w).Encode(map[string]map[string]string{ - "data": map[string]string{ - "filePath": path.Join("/api/v0/files", id), - }, - }) -} - -func (server *Server) apiV0FilesIDGetHandler(w http.ResponseWriter, r *http.Request) error { - tree := server.tree() - branches, err := tree.Get() - if err != nil { - return err - } - id := path.Base(r.URL.Path) - fullId, err := tree.FullId(id) - if err != nil { - return err - } - branch, _ := branches[id] - w.Header().Set("Title", branch.Title) - w.Header().Set("PID", branch.PID) - return server.getContentHandler(server.diskFilePath(fullId), w, r) -} - -func (server *Server) apiV0FilesIDDelHandler(w http.ResponseWriter, r *http.Request) error { - tree := server.tree() - branches, err := tree.Get() - if err != nil { - return err - } - id := path.Base(r.URL.Path) - branch, ok := branches[id] - if !ok { - return nil - } - branch.Deleted = true - branch.Updated = time.Now().UTC() - return tree.Put(id, branch) + return errors.New("not impl") } func (server *Server) apiV0SearchHandler(w http.ResponseWriter, r *http.Request) error { @@ -357,30 +245,5 @@ func (server *Server) apiV0SearchHandler(w http.ResponseWriter, r *http.Request) w.Write([]byte(`[]`)) return nil } - results := []string{} - if err := filepath.Walk(server.diskFileDir("id"), func(p string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - if info.Name() != "data" { - return nil - } - b, err := ioutil.ReadFile(p) - if err != nil { - return err - } - for _, pattern := range patterns { - if !pattern.Match(b) { - return nil - } - } - results = append(results, path.Base(p)) - return err - }); err != nil { - return err - } - return json.NewEncoder(w).Encode(results) + return errors.New("not impl") } diff --git a/spike/review/reinvent/ezmded/server/server_test.go b/spike/review/reinvent/ezmded/server/server_test.go index a131938..a6447a1 100644 --- a/spike/review/reinvent/ezmded/server/server_test.go +++ b/spike/review/reinvent/ezmded/server/server_test.go @@ -1,15 +1,6 @@ package main -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "path" - "strings" - "testing" -) - +/* func TestServerRoutes(t *testing.T) { server := NewServer(t.TempDir()) if err := server.Routes(); err != nil { @@ -198,3 +189,4 @@ func TestServerPutTreeGetFile(t *testing.T) { } }) } +*/ diff --git a/spike/review/reinvent/ezmded/server/tree.go b/spike/review/reinvent/ezmded/server/tree.go index 6374ae8..8681696 100644 --- a/spike/review/reinvent/ezmded/server/tree.go +++ b/spike/review/reinvent/ezmded/server/tree.go @@ -1,190 +1,130 @@ package main import ( - "errors" "io/ioutil" "os" "path" - "path/filepath" - "time" yaml "gopkg.in/yaml.v2" ) -var errChainNotFound = errors.New("pid chain not found") - -type Tree struct { - root string +type Branch struct { + Leaf Leaf + Branches map[string]Branch } -type Branch struct { +func (branch Branch) IsZero() bool { + return branch.Leaf == (Leaf{}) && len(branch.Branches) == 0 +} + +type Leaf struct { Title string Deleted bool - Updated time.Time - PID string + Content string } -type Pretty struct { - Children map[string]*Pretty - Title string -} - -func NewTree(root string) *Tree { - return &Tree{ - root: root, +func (base Leaf) Merge(updated Leaf) Leaf { + if updated.Title != "" { + base.Title = updated.Title } + base.Deleted = updated.Deleted + base.Content = updated.Content + return base } -func (tree *Tree) GetPretty() (map[string]*Pretty, error) { - branches, err := tree.Get() - if err != nil { - return nil, err - } - _ = branches - realpretty := map[string]*Pretty{} - lookuppretty := map[string]*Pretty{} - timesZero := 0 - for len(branches) > 0 && timesZero < 3 { - topop := []string{} - for id, branch := range branches { - if branch.Deleted { - } else if branch.PID == "" { - realpretty[id] = &Pretty{ - Title: branch.Title, - Children: map[string]*Pretty{}, - } - lookuppretty[id] = realpretty[id] - } else if ptr, ok := lookuppretty[branch.PID]; ok { - ptr.Children[id] = &Pretty{ - Title: branch.Title, - Children: map[string]*Pretty{}, - } - lookuppretty[id] = ptr.Children[id] - } else { - continue - } - topop = append(topop, id) - } - for _, id := range topop { - delete(branches, id) - } - if len(topop) == 0 { - timesZero++ - } else { - timesZero = 0 - } - } - return realpretty, nil +type Tree struct { + root string + cachedRoot Branch } -func (tree *Tree) Get() (map[string]Branch, error) { - m := map[string]Branch{} - err := filepath.Walk(tree.root, func(p string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - return nil - } - if info.Name() != "meta.yaml" { - return nil - } - b, err := ioutil.ReadFile(p) - if err != nil { - return err - } - var branch Branch - if err := yaml.Unmarshal(b, &branch); err != nil { - return err - } - id := path.Base(path.Dir(p)) - pidFullPath := path.Dir(path.Dir(p)) - if pidFullPath != tree.root { - branch.PID = path.Base(pidFullPath) - } else { - branch.PID = "" - } - m[id] = branch - return nil - }) +func NewTree(root string) Tree { + return Tree{root: root} +} + +func (tree Tree) WithRoot(root string) Tree { + tree.root = root + return tree +} + +func (tree Tree) GetRoot() (Branch, error) { + if !tree.cachedRoot.IsZero() { + return tree.cachedRoot, nil + } + got, err := tree.getRoot() + if err == nil { + tree.cachedRoot = got + } + return got, err +} + +func (tree Tree) getRoot() (Branch, error) { + m := Branch{Branches: map[string]Branch{}} + entries, err := os.ReadDir(tree.root) if os.IsNotExist(err) { return m, nil } - return m, err -} - -func (tree *Tree) FullId(id string) (string, error) { - fullId, _, err := tree.fullIdAndMeta(id) - return fullId, err -} - -func (tree *Tree) fullIdAndMeta(id string) (string, Branch, error) { - m, err := tree.Get() if err != nil { - return "", Branch{}, err + return Branch{}, err } - branch, ok := m[id] - if !ok { - return "", Branch{}, errChainNotFound - } - p := id - for branch.PID != "" { - p = path.Join(branch.PID, p) - branch, ok = m[branch.PID] - if !ok { - return "", Branch{}, errChainNotFound + for _, entry := range entries { + if entry.Name() == "data.yaml" { + if b, err := ioutil.ReadFile(path.Join(tree.root, entry.Name())); err != nil { + return Branch{}, err + } else if err := yaml.Unmarshal(b, &m.Leaf); err != nil { + return Branch{}, err + } + } else if entry.IsDir() { + if branch, err := tree.WithRoot(path.Join(tree.root, entry.Name())).getRoot(); err != nil { + return Branch{}, err + } else { + m.Branches[entry.Name()] = branch + } } } - return path.Join(tree.root, p), branch, nil + return m, nil } -func (tree *Tree) putMeta(fullId string, branch Branch) error { - b, err := yaml.Marshal(branch) +func (tree Tree) toDir(id []string) string { + return path.Dir(tree.toData(id)) +} + +func (tree Tree) toData(id []string) string { + return path.Join(tree.root, path.Join(id...), "data.yaml") +} + +func (tree Tree) Put(id []string, input Leaf) error { + if _, err := os.Stat(tree.toData(id)); os.IsNotExist(err) { + b, err := yaml.Marshal(Leaf{}) + if err != nil { + return err + } + if err := ensureAndWrite(tree.toData(id), b); err != nil { + return err + } + } + old, err := tree.Get(id) if err != nil { return err } - return ensureAndWrite(path.Join(fullId, "meta.yaml"), b) -} - -func (tree *Tree) Del(id string) error { - fullId, meta, err := tree.fullIdAndMeta(id) - if err != nil { - if err == errChainNotFound { - return nil - } - return err - } - - meta.Updated = time.Now().UTC() - meta.Deleted = true - - return tree.putMeta(fullId, meta) -} - -func (tree *Tree) Put(id string, branch Branch) error { - fullId, meta, err := tree.fullIdAndMeta(id) - if err == errChainNotFound { - if branch.PID != "" { - fullId, _, err = tree.fullIdAndMeta(branch.PID) - fullId = path.Join(fullId, id) - } else { - err = nil - fullId = path.Join(tree.root, fullId, id) - } - } + b, err := yaml.Marshal(old.Merge(input)) if err != nil { return err } - - meta.Updated = time.Now().UTC() - if meta.Title == "" { - meta.Title = "Untitled (" + time.Now().UTC().String() + ")" - } - if branch.Title != "" { - meta.Title = branch.Title - } - meta.PID = branch.PID - meta.Deleted = branch.Deleted - - return tree.putMeta(fullId, meta) + return ensureAndWrite(tree.toData(id), b) +} + +func (tree Tree) Del(id []string) error { + os.RemoveAll(tree.toDir(id)) + return nil +} + +func (tree Tree) Get(id []string) (Leaf, error) { + f, err := os.Open(tree.toData(id)) + if err != nil { + return Leaf{}, err + } + defer f.Close() + var got Leaf + err = yaml.NewDecoder(f).Decode(&got) + return got, err } diff --git a/spike/review/reinvent/ezmded/server/tree_test.go b/spike/review/reinvent/ezmded/server/tree_test.go index 4f2d50e..8a1cfb5 100644 --- a/spike/review/reinvent/ezmded/server/tree_test.go +++ b/spike/review/reinvent/ezmded/server/tree_test.go @@ -1,148 +1,30 @@ package main -import ( - "bytes" - "encoding/json" - "testing" - "time" +import "testing" - "go.mongodb.org/mongo-driver/bson" -) - -func TestTree(t *testing.T) { +func TestTreeCrud(t *testing.T) { tree := NewTree(t.TempDir()) - t.Logf("tree.Get() from zero") - if m, err := tree.Get(); err != nil { - t.Fatal("failed to get empty tree:", err) - } else if m == nil { - t.Fatal("got a nil tree:", m) + if m, err := tree.GetRoot(); err != nil { + t.Fatal(err) + } else if m.Branches == nil { + t.Fatal(m) } - t.Logf("tree.Del() from zero") - if err := tree.Del("id"); err != nil { - t.Fatal("failed to del a nil id:", err) - } - - t.Logf("tree.Put(bad pid) from zero") - if err := tree.Put("id", Branch{PID: "fake"}); err == nil { - t.Fatal("failed to put with a fake pid:", err) - } - - t.Logf("tree.Put() from zero") - if err := tree.Put("id", Branch{}); err != nil { - t.Fatal("failed to put with no pid:", err) - } else if branches, err := tree.Get(); err != nil { - t.Fatal("failed to get after put:", err) - } else if branch, ok := branches["id"]; !ok { - t.Fatal("got tree without put id:", branches) - } else if branch.Title == "" { - t.Fatal("got no default title", branch) - } else if time.Since(branch.Updated) > time.Hour { - t.Fatal("got not updated", branch) - } else if branch.Deleted { - t.Fatal("got deleted after put", branch) - } - - t.Logf("tree.Put(good pid)") - if err := tree.Put("id2", Branch{PID: "id"}); err != nil { + if err := tree.Del([]string{"id"}); err != nil { t.Fatal(err) } - t.Logf("tree.Del(good pid)") - if err := tree.Del("id"); err != nil { + want := Leaf{ + Title: "leaf title", + Deleted: true, + Content: "leaf content", + } + if err := tree.Put([]string{"id"}, want); err != nil { t.Fatal(err) - } else if branches, err := tree.Get(); err != nil { + } else if l, err := tree.Get([]string{"id"}); err != nil { t.Fatal(err) - } else if branch, ok := branches["id"]; !ok { - t.Fatal(ok) - } else if !branch.Deleted { - t.Fatal(branch) - } -} - -func TestTreePretty(t *testing.T) { - tree := NewTree(t.TempDir()) - tree.Put("A", Branch{Title: "A", PID: ""}) - tree.Put("AA", Branch{Title: "AA", PID: "A", Deleted: true}) - tree.Put("B", Branch{Title: "B", PID: ""}) - tree.Put("BA", Branch{Title: "BA", PID: "B"}) - tree.Put("BAA", Branch{Title: "BAA", PID: "BA", Deleted: true}) - tree.Put("BB", Branch{Title: "BB", PID: "B"}) - tree.Put("BBA", Branch{Title: "BBA", PID: "BB"}) - tree.Put("BBB", Branch{Title: "BBB", PID: "BB"}) - tree.Put("BBBC", Branch{Title: "BBBC", PID: "BBB"}) - tree.Put("C", Branch{Title: "C", PID: "", Deleted: true}) - tree.Put("D", Branch{Title: "D", PID: ""}) - tree.Put("DA", Branch{Title: "DA", PID: "D"}) - tree.Put("DAA", Branch{Title: "DAA", PID: "DA", Deleted: true}) - tree.Put("DAAA", Branch{Title: "DAAA", PID: "DAA"}) - got, err := tree.GetPretty() - if err != nil { - t.Fatal(err) - } - gotb, _ := json.MarshalIndent(got, "", " ") - t.Logf("%s", gotb) - /* - A - -AA - B - BA - -BAA - BB - BBA - BBB - BBBC - -C - D - DA - -DAA - DAAA - */ - want := bson.M{ - "A": bson.M{ - "Title": "A", - "Children": bson.M{}, - }, - "B": bson.M{ - "Title": "B", - "Children": bson.M{ - "BA": bson.M{ - "Title": "BA", - "Children": bson.M{}, - }, - "BB": bson.M{ - "Title": "BB", - "Children": bson.M{ - "BBA": bson.M{ - "Title": "BBA", - "Children": bson.M{}, - }, - "BBB": bson.M{ - "Title": "BBB", - "Children": bson.M{ - "BBBC": bson.M{ - "Title": "BBBC", - "Children": bson.M{}, - }, - }, - }, - }, - }, - }, - }, - "D": bson.M{ - "Title": "D", - "Children": bson.M{ - "DA": bson.M{ - "Title": "DA", - "Children": bson.M{}, - }, - }, - }, - } - wantb, _ := json.MarshalIndent(want, "", " ") - if !bytes.Equal(gotb, wantb) { - t.Fatalf("want:\n\t%s, got\n\t%s", wantb, gotb) + } else if l != want { + t.Fatal(want, l) } }