diff --git a/cmd/cli/command/done.go b/cmd/cli/command/done.go index 1e51a7e..a352249 100644 --- a/cmd/cli/command/done.go +++ b/cmd/cli/command/done.go @@ -26,7 +26,12 @@ func NewDone(localId int, conf *configuration.Configuration) (*Done, error) { return &Done{}, err } - updater := process.NewUpdate(local, disp, localTask.Id, task.LocalUpdate{Done: true}) + update := &task.LocalUpdate{ + ForVersion: localTask.Version, + Fields: []string{task.FIELD_DONE}, + Done: true, + } + updater := process.NewUpdate(local, disp, localTask.Id, update) return &Done{ doner: updater, diff --git a/cmd/cli/command/update.go b/cmd/cli/command/update.go index 6fc585f..aac2f6b 100644 --- a/cmd/cli/command/update.go +++ b/cmd/cli/command/update.go @@ -23,7 +23,7 @@ func NewUpdate(localId int, conf *configuration.Configuration, cmdArgs []string) } disp := storage.NewDispatcher(msend.NewSSLSMTP(conf.SMTP())) - fields, err := ParseTaskFieldArgs(cmdArgs) + update, err := ParseTaskFieldArgs(cmdArgs) if err != nil { return &Update{}, err } @@ -31,8 +31,9 @@ func NewUpdate(localId int, conf *configuration.Configuration, cmdArgs []string) if err != nil { return &Update{}, err } + update.ForVersion = localTask.Version - updater := process.NewUpdate(local, disp, localTask.Id, fields) + updater := process.NewUpdate(local, disp, localTask.Id, update) return &Update{ updater: updater, @@ -47,33 +48,40 @@ func (u *Update) Do() string { return "message sent\n" } -func ParseTaskFieldArgs(args []string) (task.LocalUpdate, error) { - lu := task.LocalUpdate{} +func ParseTaskFieldArgs(args []string) (*task.LocalUpdate, error) { + lu := &task.LocalUpdate{} - var action []string + action, fields := []string{}, []string{} for _, f := range args { split := strings.SplitN(f, ":", 2) if len(split) == 2 { switch split[0] { case "project": if lu.Project != "" { - return task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_PROJECT) + return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_PROJECT) } lu.Project = split[1] + fields = append(fields, task.FIELD_PROJECT) case "due": if !lu.Due.IsZero() { - return task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_DUE) + return &task.LocalUpdate{}, fmt.Errorf("%w: %s", ErrFieldAlreadyUsed, task.FIELD_DUE) } lu.Due = task.NewDateFromString(split[1]) + fields = append(fields, task.FIELD_DUE) } } else { - action = append(action, f) + if len(f) > 0 { + action = append(action, f) + } } } if len(action) > 0 { lu.Action = strings.Join(action, " ") + fields = append(fields, task.FIELD_ACTION) } + lu.Fields = fields + return lu, nil } diff --git a/cmd/cli/command/update_test.go b/cmd/cli/command/update_test.go index e314515..0be059f 100644 --- a/cmd/cli/command/update_test.go +++ b/cmd/cli/command/update_test.go @@ -14,24 +14,28 @@ func TestParseTaskFieldArgs(t *testing.T) { for _, tc := range []struct { name string input string - expUpdate task.LocalUpdate + expUpdate *task.LocalUpdate expErr error }{ { - name: "empty", - expUpdate: task.LocalUpdate{}, + name: "empty", + expUpdate: &task.LocalUpdate{ + Fields: []string{}, + }, }, { name: "join action", input: "some things to do", - expUpdate: task.LocalUpdate{ + expUpdate: &task.LocalUpdate{ + Fields: []string{task.FIELD_ACTION}, Action: "some things to do", }, }, { name: "all", input: "project:project do stuff due:2021-08-06", - expUpdate: task.LocalUpdate{ + expUpdate: &task.LocalUpdate{ + Fields: []string{task.FIELD_PROJECT, task.FIELD_DUE, task.FIELD_ACTION}, Action: "do stuff", Project: "project", Due: task.NewDate(2021, 8, 6), @@ -40,14 +44,15 @@ func TestParseTaskFieldArgs(t *testing.T) { { name: "no action", input: "due:2021-08-06", - expUpdate: task.LocalUpdate{ - Due: task.NewDate(2021, 8, 6), + expUpdate: &task.LocalUpdate{ + Fields: []string{task.FIELD_DUE}, + Due: task.NewDate(2021, 8, 6), }, }, { name: "two projects", input: "project:project1 project:project2", - expUpdate: task.LocalUpdate{}, + expUpdate: &task.LocalUpdate{}, expErr: command.ErrFieldAlreadyUsed, }, } { diff --git a/internal/process/update.go b/internal/process/update.go index 96f4365..7d7abb8 100644 --- a/internal/process/update.go +++ b/internal/process/update.go @@ -17,10 +17,10 @@ type Update struct { local storage.LocalRepository disp *storage.Dispatcher taskId string - update task.LocalUpdate + update *task.LocalUpdate } -func NewUpdate(local storage.LocalRepository, disp *storage.Dispatcher, taskId string, update task.LocalUpdate) *Update { +func NewUpdate(local storage.LocalRepository, disp *storage.Dispatcher, taskId string, update *task.LocalUpdate) *Update { return &Update{ local: local, disp: disp, @@ -34,13 +34,11 @@ func (u *Update) Process() error { if err != nil { return fmt.Errorf("%w: %v", ErrUpdateTask, err) } - u.update.ForVersion = tsk.Version - if err := u.local.SetLocalUpdate(tsk.LocalId, &u.update); err != nil { + tsk.AddUpdate(u.update) + if err := u.local.SetLocalUpdate(tsk); err != nil { return fmt.Errorf("%w: %v", ErrUpdateTask, err) } - - tsk.Apply(u.update) - + tsk.ApplyUpdate() if err := u.disp.Dispatch(&tsk.Task); err != nil { return fmt.Errorf("%w: %v", ErrUpdateTask, err) } diff --git a/internal/process/update_test.go b/internal/process/update_test.go index 1508b27..d41a277 100644 --- a/internal/process/update_test.go +++ b/internal/process/update_test.go @@ -13,6 +13,7 @@ import ( func TestUpdate(t *testing.T) { task1 := &task.Task{ Id: "id-1", + Version: 2, Project: "project1", Action: "action1", Due: task.NewDate(2021, 7, 29), @@ -23,16 +24,19 @@ func TestUpdate(t *testing.T) { for _, tc := range []struct { name string - updates task.LocalUpdate + updates *task.LocalUpdate exp *task.Task }{ { name: "done", - updates: task.LocalUpdate{ - Done: true, + updates: &task.LocalUpdate{ + ForVersion: 2, + Fields: []string{task.FIELD_DONE}, + Done: true, }, exp: &task.Task{ Id: "id-1", + Version: 2, Project: "project1", Action: "action1", Due: task.NewDate(2021, 7, 29), @@ -42,13 +46,16 @@ func TestUpdate(t *testing.T) { }, { name: "fields", - updates: task.LocalUpdate{ - Project: "project2", - Action: "action2", - Due: task.NewDate(2021, 8, 1), + updates: &task.LocalUpdate{ + ForVersion: 2, + Fields: []string{task.FIELD_ACTION, task.FIELD_PROJECT, task.FIELD_DUE}, + Project: "project2", + Action: "action2", + Due: task.NewDate(2021, 8, 1), }, exp: &task.Task{ Id: "id-1", + Version: 2, Project: "project2", Action: "action2", Due: task.NewDate(2021, 8, 1), diff --git a/internal/storage/local.go b/internal/storage/local.go index eab6023..c3ac2b4 100644 --- a/internal/storage/local.go +++ b/internal/storage/local.go @@ -19,7 +19,7 @@ type LocalRepository interface { FindAllInProject(project string) ([]*task.LocalTask, error) FindById(id string) (*task.LocalTask, error) FindByLocalId(id int) (*task.LocalTask, error) - SetLocalUpdate(localId int, localUpdate *task.LocalUpdate) error + SetLocalUpdate(tsk *task.LocalTask) error } func NextLocalId(used []int) int { diff --git a/internal/storage/memory.go b/internal/storage/memory.go index 12b12c2..ad3e6ea 100644 --- a/internal/storage/memory.go +++ b/internal/storage/memory.go @@ -113,15 +113,10 @@ func (m *Memory) FindByLocalId(localId int) (*task.LocalTask, error) { return &task.LocalTask{}, ErrTaskNotFound } -func (m *Memory) SetLocalUpdate(localId int, localUpdate *task.LocalUpdate) error { - t, err := m.FindByLocalId(localId) - if err != nil { - return err - } - - m.localData[t.Id] = localData{ - LocalId: localId, - LocalUpdate: localUpdate, +func (m *Memory) SetLocalUpdate(tsk *task.LocalTask) error { + m.localData[tsk.Id] = localData{ + LocalId: tsk.LocalId, + LocalUpdate: tsk.LocalUpdate, } return nil diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go index 32afa67..613c150 100644 --- a/internal/storage/memory_test.go +++ b/internal/storage/memory_test.go @@ -109,7 +109,12 @@ func TestMemory(t *testing.T) { Recur: task.NewRecurrer("today, weekly, monday"), Done: true, } - test.OK(t, mem.SetLocalUpdate(2, expUpdate)) + lt := &task.LocalTask{ + Task: *task2, + LocalId: 2, + LocalUpdate: expUpdate, + } + test.OK(t, mem.SetLocalUpdate(lt)) actTask, err := mem.FindByLocalId(2) test.OK(t, err) test.Equals(t, expUpdate, actTask.LocalUpdate) diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index 5683221..b6fc48a 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -222,11 +222,11 @@ func (s *Sqlite) FindByLocalId(localId int) (*task.LocalTask, error) { return t, nil } -func (s *Sqlite) SetLocalUpdate(localId int, localUpdate *task.LocalUpdate) error { +func (s *Sqlite) SetLocalUpdate(tsk *task.LocalTask) error { if _, err := s.db.Exec(` UPDATE local_task SET local_update = ? -WHERE local_id = ?`, localUpdate, localId); err != nil { +WHERE local_id = ?`, tsk.LocalUpdate, tsk.LocalId); err != nil { return fmt.Errorf("%w: %v", ErrSqliteFailure, err) } diff --git a/internal/task/localtask.go b/internal/task/localtask.go index b8b7fd2..c40d3fd 100644 --- a/internal/task/localtask.go +++ b/internal/task/localtask.go @@ -13,22 +13,40 @@ type LocalTask struct { LocalUpdate *LocalUpdate } -func (lt *LocalTask) Apply(lu LocalUpdate) { - if lu.Action != "" { - lt.Action = lu.Action +func (lt *LocalTask) AddUpdate(update *LocalUpdate) { + if lt.LocalUpdate == nil { + lt.LocalUpdate = &LocalUpdate{} } - if lu.Project != "" { - lt.Project = lu.Project + + lt.LocalUpdate.Add(update) +} + +func (lt *LocalTask) ApplyUpdate() { + if lt.LocalUpdate == nil { + return } - if lu.Recur != nil { - lt.Recur = lu.Recur + u := lt.LocalUpdate + if u.ForVersion == 0 || u.ForVersion != lt.Version { + lt.LocalUpdate = &LocalUpdate{} + return } - if !lu.Due.IsZero() { - lt.Due = lu.Due - } - if lu.Done { - lt.Done = lu.Done + + for _, field := range u.Fields { + switch field { + case FIELD_ACTION: + lt.Action = u.Action + case FIELD_PROJECT: + lt.Project = u.Project + case FIELD_DUE: + lt.Due = u.Due + case FIELD_RECUR: + lt.Recur = u.Recur + case FIELD_DONE: + lt.Done = u.Done + } } + + lt.LocalUpdate = &LocalUpdate{} } type ByDue []*LocalTask @@ -55,6 +73,7 @@ func (lt ByDefault) Less(i, j int) bool { type LocalUpdate struct { ForVersion int + Fields []string Action string Project string Due Date @@ -62,6 +81,39 @@ type LocalUpdate struct { Done bool } +func (lu *LocalUpdate) Add(newUpdate *LocalUpdate) { + if lu.ForVersion > newUpdate.ForVersion { + return + } + lu.ForVersion = newUpdate.ForVersion + + for _, nf := range newUpdate.Fields { + switch nf { + case FIELD_ACTION: + lu.Action = newUpdate.Action + case FIELD_PROJECT: + lu.Project = newUpdate.Project + case FIELD_DUE: + lu.Due = newUpdate.Due + case FIELD_RECUR: + lu.Recur = newUpdate.Recur + case FIELD_DONE: + lu.Done = newUpdate.Done + } + + add := true + for _, of := range lu.Fields { + if nf == of { + add = false + break + } + } + if add { + lu.Fields = append(lu.Fields, nf) + } + } +} + func (lu LocalUpdate) Value() (driver.Value, error) { var recurStr string if lu.Recur != nil { diff --git a/internal/task/localtask_test.go b/internal/task/localtask_test.go new file mode 100644 index 0000000..c609e0f --- /dev/null +++ b/internal/task/localtask_test.go @@ -0,0 +1,67 @@ +package task_test + +import ( + "testing" + + "git.ewintr.nl/go-kit/test" + "git.ewintr.nl/gte/internal/task" +) + +func TestLocalTaskApply(t *testing.T) { + for _, tc := range []struct { + name string + input *task.LocalTask + exp *task.LocalTask + }{ + { + name: "empty", + input: &task.LocalTask{ + Task: task.Task{ + Action: "action", + Project: "project", + Due: task.NewDate(2021, 8, 22), + }, + LocalUpdate: &task.LocalUpdate{}, + }, + exp: &task.LocalTask{ + Task: task.Task{ + Action: "action", + Project: "project", + Due: task.NewDate(2021, 8, 22), + }, + LocalUpdate: &task.LocalUpdate{}, + }, + }, + { + name: "all", + input: &task.LocalTask{ + Task: task.Task{ + Version: 3, + }, + LocalUpdate: &task.LocalUpdate{ + ForVersion: 3, + Fields: []string{task.FIELD_ACTION, task.FIELD_PROJECT, task.FIELD_DUE, task.FIELD_DONE}, + Action: "action", + Project: "project", + Due: task.NewDate(2021, 8, 22), + Done: true, + }, + }, + exp: &task.LocalTask{ + Task: task.Task{ + Version: 3, + Action: "action", + Project: "project", + Due: task.NewDate(2021, 8, 22), + Done: true, + }, + LocalUpdate: &task.LocalUpdate{}, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + tc.input.ApplyUpdate() + test.Equals(t, tc.exp, tc.input) + }) + } +}