From a2a0e9bb7b9bd04f3575df55638eec367c73d537 Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Wed, 25 Aug 2021 06:52:48 +0200 Subject: [PATCH] split off sync merge func --- internal/process/list.go | 24 +++--- internal/process/list_test.go | 21 +++-- internal/process/projects.go | 11 +-- internal/process/sync_test.go | 21 +++-- internal/storage/local.go | 73 ++++++++++++++++- internal/storage/local_test.go | 104 ++++++++++++++++++++++++ internal/storage/memory.go | 87 +++++--------------- internal/storage/memory_test.go | 44 +++++------ internal/storage/sqlite.go | 136 ++++++-------------------------- internal/task/localtask.go | 6 ++ 10 files changed, 287 insertions(+), 240 deletions(-) diff --git a/internal/process/list.go b/internal/process/list.go index 2717d67..69918d7 100644 --- a/internal/process/list.go +++ b/internal/process/list.go @@ -56,20 +56,21 @@ func (l *List) Process() (*ListResult, error) { return &ListResult{}, ErrInvalidReqs } - folders := []string{task.FOLDER_NEW, task.FOLDER_PLANNED, task.FOLDER_UNPLANNED} - if l.reqs.Folder != "" { - folders = []string{l.reqs.Folder} + potentialTasks, err := l.local.FindAll() + if err != nil { + return &ListResult{}, fmt.Errorf("%w: %v", ErrListProcess, err) } - var potentialTasks []*task.LocalTask - for _, folder := range folders { - folderTasks, err := l.local.FindAllInFolder(folder) - if err != nil { - return &ListResult{}, fmt.Errorf("%w: %v", ErrListProcess, err) - } - for _, ft := range folderTasks { - potentialTasks = append(potentialTasks, ft) + // folder + if l.reqs.Folder != "" { + var folderTasks []*task.LocalTask + for _, pt := range potentialTasks { + if pt.Folder == l.reqs.Folder { + folderTasks = append(folderTasks, pt) + } } + + potentialTasks = folderTasks } if l.reqs.Due.IsZero() && l.reqs.Project == "" { @@ -78,6 +79,7 @@ func (l *List) Process() (*ListResult, error) { }, nil } + // project if l.reqs.Project != "" { var projectTasks []*task.LocalTask for _, pt := range potentialTasks { diff --git a/internal/process/list_test.go b/internal/process/list_test.go index 8285510..99da8f3 100644 --- a/internal/process/list_test.go +++ b/internal/process/list_test.go @@ -2,6 +2,7 @@ package process_test import ( "errors" + "sort" "testing" "git.ewintr.nl/go-kit/test" @@ -47,9 +48,9 @@ func TestListProcess(t *testing.T) { Project: "project2", } allTasks := []*task.Task{task1, task2, task3, task4} - localTask2 := &task.LocalTask{Task: *task2, LocalId: 2} - localTask3 := &task.LocalTask{Task: *task3, LocalId: 3} - localTask4 := &task.LocalTask{Task: *task4, LocalId: 4} + localTask2 := &task.LocalTask{Task: *task2, LocalUpdate: &task.LocalUpdate{}} + localTask3 := &task.LocalTask{Task: *task3, LocalUpdate: &task.LocalUpdate{}} + localTask4 := &task.LocalTask{Task: *task4, LocalUpdate: &task.LocalUpdate{}} local := storage.NewMemory() test.OK(t, local.SetTasks(allTasks)) @@ -96,10 +97,18 @@ func TestListProcess(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { list := process.NewList(local, tc.reqs) - - act, err := list.Process() + actRes, err := list.Process() test.OK(t, err) - test.Equals(t, tc.exp, act.Tasks) + act := actRes.Tasks + for _, a := range act { + a.LocalId = 0 + } + sAct := task.ById(act) + sExp := task.ById(tc.exp) + sort.Sort(sAct) + sort.Sort(sExp) + + test.Equals(t, sExp, sAct) }) } } diff --git a/internal/process/projects.go b/internal/process/projects.go index f37e8e8..e481afc 100644 --- a/internal/process/projects.go +++ b/internal/process/projects.go @@ -6,7 +6,6 @@ import ( "sort" "git.ewintr.nl/gte/internal/storage" - "git.ewintr.nl/gte/internal/task" ) var ( @@ -24,13 +23,9 @@ func NewProjects(local storage.LocalRepository) *Projects { } func (p *Projects) Process() ([]string, error) { - allTasks := []*task.LocalTask{} - for _, folder := range []string{task.FOLDER_NEW, task.FOLDER_PLANNED, task.FOLDER_UNPLANNED} { - folderTasks, err := p.local.FindAllInFolder(folder) - if err != nil { - return []string{}, fmt.Errorf("%w: %v", ErrCouldNotFetchProjects, err) - } - allTasks = append(allTasks, folderTasks...) + allTasks, err := p.local.FindAll() + if err != nil { + return []string{}, fmt.Errorf("%w: %v", ErrCouldNotFetchProjects, err) } knownMap := map[string]bool{} diff --git a/internal/process/sync_test.go b/internal/process/sync_test.go index d4d18fb..29d86f9 100644 --- a/internal/process/sync_test.go +++ b/internal/process/sync_test.go @@ -1,6 +1,7 @@ package process_test import ( + "sort" "testing" "git.ewintr.nl/go-kit/test" @@ -24,8 +25,8 @@ func TestSyncProcess(t *testing.T) { Folder: task.FOLDER_UNPLANNED, } - localTask1 := &task.LocalTask{Task: *task1, LocalId: 1} - localTask2 := &task.LocalTask{Task: *task2, LocalId: 2} + localTask1 := &task.LocalTask{Task: *task1, LocalUpdate: &task.LocalUpdate{}} + localTask2 := &task.LocalTask{Task: *task2, LocalUpdate: &task.LocalUpdate{}} mstorer, err := mstore.NewMemory(task.KnownFolders) test.OK(t, err) @@ -38,10 +39,16 @@ func TestSyncProcess(t *testing.T) { actResult, err := syncer.Process() test.OK(t, err) test.Equals(t, 2, actResult.Count) - actTasks1, err := local.FindAllInFolder(task.FOLDER_NEW) + actTasks, err := local.FindAll() test.OK(t, err) - test.Equals(t, []*task.LocalTask{localTask1}, actTasks1) - actTasks2, err := local.FindAllInFolder(task.FOLDER_UNPLANNED) - test.OK(t, err) - test.Equals(t, []*task.LocalTask{localTask2}, actTasks2) + for _, a := range actTasks { + a.LocalId = 0 + a.Message = nil + } + exp := task.ById([]*task.LocalTask{localTask1, localTask2}) + sExp := task.ById(exp) + sAct := task.ById(actTasks) + sort.Sort(sAct) + sort.Sort(sExp) + test.Equals(t, sExp, sAct) } diff --git a/internal/storage/local.go b/internal/storage/local.go index c3ac2b4..cfecd2e 100644 --- a/internal/storage/local.go +++ b/internal/storage/local.go @@ -15,13 +15,25 @@ var ( type LocalRepository interface { LatestSync() (time.Time, error) SetTasks(tasks []*task.Task) error - FindAllInFolder(folder string) ([]*task.LocalTask, error) - FindAllInProject(project string) ([]*task.LocalTask, error) + FindAll() ([]*task.LocalTask, error) FindById(id string) (*task.LocalTask, error) FindByLocalId(id int) (*task.LocalTask, error) SetLocalUpdate(tsk *task.LocalTask) error } +// NextLocalId finds a new local id by incrememting to a variable limit. +// +// When tasks are edited, some get removed because they are done or deleted. +// It is very confusing if existing tasks get renumbered, or if a new one +// immediatly gets the id of an removed one. So it is better to just +// increment. However, local id's also benefit from being short, so we +// don't want to keep incrementing forever. +// +// This function takes a list if id's that are in use and sets the limit +// to the nearest power of ten depening on the current highest id used. +// The new id is an incremented one from that max. However, if the limit +// is reached, it first tries to find "holes" in the current sequence, +// starting from the bottom. If there are no holes, the limit is increased. func NextLocalId(used []int) int { if len(used) == 0 { return 1 @@ -57,3 +69,60 @@ func NextLocalId(used []int) int { return limit } + +// MergeNewTaskSet updates a local set of tasks with a remote one +// +// New set is leading and tasks that are not in there get dismissed. Tasks that +// were created locally and got dispatched might temporarily dissappear if the +// remote inbox has a delay in processing. +func MergeNewTaskSet(oldTasks []*task.LocalTask, newTasks []*task.Task) []*task.LocalTask { + + // create lookups + resultMap := map[string]*task.LocalTask{} + for _, nt := range newTasks { + resultMap[nt.Id] = &task.LocalTask{ + Task: *nt, + LocalId: 0, + LocalUpdate: &task.LocalUpdate{}, + } + } + oldMap := map[string]*task.LocalTask{} + for _, ot := range oldTasks { + oldMap[ot.Id] = ot + } + + // apply local id rules: + // - keep id's that were present in the old set + // - find new id's for new tasks + // - assignment of local id's is non deterministic + var used []int + for _, ot := range oldTasks { + if _, ok := resultMap[ot.Id]; ok { + resultMap[ot.Id].LocalId = ot.LocalId + used = append(used, ot.LocalId) + } + } + for id, nt := range resultMap { + if nt.LocalId == 0 { + newLocalId := NextLocalId(used) + resultMap[id].LocalId = newLocalId + used = append(used, newLocalId) + } + } + + // apply local update rules: + // - only keep local updates if the new task hasn't moved to a new version yet + for _, ot := range oldTasks { + if nt, ok := resultMap[ot.Id]; ok { + if ot.LocalUpdate.ForVersion >= nt.Version { + resultMap[ot.Id].LocalUpdate = ot.LocalUpdate + } + } + } + + var result []*task.LocalTask + for _, nt := range resultMap { + result = append(result, nt) + } + return result +} diff --git a/internal/storage/local_test.go b/internal/storage/local_test.go index 2011adc..a7673af 100644 --- a/internal/storage/local_test.go +++ b/internal/storage/local_test.go @@ -1,10 +1,12 @@ package storage_test import ( + "sort" "testing" "git.ewintr.nl/go-kit/test" "git.ewintr.nl/gte/internal/storage" + "git.ewintr.nl/gte/internal/task" ) func TestNextLocalId(t *testing.T) { @@ -72,3 +74,105 @@ func TestNextLocalId(t *testing.T) { }) } } + +func TestMergeNewTaskSet(t *testing.T) { + task1 := &task.Task{Id: "id-1", Version: 1, Action: "action-1"} + task1v2 := &task.Task{Id: "id-1", Version: 2, Action: "action-1v2"} + task2 := &task.Task{Id: "id-2", Version: 2, Action: "action-2"} + emptyUpdate := &task.LocalUpdate{} + + t.Run("local ids are added", func(t *testing.T) { + act1 := storage.MergeNewTaskSet([]*task.LocalTask{}, []*task.Task{task1}) + test.Assert(t, len(act1) == 1, "length was not 1") + test.Equals(t, 1, act1[0].LocalId) + + act2 := storage.MergeNewTaskSet(act1, []*task.Task{task1, task2}) + var actIds []int + for _, t := range act2 { + actIds = append(actIds, t.LocalId) + } + sort.Ints(actIds) + test.Equals(t, []int{1, 2}, actIds) + }) + + for _, tc := range []struct { + name string + oldTasks []*task.LocalTask + newTasks []*task.Task + exp []*task.LocalTask + }{ + { + name: "add tasks and find local ids", + oldTasks: []*task.LocalTask{}, + newTasks: []*task.Task{task1, task2}, + exp: []*task.LocalTask{ + {Task: *task1, LocalUpdate: emptyUpdate}, + {Task: *task2, LocalUpdate: emptyUpdate}, + }, + }, + { + name: "update existing task", + oldTasks: []*task.LocalTask{ + {Task: *task1, LocalUpdate: emptyUpdate}, + {Task: *task2, LocalId: 2, LocalUpdate: emptyUpdate}, + }, + newTasks: []*task.Task{task1v2, task2}, + exp: []*task.LocalTask{ + {Task: *task1v2, LocalUpdate: emptyUpdate}, + {Task: *task2, LocalUpdate: emptyUpdate}, + }, + }, + { + name: "remove deleted task", + oldTasks: []*task.LocalTask{ + {Task: *task1, LocalUpdate: emptyUpdate}, + {Task: *task2, LocalUpdate: emptyUpdate}, + }, + newTasks: []*task.Task{task2}, + exp: []*task.LocalTask{ + {Task: *task2, LocalUpdate: emptyUpdate}, + }, + }, + { + name: "remove only outdated updates", + oldTasks: []*task.LocalTask{ + { + Task: *task1, + LocalUpdate: &task.LocalUpdate{ + ForVersion: 1, + Project: "project-v2", + }, + }, + { + Task: *task2, + LocalUpdate: &task.LocalUpdate{ + ForVersion: 2, + Project: "project-v3", + }, + }, + }, + newTasks: []*task.Task{task1v2, task2}, + exp: []*task.LocalTask{ + {Task: *task1v2, LocalUpdate: emptyUpdate}, + { + Task: *task2, + LocalUpdate: &task.LocalUpdate{ + ForVersion: 2, + Project: "project-v3", + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + sExp := task.ById(tc.exp) + sAct := task.ById(storage.MergeNewTaskSet(tc.oldTasks, tc.newTasks)) + for i := range sAct { + sAct[i].LocalId = 0 + } + sort.Sort(sExp) + sort.Sort(sAct) + test.Equals(t, sExp, sAct) + }) + } +} diff --git a/internal/storage/memory.go b/internal/storage/memory.go index a6382b4..d3e1e5e 100644 --- a/internal/storage/memory.go +++ b/internal/storage/memory.go @@ -6,25 +6,15 @@ import ( "git.ewintr.nl/gte/internal/task" ) -type localData struct { - LocalId int - LocalUpdate *task.LocalUpdate -} - // Memory is an in memory implementation of LocalRepository -// -// It is meant for testing and does not make an attempt to -// keep local state between consecutive calls to SetTasks() type Memory struct { - tasks []*task.Task + tasks map[string]*task.LocalTask latestSync time.Time - localData map[string]localData } func NewMemory() *Memory { return &Memory{ - tasks: []*task.Task{}, - localData: map[string]localData{}, + tasks: map[string]*task.LocalTask{}, } } @@ -33,56 +23,26 @@ func (m *Memory) LatestSync() (time.Time, error) { } func (m *Memory) SetTasks(tasks []*task.Task) error { - nTasks := []*task.Task{} - for _, t := range tasks { - nt := *t - nt.Message = nil - nTasks = append(nTasks, &nt) - m.setLocalId(t.Id) + var oldTasks []*task.LocalTask + for _, ot := range m.tasks { + oldTasks = append(oldTasks, ot) + } + + newTasks := MergeNewTaskSet(oldTasks, tasks) + + m.tasks = map[string]*task.LocalTask{} + for _, nt := range newTasks { + m.tasks[nt.Id] = nt } - m.tasks = nTasks m.latestSync = time.Now() return nil } -func (m *Memory) setLocalId(id string) { - used := []int{} - for _, ld := range m.localData { - used = append(used, ld.LocalId) - } - - next := NextLocalId(used) - m.localData[id] = localData{ - LocalId: next, - } -} - -func (m *Memory) FindAllInFolder(folder string) ([]*task.LocalTask, error) { +func (m *Memory) FindAll() ([]*task.LocalTask, error) { tasks := []*task.LocalTask{} for _, t := range m.tasks { - if t.Folder == folder { - tasks = append(tasks, &task.LocalTask{ - Task: *t, - LocalId: m.localData[t.Id].LocalId, - LocalUpdate: m.localData[t.Id].LocalUpdate, - }) - } - } - - return tasks, nil -} - -func (m *Memory) FindAllInProject(project string) ([]*task.LocalTask, error) { - tasks := []*task.LocalTask{} - for _, t := range m.tasks { - if t.Project == project { - tasks = append(tasks, &task.LocalTask{ - Task: *t, - LocalId: m.localData[t.Id].LocalId, - LocalUpdate: m.localData[t.Id].LocalUpdate, - }) - } + tasks = append(tasks, t) } return tasks, nil @@ -91,11 +51,7 @@ func (m *Memory) FindAllInProject(project string) ([]*task.LocalTask, error) { func (m *Memory) FindById(id string) (*task.LocalTask, error) { for _, t := range m.tasks { if t.Id == id { - return &task.LocalTask{ - Task: *t, - LocalId: m.localData[t.Id].LocalId, - LocalUpdate: m.localData[t.Id].LocalUpdate, - }, nil + return t, nil } } @@ -104,12 +60,8 @@ func (m *Memory) FindById(id string) (*task.LocalTask, error) { func (m *Memory) FindByLocalId(localId int) (*task.LocalTask, error) { for _, t := range m.tasks { - if m.localData[t.Id].LocalId == localId { - return &task.LocalTask{ - Task: *t, - LocalId: localId, - LocalUpdate: m.localData[t.Id].LocalUpdate, - }, nil + if t.LocalId == localId { + return t, nil } } @@ -117,10 +69,7 @@ func (m *Memory) FindByLocalId(localId int) (*task.LocalTask, error) { } func (m *Memory) SetLocalUpdate(tsk *task.LocalTask) error { - m.localData[tsk.Id] = localData{ - LocalId: tsk.LocalId, - LocalUpdate: tsk.LocalUpdate, - } + m.tasks[tsk.Id] = tsk return nil } diff --git a/internal/storage/memory_test.go b/internal/storage/memory_test.go index 613c150..7bff7f4 100644 --- a/internal/storage/memory_test.go +++ b/internal/storage/memory_test.go @@ -1,6 +1,7 @@ package storage_test import ( + "sort" "testing" "time" @@ -41,9 +42,10 @@ func TestMemory(t *testing.T) { }, } tasks := []*task.Task{task1, task2, task3} - localTask1 := &task.LocalTask{Task: *task1, LocalId: 1} - localTask2 := &task.LocalTask{Task: *task2, LocalId: 2} - localTask3 := &task.LocalTask{Task: *task3, LocalId: 3} + emptyUpdate := &task.LocalUpdate{} + localTask1 := &task.LocalTask{Task: *task1, LocalUpdate: emptyUpdate} + localTask2 := &task.LocalTask{Task: *task2, LocalUpdate: emptyUpdate} + localTask3 := &task.LocalTask{Task: *task3, LocalUpdate: emptyUpdate} t.Run("sync", func(t *testing.T) { mem := storage.NewMemory() @@ -58,28 +60,20 @@ func TestMemory(t *testing.T) { test.Assert(t, latest.After(start), "latest was not after start") }) - t.Run("findallinfolder", func(t *testing.T) { + t.Run("findallin", func(t *testing.T) { mem := storage.NewMemory() test.OK(t, mem.SetTasks(tasks)) - act, err := mem.FindAllInFolder(folder1) + act, err := mem.FindAll() test.OK(t, err) - exp := []*task.LocalTask{localTask1, localTask2} - for _, tsk := range exp { - tsk.Message = nil + exp := []*task.LocalTask{localTask1, localTask2, localTask3} + for _, tsk := range act { + tsk.LocalId = 0 } - test.Equals(t, exp, act) - }) - - t.Run("findallinproject", func(t *testing.T) { - mem := storage.NewMemory() - test.OK(t, mem.SetTasks(tasks)) - act, err := mem.FindAllInProject(project1) - test.OK(t, err) - exp := []*task.LocalTask{localTask1, localTask3} - for _, tsk := range exp { - tsk.Message = nil - } - test.Equals(t, exp, act) + sExp := task.ById(exp) + sAct := task.ById(act) + sort.Sort(sExp) + sort.Sort(sAct) + test.Equals(t, sExp, sAct) }) t.Run("findbyid", func(t *testing.T) { @@ -87,15 +81,17 @@ func TestMemory(t *testing.T) { test.OK(t, mem.SetTasks(tasks)) act, err := mem.FindById("id-2") test.OK(t, err) + act.LocalId = 0 test.Equals(t, localTask2, act) }) t.Run("findbylocalid", func(t *testing.T) { mem := storage.NewMemory() - test.OK(t, mem.SetTasks(tasks)) - act, err := mem.FindByLocalId(2) + test.OK(t, mem.SetTasks([]*task.Task{task1})) + act, err := mem.FindByLocalId(1) test.OK(t, err) - test.Equals(t, localTask2, act) + act.LocalId = 0 + test.Equals(t, localTask1, act) }) t.Run("setlocalupdate", func(t *testing.T) { diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index 4ec28e3..3946283 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -19,6 +19,11 @@ var sqliteMigrations = []sqliteMigration{ `CREATE TABLE local_id ("id" TEXT UNIQUE, "local_id" INTEGER UNIQUE)`, `ALTER TABLE local_id RENAME TO local_task`, `ALTER TABLE local_task ADD COLUMN local_update TEXT`, + `ALTER TABLE task ADD COLUMN local_id INTEGER`, + `ALTER TABLE task ADD COLUMN local_update TEXT`, + `UPDATE task SET local_id = (SELECT local_id FROM local_task WHERE local_task.id=task.id)`, + `UPDATE task SET local_update = (SELECT local_update FROM local_task WHERE local_task.id=task.id)`, + `DROP TABLE local_task`, } var ( @@ -71,134 +76,40 @@ func (s *Sqlite) LatestSync() (time.Time, error) { } func (s *Sqlite) SetTasks(tasks []*task.Task) error { - // set tasks + oldTasks, err := s.FindAll() + if err != nil { + return err + } + newTasks := MergeNewTaskSet(oldTasks, tasks) + if _, err := s.db.Exec(`DELETE FROM task`); err != nil { return fmt.Errorf("%w: %v", ErrSqliteFailure, err) } - - type localTaskInfo struct { - TaskId string - TaskVersion int - LocalId int - LocalUpdate task.LocalUpdate - } - localIdMap := map[string]localTaskInfo{} - for _, t := range tasks { + for _, t := range newTasks { var recurStr string if t.Recur != nil { recurStr = t.Recur.String() } + _, err := s.db.Exec(` INSERT INTO task -(id, version, folder, action, project, due, recur) +(id, local_id, version, folder, action, project, due, recur, local_update) VALUES -(?, ?, ?, ?, ?, ?, ?)`, - t.Id, t.Version, t.Folder, t.Action, t.Project, t.Due.String(), recurStr) +(?, ?, ?, ?, ?, ?, ?, ?, ?)`, + t.Id, t.LocalId, t.Version, t.Folder, t.Action, t.Project, t.Due.String(), recurStr, t.LocalUpdate) + if err != nil { return fmt.Errorf("%w: %v", ErrSqliteFailure, err) } - - localIdMap[t.Id] = localTaskInfo{ - TaskId: t.Id, - TaskVersion: t.Version, - LocalId: 0, - LocalUpdate: task.LocalUpdate{}, - } - } - - // set local_ids and local_updates: - // 1 - find existing - rows, err := s.db.Query(`SELECT id, local_id, local_update FROM local_task`) - if err != nil { - return fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - defer rows.Close() - for rows.Next() { - var id string - var localId int - var localUpdate task.LocalUpdate - if err := rows.Scan(&id, &localId, &localUpdate); err != nil { - return fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - if oldInfo, ok := localIdMap[id]; ok { - newInfo := localTaskInfo{ - TaskId: oldInfo.TaskId, - TaskVersion: oldInfo.TaskVersion, - LocalId: localId, - LocalUpdate: localUpdate, - } - localIdMap[id] = newInfo - } - } - - // 2 - remove old values - if _, err := s.db.Exec(`DELETE FROM local_task`); err != nil { - return fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - - // 3 - figure out new values - var used []int - for _, info := range localIdMap { - if info.LocalId != 0 { - used = append(used, info.LocalId) - } - } - for id, info := range localIdMap { - newInfo := info - // find new local_id when needed - if info.LocalId == 0 { - newLocalId := NextLocalId(used) - used = append(used, newLocalId) - newInfo.LocalId = newLocalId - } - // remove local_update when outdated - if info.LocalUpdate.ForVersion < info.TaskVersion { - newInfo.LocalUpdate = task.LocalUpdate{} - } - localIdMap[id] = newInfo - } - - // 4 - store new values - for id, info := range localIdMap { - if _, err := s.db.Exec(` -INSERT INTO local_task -(id, local_id, local_update) -VALUES -(?, ?, ?)`, id, info.LocalId, info.LocalUpdate); err != nil { - return fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - } - - // update system - if _, err := s.db.Exec(` -UPDATE system -SET latest_sync = ?`, - time.Now().Unix()); err != nil { - return fmt.Errorf("%w: %v", ErrSqliteFailure, err) } return nil } -func (s *Sqlite) FindAllInFolder(folder string) ([]*task.LocalTask, error) { +func (s *Sqlite) FindAll() ([]*task.LocalTask, error) { rows, err := s.db.Query(` -SELECT task.id, local_task.local_id, version, folder, action, project, due, recur, local_task.local_update -FROM task -LEFT JOIN local_task ON task.id = local_task.id -WHERE folder = ?`, folder) - if err != nil { - return []*task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) - } - - return tasksFromRows(rows) -} - -func (s *Sqlite) FindAllInProject(project string) ([]*task.LocalTask, error) { - rows, err := s.db.Query(` -SELECT task.id, local_task.local_id, version, folder, action, project, due, recur, local_task.local_update -FROM task -LEFT JOIN local_task ON task.id = local_task.id -WHERE project = ?`, project) +SELECT id, local_id, version, folder, action, project, due, recur, local_update +FROM task`) if err != nil { return []*task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) } @@ -211,9 +122,8 @@ func (s *Sqlite) FindById(id string) (*task.LocalTask, error) { var localId, version int var localUpdate task.LocalUpdate row := s.db.QueryRow(` -SELECT local_task.local_id, version, folder, action, project, due, recur, local_task.local_update +SELECT local_id, version, folder, action, project, due, recur, local_update FROM task -LEFT JOIN local_task ON task.id = local_task.id WHERE task.id = ? LIMIT 1`, id) if err := row.Scan(&localId, &version, &folder, &action, &project, &due, &recur, &localUpdate); err != nil { @@ -237,7 +147,7 @@ LIMIT 1`, id) func (s *Sqlite) FindByLocalId(localId int) (*task.LocalTask, error) { var id string - row := s.db.QueryRow(`SELECT id FROM local_task WHERE local_id = ?`, localId) + row := s.db.QueryRow(`SELECT id FROM task WHERE local_id = ?`, localId) if err := row.Scan(&id); err != nil { return &task.LocalTask{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err) } @@ -252,7 +162,7 @@ func (s *Sqlite) FindByLocalId(localId int) (*task.LocalTask, error) { func (s *Sqlite) SetLocalUpdate(tsk *task.LocalTask) error { if _, err := s.db.Exec(` -UPDATE local_task +UPDATE task SET local_update = ? 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 543327f..88b759e 100644 --- a/internal/task/localtask.go +++ b/internal/task/localtask.go @@ -53,6 +53,12 @@ func (lt *LocalTask) ApplyUpdate() { lt.LocalUpdate = &LocalUpdate{} } +type ById []*LocalTask + +func (lt ById) Len() int { return len(lt) } +func (lt ById) Swap(i, j int) { lt[i], lt[j] = lt[j], lt[i] } +func (lt ById) Less(i, j int) bool { return lt[i].Id < lt[j].Id } + type ByDue []*LocalTask func (lt ByDue) Len() int { return len(lt) }