diff --git a/spike/review/reinvent/ezmded/server/id/meta.yaml b/spike/review/reinvent/ezmded/server/id/meta.yaml new file mode 100755 index 0000000..8329c52 --- /dev/null +++ b/spike/review/reinvent/ezmded/server/id/meta.yaml @@ -0,0 +1,4 @@ +title: Untitled (2022-02-09 15:31:57.033525 +0000 UTC) +deleted: false +updated: 2022-02-09T15:31:57.033525Z +pid: "" diff --git a/spike/review/reinvent/ezmded/server/meta.yaml b/spike/review/reinvent/ezmded/server/meta.yaml new file mode 100755 index 0000000..76792d5 --- /dev/null +++ b/spike/review/reinvent/ezmded/server/meta.yaml @@ -0,0 +1,4 @@ +title: Untitled (2022-02-09 15:31:36.139835 +0000 UTC) +deleted: false +updated: 2022-02-09T15:31:36.139835Z +pid: "" diff --git a/spike/review/reinvent/ezmded/server/server.go b/spike/review/reinvent/ezmded/server/server.go index fd1527d..6fbb575 100644 --- a/spike/review/reinvent/ezmded/server/server.go +++ b/spike/review/reinvent/ezmded/server/server.go @@ -212,7 +212,7 @@ func (server *Server) rootHandler(w http.ResponseWriter, r *http.Request) error } func (server *Server) tree() *Tree { - return NewTree(path.Join(server.root, "tree.yaml")) + return NewTree(path.Dir(server.diskFilePath("id"))) } func (server *Server) diskFilePath(id string) string { diff --git a/spike/review/reinvent/ezmded/server/tree.go b/spike/review/reinvent/ezmded/server/tree.go index ffd1cb7..7fcf25c 100644 --- a/spike/review/reinvent/ezmded/server/tree.go +++ b/spike/review/reinvent/ezmded/server/tree.go @@ -4,13 +4,17 @@ 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 { - path string + root string } type Branch struct { @@ -25,9 +29,9 @@ type Pretty struct { Title string } -func NewTree(path string) *Tree { +func NewTree(root string) *Tree { return &Tree{ - path: path, + root: root, } } @@ -74,53 +78,108 @@ func (tree *Tree) GetPretty() (map[string]*Pretty, error) { } func (tree *Tree) Get() (map[string]Branch, error) { - b, err := ioutil.ReadFile(tree.path) + 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 + }) if os.IsNotExist(err) { - return map[string]Branch{}, nil + return m, nil } - if err != nil { - return nil, err - } - var m map[string]Branch - err = yaml.Unmarshal(b, &m) return m, err } -func (tree *Tree) Set(m map[string]Branch) error { - b, err := yaml.Marshal(m) - if err != nil { - return err - } - return ioutil.WriteFile(tree.path, b, os.ModePerm) -} - -func (tree *Tree) Del(id string) error { +func (tree *Tree) fullIdAndMeta(id string) (string, Branch, error) { m, err := tree.Get() if err != nil { - return err + return "", Branch{}, err } branch, ok := m[id] if !ok { - return nil + return "", Branch{}, errChainNotFound } - branch.Updated = time.Now().UTC() - branch.Deleted = true - m[id] = branch - return tree.Set(m) + p := id + for branch.PID != "" { + p = path.Join(branch.PID, p) + branch, ok = m[branch.PID] + if !ok { + return "", Branch{}, errChainNotFound + } + } + return path.Join(tree.root, p), branch, nil } -func (tree *Tree) Put(id string, branch Branch) error { - branch.Updated = time.Now().UTC() - if branch.Title == "" { - branch.Title = "Untitled (" + time.Now().UTC().String() + ")" - } - m, err := tree.Get() +func (tree *Tree) putMeta(fullId string, branch Branch) error { + b, err := yaml.Marshal(branch) if err != nil { return err } - if _, ok := m[branch.PID]; !ok && branch.PID != "" { - return errors.New("PID does not exist") - } - m[id] = branch - return tree.Set(m) + 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) + } + } + 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) } diff --git a/spike/review/reinvent/ezmded/server/tree_test.go b/spike/review/reinvent/ezmded/server/tree_test.go index a0e2ed9..4f2d50e 100644 --- a/spike/review/reinvent/ezmded/server/tree_test.go +++ b/spike/review/reinvent/ezmded/server/tree_test.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/json" - "path" "testing" "time" @@ -11,41 +10,46 @@ import ( ) func TestTree(t *testing.T) { - path := path.Join(t.TempDir(), "index.yaml") - tree := NewTree(path) + tree := NewTree(t.TempDir()) + t.Logf("tree.Get() from zero") if m, err := tree.Get(); err != nil { - t.Fatal(err) + t.Fatal("failed to get empty tree:", err) } else if m == nil { - t.Fatal(m) + t.Fatal("got a nil tree:", m) } + t.Logf("tree.Del() from zero") if err := tree.Del("id"); err != nil { - t.Fatal(err) + 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(err) + 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(err) + t.Fatal("failed to put with no pid:", err) } else if branches, err := tree.Get(); err != nil { - t.Fatal(err) + t.Fatal("failed to get after put:", err) } else if branch, ok := branches["id"]; !ok { - t.Fatal(err) + t.Fatal("got tree without put id:", branches) } else if branch.Title == "" { - t.Fatal(branch) + t.Fatal("got no default title", branch) } else if time.Since(branch.Updated) > time.Hour { - t.Fatal(branch) + t.Fatal("got not updated", branch) } else if branch.Deleted { - t.Fatal(branch) + t.Fatal("got deleted after put", branch) } + t.Logf("tree.Put(good pid)") if err := tree.Put("id2", Branch{PID: "id"}); err != nil { t.Fatal(err) } + t.Logf("tree.Del(good pid)") if err := tree.Del("id"); err != nil { t.Fatal(err) } else if branches, err := tree.Get(); err != nil { @@ -58,7 +62,7 @@ func TestTree(t *testing.T) { } func TestTreePretty(t *testing.T) { - tree := NewTree(path.Join(t.TempDir(), "tree_pretty.yaml")) + 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: ""})