From 109c7c48bc8bb2b423c07c8018aee4f9ffc88fc3 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Sat, 2 Mar 2019 11:56:05 -0700 Subject: [PATCH] seemingly good --- mytinytodo/buffer.go | 137 +++++++++++++++++++++++---------- mytinytodo/buffer_test.go | 64 ++++++++++++++- mytinytodo/packable.go | 4 +- mytinytodo/packable_test.go | 4 +- mytinytodo/remote/client.go | 8 +- mytinytodo/remote/list.go | 43 +++++++++++ mytinytodo/remote/list_test.go | 46 +++++++++++ mytinytodo/remote/task.go | 43 +++++++++++ mytinytodo/remote/task_test.go | 48 ++++++++++++ 9 files changed, 344 insertions(+), 53 deletions(-) create mode 100644 mytinytodo/remote/list_test.go create mode 100644 mytinytodo/remote/task_test.go diff --git a/mytinytodo/buffer.go b/mytinytodo/buffer.go index 39e26c6..cbb04e2 100644 --- a/mytinytodo/buffer.go +++ b/mytinytodo/buffer.go @@ -1,9 +1,11 @@ package mytinytodo import ( + "fmt" "local/mytinytodoclient/mytinytodo/remote" "local/rproxy3/storage" "log" + "strings" "sync" "time" @@ -12,7 +14,10 @@ import ( const nsQueue = "delta" const keyQueue = "queue" -const nsTasks = "delta" +const nsLists = "lists" +const keyLists = "lists" +const nsTasks = "tasks" +const keyTasks = "tasks" type Buffer struct { config *Config @@ -31,8 +36,8 @@ func NewBuffer(config *Config) (*Buffer, error) { dbLock: &sync.RWMutex{}, interval: time.Second * 10, } - go b.Dequeue() - go b.RefreshLocal() + go b.notDoneCallback(b.Dequeue) + go b.notDoneCallback(b.RefreshLocal) return b, nil } @@ -40,75 +45,123 @@ func (buffer *Buffer) Close() { close(buffer.done) } -func (buffer *Buffer) Enqueue(op remote.Op, listID, taskName string, taskTags ...string) error { +func (buffer *Buffer) Enqueue(op remote.Op, listID, taskTitle string, taskTags ...string) error { buffer.dbLock.Lock() defer buffer.dbLock.Unlock() qop := &QueuedOp{ Op: op, - ListID: listID, - TaskName: taskName, + List: remote.List{ID: listID}, + Task: remote.Task{Title: taskTitle}, TaskTags: taskTags, } uuid, _ := uuid.NewRandom() key := uuid.String() + todo := NewStringArray() - if err := buffer.db.Get(nsQueue, keyQueue, todo); err != nil { + if err := buffer.db.Get(nsQueue, keyQueue, todo); err != nil && err != storage.ErrNotFound { return err } + sa := todo.StringArray() sa = append(sa, key) todo = NewStringArray(sa...) - if err := buffer.db.Set(nsTasks, key, qop); err != nil { + if err := buffer.db.Set(nsQueue, key, qop); err != nil { return err } if err := buffer.db.Set(nsQueue, keyQueue, todo); err != nil { return err } + log.Printf("enqueued task %v as %v, %vth in line", qop, key, len(sa)) return nil } func (buffer *Buffer) Dequeue() { - buffer.notDoneCallback(func() { - client, err := remote.NewClient(buffer.config.Config) + client, err := remote.NewClient(buffer.config.Config) + if err != nil { + log.Printf("cannot create client: %v", err) + return + } + if _, err := client.Lists(); err != nil { + log.Printf("cannot client.lists: %v", err) + return + } + + buffer.dbLock.Lock() + defer buffer.dbLock.Unlock() + todo := NewStringArray() + if err := buffer.db.Get(nsQueue, keyQueue, todo); err != nil { + log.Printf("cannot get %v.%v: %v", nsQueue, keyQueue, err) + return + } + + sa := todo.StringArray() + nsa := []string{} + for i := range sa { + qop := &QueuedOp{} + if err := buffer.db.Get(nsQueue, sa[i], qop); err != nil { + log.Printf("cannot get %v.%v: %v", nsQueue, sa[i], err) + nsa = append(nsa, sa[i]) + continue + } + var err error + switch qop.Op { + case remote.NEW: + err = client.NewTask(qop.List, qop.Task, strings.Join(qop.TaskTags, ",")) + case remote.CLOSE: + err = client.CloseTask(qop.Task) + case remote.OPEN: + err = client.OpenTask(qop.Task) + default: + err = fmt.Errorf("cannot dequeue op %v", qop.Op) + } if err != nil { - log.Printf("cannot create client: %v", err) - return + log.Printf("failed op %v: %v", qop.Op, err) + continue } - if _, err := client.Lists(); err != nil { - log.Printf("cannot client.lists: %v", err) - return + if err := buffer.db.Set(nsQueue, sa[i], nil); err != nil { + log.Printf("cannot unset %v.%v: %v", nsQueue, sa[i], err) + nsa = append(nsa, sa[i]) + continue } - buffer.dbLock.Lock() - defer buffer.dbLock.Unlock() - todo := NewStringArray() - if err := buffer.db.Get(nsQueue, keyQueue, todo); err != nil { - log.Printf("cannot get %v.%v: %v", nsQueue, keyQueue, err) - return - } - sa := todo.StringArray() - nsa := []string{} - for i := range sa { - qop := &QueuedOp{} - if err := buffer.db.Get(nsTasks, sa[i], qop); err != nil { - log.Printf("cannot get %v.%v: %v", nsTasks, sa[i], err) - nsa = append(nsa, sa[i]) - continue - } - log.Printf("UPSERT %v", qop) - } - if err := buffer.db.Set(nsQueue, keyQueue, NewStringArray(nsa...)); err != nil { - log.Printf("cannot update queue: %v", err) - return - } - }) + } + + if err := buffer.db.Set(nsQueue, keyQueue, NewStringArray(nsa...)); err != nil { + log.Printf("cannot update queue: %v", err) + return + } } func (buffer *Buffer) RefreshLocal() { - buffer.notDoneCallback(func() { - buffer.dbLock.Lock() - defer buffer.dbLock.Unlock() - }) + client, err := remote.NewClient(buffer.config.Config) + if err != nil { + log.Printf("cannot create client: %v", err) + return + } + lists, err := client.Lists() + if err != nil { + log.Printf("cannot client.lists: %v", err) + return + } + + buffer.dbLock.Lock() + defer buffer.dbLock.Unlock() + if err := buffer.db.Set(nsLists, keyLists, &lists); err != nil { + log.Printf("cannot set lists: %v", err) + return + } + + for _, list := range lists { + tasks, err := client.Tasks(list) + if err != nil { + log.Printf("cannot client.tasks(%v): %v", list, err) + continue + } + if err := buffer.db.Set(nsTasks, list.ID, &tasks); err != nil { + log.Printf("cannot set tasks(%v): %v", list.ID, tasks) + continue + } + } } func (buffer *Buffer) notDoneCallback(foo func()) { diff --git a/mytinytodo/buffer_test.go b/mytinytodo/buffer_test.go index e711fb0..cbe5543 100644 --- a/mytinytodo/buffer_test.go +++ b/mytinytodo/buffer_test.go @@ -1,6 +1,7 @@ package mytinytodo import ( + "local/mytinytodoclient/mytinytodo/remote" "net/http" "net/http/httptest" "os" @@ -15,15 +16,59 @@ func TestNewBuffer(t *testing.T) { } func TestBufferEnqueue(t *testing.T) { - t.Fatal("not implemented") + b, s := mockBuffer(t) + defer b.Close() + defer s.Close() + + if err := b.Enqueue(remote.NEW, "list", "task", "one", "tag", "two"); err != nil { + t.Errorf("cannot enqueue: %v", err) + } + + todo1 := NewStringArray() + if err := b.db.Get(nsQueue, keyQueue, todo1); err != nil || len(todo1.StringArray()) != 1 { + t.Fatalf("enqueue didnt create list: %v (%d)", err, len(todo1.StringArray())) + } } func TestBufferDequeue(t *testing.T) { - t.Fatal("not implemented") + b, s := mockBuffer(t) + defer b.Close() + defer s.Close() + + if err := b.Enqueue(remote.NEW, "list", "task", "one", "tag", "two"); err != nil { + t.Fatalf("cannot enqueue to dequeue: %v", err) + } + + todo1 := NewStringArray() + if err := b.db.Get(nsQueue, keyQueue, todo1); err != nil || len(todo1.StringArray()) != 1 { + t.Fatalf("enqueue didnt create list: %v (%d)", err, len(todo1.StringArray())) + } + + b.Dequeue() + + todo2 := NewStringArray() + if err := b.db.Get(nsQueue, keyQueue, todo2); err != nil || len(todo2.StringArray()) != 0 { + t.Fatalf("dequeue didnt remove from list: %v (%d)", err, len(todo2.StringArray())) + } + } func TestBufferRefreshLocal(t *testing.T) { - t.Fatal("not implemented") + b, s := mockBuffer(t) + defer b.Close() + defer s.Close() + + b.RefreshLocal() + + var lists remote.Lists + if err := b.db.Get(nsLists, keyLists, &lists); err != nil || lists.Length() != 1 { + t.Fatalf("refreshLocal didnt create list: %v (%d)", err, lists.Length()) + } + + var tasks remote.Tasks + if err := b.db.Get(nsTasks, lists.ListArray()[0].ID, &tasks); err != nil || tasks.Length() != 1 { + t.Fatalf("failed to parse task on refresh: %v (%d)", err, tasks.Length()) + } } func TestBufferNotDoneCallback(t *testing.T) { @@ -41,6 +86,19 @@ func TestBufferNotDoneCallback(t *testing.T) { func mockBuffer(t *testing.T) (*Buffer, *httptest.Server) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(` + { + "list":[ + { + "id":"id", + "name":"name", + "title":"title", + "compl":0 + } + ] + } + `)) })) osArgsWas := os.Args[:] os.Args = []string{"skip", "-remote", srv.URL, "-p", ""} diff --git a/mytinytodo/packable.go b/mytinytodo/packable.go index 1429b15..38bb4a6 100644 --- a/mytinytodo/packable.go +++ b/mytinytodo/packable.go @@ -36,8 +36,8 @@ func (sa *StringArray) Decode(b []byte) error { type QueuedOp struct { Op remote.Op - ListID string - TaskName string + List remote.List + Task remote.Task TaskTags []string } diff --git a/mytinytodo/packable_test.go b/mytinytodo/packable_test.go index b6f91a7..9642224 100644 --- a/mytinytodo/packable_test.go +++ b/mytinytodo/packable_test.go @@ -38,8 +38,8 @@ func TestPackableQueuedOp(t *testing.T) { cases := []*QueuedOp{ &QueuedOp{ Op: remote.NEW, - ListID: "1", - TaskName: "name", + List: remote.List{ID: "1"}, + Task: remote.Task{Title: "title"}, TaskTags: []string{"some", "tags"}, }, &QueuedOp{}, diff --git a/mytinytodo/remote/client.go b/mytinytodo/remote/client.go index 20619cd..767fea2 100644 --- a/mytinytodo/remote/client.go +++ b/mytinytodo/remote/client.go @@ -52,7 +52,7 @@ func (c *Client) ParseArgs() error { return nil } -func (c *Client) Lists() ([]List, error) { +func (c *Client) Lists() (Lists, error) { client, err := NewHTTP(c.config.remote, c.config.password) if err != nil { return nil, err @@ -63,10 +63,10 @@ func (c *Client) Lists() ([]List, error) { } else if err := json.NewDecoder(resp.Body).Decode(&lists); err != nil { return nil, fmt.Errorf("cannot read lists: %v", err) } - return lists.Lists, nil + return Lists(lists.Lists), nil } -func (c *Client) Tasks(list List) ([]Task, error) { +func (c *Client) Tasks(list List) (Tasks, error) { client, err := NewHTTP(c.config.remote, c.config.password) if err != nil { return nil, err @@ -77,7 +77,7 @@ func (c *Client) Tasks(list List) ([]Task, error) { } else if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil { return nil, fmt.Errorf("cannot read tasks: %v", err) } - return tasks.Tasks, nil + return Tasks(tasks.Tasks), nil } func (c *Client) NewTask(list List, task Task, tags string) error { diff --git a/mytinytodo/remote/list.go b/mytinytodo/remote/list.go index 2bb9077..086d521 100644 --- a/mytinytodo/remote/list.go +++ b/mytinytodo/remote/list.go @@ -1,5 +1,10 @@ package remote +import ( + "bytes" + "encoding/gob" +) + type loadListsResponse struct { Lists []List `json:"list"` } @@ -8,3 +13,41 @@ type List struct { ID string `json:"id"` Name string `json:"name"` } + +type Lists []List + +func (l *List) Encode() ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(*l) + return buf.Bytes(), err +} + +func (l *List) Decode(b []byte) error { + buf := bytes.NewBuffer(b) + enc := gob.NewDecoder(buf) + err := enc.Decode(l) + return err +} + +func (ls *Lists) Encode() ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(*ls) + return buf.Bytes(), err +} + +func (ls *Lists) Decode(b []byte) error { + buf := bytes.NewBuffer(b) + enc := gob.NewDecoder(buf) + err := enc.Decode(ls) + return err +} + +func (ls Lists) Length() int { + return len(ls.ListArray()) +} + +func (ls Lists) ListArray() []List { + return []List(ls) +} diff --git a/mytinytodo/remote/list_test.go b/mytinytodo/remote/list_test.go new file mode 100644 index 0000000..6f98d01 --- /dev/null +++ b/mytinytodo/remote/list_test.go @@ -0,0 +1,46 @@ +package remote + +import ( + "fmt" + "testing" +) + +func TestListEncodeDecode(t *testing.T) { + L := &List{ + ID: "id", + Name: "name", + } + + b, err := L.Encode() + if err != nil { + t.Fatalf("cannot encode list: %v", err) + } + + M := &List{} + if err := M.Decode(b); err != nil { + t.Fatalf("cannot decode list: %v", err) + } else if *L != *M { + t.Fatalf("wrong decode list: %v", err) + } +} + +func TestListsEncodeDecode(t *testing.T) { + L := Lists([]List{List{ + ID: "id", + Name: "name", + }}) + LS := &L + + b, err := LS.Encode() + if err != nil { + t.Fatalf("cannot encode list: %v", err) + } + + M := Lists([]List{}) + MS := &M + if err := MS.Decode(b); err != nil { + t.Fatalf("cannot decode list: %v", err) + } else if fmt.Sprintf("%v", *LS) != fmt.Sprintf("%v", *MS) { + t.Fatalf("wrong decode list: %v", err) + } +} diff --git a/mytinytodo/remote/task.go b/mytinytodo/remote/task.go index 06fe6f7..8a56f6b 100644 --- a/mytinytodo/remote/task.go +++ b/mytinytodo/remote/task.go @@ -1,5 +1,10 @@ package remote +import ( + "bytes" + "encoding/gob" +) + type loadTasksResponse struct { Tasks []Task `json:"list"` } @@ -9,3 +14,41 @@ type Task struct { Title string `json:"title"` Complete int `json:"compl"` } + +type Tasks []Task + +func (t *Task) Encode() ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(*t) + return buf.Bytes(), err +} + +func (t *Task) Decode(b []byte) error { + buf := bytes.NewBuffer(b) + enc := gob.NewDecoder(buf) + err := enc.Decode(t) + return err +} + +func (ts *Tasks) Encode() ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + err := enc.Encode(*ts) + return buf.Bytes(), err +} + +func (ts *Tasks) Decode(b []byte) error { + buf := bytes.NewBuffer(b) + enc := gob.NewDecoder(buf) + err := enc.Decode(ts) + return err +} + +func (ts Tasks) Length() int { + return len(ts.TaskArray()) +} + +func (ts Tasks) TaskArray() []Task { + return []Task(ts) +} diff --git a/mytinytodo/remote/task_test.go b/mytinytodo/remote/task_test.go new file mode 100644 index 0000000..a4d2884 --- /dev/null +++ b/mytinytodo/remote/task_test.go @@ -0,0 +1,48 @@ +package remote + +import ( + "fmt" + "testing" +) + +func TestTaskEncodeDecode(t *testing.T) { + L := &Task{ + ID: "id", + Title: "title", + Complete: 0, + } + + b, err := L.Encode() + if err != nil { + t.Fatalf("cannot encode task: %v", err) + } + + M := &Task{} + if err := M.Decode(b); err != nil { + t.Fatalf("cannot decode task: %v", err) + } else if *L != *M { + t.Fatalf("wrong decode task: %v", err) + } +} + +func TestTasksEncodeDecode(t *testing.T) { + L := Tasks([]Task{Task{ + ID: "id", + Title: "title", + Complete: 0, + }}) + LS := &L + + b, err := LS.Encode() + if err != nil { + t.Fatalf("cannot encode task: %v", err) + } + + M := Tasks([]Task{}) + MS := &M + if err := MS.Decode(b); err != nil { + t.Fatalf("cannot decode task: %v", err) + } else if fmt.Sprintf("%v", *LS) != fmt.Sprintf("%v", *MS) { + t.Fatalf("wrong decode task: %v", err) + } +}