split off sync merge func
This commit is contained in:
parent
41a27a4799
commit
a2a0e9bb7b
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) }
|
||||
|
|
Loading…
Reference in New Issue