Several improvements

Fix 'switch' issue
Archive by tag
Much faster archiving
This commit is contained in:
Brian Buller 2018-04-12 14:31:39 -05:00
parent f0c8bcb408
commit dbd838e77f
6 changed files with 228 additions and 94 deletions

2
Godeps/Godeps.json generated
View File

@ -5,7 +5,7 @@
"Deps": [ "Deps": [
{ {
"ImportPath": "git.bullercodeworks.com/brian/gime-lib", "ImportPath": "git.bullercodeworks.com/brian/gime-lib",
"Rev": "579fc6c62aa8f5452e02d3fe3334e2d035b0b712" "Rev": "2a2aea8641e8b327544452f900ea4f5fcc43fc7e"
}, },
{ {
"ImportPath": "github.com/BurntSushi/toml", "ImportPath": "github.com/BurntSushi/toml",

View File

@ -145,6 +145,20 @@ func findIdOfTimer(tmr *gime.TimeEntry) (int, error) {
return -1, errors.New("Unable to find timer") return -1, errors.New("Unable to find timer")
} }
// pullTagsFromArgs takes a list of arguments, removes all tags from them
// then returns the tags and the remaining args
func pullTagsFromArgs(args []string) ([]string, []string) {
var tags, rem []string
for _, opt := range args {
if opt[0] == '+' {
tags = append(tags, opt[1:])
} else {
rem = append(rem, opt)
}
}
return tags, rem
}
func parseFuzzyTime(t string) (time.Time, error) { func parseFuzzyTime(t string) (time.Time, error) {
var ret time.Time var ret time.Time
var err error var err error
@ -167,6 +181,22 @@ func parseFuzzyTime(t string) (time.Time, error) {
return time.Time{}, errors.New("Unable to parse time: " + t) return time.Time{}, errors.New("Unable to parse time: " + t)
} }
func friendlyFormatForTime(t time.Time) string {
nowTime := time.Now()
if t.Year() != nowTime.Year() || t.Month() != nowTime.Month() {
return "2006-01-02 15:04"
} else if t.Day() != nowTime.Day() {
return "01/02 15:04"
}
return "15:04"
}
// timeToFriendlyString returns an easier to read version of the time
// giving enough details that the user should be fine inferring the rest
func timeToFriendlyString(t time.Time) string {
return t.Format(friendlyFormatForTime(t))
}
func sinceToString(tm time.Time) string { func sinceToString(tm time.Time) string {
return diffToString(tm, time.Now()) return diffToString(tm, time.Now())
} }

View File

@ -244,10 +244,11 @@ func initialize() {
validOperations["list"] = []string{ validOperations["list"] = []string{
"list [duration] [+tags] - List time entries", "list [duration] [+tags] - List time entries",
" valid values of [duration] include:", " valid values of [duration] include:",
" day - List all entries for the current day", " :day - List all entries for the current day",
" week - List all entries for the current week", " :week - List all entries for the current week",
" month - List all entries for the current month", " :month - List all entries for the current month",
" year - List all entries for the current year", " :year - List all entries for the current year",
" Or other date values, we'll try to parse it.",
" To list entries by tag, preceed the tags with a +", " To list entries by tag, preceed the tags with a +",
} }

View File

@ -9,6 +9,39 @@ import (
"git.bullercodeworks.com/brian/gime-lib" "git.bullercodeworks.com/brian/gime-lib"
) )
// cmdStartTimer takes a list of arguments and returns the return code
// to be passed along to os.Exit
func cmdStartTimer(args []string) int {
var err error
var entry *gime.TimeEntry
// By default we start the timer now
tm := time.Now()
tags, rem := pullTagsFromArgs(args)
if len(rem) > 0 {
// Check if the first argument looks like a date/time
tm, err = parseFuzzyTime(rem[0])
}
tc := new(gime.TagCollection)
for i := range tags {
tc.Push(tags[i])
}
if entry, err = gime.CreateTimeEntry(tm, time.Time{}, tc); err != nil {
fmt.Println(err)
return 1
}
if err = gdb.SaveTimeEntry(entry); err != nil {
fmt.Println(err)
return 1
}
fmt.Println(" " + TimerToString(entry))
return 0
}
func cmdContinueTimer(args []string) int { func cmdContinueTimer(args []string) int {
// Get the last running timer and start a new one with the same tags // Get the last running timer and start a new one with the same tags
te, err := getMostRecentTimeEntry() te, err := getMostRecentTimeEntry()
@ -19,7 +52,7 @@ func cmdContinueTimer(args []string) int {
tagColl := te.GetTags() tagColl := te.GetTags()
var tags []string var tags []string
for i := 0; i < tagColl.Length(); i++ { for i := 0; i < tagColl.Length(); i++ {
tags = append(tags, tagColl.Get(i)) tags = append(tags, "+"+tagColl.Get(i))
} }
args = append(args, tags...) args = append(args, tags...)
return cmdStartTimer(args) return cmdStartTimer(args)
@ -28,73 +61,40 @@ func cmdContinueTimer(args []string) int {
// switchTimer performs a stop on any currently running timers // switchTimer performs a stop on any currently running timers
// and starts a new timer with the given arguments // and starts a new timer with the given arguments
func cmdSwitchTimer(args []string) int { func cmdSwitchTimer(args []string) int {
loadActiveTimeEntries() var foundId bool
tm := time.Now() for i := range args {
if timeEntries.Length() > 0 { // see if we have a timer id in the args
fmt.Println("Stopped Timers:") if args[i][0] == '@' {
foundId = true
} }
for i := 0; i < timeEntries.Length(); i++ { }
tmr := timeEntries.Get(i) stopArgs := make([]string, len(args))
tmr.SetEnd(tm) copy(stopArgs, args)
if err := gdb.UpdateTimeEntry(tmr); err != nil { if !foundId {
fmt.Println(err.Error()) // We didn't find one, so make sure we stop _all_ timers
stopArgs = append(stopArgs, "@all")
}
fmt.Println(stopArgs)
fmt.Println(args)
return 0
if cmdStopTimer(args) != 0 {
// Error while stopping timers
return 1 return 1
} }
fmt.Println(" " + TimerToString(tmr))
}
fmt.Println("Started Timer:") fmt.Println("Started Timer:")
return cmdStartTimer(args) return cmdStartTimer(args)
} }
// cmdStartTimer takes a list of arguments and returns the return code
// to be passed along to os.Exit
func cmdStartTimer(args []string) int {
var err error
var tm time.Time
tagStart := 0
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = parseFuzzyTime(args[0])
}
if len(args) == 0 || err != nil {
// Just start it now
tm = time.Now()
} else {
tagStart = 1
}
var entry *gime.TimeEntry
var timerArgs []string
if tagStart < len(args) {
timerArgs = args[tagStart:]
}
tc := new(gime.TagCollection)
for i := range timerArgs {
tc.Push(timerArgs[i])
}
if entry, err = gime.CreateTimeEntry(tm, time.Time{}, tc); err != nil {
fmt.Println(err)
return 1
}
if err = gdb.SaveTimeEntry(entry); err != nil {
fmt.Println(err)
return 1
}
fmt.Println(" " + TimerToString(entry))
return 0
}
// cmdStopTimer takes parameters that describe which times to stop // cmdStopTimer takes parameters that describe which times to stop
func cmdStopTimer(args []string) int { func cmdStopTimer(args []string) int {
// args[0] should either be a timer id (starting with '@') or 'all' // args[0] should be a timer id (starting with '@')
var err error var err error
tm := time.Now() tm := time.Now()
actTimers := gdb.LoadTimeEntryCollection(gime.TypeCurrent) actTimers := gdb.LoadTimeEntryCollection(gime.TypeCurrent)
var tmr *gime.TimeEntry var tmr *gime.TimeEntry
stopId := "@0" // By default, stop the first timer stopId := "@0" // By default, stop the first timer
for i := range args { for i := range args {
if args[i] == "all" || args[i][0] == '@' { if args[i][0] == '@' {
stopId = args[i] stopId = args[i]
continue continue
} }
@ -105,7 +105,7 @@ func cmdStopTimer(args []string) int {
continue continue
} }
} }
if stopId != "all" { if stopId != "@all" {
// Find the timer that we're stopping // Find the timer that we're stopping
timerId, err := strconv.Atoi(stopId[1:]) timerId, err := strconv.Atoi(stopId[1:])
if err != nil { if err != nil {
@ -128,7 +128,7 @@ func cmdStopTimer(args []string) int {
fmt.Println("Stopped:", InferTimerDetailString(tmr)) fmt.Println("Stopped:", InferTimerDetailString(tmr))
return 0 return 0
} }
if stopId == "all" { if stopId == "@all" {
var ret int var ret int
for i := 0; i < actTimers.Length(); i++ { for i := 0; i < actTimers.Length(); i++ {
ret += stopTimer(actTimers.Get(i), tm) ret += stopTimer(actTimers.Get(i), tm)
@ -161,7 +161,7 @@ func cmdDeleteTimer(args []string) int {
fmt.Println(err.Error()) fmt.Println(err.Error())
return 1 return 1
} }
if gdb.RemoveTimeEntry(tmr.GetUUID()) != nil { if gdb.RemoveTimeEntry(tmr) != nil {
fmt.Println("Error removing entry " + gime.TypeToString(tp) + "." + tmr.GetUUID()) fmt.Println("Error removing entry " + gime.TypeToString(tp) + "." + tmr.GetUUID())
return 1 return 1
} }
@ -179,12 +179,35 @@ func cmdPrintList(args []string) int {
var tmpBeg, tmpEnd time.Time var tmpBeg, tmpEnd time.Time
// Check for command modifiers // Check for command modifiers
if strings.HasPrefix(opt, ":") { if strings.HasPrefix(opt, ":") {
if opt == ":ids" { switch opt {
case ":ids":
showIds = true showIds = true
// Special durations
case ":day":
beg, _ = parseFuzzyTime("00:00")
end, _ = parseFuzzyTime("23:59")
case ":week":
currDoW := time.Now().Weekday()
beg = time.Now().AddDate(0, 0, int(currDoW)*-1)
beg = time.Date(beg.Year(), beg.Month(), beg.Day(), 0, 0, 0, 0, beg.Location())
case ":month":
currDoM := time.Now().Day()
beg = time.Now().AddDate(0, 0, int(currDoM)*-1)
beg = time.Date(beg.Year(), beg.Month(), beg.Day(), 0, 0, 0, 0, beg.Location())
case ":year":
yr := strconv.Itoa(time.Now().Year())
beg, _ = parseFuzzyTime(yr + "0101T00:00")
end, _ = parseFuzzyTime(yr + "1231T23:59")
} }
continue continue
} }
// Find tags
if strings.HasPrefix(opt, "+") {
searchTags = append(searchTags, opt[1:])
continue
}
// Do our best to figure out what timers the user wants to list // Do our best to figure out what timers the user wants to list
var err error var err error
if strings.Contains(opt, "-") { if strings.Contains(opt, "-") {
@ -194,9 +217,6 @@ func cmdPrintList(args []string) int {
// This should be the starting date // This should be the starting date
tmpBeg, err = parseFuzzyTime(pts[0]) tmpBeg, err = parseFuzzyTime(pts[0])
if err != nil { if err != nil {
// We couldn't parse it as a time,
// Probably this is just a tag
searchTags = append(searchTags, opt)
continue continue
} }
} }
@ -204,13 +224,9 @@ func cmdPrintList(args []string) int {
// This should be the ending date // This should be the ending date
tmpEnd, err = parseFuzzyTime(pts[1]) tmpEnd, err = parseFuzzyTime(pts[1])
if err != nil { if err != nil {
searchTags = append(searchTags, opt)
continue continue
} }
} }
} else {
// Tag filters
searchTags = append(searchTags, opt)
} }
if !tmpBeg.IsZero() || !tmpEnd.IsZero() { if !tmpBeg.IsZero() || !tmpEnd.IsZero() {
beg, end = tmpBeg, tmpEnd beg, end = tmpBeg, tmpEnd
@ -248,10 +264,19 @@ func cmdPrintList(args []string) int {
dayStr := "" dayStr := ""
timers := filterTimerCollection(timeEntries, compoundFilter) timers := filterTimerCollection(timeEntries, compoundFilter)
_ = dayStr
var str string var str string
var currId int var currId int
var err error var err error
if timers.Length() == 0 {
begFmt := friendlyFormatForTime(beg)
endFmt := friendlyFormatForTime(end)
useFmt := endFmt
if len(begFmt) > len(endFmt) {
useFmt = begFmt
}
fmt.Println("No timers found in period " + beg.Format(useFmt) + " - " + end.Format(useFmt))
return 0
}
for i := 0; i < timers.Length(); i++ { for i := 0; i < timers.Length(); i++ {
wrk := timers.Get(i) wrk := timers.Get(i)
oldDayStr := dayStr oldDayStr := dayStr
@ -283,19 +308,49 @@ func cmdDoArchive(args []string) int {
fmt.Println("Nothing to do") fmt.Println("Nothing to do")
return 1 return 1
} }
bef, err := parseFuzzyTime(args[0])
if err != nil { var tags []string
fmt.Println("Error parsing time") tags, args = pullTagsFromArgs(args)
var bef time.Time
var err error
if len(args) > 0 {
bef, err = parseFuzzyTime(args[0])
}
if bef.IsZero() && len(tags) == 0 {
fmt.Println("Couldn't figure out what to archive")
return 1 return 1
} }
ret := 0 ret := 0
fmt.Print("Archive all timers before ", bef)
loadActiveAndRecentTimeEntries() loadActiveAndRecentTimeEntries()
tagFilter := func(t *gime.TimeEntry) bool {
for i := range tags {
if !t.HasTag(tags[i]) {
return false
}
}
return true
}
if len(tags) > 0 {
timeEntries = filterTimerCollection(timeEntries, tagFilter)
}
fmt.Print("Archive all timers ")
if !bef.IsZero() {
fmt.Print("before ", bef, " ")
}
if len(tags) > 0 {
fmt.Print("with tags ", tags)
}
for i := 0; i < timeEntries.Length(); i++ { for i := 0; i < timeEntries.Length(); i++ {
tst := timeEntries.Get(i) tst := timeEntries.Get(i)
if tst.GetEnd().Before(bef) { archIt := false
if !bef.IsZero() {
archIt = tst.GetEnd().Before(bef)
} else {
archIt = true
}
if archIt {
fmt.Print(".") fmt.Print(".")
if err = gdb.ArchiveTimeEntry(tst.GetUUID()); err != nil { if err = gdb.ArchiveTimeEntry(tst); err != nil {
fmt.Print("Error archiving entry (", tst.GetUUID(), ")", err.Error()) fmt.Print("Error archiving entry (", tst.GetUUID(), ")", err.Error())
ret = 1 ret = 1
} }

View File

@ -25,6 +25,7 @@ const (
TypeNoArchive = 3 // 011 TypeNoArchive = 3 // 011
TypeArchive = 4 // 100 TypeArchive = 4 // 100
TypeAll = 7 // 111 TypeAll = 7 // 111
TypeError = 8 //1000
ArchiveDays = time.Hour * 24 * 90 // Archive anything older than 90 days ArchiveDays = time.Hour * 24 * 90 // Archive anything older than 90 days
) )

View File

@ -13,21 +13,25 @@ import (
func (gdb *GimeDB) GetLatestTimeEntry() (*TimeEntry, error) { func (gdb *GimeDB) GetLatestTimeEntry() (*TimeEntry, error) {
var ret *TimeEntry var ret *TimeEntry
var err error var err error
tc := gdb.LoadTimeEntryCollection(TypeRecent)
for i := 0; i < tc.Length(); i++ { var useDb *boltease.DB
te := tc.Get(i) if useDb, err = gdb.openDBType(TypeRecent); err != nil {
if ret == nil {
ret = te
} else {
if te.GetEnd().After(ret.GetEnd()) {
ret = te
}
}
}
if ret == nil {
err = errors.New("Could not find latest time entry")
}
return ret, err return ret, err
}
defer gdb.closeDBType(TypeRecent)
var sttimes []string
if sttimes, err = useDb.GetBucketList([]string{TypeToString(TypeRecent)}); err != nil {
return ret, err
}
sort.Slice(sttimes, func(i, j int) bool {
return sttimes[j] < sttimes[i]
})
// The first entry should be the most recent
if len(sttimes) > 0 {
return gdb.dbGetTimeEntry(TypeRecent, sttimes[0])
}
return nil, errors.New("No recent time entries found")
} }
// findTimeEntryAndTypeByUUID searches all entries // findTimeEntryAndTypeByUUID searches all entries
@ -87,13 +91,20 @@ func (gdb *GimeDB) SaveTimeEntryType(tp int, te *TimeEntry) error {
return nil return nil
} }
func (gdb *GimeDB) ArchiveTimeEntry(te *TimeEntry) error {
if err := gdb.RemoveTimeEntryFromCategory(te, TypeRecent); err != nil {
return err
}
return gdb.SaveTimeEntryType(TypeArchive, te)
}
// ArchiveTimeEntry takes a time from TypeRecent and moves it to TypeArchive // ArchiveTimeEntry takes a time from TypeRecent and moves it to TypeArchive
func (gdb *GimeDB) ArchiveTimeEntry(uuid string) error { func (gdb *GimeDB) ArchiveTimeEntryByUUID(uuid string) error {
archTime, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid) archTime, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid)
if tp != TypeRecent { if tp != TypeRecent {
return errors.New("Couldn't find timer to archive in the 'Recent' bucket") return errors.New("Couldn't find timer to archive in the 'Recent' bucket")
} }
if err = gdb.RemoveTimeEntry(archTime.uuid); err != nil { if err = gdb.RemoveTimeEntry(archTime); err != nil {
return err return err
} }
return gdb.SaveTimeEntryType(TypeArchive, archTime) return gdb.SaveTimeEntryType(TypeArchive, archTime)
@ -106,14 +117,36 @@ func (gdb *GimeDB) UpdateTimeEntry(te *TimeEntry) error {
if te.uuid == "" { if te.uuid == "" {
return errors.New("Given time entry has no uuid") return errors.New("Given time entry has no uuid")
} }
if err = gdb.RemoveTimeEntry(te.uuid); err != nil { if err = gdb.RemoveTimeEntry(te); err != nil {
return err return err
} }
return gdb.SaveTimeEntry(te) return gdb.SaveTimeEntry(te)
} }
// RemoveTimeEntry removes a time entry with the given uuid from the database // RemoveTimeEntry removes a time entry with the given uuid from the database
func (gdb *GimeDB) RemoveTimeEntry(uuid string) error { func (gdb *GimeDB) RemoveTimeEntry(te *TimeEntry) error {
for _, v := range gdb.AllTypes {
if gdb.RemoveTimeEntryFromCategory(te, v) == nil {
return nil
}
}
return errors.New("Couldn't find Time Entry to Remove")
}
func (gdb *GimeDB) RemoveTimeEntryFromCategory(te *TimeEntry, tp int) error {
if !gdb.dbTimeEntryIsInCategory(te, tp) {
return errors.New("Couldn't find timer to remove in the given bucket")
}
var err error
var useDb *boltease.DB
if useDb, err = gdb.openDBType(tp); err != nil {
return err
}
defer gdb.closeDBType(tp)
return useDb.DeleteBucket([]string{TypeToString(tp)}, te.start.Format(time.RFC3339))
}
func (gdb *GimeDB) RemoveTimeEntryByUUID(uuid string) error {
fndEntry, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid) fndEntry, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid)
if err != nil { if err != nil {
return errors.New("Unable to find time entry with uuid " + uuid) return errors.New("Unable to find time entry with uuid " + uuid)
@ -193,6 +226,20 @@ func (gdb *GimeDB) dbGetAllTimeEntries(tp int) []TimeEntry {
return ret return ret
} }
func (gdb *GimeDB) dbFindTimeEntryCategory(te *TimeEntry) int {
for _, v := range gdb.AllTypes {
if gdb.dbTimeEntryIsInCategory(te, v) {
return v
}
}
return TypeError
}
func (gdb *GimeDB) dbTimeEntryIsInCategory(te *TimeEntry, tp int) bool {
_, err := gdb.dbGetTimeEntry(tp, te.start.Format(time.RFC3339))
return err == nil
}
// dbGetTimeEntry pulls a time entry of type tp with the given start time // dbGetTimeEntry pulls a time entry of type tp with the given start time
// from the db and returns it. // from the db and returns it.
func (gdb *GimeDB) dbGetTimeEntry(tp int, sttm string) (*TimeEntry, error) { func (gdb *GimeDB) dbGetTimeEntry(tp int, sttm string) (*TimeEntry, error) {