local id
This commit is contained in:
parent
d57a0a8162
commit
ebe2c401ef
|
@ -1,6 +1,8 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.ewintr.nl/gte/cmd/cli/format"
|
"git.ewintr.nl/gte/cmd/cli/format"
|
||||||
"git.ewintr.nl/gte/internal/configuration"
|
"git.ewintr.nl/gte/internal/configuration"
|
||||||
"git.ewintr.nl/gte/internal/process"
|
"git.ewintr.nl/gte/internal/process"
|
||||||
|
@ -21,8 +23,22 @@ func NewDone(conf *configuration.Configuration, cmdArgs []string) (*Done, error)
|
||||||
|
|
||||||
disp := storage.NewDispatcher(msend.NewSSLSMTP(conf.SMTP()))
|
disp := storage.NewDispatcher(msend.NewSSLSMTP(conf.SMTP()))
|
||||||
fields := process.UpdateFields{"done": "true"}
|
fields := process.UpdateFields{"done": "true"}
|
||||||
|
localIds, err := local.LocalIds()
|
||||||
|
if err != nil {
|
||||||
|
return &Done{}, err
|
||||||
|
}
|
||||||
|
var tId string
|
||||||
|
for id, localId := range localIds {
|
||||||
|
if fmt.Sprintf("%d", localId) == cmdArgs[0] {
|
||||||
|
tId = id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tId == "" {
|
||||||
|
return &Done{}, fmt.Errorf("could not find task")
|
||||||
|
}
|
||||||
|
|
||||||
updater := process.NewUpdate(local, disp, cmdArgs[0], fields)
|
updater := process.NewUpdate(local, disp, tId, fields)
|
||||||
|
|
||||||
return &Done{
|
return &Done{
|
||||||
doner: updater,
|
doner: updater,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
// Today lists all task that are due today or past their due date
|
// Today lists all task that are due today or past their due date
|
||||||
type Today struct {
|
type Today struct {
|
||||||
|
local storage.LocalRepository
|
||||||
todayer *process.List
|
todayer *process.List
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ func NewToday(conf *configuration.Configuration) (*Today, error) {
|
||||||
todayer := process.NewList(local, reqs)
|
todayer := process.NewList(local, reqs)
|
||||||
|
|
||||||
return &Today{
|
return &Today{
|
||||||
|
local: local,
|
||||||
todayer: todayer,
|
todayer: todayer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -38,5 +40,5 @@ func (t *Today) Do() string {
|
||||||
return "nothing left\n"
|
return "nothing left\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
return format.FormatTaskTable(res.Tasks)
|
return format.FormatTaskTable(t.local, res.Tasks)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
// Tomorrow lists all tasks that are due tomorrow
|
// Tomorrow lists all tasks that are due tomorrow
|
||||||
type Tomorrow struct {
|
type Tomorrow struct {
|
||||||
|
local storage.LocalRepository
|
||||||
tomorrower *process.List
|
tomorrower *process.List
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ func NewTomorrow(conf *configuration.Configuration) (*Tomorrow, error) {
|
||||||
tomorrower := process.NewList(local, reqs)
|
tomorrower := process.NewList(local, reqs)
|
||||||
|
|
||||||
return &Tomorrow{
|
return &Tomorrow{
|
||||||
|
local: local,
|
||||||
tomorrower: tomorrower,
|
tomorrower: tomorrower,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -39,5 +41,5 @@ func (t *Tomorrow) Do() string {
|
||||||
return "nothing to do tomorrow\n"
|
return "nothing to do tomorrow\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
return format.FormatTaskTable(res.Tasks)
|
return format.FormatTaskTable(t.local, res.Tasks)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package format
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.ewintr.nl/gte/internal/storage"
|
||||||
"git.ewintr.nl/gte/internal/task"
|
"git.ewintr.nl/gte/internal/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,10 +11,19 @@ func FormatError(err error) string {
|
||||||
return fmt.Sprintf("could not perform command.\n\nerror: %s\n", err.Error())
|
return fmt.Sprintf("could not perform command.\n\nerror: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func FormatTaskTable(tasks []*task.Task) string {
|
func FormatTaskTable(local storage.LocalRepository, tasks []*task.Task) string {
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
return "no tasks to display\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
localIds, err := local.LocalIds()
|
||||||
|
if err != nil {
|
||||||
|
return FormatError(err)
|
||||||
|
}
|
||||||
|
|
||||||
var output string
|
var output string
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
output += fmt.Sprintf("%s\t%s\t%s\n", t.Id, t.Due.String(), t.Action)
|
output += fmt.Sprintf("%d\t%s\t%s\n", localIds[t.Id], t.Due.String(), t.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
|
@ -17,4 +17,41 @@ type LocalRepository interface {
|
||||||
FindAllInFolder(folder string) ([]*task.Task, error)
|
FindAllInFolder(folder string) ([]*task.Task, error)
|
||||||
FindAllInProject(project string) ([]*task.Task, error)
|
FindAllInProject(project string) ([]*task.Task, error)
|
||||||
FindById(id string) (*task.Task, error)
|
FindById(id string) (*task.Task, error)
|
||||||
|
FindByLocalId(id int) (*task.Task, error)
|
||||||
|
LocalIds() (map[string]int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextLocalId(used []int) int {
|
||||||
|
if len(used) == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package storage_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.ewintr.nl/go-kit/test"
|
||||||
|
"git.ewintr.nl/gte/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNextLocalId(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
used []int
|
||||||
|
exp int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
used: []int{},
|
||||||
|
exp: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not empty",
|
||||||
|
used: []int{5},
|
||||||
|
exp: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple",
|
||||||
|
used: []int{2, 3, 4},
|
||||||
|
exp: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "holes",
|
||||||
|
used: []int{1, 5, 8},
|
||||||
|
exp: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expand limit",
|
||||||
|
used: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||||
|
exp: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrap if possible",
|
||||||
|
used: []int{8, 9},
|
||||||
|
exp: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "find hole",
|
||||||
|
used: []int{1, 2, 3, 4, 5, 7, 8, 9},
|
||||||
|
exp: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dont wrap if expanded before",
|
||||||
|
used: []int{15, 16},
|
||||||
|
exp: 17,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "do wrap if expanded limit is reached",
|
||||||
|
used: []int{99},
|
||||||
|
exp: 1,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
test.Equals(t, tc.exp, storage.NextLocalId(tc.used))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,13 @@ import (
|
||||||
type Memory struct {
|
type Memory struct {
|
||||||
tasks []*task.Task
|
tasks []*task.Task
|
||||||
latestSync time.Time
|
latestSync time.Time
|
||||||
|
localIds map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMemory() *Memory {
|
func NewMemory() *Memory {
|
||||||
return &Memory{
|
return &Memory{
|
||||||
tasks: []*task.Task{},
|
tasks: []*task.Task{},
|
||||||
|
localIds: map[string]int{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ func (m *Memory) SetTasks(tasks []*task.Task) error {
|
||||||
nt := *t
|
nt := *t
|
||||||
nt.Message = nil
|
nt.Message = nil
|
||||||
nTasks = append(nTasks, &nt)
|
nTasks = append(nTasks, &nt)
|
||||||
|
m.setLocalId(t.Id)
|
||||||
}
|
}
|
||||||
m.tasks = nTasks
|
m.tasks = nTasks
|
||||||
m.latestSync = time.Now()
|
m.latestSync = time.Now()
|
||||||
|
@ -35,6 +38,16 @@ func (m *Memory) SetTasks(tasks []*task.Task) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Memory) setLocalId(id string) {
|
||||||
|
used := []int{}
|
||||||
|
for _, id := range m.localIds {
|
||||||
|
used = append(used, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := NextLocalId(used)
|
||||||
|
m.localIds[id] = next
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Memory) FindAllInFolder(folder string) ([]*task.Task, error) {
|
func (m *Memory) FindAllInFolder(folder string) ([]*task.Task, error) {
|
||||||
tasks := []*task.Task{}
|
tasks := []*task.Task{}
|
||||||
for _, t := range m.tasks {
|
for _, t := range m.tasks {
|
||||||
|
@ -67,3 +80,17 @@ func (m *Memory) FindById(id string) (*task.Task, error) {
|
||||||
|
|
||||||
return &task.Task{}, ErrTaskNotFound
|
return &task.Task{}, ErrTaskNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Memory) FindByLocalId(localId int) (*task.Task, error) {
|
||||||
|
for _, t := range m.tasks {
|
||||||
|
if m.localIds[t.Id] == localId {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.Task{}, ErrTaskNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Memory) LocalIds() (map[string]int, error) {
|
||||||
|
return m.localIds, nil
|
||||||
|
}
|
||||||
|
|
|
@ -86,4 +86,25 @@ func TestMemory(t *testing.T) {
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
test.Equals(t, task2, act)
|
test.Equals(t, task2, 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, err)
|
||||||
|
test.Equals(t, task2, act)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("localids", func(t *testing.T) {
|
||||||
|
mem := storage.NewMemory()
|
||||||
|
test.OK(t, mem.SetTasks(tasks))
|
||||||
|
act, err := mem.LocalIds()
|
||||||
|
test.OK(t, err)
|
||||||
|
exp := map[string]int{
|
||||||
|
"id-1": 1,
|
||||||
|
"id-2": 2,
|
||||||
|
"id-3": 3,
|
||||||
|
}
|
||||||
|
test.Equals(t, exp, act)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ var sqliteMigrations = []sqliteMigration{
|
||||||
`CREATE TABLE task ("id" TEXT, "version" INTEGER, "folder" TEXT, "action" TEXT, "project" TEXT, "due" TEXT, "recur" TEXT)`,
|
`CREATE TABLE task ("id" TEXT, "version" INTEGER, "folder" TEXT, "action" TEXT, "project" TEXT, "due" TEXT, "recur" TEXT)`,
|
||||||
`CREATE TABLE system ("latest_sync" INTEGER)`,
|
`CREATE TABLE system ("latest_sync" INTEGER)`,
|
||||||
`INSERT INTO system (latest_sync) VALUES (0)`,
|
`INSERT INTO system (latest_sync) VALUES (0)`,
|
||||||
|
`CREATE TABLE local_id ("id" TEXT, "local_id" INTEGER)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -68,10 +69,12 @@ func (s *Sqlite) LatestSync() (time.Time, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sqlite) SetTasks(tasks []*task.Task) error {
|
func (s *Sqlite) SetTasks(tasks []*task.Task) error {
|
||||||
|
// set tasks
|
||||||
if _, err := s.db.Exec(`DELETE FROM task`); err != nil {
|
if _, err := s.db.Exec(`DELETE FROM task`); err != nil {
|
||||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newIds := []string{}
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
var recurStr string
|
var recurStr string
|
||||||
if t.Recur != nil {
|
if t.Recur != nil {
|
||||||
|
@ -86,8 +89,56 @@ VALUES
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newIds = append(newIds, t.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set local_ids
|
||||||
|
oldIds := map[string]int{}
|
||||||
|
rows, err := s.db.Query(`SELECT id, local_id FROM local_id`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var id string
|
||||||
|
var local_id int
|
||||||
|
if err := rows.Scan(&id, &local_id); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
oldIds[id] = local_id
|
||||||
|
}
|
||||||
|
|
||||||
|
usedLocalIds := []int{}
|
||||||
|
newLocalIds := map[string]int{}
|
||||||
|
for _, n := range newIds {
|
||||||
|
if localId, ok := oldIds[n]; ok {
|
||||||
|
newLocalIds[n] = localId
|
||||||
|
usedLocalIds = append(usedLocalIds, localId)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
localId := NextLocalId(usedLocalIds)
|
||||||
|
newLocalIds[n] = localId
|
||||||
|
usedLocalIds = append(usedLocalIds, localId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.db.Exec(`DELETE FROM local_id`); err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
for id, localId := range newLocalIds {
|
||||||
|
_, err := s.db.Exec(`
|
||||||
|
INSERT INTO local_id
|
||||||
|
(id, local_id)
|
||||||
|
VALUES
|
||||||
|
(?, ?)`, id, localId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update system
|
||||||
if _, err := s.db.Exec(`
|
if _, err := s.db.Exec(`
|
||||||
UPDATE system
|
UPDATE system
|
||||||
SET latest_sync = ?`,
|
SET latest_sync = ?`,
|
||||||
|
@ -145,6 +196,41 @@ LIMIT 1`, id)
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Sqlite) FindByLocalId(localId int) (*task.Task, error) {
|
||||||
|
var id string
|
||||||
|
row := s.db.QueryRow(`SELECT id FROM local_id WHERE local_id = ?`, localId)
|
||||||
|
if err := row.Scan(&id); err != nil {
|
||||||
|
return &task.Task{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := s.FindById(id)
|
||||||
|
if err != nil {
|
||||||
|
return &task.Task{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sqlite) LocalIds() (map[string]int, error) {
|
||||||
|
rows, err := s.db.Query(`SELECT id, local_id FROM local_id`)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]int{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idMap := map[string]int{}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var id string
|
||||||
|
var local_id int
|
||||||
|
if err := rows.Scan(&id, &local_id); err != nil {
|
||||||
|
return map[string]int{}, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
|
||||||
|
}
|
||||||
|
idMap[id] = local_id
|
||||||
|
}
|
||||||
|
|
||||||
|
return idMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
func tasksFromRows(rows *sql.Rows) ([]*task.Task, error) {
|
func tasksFromRows(rows *sql.Rows) ([]*task.Task, error) {
|
||||||
tasks := []*task.Task{}
|
tasks := []*task.Task{}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue