Working on it
This commit is contained in:
parent
9e116d9187
commit
9347c2e4aa
1
cmd/gime/.gitignore
vendored
1
cmd/gime/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
gime
|
gime
|
||||||
|
*.db
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
gime
|
gime
|
||||||
====
|
====
|
||||||
|
|
||||||
This folder contains the cli utility for gime
|
This folder contains the cli application for gime
|
||||||
|
@ -15,10 +15,13 @@ const (
|
|||||||
|
|
||||||
var validOperations map[string][]string
|
var validOperations map[string][]string
|
||||||
var activeTimeEntry *gime.TimeEntry
|
var activeTimeEntry *gime.TimeEntry
|
||||||
|
var gdb *gime.GimeDB
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
var ret int
|
||||||
initialize()
|
initialize()
|
||||||
var parms []string
|
var parms []string
|
||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
parms = os.Args[1:]
|
parms = os.Args[1:]
|
||||||
parms[0] = matchParameter(parms[0])
|
parms[0] = matchParameter(parms[0])
|
||||||
@ -29,17 +32,21 @@ func main() {
|
|||||||
|
|
||||||
switch parms[0] {
|
switch parms[0] {
|
||||||
case "help":
|
case "help":
|
||||||
printHelp()
|
ret = printHelp()
|
||||||
case "status":
|
case "status":
|
||||||
printStatus()
|
ret = printStatus()
|
||||||
case "start":
|
case "start":
|
||||||
startTimer(parms[1:])
|
ret = startTimer(parms[1:])
|
||||||
case "end", "stop":
|
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")
|
fmt.Println("gime - A simple timekeeping application\n")
|
||||||
for _, v := range validOperations {
|
for _, v := range validOperations {
|
||||||
for vi := range v {
|
for vi := range v {
|
||||||
@ -47,9 +54,10 @@ func printHelp() {
|
|||||||
}
|
}
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func printStatus() {
|
func printStatus() int {
|
||||||
if activeTimeEntry == nil {
|
if activeTimeEntry == nil {
|
||||||
fmt.Println("No timer running")
|
fmt.Println("No timer running")
|
||||||
} else {
|
} else {
|
||||||
@ -62,12 +70,19 @@ func printStatus() {
|
|||||||
}
|
}
|
||||||
fmt.Println(time.Since(activeTimeEntry.GetStart()).String())
|
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 err error
|
||||||
var tm time.Time
|
tm := time.Now()
|
||||||
st := time.Now()
|
curr := gdb.LoadTimeEntryCollection(gime.TypeCurrent)
|
||||||
|
if curr.Length() > 0 {
|
||||||
|
// TODO: Stop all Timers in curr
|
||||||
|
}
|
||||||
|
tagStart := 0
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
// Check if the first argument looks like a date/time
|
// Check if the first argument looks like a date/time
|
||||||
tm, err = time.Parse("15:04", args[0])
|
tm, err = time.Parse("15:04", args[0])
|
||||||
@ -77,11 +92,29 @@ func startTimer(args []string) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Just start it now
|
// 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 err error
|
||||||
var tm time.Time
|
var tm time.Time
|
||||||
st := time.Now()
|
st := time.Now()
|
||||||
@ -94,9 +127,16 @@ func stopTimer(args []string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
_, _ = tm, st
|
_, _ = tm, st
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialize() {
|
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 = make(map[string][]string)
|
||||||
validOperations["status"] = []string{
|
validOperations["status"] = []string{
|
||||||
"status - Print the current status of the timer",
|
"status - Print the current status of the timer",
|
||||||
|
130
model.go
130
model.go
@ -1 +1,131 @@
|
|||||||
package gime
|
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
104
model_timeentry.go
Normal 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
|
||||||
|
}
|
@ -19,7 +19,7 @@ type TimeEntry struct {
|
|||||||
// An error is returned if no start time is given or if the end time given is
|
// 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.
|
// non-zero and earlier than the start time.
|
||||||
func CreateTimeEntry(s, e time.Time, t []string) (*TimeEntry, error) {
|
func CreateTimeEntry(s, e time.Time, t []string) (*TimeEntry, error) {
|
||||||
var ret *TimeEntry
|
ret := new(TimeEntry)
|
||||||
ret.uuid = uuid.New()
|
ret.uuid = uuid.New()
|
||||||
if s.IsZero() {
|
if s.IsZero() {
|
||||||
// No start time given, return error
|
// No start time given, return error
|
||||||
|
@ -7,6 +7,16 @@ type TimeEntryCollection struct {
|
|||||||
list []TimeEntry
|
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
|
// Length returns how many time entries are in the collection
|
||||||
func (tc *TimeEntryCollection) Length() int {
|
func (tc *TimeEntryCollection) Length() int {
|
||||||
return len(tc.list)
|
return len(tc.list)
|
||||||
|
Loading…
Reference in New Issue
Block a user