Working on it

This commit is contained in:
Brian Buller 2017-07-28 06:44:08 -05:00
parent 9e116d9187
commit 9347c2e4aa
7 changed files with 298 additions and 13 deletions

1
cmd/gime/.gitignore vendored
View File

@ -1 +1,2 @@
gime
*.db

View File

@ -1,4 +1,4 @@
gime
====
This folder contains the cli utility for gime
This folder contains the cli application for gime

View File

@ -15,10 +15,13 @@ const (
var validOperations map[string][]string
var activeTimeEntry *gime.TimeEntry
var gdb *gime.GimeDB
func main() {
var ret int
initialize()
var parms []string
if len(os.Args) > 1 {
parms = os.Args[1:]
parms[0] = matchParameter(parms[0])
@ -29,17 +32,21 @@ func main() {
switch parms[0] {
case "help":
printHelp()
ret = printHelp()
case "status":
printStatus()
ret = printStatus()
case "start":
startTimer(parms[1:])
ret = startTimer(parms[1:])
case "end", "stop":
stopTimer(parms[1:])
ret = stopTimer(parms[1:])
default:
fmt.Println("Unknown command")
ret = 1
}
os.Exit(ret)
}
func printHelp() {
func printHelp() int {
fmt.Println("gime - A simple timekeeping application\n")
for _, v := range validOperations {
for vi := range v {
@ -47,9 +54,10 @@ func printHelp() {
}
fmt.Println("")
}
return 0
}
func printStatus() {
func printStatus() int {
if activeTimeEntry == nil {
fmt.Println("No timer running")
} else {
@ -62,12 +70,19 @@ func printStatus() {
}
fmt.Println(time.Since(activeTimeEntry.GetStart()).String())
}
return 0
}
func startTimer(args []string) {
// 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
var tm time.Time
st := time.Now()
tm := time.Now()
curr := gdb.LoadTimeEntryCollection(gime.TypeCurrent)
if curr.Length() > 0 {
// TODO: Stop all Timers in curr
}
tagStart := 0
if len(args) > 0 {
// Check if the first argument looks like a date/time
tm, err = time.Parse("15:04", args[0])
@ -77,11 +92,29 @@ func startTimer(args []string) {
}
if err != nil {
// Just start it now
tm = time.Now()
} else {
tagStart = 1
}
_, _ = tm, st
var entry *gime.TimeEntry
var timerArgs []string
if tagStart < len(args) {
timerArgs = args[tagStart:]
}
fmt.Println(tm)
if entry, err = gime.CreateTimeEntry(tm, time.Time{}, timerArgs); 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) {
func stopTimer(args []string) int {
var err error
var tm time.Time
st := time.Now()
@ -94,9 +127,16 @@ func stopTimer(args []string) {
}
_, _ = tm, st
return 0
}
func initialize() {
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["status"] = []string{
"status - Print the current status of the timer",

130
model.go
View File

@ -1 +1,131 @@
package gime
import (
"errors"
"github.com/br0xen/boltease"
)
type GimeDB struct {
db *boltease.DB
archDb *boltease.DB
dbOpened int
dbArchOpened int
path string
filename, arch string
}
const (
TypeCurrent = "current"
TypeRecent = "recent"
TypeArchive = "archive"
)
// Load Database returns a database loaded off the files given
// name and archName located in path
func LoadDatabase(path, name, archName string) (*GimeDB, error) {
if path[len(path)-1] != '/' {
path = path + "/"
}
gdb := GimeDB{
path: path,
filename: name,
arch: archName,
}
gdb.initDatabase()
return &gdb, nil
}
func (gdb *GimeDB) openDatabase() error {
gdb.dbOpened += 1
if gdb.dbOpened == 1 {
var err error
gdb.db, err = boltease.Create(gdb.path+gdb.filename, 0600, nil)
if err != nil {
return err
}
}
return nil
}
func (gdb *GimeDB) closeDatabase() error {
gdb.dbOpened -= 1
if gdb.dbOpened == 0 {
return gdb.db.CloseDB()
}
return nil
}
func (gdb *GimeDB) openArchiveDatabase() error {
gdb.dbArchOpened += 1
if gdb.dbArchOpened == 1 {
var err error
gdb.archDb, err = boltease.Create(gdb.path+gdb.arch, 0600, nil)
if err != nil {
return err
}
}
return nil
}
func (gdb *GimeDB) closeArchiveDatabase() error {
gdb.dbArchOpened -= 1
if gdb.dbArchOpened == 0 {
return gdb.archDb.CloseDB()
}
return nil
}
func (gdb *GimeDB) initDatabase() error {
// Initialize the current/recent/settings database
var err error
if err = gdb.openDatabase(); err != nil {
return err
}
defer gdb.closeDatabase()
// Create the path to the bucket to store application settings
if err := gdb.db.MkBucketPath([]string{"settings"}); err != nil {
return err
}
// Create the path to the bucket to store the current time entry
if err := gdb.db.MkBucketPath([]string{TypeCurrent}); err != nil {
return err
}
// Create the path to the bucket to store recent time entries
if err := gdb.db.MkBucketPath([]string{TypeRecent}); err != nil {
return err
}
// Now initialize the Archive Database
if err = gdb.openArchiveDatabase(); err != nil {
return err
}
defer gdb.closeArchiveDatabase()
// Create the path to the bucket to store archived time entries
return gdb.archDb.MkBucketPath([]string{TypeArchive})
}
func (gdb *GimeDB) openDBType(tp string) (*boltease.DB, error) {
var err error
if tp == TypeCurrent || tp == TypeRecent {
if err = gdb.openDatabase(); err != nil {
return nil, err
}
return gdb.db, err
} else if tp == TypeArchive {
if err = gdb.openArchiveDatabase(); err != nil {
return nil, err
}
return gdb.archDb, err
}
return nil, errors.New("Invalid database type: " + tp)
}
func (gdb *GimeDB) closeDBType(tp string) error {
if tp == TypeCurrent || tp == TypeRecent {
return gdb.closeDatabase()
} else if tp == TypeArchive {
return gdb.closeArchiveDatabase()
}
return errors.New("Invalid database type: " + tp)
}

104
model_timeentry.go Normal file
View File

@ -0,0 +1,104 @@
package gime
import (
"errors"
"strconv"
"time"
"github.com/br0xen/boltease"
)
// 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 {
var err error
var useDb *boltease.DB
tp := TypeRecent
if te.end.IsZero() {
// Currently running
tp = TypeCurrent
useDb, err = gdb.openDBType(tp)
if err != nil {
return err
}
} else {
// We have an end time. Does this entry go in 'recent' or 'archive'
// We shove times that happened 30 days ago into 'archive'
if time.Since(te.end) > (time.Hour * 24 * 30) {
tp = TypeArchive
}
}
tePath := []string{tp, te.uuid}
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 := range te.tags {
err = useDb.SetValue(append(tePath, "tags"), strconv.Itoa(i), te.tags[i])
}
return nil
}
// dbGetAllTimeEntries gets all time entries of a specific type
// tp can be:
// TypeCurrent = "current"
// TypeRecent = "recent"
// TypeArchive = "archive"
// Getting all archived time entries has the potential to be a lot of data
func (gdb *GimeDB) dbGetAllTimeEntries(tp string) []TimeEntry {
var ret []TimeEntry
var useDb *boltease.DB
var err error
if useDb, err = gdb.openDBType(tp); err != nil {
return ret
}
defer gdb.closeDBType(tp)
var uuids []string
if uuids, err = useDb.GetBucketList([]string{tp}); err != nil {
return ret
}
for _, v := range uuids {
if te, _ := gdb.dbGetTimeEntry(tp, v); te != nil {
ret = append(ret, *te)
}
}
return ret
}
// dbGetTimeEntry pulls a time entry of type tp with the given id
// from the db and returns it.
func (gdb *GimeDB) dbGetTimeEntry(tp, id string) (*TimeEntry, error) {
var ret *TimeEntry
var err error
var useDb *boltease.DB
if useDb, err = gdb.openDBType(tp); err != nil {
return ret, err
}
defer gdb.closeDBType(tp)
entryPath := []string{tp, id}
ret = new(TimeEntry)
ret.uuid = id
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{}
}
for i := range keys {
var val string
if val, err = useDb.GetValue(entryTagsPath, keys[i]); err != nil {
ret.tags = append(ret.tags, val)
}
}
return ret, nil
}

View File

@ -19,7 +19,7 @@ type TimeEntry struct {
// An error is returned if no start time is given or if the end time given is
// non-zero and earlier than the start time.
func CreateTimeEntry(s, e time.Time, t []string) (*TimeEntry, error) {
var ret *TimeEntry
ret := new(TimeEntry)
ret.uuid = uuid.New()
if s.IsZero() {
// No start time given, return error

View File

@ -7,6 +7,16 @@ type TimeEntryCollection struct {
list []TimeEntry
}
// Load a TimeEntry collection from a database
func (gdb *GimeDB) LoadTimeEntryCollection(tp string) *TimeEntryCollection {
ret := new(TimeEntryCollection)
entries := gdb.dbGetAllTimeEntries(tp)
for i := range entries {
ret.Push(&entries[i])
}
return ret
}
// Length returns how many time entries are in the collection
func (tc *TimeEntryCollection) Length() int {
return len(tc.list)