split off sync merge func

This commit is contained in:
Erik Winter 2021-08-25 06:52:48 +02:00
parent 41a27a4799
commit a2a0e9bb7b
10 changed files with 287 additions and 240 deletions

View File

@ -56,28 +56,30 @@ 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}
}
var potentialTasks []*task.LocalTask
for _, folder := range folders {
folderTasks, err := l.local.FindAllInFolder(folder)
potentialTasks, err := l.local.FindAll()
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 == "" {
return &ListResult{
Tasks: potentialTasks,
}, nil
}
// project
if l.reqs.Project != "" {
var projectTasks []*task.LocalTask
for _, pt := range potentialTasks {

View File

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

View File

@ -6,7 +6,6 @@ import (
"sort"
"git.ewintr.nl/gte/internal/storage"
"git.ewintr.nl/gte/internal/task"
)
var (
@ -24,14 +23,10 @@ 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)
allTasks, err := p.local.FindAll()
if err != nil {
return []string{}, fmt.Errorf("%w: %v", ErrCouldNotFetchProjects, err)
}
allTasks = append(allTasks, folderTasks...)
}
knownMap := map[string]bool{}
for _, t := range allTasks {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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