split message->task->message chain
This commit is contained in:
parent
05ad9720e1
commit
ad5a864a17
|
@ -43,12 +43,10 @@ func (inbox *Inbox) Process() (*InboxResult, error) {
|
|||
|
||||
var cleanupNeeded bool
|
||||
for _, t := range tasks {
|
||||
if t.Dirty {
|
||||
if err := inbox.taskRepo.Update(t); err != nil {
|
||||
return &InboxResult{}, fmt.Errorf("%w: %v", ErrInboxProcess, err)
|
||||
}
|
||||
cleanupNeeded = true
|
||||
if err := inbox.taskRepo.Update(t); err != nil {
|
||||
return &InboxResult{}, fmt.Errorf("%w: %v", ErrInboxProcess, err)
|
||||
}
|
||||
cleanupNeeded = true
|
||||
}
|
||||
if cleanupNeeded {
|
||||
if err := inbox.taskRepo.CleanUp(); err != nil {
|
||||
|
|
|
@ -75,7 +75,6 @@ func TestWeekdaysUnique(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewDateFromString(t *testing.T) {
|
||||
|
||||
t.Run("no date", func(t *testing.T) {
|
||||
task.Today = task.NewDate(2021, 1, 30)
|
||||
for _, tc := range []struct {
|
||||
|
@ -251,6 +250,49 @@ func TestNewDateFromString(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestDateDaysBetween(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
d1 task.Date
|
||||
d2 task.Date
|
||||
exp int
|
||||
}{
|
||||
{
|
||||
name: "same",
|
||||
d1: task.NewDate(2021, 6, 23),
|
||||
d2: task.NewDate(2021, 6, 23),
|
||||
},
|
||||
{
|
||||
name: "one",
|
||||
d1: task.NewDate(2021, 6, 23),
|
||||
d2: task.NewDate(2021, 6, 24),
|
||||
exp: 1,
|
||||
},
|
||||
{
|
||||
name: "many",
|
||||
d1: task.NewDate(2021, 6, 23),
|
||||
d2: task.NewDate(2024, 3, 7),
|
||||
exp: 988,
|
||||
},
|
||||
{
|
||||
name: "edge",
|
||||
d1: task.NewDate(2020, 12, 30),
|
||||
d2: task.NewDate(2021, 1, 3),
|
||||
exp: 4,
|
||||
},
|
||||
{
|
||||
name: "reverse",
|
||||
d1: task.NewDate(2021, 6, 23),
|
||||
d2: task.NewDate(2021, 5, 23),
|
||||
exp: 31,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
test.Equals(t, tc.exp, tc.d1.DaysBetween(tc.d2))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDateString(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
|
|
|
@ -33,7 +33,7 @@ func (tr *TaskRepo) FindAll(folder string) ([]*Task, error) {
|
|||
tasks := []*Task{}
|
||||
for _, msg := range msgs {
|
||||
if msg.Valid() {
|
||||
tasks = append(tasks, New(msg))
|
||||
tasks = append(tasks, NewFromMessage(msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,12 +44,6 @@ func (tr *TaskRepo) Update(t *Task) error {
|
|||
if t == nil {
|
||||
return ErrInvalidTask
|
||||
}
|
||||
if !t.Current {
|
||||
return ErrOutdatedTask
|
||||
}
|
||||
if !t.Dirty {
|
||||
return nil
|
||||
}
|
||||
|
||||
// add new
|
||||
if err := tr.Add(t); err != nil {
|
||||
|
@ -61,8 +55,6 @@ func (tr *TaskRepo) Update(t *Task) error {
|
|||
return fmt.Errorf("%w: %s", ErrMStoreError, err)
|
||||
}
|
||||
|
||||
t.Current = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -71,7 +63,8 @@ func (tr *TaskRepo) Add(t *Task) error {
|
|||
return ErrInvalidTask
|
||||
}
|
||||
|
||||
if err := tr.mstore.Add(t.Folder, t.FormatSubject(), t.FormatBody()); err != nil {
|
||||
msg := t.NextMessage()
|
||||
if err := tr.mstore.Add(msg.Folder, msg.Subject, msg.Body); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrMStoreError, err)
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ func TestRepoFindAll(t *testing.T) {
|
|||
|
||||
func TestRepoUpdate(t *testing.T) {
|
||||
id := "id"
|
||||
oldFolder := "old folder"
|
||||
folder := "folder"
|
||||
oldFolder := task.FOLDER_INBOX
|
||||
folder := task.FOLDER_NEW
|
||||
action := "action"
|
||||
|
||||
oldMsg := &mstore.Message{
|
||||
|
@ -90,45 +90,19 @@ func TestRepoUpdate(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "task without message",
|
||||
task: &task.Task{
|
||||
Id: id,
|
||||
Folder: folder,
|
||||
Action: action,
|
||||
Current: true,
|
||||
Dirty: true,
|
||||
},
|
||||
expErr: task.ErrMStoreError,
|
||||
},
|
||||
{
|
||||
name: "outdated task",
|
||||
task: &task.Task{
|
||||
Id: id,
|
||||
Folder: folder,
|
||||
Action: action,
|
||||
Dirty: true,
|
||||
},
|
||||
expErr: task.ErrOutdatedTask,
|
||||
expErr: task.ErrMStoreError,
|
||||
},
|
||||
/*
|
||||
{
|
||||
name: "unchanged task",
|
||||
task: &task.Task{
|
||||
Id: id,
|
||||
Folder: folder,
|
||||
Action: action,
|
||||
Current: true,
|
||||
},
|
||||
expMsgs: []*mstore.Message{},
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: "changed task",
|
||||
task: &task.Task{
|
||||
Id: id,
|
||||
Folder: folder,
|
||||
Action: action,
|
||||
Current: true,
|
||||
Dirty: true,
|
||||
Message: oldMsg,
|
||||
},
|
||||
expMsgs: []*mstore.Message{
|
||||
|
|
|
@ -44,141 +44,93 @@ var (
|
|||
FOLDER_PLANNED,
|
||||
FOLDER_UNPLANNED,
|
||||
}
|
||||
|
||||
subjectFieldNames = []string{FIELD_ACTION}
|
||||
bodyFieldNames = []string{
|
||||
FIELD_ID,
|
||||
FIELD_VERSION,
|
||||
FIELD_ACTION,
|
||||
FIELD_PROJECT,
|
||||
FIELD_DUE,
|
||||
FIELD_RECUR,
|
||||
}
|
||||
)
|
||||
|
||||
// Task reperesents a task based on the data stored in a message
|
||||
type Task struct {
|
||||
Message *mstore.Message
|
||||
|
||||
// Id is a UUID that gets carried over when a new message is constructed
|
||||
Id string
|
||||
// Version is a method to determine the latest version for cleanup
|
||||
Id string
|
||||
Version int
|
||||
Folder string
|
||||
|
||||
// Folder is the same name as the mstore folder
|
||||
Folder string
|
||||
|
||||
// Ordinary task attributes
|
||||
Action string
|
||||
Project string
|
||||
Due Date
|
||||
Recur Recurrer
|
||||
|
||||
//Message is the underlying message
|
||||
Message *mstore.Message
|
||||
|
||||
// Current indicates whether the task represents an existing message in the mstore
|
||||
Current bool
|
||||
|
||||
// Dirty indicates whether the task contains updates not present in the message
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// New constructs a Task based on an mstore.Message.
|
||||
//
|
||||
// The data in the message is stored as key: value pairs, one per line. The line can start with quoting marks.
|
||||
// The subject line also contains values in the format "date - project - action".
|
||||
// Keys that exist more than once are merged. The one that appears first in the body takes precedence. A value present in the Body takes precedence over one in the subject.
|
||||
// This enables updating a task by forwarding a topposted message whith new values for fields that the user wants to update.
|
||||
func New(msg *mstore.Message) *Task {
|
||||
// Id
|
||||
dirty := false
|
||||
newId := false
|
||||
id, d := FieldFromBody(FIELD_ID, msg.Body)
|
||||
func NewFromMessage(msg *mstore.Message) *Task {
|
||||
t := &Task{
|
||||
Folder: msg.Folder,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
// parse fields from message
|
||||
subjectFields := map[string]string{}
|
||||
for _, f := range subjectFieldNames {
|
||||
subjectFields[f] = FieldFromSubject(f, msg.Subject)
|
||||
}
|
||||
|
||||
bodyFields := map[string]string{}
|
||||
for _, f := range bodyFieldNames {
|
||||
value, _ := FieldFromBody(f, msg.Body)
|
||||
bodyFields[f] = value
|
||||
}
|
||||
|
||||
// apply precedence rules
|
||||
version, _ := strconv.Atoi(bodyFields[FIELD_VERSION])
|
||||
id := bodyFields[FIELD_ID]
|
||||
if id == "" {
|
||||
id = uuid.New().String()
|
||||
dirty = true
|
||||
newId = true
|
||||
version = 0
|
||||
}
|
||||
if d {
|
||||
dirty = true
|
||||
t.Id = id
|
||||
t.Version = version
|
||||
|
||||
t.Action = bodyFields[FIELD_ACTION]
|
||||
if t.Action == "" {
|
||||
t.Action = subjectFields[FIELD_ACTION]
|
||||
}
|
||||
|
||||
// Version, cannot manually be incremented from body
|
||||
versionStr, _ := FieldFromBody(FIELD_VERSION, msg.Body)
|
||||
version, _ := strconv.Atoi(versionStr)
|
||||
if version == 0 {
|
||||
dirty = true
|
||||
}
|
||||
t.Project = bodyFields[FIELD_PROJECT]
|
||||
t.Due = NewDateFromString(bodyFields[FIELD_DUE])
|
||||
t.Recur = NewRecurrer(bodyFields[FIELD_RECUR])
|
||||
|
||||
// Action
|
||||
action, d := FieldFromBody(FIELD_ACTION, msg.Body)
|
||||
if action == "" {
|
||||
action = FieldFromSubject(FIELD_ACTION, msg.Subject)
|
||||
if action != "" {
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
if d {
|
||||
dirty = true
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Due
|
||||
dueStr, d := FieldFromBody(FIELD_DUE, msg.Body)
|
||||
if dueStr == "" {
|
||||
dueStr = FieldFromSubject(FIELD_DUE, msg.Subject)
|
||||
if dueStr != "" {
|
||||
dirty = true
|
||||
}
|
||||
func (t *Task) TargetFolder() string {
|
||||
switch {
|
||||
case t.Version == 0:
|
||||
return FOLDER_NEW
|
||||
case t.IsRecurrer():
|
||||
return FOLDER_RECURRING
|
||||
case !t.Due.IsZero():
|
||||
return FOLDER_PLANNED
|
||||
default:
|
||||
return FOLDER_UNPLANNED
|
||||
}
|
||||
if d {
|
||||
dirty = true
|
||||
}
|
||||
due := NewDateFromString(dueStr)
|
||||
}
|
||||
|
||||
// Recurrer
|
||||
recurStr, d := FieldFromBody(FIELD_RECUR, msg.Body)
|
||||
if d {
|
||||
dirty = true
|
||||
}
|
||||
recur := NewRecurrer(recurStr)
|
||||
func (t *Task) NextMessage() *mstore.Message {
|
||||
tNew := t
|
||||
tNew.Folder = t.TargetFolder()
|
||||
tNew.Version++
|
||||
|
||||
// Folder
|
||||
folderOld := msg.Folder
|
||||
folderNew := folderOld
|
||||
if folderOld == FOLDER_INBOX {
|
||||
switch {
|
||||
case newId:
|
||||
folderNew = FOLDER_NEW
|
||||
case !newId && recur != nil:
|
||||
folderNew = FOLDER_RECURRING
|
||||
case !newId && recur == nil && due.IsZero():
|
||||
folderNew = FOLDER_UNPLANNED
|
||||
case !newId && recur == nil && !due.IsZero():
|
||||
folderNew = FOLDER_PLANNED
|
||||
}
|
||||
|
||||
}
|
||||
if folderOld != folderNew {
|
||||
dirty = true
|
||||
}
|
||||
|
||||
// Project
|
||||
project, d := FieldFromBody(FIELD_PROJECT, msg.Body)
|
||||
if project == "" {
|
||||
project = FieldFromSubject(FIELD_PROJECT, msg.Subject)
|
||||
if project != "" {
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
if d {
|
||||
dirty = true
|
||||
}
|
||||
|
||||
if dirty {
|
||||
version++
|
||||
}
|
||||
|
||||
return &Task{
|
||||
Id: id,
|
||||
Version: version,
|
||||
Folder: folderNew,
|
||||
Action: action,
|
||||
Due: due,
|
||||
Recur: recur,
|
||||
Project: project,
|
||||
Message: msg,
|
||||
Current: true,
|
||||
Dirty: dirty,
|
||||
return &mstore.Message{
|
||||
Folder: tNew.Folder,
|
||||
Subject: tNew.FormatSubject(),
|
||||
Body: tNew.FormatBody(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,18 +18,15 @@ func TestNewFromMessage(t *testing.T) {
|
|||
recurs := "2021-06-04, daily"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
message *mstore.Message
|
||||
hasId bool
|
||||
hasVersion bool
|
||||
exp *task.Task
|
||||
name string
|
||||
message *mstore.Message
|
||||
hasId bool
|
||||
exp *task.Task
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
message: &mstore.Message{},
|
||||
exp: &task.Task{
|
||||
Dirty: true,
|
||||
},
|
||||
exp: &task.Task{},
|
||||
},
|
||||
{
|
||||
name: "id, action, project and folder",
|
||||
|
@ -43,8 +40,7 @@ action: %s
|
|||
project: %s
|
||||
`, id, version, action, project),
|
||||
},
|
||||
hasId: true,
|
||||
hasVersion: true,
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Version: version,
|
||||
|
@ -64,8 +60,7 @@ version: %d
|
|||
action: %s
|
||||
`, id, date.String(), version, action),
|
||||
},
|
||||
hasId: true,
|
||||
hasVersion: true,
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Folder: task.FOLDER_PLANNED,
|
||||
|
@ -74,58 +69,6 @@ action: %s
|
|||
Due: date,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "folder inbox get updated to new",
|
||||
message: &mstore.Message{
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Body: fmt.Sprintf(`
|
||||
action: %s
|
||||
`, action),
|
||||
},
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Folder: task.FOLDER_NEW,
|
||||
Action: action,
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "folder inbox gets updated to planned",
|
||||
message: &mstore.Message{
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Body: fmt.Sprintf(`
|
||||
id: %s
|
||||
due: %s
|
||||
action: %s
|
||||
`, id, date.String(), action),
|
||||
},
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Folder: task.FOLDER_PLANNED,
|
||||
Action: action,
|
||||
Due: date,
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "folder new gets updated to unplanned",
|
||||
message: &mstore.Message{
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Body: fmt.Sprintf(`
|
||||
id: %s
|
||||
due: no date
|
||||
action: %s
|
||||
`, id, action),
|
||||
},
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Folder: task.FOLDER_UNPLANNED,
|
||||
Action: action,
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "action in body takes precedence",
|
||||
message: &mstore.Message{
|
||||
|
@ -138,8 +81,7 @@ version: %d
|
|||
action: %s
|
||||
`, id, version, action),
|
||||
},
|
||||
hasId: true,
|
||||
hasVersion: true,
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Version: version,
|
||||
|
@ -159,7 +101,6 @@ action: %s
|
|||
Id: id,
|
||||
Folder: task.FOLDER_PLANNED,
|
||||
Action: action,
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -175,8 +116,7 @@ action: %s
|
|||
project: %s
|
||||
`, id, version, action, project),
|
||||
},
|
||||
hasId: true,
|
||||
hasVersion: true,
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Version: version,
|
||||
|
@ -202,11 +142,10 @@ Forwarded message:
|
|||
Id: id,
|
||||
Folder: task.FOLDER_PLANNED,
|
||||
Action: action,
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recur takes precedence over date",
|
||||
name: "recur",
|
||||
message: &mstore.Message{
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Body: fmt.Sprintf(`
|
||||
|
@ -218,35 +157,123 @@ id :%s
|
|||
version: %d
|
||||
`, action, recurs, project, id, version),
|
||||
},
|
||||
hasId: true,
|
||||
hasVersion: true,
|
||||
hasId: true,
|
||||
exp: &task.Task{
|
||||
Id: id,
|
||||
Version: version + 1,
|
||||
Folder: task.FOLDER_RECURRING,
|
||||
Version: version,
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Action: action,
|
||||
Project: project,
|
||||
Recur: task.Daily{Start: task.NewDate(2021, 6, 4)},
|
||||
Dirty: true,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
act := task.New(tc.message)
|
||||
act := task.NewFromMessage(tc.message)
|
||||
if !tc.hasId {
|
||||
test.Equals(t, false, "" == act.Id)
|
||||
tc.exp.Id = act.Id
|
||||
}
|
||||
if !tc.hasVersion {
|
||||
tc.exp.Version = 1
|
||||
}
|
||||
tc.exp.Message = tc.message
|
||||
tc.exp.Current = true
|
||||
test.Equals(t, tc.exp, act)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskTargetFolder(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tsk *task.Task
|
||||
expFolder string
|
||||
}{
|
||||
{
|
||||
name: "new",
|
||||
tsk: &task.Task{},
|
||||
expFolder: task.FOLDER_NEW,
|
||||
},
|
||||
{
|
||||
name: "recurring",
|
||||
tsk: &task.Task{
|
||||
Id: "id",
|
||||
Version: 2,
|
||||
Recur: task.Daily{Start: task.NewDate(2021, 06, 21)},
|
||||
},
|
||||
expFolder: task.FOLDER_RECURRING,
|
||||
},
|
||||
{
|
||||
name: "planned",
|
||||
tsk: &task.Task{
|
||||
Id: "id",
|
||||
Version: 2,
|
||||
Due: task.NewDate(2021, 06, 21),
|
||||
},
|
||||
expFolder: task.FOLDER_PLANNED,
|
||||
},
|
||||
{
|
||||
name: "unplanned",
|
||||
tsk: &task.Task{
|
||||
Id: "id",
|
||||
Version: 2,
|
||||
},
|
||||
expFolder: task.FOLDER_UNPLANNED,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
test.Equals(t, tc.tsk.TargetFolder(), tc.expFolder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskNextMessage(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
tsk *task.Task
|
||||
expMessage *mstore.Message
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
tsk: &task.Task{},
|
||||
expMessage: &mstore.Message{
|
||||
Folder: task.FOLDER_NEW,
|
||||
Subject: "",
|
||||
Body: `
|
||||
action:
|
||||
due: no date
|
||||
project:
|
||||
version: 1
|
||||
id:
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal",
|
||||
tsk: &task.Task{
|
||||
Id: "id",
|
||||
Version: 3,
|
||||
Folder: task.FOLDER_INBOX,
|
||||
Action: "action",
|
||||
Project: "project",
|
||||
Due: task.NewDate(2021, 06, 22),
|
||||
},
|
||||
expMessage: &mstore.Message{
|
||||
Folder: task.FOLDER_PLANNED,
|
||||
Subject: "2021-06-22 (tuesday) - project - action",
|
||||
Body: `
|
||||
action: action
|
||||
due: 2021-06-22 (tuesday)
|
||||
project: project
|
||||
version: 4
|
||||
id: id
|
||||
`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
test.Equals(t, tc.expMessage, tc.tsk.NextMessage())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatSubject(t *testing.T) {
|
||||
action := "an action"
|
||||
project := " a project"
|
||||
|
|
Loading…
Reference in New Issue