Start/Stop is working

This commit is contained in:
Brian Buller 2017-08-01 15:39:24 -05:00
parent 4fd15f2c2b
commit 0127427d18
6 changed files with 395 additions and 137 deletions

53
cmd/gime/helpers.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"errors"
"strconv"
"git.bullercodeworks.com/brian/gime"
)
func TimerToString(t *gime.TimeEntry) string {
var ret string
if t.StartsToday() {
ret = t.GetStart().Format("15:04 - ")
} else {
ret = t.GetStart().Format("2006/01/02 15:04:05 - ")
}
if !t.GetEnd().IsZero() {
if t.EndsToday() {
ret += t.GetEnd().Format("15:04")
} else {
ret += t.GetEnd().Format("2006/01/02 15:04:05")
}
} else {
ret += "**:**:**"
}
tags := t.GetTags()
if tags.Length() > 0 {
ret += " [ "
for i := 0; i < tags.Length(); i++ {
ret += tags.Get(i) + " "
}
ret += "]"
}
return ret
}
// findTimerById takes a timer id and returns the TimeEntry and the type string
// of the entry corresponding to that id
// It searches TypeCurrent -> TypeRecent -> TypeArchive
func findTimerById(tmrId int) (*gime.TimeEntry, string, error) {
// Find the timer for this tmrId
var prevNum, numLoaded int
for i := range gdb.AllTypes {
timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i])
numLoaded += timeCollection.Length()
if numLoaded > tmrId {
// This is the set that it's in, the index we need is tmrId - prevNum
return timeCollection.Get(tmrId - prevNum), gdb.AllTypes[i], nil
}
prevNum = numLoaded
}
return nil, gime.TypeUnknown, errors.New("Unable to find timer with id: " + strconv.Itoa(tmrId))
}

View File

