package gime import ( "errors" "sort" "strconv" "time" "github.com/br0xen/boltease" ) // GetLastTimeEntry pulls the time entry with the most recent stop time func (gdb *GimeDB) GetLatestTimeEntry() (*TimeEntry, error) { var ret *TimeEntry var err error var useDb *boltease.DB if useDb, err = gdb.openDBType(TypeRecent); err != nil { 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 // 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) findTimeEntryAndTypeByUUID(uuid string) (*TimeEntry, int, error) { 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 } } } return nil, TypeAll, errors.New("Unable to find time entry with uuid " + uuid) } // 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 } // 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 } return gdb.SaveTimeEntryType(tp, te) } func (gdb *GimeDB) SaveTimeEntryType(tp int, te *TimeEntry) error { var err error var useDb *boltease.DB useDb, err = gdb.openDBType(tp) if err != nil { return err } tePath := []string{TypeToString(tp), te.start.Format(time.RFC3339)} if err = useDb.SetValue(tePath, "uuid", te.uuid); err != nil { return err } if err = useDb.SetTimestamp(tePath, "start", te.start); err != nil { return err } if err = useDb.SetTimestamp(tePath, "end", te.end); err != nil { return err } for i := 0; i < te.tags.Length(); i++ { err = useDb.SetValue(append(tePath, "tags"), strconv.Itoa(i), te.tags.Get(i)) } 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 func (gdb *GimeDB) ArchiveTimeEntryByUUID(uuid string) error { archTime, tp, err := gdb.findTimeEntryAndTypeByUUID(uuid) if tp != TypeRecent { return errors.New("Couldn't find timer to archive in the 'Recent' bucket") } if err = gdb.RemoveTimeEntry(archTime); err != nil { return err } return gdb.SaveTimeEntryType(TypeArchive, archTime) } // 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") } if err = gdb.RemoveTimeEntry(te); err != nil { return err } return gdb.SaveTimeEntry(te) } // RemoveTimeEntry removes a time entry with the given uuid from the database 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) if err != nil { return errors.New("Unable to find time entry with uuid " + uuid) } var useDb *boltease.DB if useDb, err = gdb.openDBType(tp); err != nil { return err } defer gdb.closeDBType(tp) if err != nil { return err } return useDb.DeleteBucket([]string{TypeToString(tp)}, fndEntry.start.Format(time.RFC3339)) } // 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 if sttimes, err = gdb.db.GetBucketList([]string{TypeToString(TypeArchive)}); err != nil { return ret } for i := len(sttimes) - 1; i > 0; i-- { } } return ret } // dbGetAllTimeEntries gets all time entries of a specific type // tp can be: // TypeCurrent = "current" // TypeRecent = "recent" // TypeArchive = "archive" // TypeAll = "unknown" - Returns _ALL_ entries // Getting all archived time entries has the potential to be a lot of data func (gdb *GimeDB) dbGetAllTimeEntries(tp int) []TimeEntry { var ret []TimeEntry if tp == TypeAll { for _, v := range gdb.AllTypes { ret = append(ret, gdb.dbGetAllTimeEntries(v)...) } return ret } else if tp == TypeNoArchive { ret = append(ret, gdb.dbGetAllTimeEntries(TypeCurrent)...) ret = append(ret, gdb.dbGetAllTimeEntries(TypeRecent)...) return ret } var useDb *boltease.DB var err error if useDb, err = gdb.openDBType(tp); err != nil { return ret } defer gdb.closeDBType(tp) var sttimes []string if sttimes, err = useDb.GetBucketList([]string{TypeToString(tp)}); err != nil { return ret } sort.Slice(sttimes, func(i, j int) bool { return sttimes[j] < sttimes[i] }) for _, v := range sttimes { if te, _ := gdb.dbGetTimeEntry(tp, v); te != nil { ret = append(ret, *te) } } 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 // from the db and returns it. func (gdb *GimeDB) dbGetTimeEntry(tp int, sttm string) (*TimeEntry, error) { var ret *TimeEntry var err error if tp == TypeAll { // Loop through the types and return the first entry found that matches for _, v := range gdb.AllTypes { ret, _ = gdb.dbGetTimeEntry(v, sttm) if ret != nil { return ret, nil } } return nil, errors.New("Couldn't find time entry") } var useDb *boltease.DB if useDb, err = gdb.openDBType(tp); err != nil { return ret, err } defer gdb.closeDBType(tp) entryPath := []string{TypeToString(tp), sttm} ret = new(TimeEntry) if ret.uuid, err = useDb.GetValue(entryPath, "uuid"); err != nil { return nil, errors.New("Unable to read entry uuid") } 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{} } ret.tags = new(TagCollection) for i := range keys { var val string if val, err = useDb.GetValue(entryTagsPath, keys[i]); err == nil { ret.tags.Push(val) } } return ret, nil }