2017-07-28 11:44:08 +00:00
|
|
|
package gime
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2018-01-18 15:54:12 +00:00
|
|
|
"sort"
|
2017-07-28 11:44:08 +00:00
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/br0xen/boltease"
|
|
|
|
)
|
|
|
|
|
2018-01-18 15:54:12 +00:00
|
|
|
// GetLastTimeEntry pulls the time entry with the most recent stop time
|
|
|
|
func (gdb *GimeDB) GetLatestTimeEntry() (*TimeEntry, error) {
|
|
|
|
var ret *TimeEntry
|
|
|
|
var err error
|
2018-04-12 19:30:52 +00:00
|
|
|
|
|
|
|
var useDb *boltease.DB
|
|
|
|
if useDb, err = gdb.openDBType(TypeRecent); err != nil {
|
|
|
|
return ret, err
|
2018-01-18 15:54:12 +00:00
|
|
|
}
|
2018-04-12 19:30:52 +00:00
|
|
|
defer gdb.closeDBType(TypeRecent)
|
|
|
|
|
|
|
|
var sttimes []string
|
|
|
|
if sttimes, err = useDb.GetBucketList([]string{TypeToString(TypeRecent)}); err != nil {
|
|
|
|
return ret, err
|
2018-01-18 15:54:12 +00:00
|
|
|
}
|
2018-04-12 19:30:52 +00:00
|
|
|
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")
|
2018-01-18 15:54:12 +00:00
|
|
|
}
|
|
|
|
|
2018-02-15 13:41:18 +00:00
|
|
|
// findTimeEntryAndTypeByUUID searches all entries
|
2017-08-01 20:39:24 +00:00
|
|
|
// for the time entry with the given uuid, return the TimeEntry, the type, and/or and error
|
|
|
|
// Types are searched TypeCurrent -> TypeRecent -> TypeArchive
|
2018-02-15 13:41:18 +00:00
|
|
|
func (gdb *GimeDB) findTimeEntryAndTypeByUUID(uuid string) (*TimeEntry, int, error) {
|
2017-08-01 20:39:24 +00:00
|
|
|
for i := range gdb.AllTypes {
|
|
|
|
timeCollection := gdb.LoadTimeEntryCollection(gdb.AllTypes[i])
|
|
|
|
for j := 0; j < timeCollection.Length(); j++ {
|
|
|
|
if timeCollection.Get(j).GetUUID() == uuid {
|
|
|
|
return timeCollection.Get(j), gdb.AllTypes[i], nil
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-11 16:52:44 +00:00
|
|
|
return nil, TypeAll, errors.New("Unable to find time entry with uuid " + uuid)
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
|
2018-02-15 13:41:18 +00:00
|
|
|
// FindTimeEntryByUUID searches all entries
|
|
|
|
// for the time entry with the given uuid, return the TimeEntry, the type, and/or and error
|
|
|
|
// Types are searched TypeCurrent -> TypeRecent -> TypeArchive
|
|
|
|
func (gdb *GimeDB) FindTimeEntryByUUID(uuid string) (*TimeEntry, error) {
|
|
|
|
te, _, err := gdb.findTimeEntryAndTypeByUUID(uuid)
|
|
|
|
return te, err
|
|
|
|
}
|
|
|
|
|
2017-07-28 11:44:08 +00:00
|
|
|
// SaveTimeEntry creates a time entry in the database
|
|
|
|
// If TimeEntry.end is zero, then it puts it in TypeCurrent
|
|
|
|
func (gdb *GimeDB) SaveTimeEntry(te *TimeEntry) error {
|
|
|
|
tp := TypeRecent
|
|
|
|
if te.end.IsZero() {
|
|
|
|
// Currently running
|
|
|
|
tp = TypeCurrent
|
|
|
|
}
|
2018-01-18 22:45:30 +00:00
|
|
|
return gdb.SaveTimeEntryType(tp, te)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (gdb *GimeDB) SaveTimeEntryType(tp int, te *TimeEntry) error {
|
|
|
|
var err error
|
|
|
|
var useDb *boltease.DB
|
2017-08-01 20:39:24 +00:00
|
|
|
useDb, err = gdb.openDBType(tp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-11 16:52:44 +00:00
|
|
|
tePath := []string{TypeToString(tp), te.start.Format(time.RFC3339)}
|
2017-07-31 12:00:16 +00:00
|
|
|
if err = useDb.SetValue(tePath, "uuid", te.uuid); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-28 11:44:08 +00:00
|
|
|
if err = useDb.SetTimestamp(tePath, "start", te.start); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = useDb.SetTimestamp(tePath, "end", te.end); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-31 12:00:16 +00:00
|
|
|
for i := 0; i < te.tags.Length(); i++ {
|
|
|
|
err = useDb.SetValue(append(tePath, "tags"), strconv.Itoa(i), te.tags.Get(i))
|
2017-07-28 11:44:08 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-04-12 19:30:52 +00:00
|
|
|
func (gdb *GimeDB) ArchiveTimeEntry(te *TimeEntry) error {
|
|
|
|
if err := gdb.RemoveTimeEntryFromCategory(te, TypeRecent); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return gdb.SaveTimeEntryType(TypeArchive, te)
|
|
|
|
}
|
|
|
|
|
2018-01-18 22:45:30 +00:00
|
|
|
// ArchiveTimeEntry takes a time from TypeRecent and moves it to TypeArchive
|
2018-04-12 19:30:52 +00:00
|
|
|
func (gdb *GimeDB) ArchiveTimeEntryByUUID(uuid string) error {
|
2018-02-15 13:41:18 +00:00
|
|
|
archTime, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid)
|
2018-01-18 22:45:30 +00:00
|
|
|
if tp != TypeRecent {
|
|
|
|
return errors.New("Couldn't find timer to archive in the 'Recent' bucket")
|
|
|
|
}
|
2018-04-12 19:30:52 +00:00
|
|
|
if err = gdb.RemoveTimeEntry(archTime); err != nil {
|
2018-01-18 22:45:30 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return gdb.SaveTimeEntryType(TypeArchive, archTime)
|
|
|
|
}
|
|
|
|
|
2017-07-31 12:00:16 +00:00
|
|
|
// UpdateTimeEntry updates the time entry in the database
|
|
|
|
// It first finds the current entry in the database by the uuid
|
|
|
|
func (gdb *GimeDB) UpdateTimeEntry(te *TimeEntry) error {
|
|
|
|
var err error
|
|
|
|
if te.uuid == "" {
|
|
|
|
return errors.New("Given time entry has no uuid")
|
|
|
|
}
|
2018-04-12 19:30:52 +00:00
|
|
|
if err = gdb.RemoveTimeEntry(te); err != nil {
|
2017-07-31 12:00:16 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-08-01 20:39:24 +00:00
|
|
|
return gdb.SaveTimeEntry(te)
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
|
2017-08-01 20:39:24 +00:00
|
|
|
// RemoveTimeEntry removes a time entry with the given uuid from the database
|
2018-04-12 19:30:52 +00:00
|
|
|
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 {
|
2018-02-15 13:41:18 +00:00
|
|
|
fndEntry, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid)
|
2017-08-01 20:39:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.New("Unable to find time entry with uuid " + uuid)
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var useDb *boltease.DB
|
|
|
|
if useDb, err = gdb.openDBType(tp); err != nil {
|
2017-08-01 20:39:24 +00:00
|
|
|
return err
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
defer gdb.closeDBType(tp)
|
2017-08-01 20:39:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-11 16:52:44 +00:00
|
|
|
return useDb.DeleteBucket([]string{TypeToString(tp)}, fndEntry.start.Format(time.RFC3339))
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
|
2017-08-02 19:14:53 +00:00
|
|
|
// GetTimeEntriesInRange takes two times and returns all time entries that occur
|
|
|
|
// on or between those dates
|
|
|
|
func (gdb *GimeDB) GetTimeEntriesInRange(st time.Time, end time.Time) *TimeEntryCollection {
|
|
|
|
var err error
|
|
|
|
ret := new(TimeEntryCollection)
|
|
|
|
if time.Since(st) > ArchiveDays {
|
|
|
|
if err = gdb.openArchiveDatabase(); err != nil {
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
defer gdb.closeArchiveDatabase()
|
|
|
|
var sttimes []string
|
2018-01-11 16:52:44 +00:00
|
|
|
if sttimes, err = gdb.db.GetBucketList([]string{TypeToString(TypeArchive)}); err != nil {
|
2017-08-02 19:14:53 +00:00
|
|
|
return ret
|
|
|
|
}
|
|
|
|
for i := len(sttimes) - 1; i > 0; i-- {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2017-07-28 11:44:08 +00:00
|
|
|
// dbGetAllTimeEntries gets all time entries of a specific type
|
|
|
|
// tp can be:
|
|
|
|
// TypeCurrent = "current"
|
|
|
|
// TypeRecent = "recent"
|
|
|
|
// TypeArchive = "archive"
|
2018-01-11 16:52:44 +00:00
|
|
|
// TypeAll = "unknown" - Returns _ALL_ entries
|
2017-07-28 11:44:08 +00:00
|
|
|
// Getting all archived time entries has the potential to be a lot of data
|
2018-01-11 16:52:44 +00:00
|
|
|
func (gdb *GimeDB) dbGetAllTimeEntries(tp int) []TimeEntry {
|
2017-07-28 11:44:08 +00:00
|
|
|
var ret []TimeEntry
|
2018-01-11 16:52:44 +00:00
|
|
|
if tp == TypeAll {
|
2017-08-01 20:39:24 +00:00
|
|
|
for _, v := range gdb.AllTypes {
|
|
|
|
ret = append(ret, gdb.dbGetAllTimeEntries(v)...)
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
|
|
|
return ret
|
2018-01-11 16:52:44 +00:00
|
|
|
} else if tp == TypeNoArchive {
|
|
|
|
ret = append(ret, gdb.dbGetAllTimeEntries(TypeCurrent)...)
|
|
|
|
ret = append(ret, gdb.dbGetAllTimeEntries(TypeRecent)...)
|
|
|
|
return ret
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
2018-01-11 16:52:44 +00:00
|
|
|
|
2017-07-28 11:44:08 +00:00
|
|
|
var useDb *boltease.DB
|
|
|
|
var err error
|
|
|
|
if useDb, err = gdb.openDBType(tp); err != nil {
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
defer gdb.closeDBType(tp)
|
|
|
|
|
2017-07-31 12:00:16 +00:00
|
|
|
var sttimes []string
|
2018-01-11 16:52:44 +00:00
|
|
|
if sttimes, err = useDb.GetBucketList([]string{TypeToString(tp)}); err != nil {
|
2017-07-28 11:44:08 +00:00
|
|
|
return ret
|
|
|
|
}
|
2018-01-18 15:54:12 +00:00
|
|
|
sort.Slice(sttimes, func(i, j int) bool {
|
|
|
|
return sttimes[j] < sttimes[i]
|
|
|
|
})
|
2017-07-31 12:00:16 +00:00
|
|
|
for _, v := range sttimes {
|
2017-07-28 11:44:08 +00:00
|
|
|
if te, _ := gdb.dbGetTimeEntry(tp, v); te != nil {
|
|
|
|
ret = append(ret, *te)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2018-04-12 19:30:52 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-08-02 19:14:53 +00:00
|
|
|
// dbGetTimeEntry pulls a time entry of type tp with the given start time
|
2017-07-28 11:44:08 +00:00
|
|
|
// from the db and returns it.
|
2018-01-11 16:52:44 +00:00
|
|
|
func (gdb *GimeDB) dbGetTimeEntry(tp int, sttm string) (*TimeEntry, error) {
|
2017-07-28 11:44:08 +00:00
|
|
|
var ret *TimeEntry
|
|
|
|
var err error
|
2018-01-11 16:52:44 +00:00
|
|
|
if tp == TypeAll {
|
2017-07-31 12:00:16 +00:00
|
|
|
// Loop through the types and return the first entry found that matches
|
2017-08-01 20:39:24 +00:00
|
|
|
for _, v := range gdb.AllTypes {
|
2017-07-31 12:00:16 +00:00
|
|
|
ret, _ = gdb.dbGetTimeEntry(v, sttm)
|
|
|
|
if ret != nil {
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
}
|
2017-08-01 20:39:24 +00:00
|
|
|
return nil, errors.New("Couldn't find time entry")
|
2017-07-31 12:00:16 +00:00
|
|
|
}
|
2017-07-28 11:44:08 +00:00
|
|
|
var useDb *boltease.DB
|
|
|
|
if useDb, err = gdb.openDBType(tp); err != nil {
|
|
|
|
return ret, err
|
|
|
|
}
|
|
|
|
defer gdb.closeDBType(tp)
|
|
|
|
|
2018-01-11 16:52:44 +00:00
|
|
|
entryPath := []string{TypeToString(tp), sttm}
|
2017-07-28 11:44:08 +00:00
|
|
|
ret = new(TimeEntry)
|
2017-07-31 12:00:16 +00:00
|
|
|
if ret.uuid, err = useDb.GetValue(entryPath, "uuid"); err != nil {
|
|
|
|
return nil, errors.New("Unable to read entry uuid")
|
|
|
|
}
|
2017-07-28 11:44:08 +00:00
|
|
|
if ret.start, err = useDb.GetTimestamp(entryPath, "start"); err != nil {
|
|
|
|
return nil, errors.New("Unable to read entry start time")
|
|
|
|
}
|
|
|
|
if ret.end, err = useDb.GetTimestamp(entryPath, "end"); err != nil {
|
|
|
|
return nil, errors.New("Unable to read entry end time")
|
|
|
|
}
|
|
|
|
var keys []string
|
|
|
|
entryTagsPath := append(entryPath, "tags")
|
|
|
|
if keys, err = useDb.GetKeyList(entryTagsPath); err != nil {
|
|
|
|
keys = []string{}
|
|
|
|
}
|
2017-07-31 12:00:16 +00:00
|
|
|
ret.tags = new(TagCollection)
|
2017-07-28 11:44:08 +00:00
|
|
|
for i := range keys {
|
|
|
|
var val string
|
2017-07-31 12:00:16 +00:00
|
|
|
if val, err = useDb.GetValue(entryTagsPath, keys[i]); err == nil {
|
|
|
|
ret.tags.Push(val)
|
2017-07-28 11:44:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|