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
|
||||
*.db
|
||||
|
@ -1,4 +1,4 @@
|
||||
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 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
130
model.go
@ -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
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
|
||||
// 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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user