Better Resource Management

This commit is contained in:
Brian Buller 2019-02-26 13:22:37 -06:00
parent 427468c6a8
commit dbf94a42a2
6 changed files with 190 additions and 22 deletions

View File

@ -20,10 +20,17 @@ type AppState struct {
ValidOperations map[string][]string ValidOperations map[string][]string
OpFuncs map[string]func([]string) int OpFuncs map[string]func([]string) int
mode ResourceId
TaskList *todotxt.TaskList TaskList *todotxt.TaskList
DoneList *todotxt.TaskList DoneList *todotxt.TaskList
taskListLoaded bool
doneListLoaded bool
screens []Screen screens []Screen
lang *Translator
} }
func NewApp() *AppState { func NewApp() *AppState {
@ -41,8 +48,10 @@ func NewApp() *AppState {
func (a *AppState) run(parms []string) int { func (a *AppState) run(parms []string) int {
if len(parms) == 0 || parms[0] == "ui" { if len(parms) == 0 || parms[0] == "ui" {
// UI Mode // UI Mode
a.mode = ResModeUI
return uiLoop() return uiLoop()
} }
a.mode = ResModeCLI
if fn, ok := a.OpFuncs[parms[0]]; ok { if fn, ok := a.OpFuncs[parms[0]]; ok {
return fn(parms[1:]) return fn(parms[1:])
} }
@ -117,6 +126,8 @@ func (a *AppState) migrate(from, to int) int {
} }
func (a *AppState) initialize() { func (a *AppState) initialize() {
a.initLanguage()
var err error var err error
a.config, err = userConfig.NewConfig(a.Name) a.config, err = userConfig.NewConfig(a.Name)
if err != nil { if err != nil {

View File

@ -7,7 +7,36 @@ import (
todotxt "github.com/br0xen/go-todotxt" todotxt "github.com/br0xen/go-todotxt"
) )
// diskListChanged returns true if the task list in todo.txt
// is different than what we have previously loaded.
func (a *AppState) diskListChanged() bool {
if !a.taskListLoaded {
return false
}
curr, err := todotxt.LoadFromFilename(a.getTodoFile())
if err != nil {
return false
}
return curr.String() != a.TaskList.String()
}
// diskDoneListChanged returns true if the task list in done.txt
// is different than what we have previously loaded.
func (a *AppState) diskDoneListChanged() bool {
if !a.doneListLoaded {
return false
}
curr, err := todotxt.LoadFromFilename(a.getDoneFile())
if err != nil {
return false
}
return curr.String() != a.DoneList.String()
}
func (a *AppState) addTask(taskString string) error { func (a *AppState) addTask(taskString string) error {
if a.diskListChanged() {
return a.e(ResStrListChanged)
}
t, err := todotxt.ParseTask(taskString) t, err := todotxt.ParseTask(taskString)
if err != nil { if err != nil {
return err return err
@ -20,6 +49,9 @@ func (a *AppState) addTask(taskString string) error {
} }
func (a *AppState) toggleTaskComplete(id int) error { func (a *AppState) toggleTaskComplete(id int) error {
if a.diskListChanged() {
return a.e(ResStrListChanged)
}
var task *todotxt.Task var task *todotxt.Task
var err error var err error
if task, err = a.TaskList.GetTask(id); err != nil { if task, err = a.TaskList.GetTask(id); err != nil {
@ -34,12 +66,15 @@ func (a *AppState) toggleTaskComplete(id int) error {
} }
func (a *AppState) archiveTask(id int) error { func (a *AppState) archiveTask(id int) error {
if a.diskListChanged() {
return a.e(ResStrListChanged)
}
var err error var err error
var task *todotxt.Task var task *todotxt.Task
if task, err = a.TaskList.GetTask(id); err != nil { if task, err = a.TaskList.GetTask(id); err != nil {
return err return err
} }
if err := a.TaskList.ArchiveTaskToFile(*task, app.getDoneFile()); err != nil { if err := a.TaskList.ArchiveTaskToFile(*task, a.getDoneFile()); err != nil {
return err return err
} }
a.TaskList.RemoveTask(*task) a.TaskList.RemoveTask(*task)
@ -47,6 +82,9 @@ func (a *AppState) archiveTask(id int) error {
} }
func (a *AppState) unarchiveTask(id int) error { func (a *AppState) unarchiveTask(id int) error {
if a.diskListChanged() {
return a.e(ResStrListChanged)
}
var err error var err error
var task *todotxt.Task var task *todotxt.Task
if task, err = a.DoneList.GetTask(id); err != nil { if task, err = a.DoneList.GetTask(id); err != nil {
@ -112,6 +150,7 @@ func (a *AppState) LoadTaskList() error {
var tl todotxt.TaskList var tl todotxt.TaskList
tl, err = todotxt.LoadFromFilename(a.getTodoFile()) tl, err = todotxt.LoadFromFilename(a.getTodoFile())
a.TaskList = &tl a.TaskList = &tl
a.taskListLoaded = true
return err return err
} }
@ -120,13 +159,20 @@ func (a *AppState) LoadDoneList() error {
var tl todotxt.TaskList var tl todotxt.TaskList
tl, err = todotxt.LoadFromFilename(a.getDoneFile()) tl, err = todotxt.LoadFromFilename(a.getDoneFile())
a.DoneList = &tl a.DoneList = &tl
a.doneListLoaded = true
return err return err
} }
func (a *AppState) WriteList() error { func (a *AppState) WriteList() error {
if !a.taskListLoaded {
return a.e(ResStrTaskListNotLoaded)
}
return a.TaskList.WriteToFilename(a.getTodoFile()) return a.TaskList.WriteToFilename(a.getTodoFile())
} }
func (a *AppState) WriteDoneList() error { func (a *AppState) WriteDoneList() error {
if !a.doneListLoaded {
return a.e(ResStrDoneListNotLoaded)
}
return a.DoneList.WriteToFilename(a.getDoneFile()) return a.DoneList.WriteToFilename(a.getDoneFile())
} }

79
resources.go Normal file
View File

@ -0,0 +1,79 @@
package main
import "errors"
type ResourceId uint16
type Translator struct {
values map[ResourceId]string
}
func NewTranslator() *Translator {
return &Translator{
values: make(map[ResourceId]string),
}
}
func (t *Translator) addString(res ResourceId, val string) {
t.values[res] = val
if _, ok := t.values[(res & 255)]; !ok {
t.values[(res & 255)] = val
}
}
func (t *Translator) getString(res ResourceId) (string, error) {
if v, ok := t.values[res]; ok {
return v, nil
}
// Couldn't pull the exact resource, try the basic one
if v, ok := t.values[(res & 255)]; ok {
return v, nil
}
return "", errors.New("String Resource Not Found")
}
const (
ResStrListChanged ResourceId = iota
ResStrTaskListNotLoaded
ResStrDoneListNotLoaded
ResStrInvalidRefreshRequest
ResModeCLI ResourceId = 1 << (iota + 9)
ResModeUI
)
func (a *AppState) initLanguage() {
a.lang = NewTranslator()
// Strings that are the same regardless of mode
a.lang.addString((ResStrTaskListNotLoaded), "Task list hasn't been loaded")
a.lang.addString((ResStrDoneListNotLoaded), "Done list hasn't been loaded")
// CLI Strings
a.lang.addString((ResStrListChanged | ResModeCLI), "List changed somewhere else")
// UI Strings
a.lang.addString((ResStrListChanged | ResModeUI), "List changed somewhere else, reload and try again (Ctrl+R)")
}
// s returns a string with the given stringId
// (per the constants above)
func (a *AppState) s(stringId ResourceId) string {
if stringId&255 != stringId {
// the attribute already has extended data in it.
if v, err := a.lang.getString(stringId); err == nil {
return v
}
return ""
}
if v, err := a.lang.getString((stringId | a.mode)); err == nil {
return v
}
return ""
}
// e is basically the same as 's', but returns
// the string in an error object
func (a *AppState) e(stringId ResourceId) error {
return errors.New(a.s(stringId))
}

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"time"
termbox "github.com/nsf/termbox-go" termbox "github.com/nsf/termbox-go"
) )
@ -49,15 +51,16 @@ func readUserInput(e chan termbox.Event) {
} }
} }
func refreshList(e chan termbox.Event) { func checkForUpdate(e chan termbox.Event) {
/* for {
for { time.Sleep(time.Minute)
time.Sleep(5 * time.Minute) if app.diskListChanged() {
app.LoadTasklist() e <- termbox.Event{
app.LoadDoneList() Type: termbox.EventError,
e <- termbox.Event{Type: termbox.EventNone} Err: app.e(ResStrListChanged),
}
} }
*/ }
} }
/* /*

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -16,6 +17,8 @@ type MainScreen struct {
message string message string
messageTimeout time.Duration messageTimeout time.Duration
messageTime time.Time messageTime time.Time
messageColorBg termbox.Attribute
messageColorFg termbox.Attribute
mode int mode int
cursor map[string]int cursor map[string]int
@ -64,6 +67,17 @@ func (screen *MainScreen) initialize(bundle Bundle) error {
return nil return nil
} }
func (screen *MainScreen) refreshList(bundle Bundle) error {
whichList := bundle.getString(MainBundleListKey, MainBundleListTodo)
switch whichList {
case MainBundleListTodo:
return app.LoadTaskList()
case MainBundleListDone:
return app.LoadDoneList()
}
return errors.New("Invalid refresh request.")
}
func (screen *MainScreen) reloadList(bundle Bundle) error { func (screen *MainScreen) reloadList(bundle Bundle) error {
// We add tasks to the display list using append because we want to persist task Ids // We add tasks to the display list using append because we want to persist task Ids
screen.displayList = todotxt.NewTaskList() screen.displayList = todotxt.NewTaskList()
@ -121,7 +135,7 @@ func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
// Go to About Screen // Go to About Screen
b := Bundle{} b := Bundle{}
if err := app.screens[ScreenAbout].initialize(b); err != nil { if err := app.screens[ScreenAbout].initialize(b); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
} }
return ScreenAbout return ScreenAbout
} else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { } else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 {
@ -149,7 +163,9 @@ func (screen *MainScreen) handleKeyEvent(event termbox.Event) int {
screen.cursor[screen.currentList] = len(*screen.displayList) - 1 screen.cursor[screen.currentList] = len(*screen.displayList) - 1
} else if event.Key == termbox.KeyCtrlR { } else if event.Key == termbox.KeyCtrlR {
screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter)) b := screen.buildBundle(screen.currentList, screen.currentFilter)
screen.refreshList(b)
screen.reloadList(b)
} else if event.Key == termbox.KeyCtrlF { } else if event.Key == termbox.KeyCtrlF {
// Jump forward half a screen // Jump forward half a screen
@ -217,7 +233,7 @@ func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int {
// Create the new item // Create the new item
err := app.addTask(screen.inputField.GetValue()) err := app.addTask(screen.inputField.GetValue())
if err != nil { if err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
} }
screen.inputField.SetID("") screen.inputField.SetID("")
screen.inputField.SetValue("") screen.inputField.SetValue("")
@ -330,7 +346,7 @@ func (screen *MainScreen) drawFooter() {
screen.inputField.Draw() screen.inputField.Draw()
} }
// And the 'message' // And the 'message'
termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, DefaultFg, DefaultBg) termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, screen.messageColorFg, screen.messageColorBg)
} }
func (screen *MainScreen) confirmArchiveItem() int { func (screen *MainScreen) confirmArchiveItem() int {
@ -353,20 +369,20 @@ func (screen *MainScreen) confirmArchiveItem() int {
func (screen *MainScreen) archiveCurrentItem() int { func (screen *MainScreen) archiveCurrentItem() int {
if screen.currentList != MainBundleListTodo { if screen.currentList != MainBundleListTodo {
screen.setMessage("Task is already archived") screen.setErrorMessage("Task is already archived")
return ScreenMain return ScreenMain
} }
// Find the task under the cursor // Find the task under the cursor
if len(*screen.displayList) > screen.cursor[screen.currentList] { if len(*screen.displayList) > screen.cursor[screen.currentList] {
t := (*screen.displayList)[screen.cursor[screen.currentList]] t := (*screen.displayList)[screen.cursor[screen.currentList]]
if err := app.archiveTask(t.Id); err != nil { if err := app.archiveTask(t.Id); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
return ScreenMain return ScreenMain
} }
// Reload the list // Reload the list
b := screen.buildBundle(screen.currentList, screen.currentFilter) b := screen.buildBundle(screen.currentList, screen.currentFilter)
if err := screen.reloadList(b); err != nil { if err := screen.reloadList(b); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
} }
} }
return ScreenMain return ScreenMain
@ -374,20 +390,20 @@ func (screen *MainScreen) archiveCurrentItem() int {
func (screen *MainScreen) unarchiveCurrentItem() int { func (screen *MainScreen) unarchiveCurrentItem() int {
if screen.currentList == MainBundleListTodo { if screen.currentList == MainBundleListTodo {
screen.setMessage("Task is not archived") screen.setErrorMessage("Task is not archived")
return ScreenMain return ScreenMain
} }
// Find the task under the cursor // Find the task under the cursor
if len(*screen.displayList) > screen.cursor[screen.currentList] { if len(*screen.displayList) > screen.cursor[screen.currentList] {
t := (*screen.displayList)[screen.cursor[screen.currentList]] t := (*screen.displayList)[screen.cursor[screen.currentList]]
if err := app.unarchiveTask(t.Id); err != nil { if err := app.unarchiveTask(t.Id); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
return ScreenMain return ScreenMain
} }
// Reload the list // Reload the list
b := screen.buildBundle(screen.currentList, screen.currentFilter) b := screen.buildBundle(screen.currentList, screen.currentFilter)
if err := screen.reloadList(b); err != nil { if err := screen.reloadList(b); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
} }
} }
return ScreenMain return ScreenMain
@ -401,7 +417,7 @@ func (screen *MainScreen) startEditTaskScreen() int {
b := Bundle{} b := Bundle{}
b.setValue(TaskBundleTaskIdKey, t.Id) b.setValue(TaskBundleTaskIdKey, t.Id)
if err := app.screens[ScreenTask].initialize(b); err != nil { if err := app.screens[ScreenTask].initialize(b); err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
return ScreenMain return ScreenMain
} }
return ScreenTask return ScreenTask
@ -436,7 +452,7 @@ func (screen *MainScreen) startAddNewTask() int {
func (screen *MainScreen) toggleTaskComplete() int { func (screen *MainScreen) toggleTaskComplete() int {
if screen.currentList == MainBundleListDone { if screen.currentList == MainBundleListDone {
screen.setMessage("Task is archived, unable to modify.") screen.setErrorMessage("Task is archived, unable to modify.")
return ScreenMain return ScreenMain
} }
@ -445,7 +461,7 @@ func (screen *MainScreen) toggleTaskComplete() int {
t := (*screen.displayList)[screen.cursor[screen.currentList]] t := (*screen.displayList)[screen.cursor[screen.currentList]]
err := app.toggleTaskComplete(t.Id) err := app.toggleTaskComplete(t.Id)
if err != nil { if err != nil {
screen.setMessage(err.Error()) screen.setErrorMessage(err.Error())
return ScreenMain return ScreenMain
} }
} }
@ -476,10 +492,20 @@ func (screen *MainScreen) startFilter() int {
return ScreenMain return ScreenMain
} }
func (screen *MainScreen) setErrorMessage(msg string) {
screen.message = " " + msg + " "
screen.messageTime = time.Now()
screen.messageTimeout = time.Second * 2
screen.messageColorBg = termbox.ColorRed
screen.messageColorFg = termbox.ColorWhite | termbox.AttrBold
}
func (screen *MainScreen) setMessage(msg string) { func (screen *MainScreen) setMessage(msg string) {
screen.message = msg screen.message = msg
screen.messageTime = time.Now() screen.messageTime = time.Now()
screen.messageTimeout = time.Second * 2 screen.messageTimeout = time.Second * 2
screen.messageColorBg = DefaultBg
screen.messageColorFg = DefaultFg
} }
/* setMessageWithTimeout lets you specify the timeout for the message /* setMessageWithTimeout lets you specify the timeout for the message
@ -494,6 +520,8 @@ func (screen *MainScreen) setMessageWithTimeout(msg string, timeout time.Duratio
func (screen *MainScreen) clearMessage() { func (screen *MainScreen) clearMessage() {
screen.message = fmt.Sprintf("%d Total Tasks", len(*screen.activeList)) screen.message = fmt.Sprintf("%d Total Tasks", len(*screen.activeList))
screen.messageTimeout = -1 screen.messageTimeout = -1
screen.messageColorBg = DefaultBg
screen.messageColorFg = DefaultFg
} }
func (screen *MainScreen) buildBundle(list, filter string) Bundle { func (screen *MainScreen) buildBundle(list, filter string) Bundle {

View File

@ -25,6 +25,7 @@ func uiLoop() int {
app.layoutAndDrawScreen(displayScreen) app.layoutAndDrawScreen(displayScreen)
eventChan := make(chan termbox.Event) eventChan := make(chan termbox.Event)
go readUserInput(eventChan) go readUserInput(eventChan)
go checkForUpdate(eventChan)
for { for {
event := <-eventChan event := <-eventChan
if event.Type == termbox.EventKey { if event.Type == termbox.EventKey {