gte/internal/storage/local.go

133 lines
3.4 KiB
Go

package storage
import (
"errors"
"sort"
"time"
"ewintr.nl/gte/internal/task"
)
var (
ErrTaskNotFound = errors.New("task was not found")
)
type LocalRepository interface {
LatestSync() (time.Time, error)
SetTasks(tasks []*task.Task) error
FindAll() ([]*task.LocalTask, error)
FindById(id string) (*task.LocalTask, error)
FindByLocalId(id int) (*task.LocalTask, error)
SetLocalUpdate(id string, update *task.LocalUpdate) error
MarkDispatched(id int) error
Add(update *task.LocalUpdate) (*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
}
sort.Ints(used)
usedMax := 1
for _, u := range used {
if u > usedMax {
usedMax = u
}
}
var limit int
for limit = 1; limit <= len(used) || limit < usedMax; limit *= 10 {
}
newId := used[len(used)-1] + 1
if newId < limit {
return newId
}
usedMap := map[int]bool{}
for _, u := range used {
usedMap[u] = true
}
for i := 1; i < limit; i++ {
if _, ok := usedMap[i]; !ok {
return i
}
}
return limit
}
// MergeNewTaskSet updates a local set of tasks with a remote one
//
// The 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{},
LocalStatus: task.STATUS_FETCHED,
}
}
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
resultMap[ot.Id].LocalStatus = task.STATUS_UPDATED
}
}
}
var result []*task.LocalTask
for _, nt := range resultMap {
result = append(result, nt)
}
return result
}