@ -8,16 +8,20 @@ import (
"time" "time"
"git.bullercodeworks.com/brian/gime" "git.bullercodeworks.com/brian/gime"
userConfig "github.com/br0xen/user-config"
) )
const ( const (
AppName = "gime" AppName = "gime"
AppVersion = 1 AppVersion = 1
DefDBName = "gime.db"
DefArchDBName = "gimearch.db"
) )
var validOperations map[string][]string var validOperations map[string][]string
var activeTimeEntries *gime.TimeEntryCollection var activeTimeEntries *gime.TimeEntryCollection
var gdb *gime.GimeDB var gdb *gime.GimeDB
var cfg *userConfig.Config
func main() { func main() {
var ret int var ret int
@ -33,14 +37,20 @@ func main() {
} }
switch parms[0] { switch parms[0] {
case "config":
ret = cmdDoConfig(parms[1:])
case "delete", "remove":
ret = cmdDeleteTimer(parms[1:])
case "help": case "help":
ret = printHelp() ret = cmdPrintHelp()
case "status": case "status":
ret = printStatus() ret = cmdPrintStatus()
case "start": case "start":
ret = startTimer(parms[1:]) ret = cmdStartTimer(parms[1:])
case "end", "stop": case "end", "stop":
ret = stopTimer(parms[1:]) ret = cmdStopTimer(parms[1:])
case "list", "ls":
ret = cmdPrintList(parms[1:])
default: default:
fmt.Println("Unknown command") fmt.Println("Unknown command")
ret = 1 ret = 1
@ -48,7 +58,50 @@ func main() {
os.Exit(ret) os.Exit(ret)
} }
func printHelp() int { func cmdDoConfig(args []string) int {
if len(args) == 0 {
fmt.Println("Invalid configuration options passed")
return 1
}
for _, opt := range args {
if strings.Index(opt, "=") == -1 {
// Single word triggers
switch opt {
case "reset":
fmt.Println("Resetting Configuration...")
cfg.Set("dbdir", cfg.GetConfigPath()+"/")
cfg.Set("dbname", DefDBName)
cfg.Set("dbarchname", DefArchDBName)
case "list":
fmt.Println("Current " + AppName + " config")
for _, v := range cfg.GetKeyList() {
fmt.Println(" " + v + ": " + cfg.Get(v))
}
}
} else {
// Key=Value options
pts := strings.Split(opt, "=")
if len(pts) == 2 {
switch pts[0] {
case "dbdir":
val := pts[1]
if val[len(val)-1] != '/' {
val = val + "/"
}
cfg.Set("dbdir", val)
case "dbname":
cfg.Set("dbname", pts[1])
case "dbarchname":
cfg.Set("dbarchname", pts[1])
}
}
}
}
return 0
}
func cmdPrintHelp() int {
fmt.Println("gime - A simple timekeeping application\n") fmt.Println("gime - A simple timekeeping application\n")
fmt.Println("Usage: gime [@timerID] [operation] [tags...]") fmt.Println("Usage: gime [@timerID] [operation] [tags...]")
for _, v := range validOperations { for _, v := range validOperations {
@ -64,14 +117,28 @@ func loadActiveTimeEntries() {
activeTimeEntries = gdb.LoadTimeEntryCollection(gime.TypeCurrent) activeTimeEntries = gdb.LoadTimeEntryCollection(gime.TypeCurrent)
} }
func printStatus() int { func cmdPrintList(args []string) int {
/*
var err error
loadActiveTimeEntries()
// By default, list all entries for today
currTime := time.Now()
dur := currTime.Hour()*time.Hour + currTime.Minute()*time.Minute
if len(args) < 1 {
}
*/
return 0
}
func cmdPrintStatus() int {
loadActiveTimeEntries() loadActiveTimeEntries()
curr := time.Now() curr := time.Now()
fmt.Println("Current Time:", curr.Format(time.Stamp)) fmt.Println("Current Time:", curr.Format(time.Stamp))
if activeTimeEntries.Length() == 0 { if activeTimeEntries.Length() == 0 {
fmt.Println("No timer running") fmt.Println("No timer running")
} else { } else {
fmt.Print("Currently Active Timers\n") fmt.Print("Active Timers (", activeTimeEntries.Length(), ")\n")
for i := 0; i < activeTimeEntries.Length(); i++ { for i := 0; i < activeTimeEntries.Length(); i++ {
v := activeTimeEntries.Get(i) v := activeTimeEntries.Get(i)
if v.GetStart().Day() == curr.Day() { if v.GetStart().Day() == curr.Day() {
@ -99,78 +166,44 @@ func printStatus() int {
return 0 return 0
} }
// switchTimer performs a stop on any currently running timers
// and starts a new timer with the given arguments
func switchTimer(args []string) int {
return 0
}
// startTimer takes a list of arguments and returns the return code
// to be passed along to os.Exit
func startTimer(args []string) int {
var err error
tm := time.Now()
tagStart := 0
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = time.Parse("15:04", args[0])
if err != nil {
tm, err = time.Parse(time.Kitchen, args[0])
}
}
if 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:]
}
fmt.Println(tm)
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
}
return 0
}
func stopTimer(args []string) int {
var err error
var tm time.Time
st := time.Now()
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = time.Parse("15:04", args[0])
if err != nil {
tm, err = time.Parse(time.Kitchen, args[0])
}
}
_, _ = tm, st
return 0
}
func initialize() { func initialize() {
var err error var err error
if gdb, err = gime.LoadDatabase("./", "gime.db", "gimearch.db"); err != nil {
fmt.Println("Error loading the database")
os.Exit(1)
}
validOperations = make(map[string][]string) validOperations = make(map[string][]string)
validOperations["config"] = []string{
"config [command] - Perform configuration",
" list - List current configuration",
" reset - Reset current configuration",
" Configuration Options:",
" dbdir=[database directory]",
" dbname=[database filename]",
" dbarchname=[archive database filename]",
}
validOperations["detail"] = []string{
"detail @id - Print details about a timer",
}
validOperations["delete"] = []string{
"delete @id - Delete a timer",
}
validOperations["end"] = []string{
"end - The same as stop",
}
validOperations["help"] = []string{
"help - Print this",
}
validOperations["list"] = []string{
"list [duration] [+tags] - List time entries",
" valid values of [duration] include:",
" day - List all entries for the current day",
" week - List all entries for the current week",
" month - List all entries for the current month",
" year - List all entries for the current year",
" To list entries by tag, preceed the tags with a +",
}
validOperations["ls"] = []string{
"ls [duration] [+tags] - The same as list",
}
validOperations["status"] = []string{ validOperations["status"] = []string{
"status - Print the current status of the timer", "status - Print the status of all active timers",
} }
validOperations["start"] = []string{ validOperations["start"] = []string{
"start [time] [tags ...] - Start a timer with the given tags (space separated)", "start [time] [tags ...] - Start a timer with the given tags (space separated)",
@ -183,11 +216,29 @@ func initialize() {
" If the first sub-argument given looks like a time,", " If the first sub-argument given looks like a time,",
" the timer will be stopped then (past or future).", " the timer will be stopped then (past or future).",
} }
validOperations["end"] = []string{
"end - The same as stop", // Load the Config
cfg, err = userConfig.NewConfig(AppName)
if err != nil {
fmt.Println(err.Error())
fmt.Println("Creating new config")
cfg.Save()
} }
validOperations["help"] = []string{ // If dbdir isn't set, set it to the config directory
"help - Print this", if cfg.Get("dbdir") == "" {
cfg.Set("dbdir", cfg.GetConfigPath()+"/")
}
// If dbname isn't set, set it to the default database filename
if cfg.Get("dbname") == "" {
cfg.Set("dbname", DefDBName)
}
// If dbarchivename isn't set, set it to the default archive database filename
if cfg.Get("dbarchname") == "" {
cfg.Set("dbarchname", DefArchDBName)
}
if gdb, err = gime.LoadDatabase(cfg.Get("dbdir"), cfg.Get("dbname"), cfg.Get("dbarchname")); err != nil {
fmt.Println("Error loading the database")
os.Exit(1)
} }
} }

View File

@ -0,0 +1,128 @@
package main
import (
"fmt"
"strconv"
"time"
"git.bullercodeworks.com/brian/gime"
)
// switchTimer performs a stop on any currently running timers
// and starts a new timer with the given arguments
func switchTimer(args []string) int {
loadActiveTimeEntries()
return 0
}
// 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
tm := time.Now()
tagStart := 0
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = time.Parse("15:04", args[0])
if err != nil {
tm, err = time.Parse(time.Kitchen, args[0])
}
}
if 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:]
}
fmt.Println(tm)
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
}
return 0
}
// cmdStopTimer takes parameters that describe which times to stop
func cmdStopTimer(args []string) int {
var err error
tm := time.Now()
actTimers := gdb.LoadTimeEntryCollection(gime.TypeCurrent)
if actTimers.Length() != 1 && (len(args) < 1 || args[0][0] != '@') {
fmt.Println("Couldn't determine which timer(s) to stop")
return 1
}
var tmr *gime.TimeEntry
if actTimers.Length() == 1 {
// only one timer running
tmr = actTimers.Get(0)
} else {
// We've got a timer id to delete
timerId, err := strconv.Atoi(args[0][1:])
if err != nil {
fmt.Println("Error parsing timer id: " + err.Error())
return 1
}
tmr, _, err = findTimerById(timerId)
if err != nil {
fmt.Println(err.Error())
return 1
}
}
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = time.Parse("15:04", args[0])
if err != nil {
tm, err = time.Parse(time.Kitchen, args[0])
}
}
tmr.SetEnd(tm)
if err = gdb.UpdateTimeEntry(tmr); err != nil {
fmt.Println(err.Error())
return 1
}
fmt.Println(TimerToString(tmr))
return 0
}
// cmdDeleteTimer takes parameters that describe the timers to be deleted.
func cmdDeleteTimer(args []string) int {
var err error
if len(args) < 1 || args[0][0] != '@' {
fmt.Println("Couldn't determine which timer(s) to delete")
return 1
}
// We've got a timer id to delete
timerId, err := strconv.Atoi(args[0][1:])
if err != nil {
fmt.Println("Error parsing timer id: " + err.Error())
return 1
}
tmr, tp, err := findTimerById(timerId)
if err != nil {
fmt.Println(err.Error())
return 1
}
if gdb.RemoveTimeEntry(tmr.GetUUID()) != nil {
fmt.Println("Error removing entry " + tp + "." + tmr.GetUUID())
return 1
}
fmt.Println("Deleted Time Entry: " + TimerToString(tmr))
return 0
}

View File

@ -2,6 +2,7 @@ package gime
import ( import (
"errors" "errors"
"fmt"
"github.com/br0xen/boltease" "github.com/br0xen/boltease"
) )
@ -13,6 +14,8 @@ type GimeDB struct {
dbArchOpened int dbArchOpened int
path string path string
filename, arch string filename, arch string
AllTypes []string
} }
const ( const (
@ -22,8 +25,6 @@ const (
TypeUnknown = "unknown" // Not really a type, used for searches and stuff TypeUnknown = "unknown" // Not really a type, used for searches and stuff
) )
const AllTypes = []string{TypeCurrent, TypeRecent, TypeArchive}
// Load Database returns a database loaded off the files given // Load Database returns a database loaded off the files given
// name and archName located in path // name and archName located in path
func LoadDatabase(path, name, archName string) (*GimeDB, error) { func LoadDatabase(path, name, archName string) (*GimeDB, error) {
@ -34,8 +35,12 @@ func LoadDatabase(path, name, archName string) (*GimeDB, error) {
path: path, path: path,
filename: name, filename: name,
arch: archName, arch: archName,
AllTypes: []string{TypeCurrent, TypeRecent, TypeArchive},
}
if err := gdb.initDatabase(); err != nil {
fmt.Println(err.Error())
return nil, err
} }
gdb.initDatabase()
return &gdb, nil return &gdb, nil
} }

View File

@ -8,28 +8,19 @@ import (
"github.com/br0xen/boltease" "github.com/br0xen/boltease"
) )
// FindTimeEntryByUUID searches all entries of type tp (can be TypeUnknown) // FindTimeEntryByUUID searches all entries
// for the time entry with the given uuid // for the time entry with the given uuid, return the TimeEntry, the type, and/or and error
func (gdb *GimeDB) FindTimeEntryByUUID(tp, uuid string) (*TimeEntry, error) { // Types are searched TypeCurrent -> TypeRecent -> TypeArchive
var ret *TimeEntry func (gdb *GimeDB) FindTimeEntryByUUID(uuid string) (*TimeEntry, string, error) {
var err error for i := range gdb.AllTypes {
if tp == TypeUnknown { timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i])
for _, v := range AllTypes { for j := 0; j < timeCollection.Length(); j++ {
ret, err = gdb.FindTimeEntryByUUID(v, uuid) if timeCollection.Get(j).GetUUID() == uuid {
if ret != nil { return timeCollection.Get(j), gdb.AllTypes[i], nil
return ret, nil
} }
} }
return nil, errors.New("Unable to find time entry with uuid " + uuid)
} }
tstEntries := gdb.dbGetAllTimeEntries(tp) return nil, TypeUnknown, errors.New("Unable to find time entry with uuid " + uuid)
for i := range tstEntries {
if tstEntries[i].uuid == uuid {
// Found it!
return tstEntries[i], nil
}
}
return nil, errors.New("Unable to find time entry with uuid " + uuid)
} }
// SaveTimeEntry creates a time entry in the database // SaveTimeEntry creates a time entry in the database
@ -41,10 +32,6 @@ func (gdb *GimeDB) SaveTimeEntry(te *TimeEntry) error {
if te.end.IsZero() { if te.end.IsZero() {
// Currently running // Currently running
tp = TypeCurrent tp = TypeCurrent
useDb, err = gdb.openDBType(tp)
if err != nil {
return err
}
} else { } else {
// We have an end time. Does this entry go in 'recent' or 'archive' // We have an end time. Does this entry go in 'recent' or 'archive'
// We shove times that happened 30 days ago into 'archive' // We shove times that happened 30 days ago into 'archive'
@ -52,6 +39,10 @@ func (gdb *GimeDB) SaveTimeEntry(te *TimeEntry) error {
tp = TypeArchive tp = TypeArchive
} }
} }
useDb, err = gdb.openDBType(tp)
if err != nil {
return err
}
tePath := []string{tp, te.start.Format(time.RFC3339)} tePath := []string{tp, te.start.Format(time.RFC3339)}
if err = useDb.SetValue(tePath, "uuid", te.uuid); err != nil { if err = useDb.SetValue(tePath, "uuid", te.uuid); err != nil {
return err return err
@ -75,40 +66,28 @@ 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")
} }
oldTe, err := gdb.FindTimeEntryByUUID(TypeUnknown, te.uuid) if err = gdb.RemoveTimeEntry(te.uuid); err != nil {
if err != nil {
return err return err
} }
return gdb.SaveTimeEntry(te)
} }
// RemoveTimeEntry removes a time entry of type tp (can be TypeUnknown) // RemoveTimeEntry removes a time entry with the given uuid from the database
// with the given uuid from the database func (gdb *GimeDB) RemoveTimeEntry(uuid string) error {
func (gdb *GimeDB) RemoveTimeEntry(tp, uuid string) error { fndEntry, tp, err := gdb.FindTimeEntryByUUID(uuid)
var fndEntry *TimeEntry if err != nil {
var err error
// For TypeUnknown, we want to manually loop, so we know where we found it
if tp == TypeUnknown {
for _, v := range AllTypes {
fndEntry, err = gdb.FindTimeEntryByUUID(v, uuid)
if err == nil {
tp = v
break
}
}
if fndEntry == nil {
return errors.New("Unable to find time entry with uuid " + uuid) return errors.New("Unable to find time entry with uuid " + uuid)
} }
}
var useDb *boltease.DB var useDb *boltease.DB
var err error
if useDb, err = gdb.openDBType(tp); err != nil { if useDb, err = gdb.openDBType(tp); err != nil {
return ret return err
} }
defer gdb.closeDBType(tp) defer gdb.closeDBType(tp)
if err != nil {
return nil, errors.New("Unable to find time entry with uuid " + uuid) return err
}
return useDb.DeleteBucket([]string{tp}, fndEntry.start.Format(time.RFC3339))
} }
// dbGetAllTimeEntries gets all time entries of a specific type // dbGetAllTimeEntries gets all time entries of a specific type
@ -121,8 +100,8 @@ func (gdb *GimeDB) RemoveTimeEntry(tp, uuid string) error {
func (gdb *GimeDB) dbGetAllTimeEntries(tp string) []TimeEntry { func (gdb *GimeDB) dbGetAllTimeEntries(tp string) []TimeEntry {
var ret []TimeEntry var ret []TimeEntry
if tp == TypeUnknown { if tp == TypeUnknown {
for _, v := range AllTypes { for _, v := range gdb.AllTypes {
ret = append(ret, gdb.dbGetAllTimeEntries(v)) ret = append(ret, gdb.dbGetAllTimeEntries(v)...)
} }
return ret return ret
} }
@ -152,13 +131,13 @@ func (gdb *GimeDB) dbGetTimeEntry(tp, sttm string) (*TimeEntry, error) {
var err error var err error
if tp == TypeUnknown { if tp == TypeUnknown {
// Loop through the types and return the first entry found that matches // Loop through the types and return the first entry found that matches
for _, v := range AllTypes { for _, v := range gdb.AllTypes {
ret, _ = gdb.dbGetTimeEntry(v, sttm) ret, _ = gdb.dbGetTimeEntry(v, sttm)
if ret != nil { if ret != nil {
return ret, nil return ret, nil
} }
} }
return nil, errors.Error("Couldn't find time entry") return nil, errors.New("Couldn't find time entry")
} }
var useDb *boltease.DB var useDb *boltease.DB
if useDb, err = gdb.openDBType(tp); err != nil { if useDb, err = gdb.openDBType(tp); err != nil {

View File

@ -37,6 +37,11 @@ func CreateTimeEntry(s, e time.Time, t *TagCollection) (*TimeEntry, error) {
return ret, nil return ret, nil
} }
// GetUUID returns the UUID for the entry
func (t *TimeEntry) GetUUID() string {
return t.uuid
}
// GetStart returns the start time for the entry // GetStart returns the start time for the entry
func (t *TimeEntry) GetStart() time.Time { func (t *TimeEntry) GetStart() time.Time {
return t.start return t.start
@ -95,3 +100,40 @@ func (t *TimeEntry) RemoveTag(s string) {
func (t *TimeEntry) Equals(tst *TimeEntry) bool { func (t *TimeEntry) Equals(tst *TimeEntry) bool {
return t.uuid == tst.uuid return t.uuid == tst.uuid
} }
func (t *TimeEntry) StartsToday() bool {
if time.Since(t.GetStart()).Hours() > 24 {
return false
}
return t.GetStart().Day() == time.Now().Day()
}
func (t *TimeEntry) EndsToday() bool {
if time.Since(t.GetEnd()).Hours() > 24 {
return false
}
return t.GetEnd().Day() == time.Now().Day()
}
func (t *TimeEntry) String() string {
var ret string
ret = t.GetStart().Format(time.RFC3339)
if !t.GetEnd().IsZero() {
ret += " - " + t.GetEnd().Format(time.RFC3339)
}
tags := t.GetTags()
if tags.Length() > 0 {
ret += " [ "
for i := 0; i < tags.Length(); i++ {
ret += tags.Get(i) + " "
}
ret += "]"
}
if t.GetEnd().IsZero() {
ret += " Running"
}
if t.GetUUID() != "" {
ret += " (" + t.GetUUID() + ")"
}
return ret
}