seemingly good

master
Bel LaPointe 2019-03-02 11:56:05 -07:00
parent 28310b0dd6
commit 109c7c48bc
9 changed files with 344 additions and 53 deletions

View File

@ -1,9 +1,11 @@
package mytinytodo package mytinytodo
import ( import (
"fmt"
"local/mytinytodoclient/mytinytodo/remote" "local/mytinytodoclient/mytinytodo/remote"
"local/rproxy3/storage" "local/rproxy3/storage"
"log" "log"
"strings"
"sync" "sync"
"time" "time"
@ -12,7 +14,10 @@ import (
const nsQueue = "delta" const nsQueue = "delta"
const keyQueue = "queue" const keyQueue = "queue"
const nsTasks = "delta" const nsLists = "lists"
const keyLists = "lists"
const nsTasks = "tasks"
const keyTasks = "tasks"
type Buffer struct { type Buffer struct {
config *Config config *Config
@ -31,8 +36,8 @@ func NewBuffer(config *Config) (*Buffer, error) {
dbLock: &sync.RWMutex{}, dbLock: &sync.RWMutex{},
interval: time.Second * 10, interval: time.Second * 10,
} }
go b.Dequeue() go b.notDoneCallback(b.Dequeue)
go b.RefreshLocal() go b.notDoneCallback(b.RefreshLocal)
return b, nil return b, nil
} }
@ -40,75 +45,123 @@ func (buffer *Buffer) Close() {
close(buffer.done) 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() buffer.dbLock.Lock()
defer buffer.dbLock.Unlock() defer buffer.dbLock.Unlock()
qop := &QueuedOp{ qop := &QueuedOp{
Op: op, Op: op,
ListID: listID, List: remote.List{ID: listID},
TaskName: taskName, Task: remote.Task{Title: taskTitle},
TaskTags: taskTags, TaskTags: taskTags,
} }
uuid, _ := uuid.NewRandom() uuid, _ := uuid.NewRandom()
key := uuid.String() key := uuid.String()
todo := NewStringArray() 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 return err
} }
sa := todo.StringArray() sa := todo.StringArray()
sa = append(sa, key) sa = append(sa, key)
todo = NewStringArray(sa...) 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 return err
} }
if err := buffer.db.Set(nsQueue, keyQueue, todo); err != nil { if err := buffer.db.Set(nsQueue, keyQueue, todo); err != nil {
return err return err
} }
log.Printf("enqueued task %v as %v, %vth in line", qop, key, len(sa)) log.Printf("enqueued task %v as %v, %vth in line", qop, key, len(sa))
return nil return nil
} }
func (buffer *Buffer) Dequeue() { 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 { if err != nil {
log.Printf("cannot create client: %v", err) log.Printf("failed op %v: %v", qop.Op, err)
return continue
} }
if _, err := client.Lists(); err != nil { if err := buffer.db.Set(nsQueue, sa[i], nil); err != nil {
log.Printf("cannot client.lists: %v", err) log.Printf("cannot unset %v.%v: %v", nsQueue, sa[i], err)
return nsa = append(nsa, sa[i])
continue
} }
buffer.dbLock.Lock() }
defer buffer.dbLock.Unlock()
todo := NewStringArray() if err := buffer.db.Set(nsQueue, keyQueue, NewStringArray(nsa...)); err != nil {
if err := buffer.db.Get(nsQueue, keyQueue, todo); err != nil { log.Printf("cannot update queue: %v", err)
log.Printf("cannot get %v.%v: %v", nsQueue, keyQueue, err) return
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
}
})
} }
func (buffer *Buffer) RefreshLocal() { func (buffer *Buffer) RefreshLocal() {
buffer.notDoneCallback(func() { client, err := remote.NewClient(buffer.config.Config)
buffer.dbLock.Lock() if err != nil {
defer buffer.dbLock.Unlock() 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()) { func (buffer *Buffer) notDoneCallback(foo func()) {

View File

@ -1,6 +1,7 @@
package mytinytodo package mytinytodo
import ( import (
"local/mytinytodoclient/mytinytodo/remote"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -15,15 +16,59 @@ func TestNewBuffer(t *testing.T) {
} }
func TestBufferEnqueue(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) { 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) { 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) { func TestBufferNotDoneCallback(t *testing.T) {
@ -41,6 +86,19 @@ func TestBufferNotDoneCallback(t *testing.T) {
func mockBuffer(t *testing.T) (*Buffer, *httptest.Server) { func mockBuffer(t *testing.T) (*Buffer, *httptest.Server) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 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[:] osArgsWas := os.Args[:]
os.Args = []string{"skip", "-remote", srv.URL, "-p", ""} os.Args = []string{"skip", "-remote", srv.URL, "-p", ""}

View File

@ -36,8 +36,8 @@ func (sa *StringArray) Decode(b []byte) error {
type QueuedOp struct { type QueuedOp struct {
Op remote.Op Op remote.Op
ListID string List remote.List
TaskName string Task remote.Task
TaskTags []string TaskTags []string
} }

View File

@ -38,8 +38,8 @@ func TestPackableQueuedOp(t *testing.T) {
cases := []*QueuedOp{ cases := []*QueuedOp{
&QueuedOp{ &QueuedOp{
Op: remote.NEW, Op: remote.NEW,
ListID: "1", List: remote.List{ID: "1"},
TaskName: "name", Task: remote.Task{Title: "title"},
TaskTags: []string{"some", "tags"}, TaskTags: []string{"some", "tags"},
}, },
&QueuedOp{}, &QueuedOp{},

View File

@ -52,7 +52,7 @@ func (c *Client) ParseArgs() error {
return nil return nil
} }
func (c *Client) Lists() ([]List, error) { func (c *Client) Lists() (Lists, error) {
client, err := NewHTTP(c.config.remote, c.config.password) client, err := NewHTTP(c.config.remote, c.config.password)
if err != nil { if err != nil {
return nil, err return nil, err
@ -63,10 +63,10 @@ func (c *Client) Lists() ([]List, error) {
} else if err := json.NewDecoder(resp.Body).Decode(&lists); err != nil { } else if err := json.NewDecoder(resp.Body).Decode(&lists); err != nil {
return nil, fmt.Errorf("cannot read lists: %v", err) 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) client, err := NewHTTP(c.config.remote, c.config.password)
if err != nil { if err != nil {
return nil, err 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 { } else if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, fmt.Errorf("cannot read tasks: %v", err) 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 { func (c *Client) NewTask(list List, task Task, tags string) error {

View File

@ -1,5 +1,10 @@
package remote package remote
import (
"bytes"
"encoding/gob"
)
type loadListsResponse struct { type loadListsResponse struct {
Lists []List `json:"list"` Lists []List `json:"list"`
} }
@ -8,3 +13,41 @@ type List struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` 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)
}

View File

@ -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)
}
}

View File

@ -1,5 +1,10 @@
package remote package remote
import (
"bytes"
"encoding/gob"
)
type loadTasksResponse struct { type loadTasksResponse struct {
Tasks []Task `json:"list"` Tasks []Task `json:"list"`
} }
@ -9,3 +14,41 @@ type Task struct {
Title string `json:"title"` Title string `json:"title"`
Complete int `json:"compl"` 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)
}

View File

@ -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)
}
}