event-time-tracking/main.go

257 lines
6.0 KiB
Go

package main
import (
"bufio"
_ "embed"
"encoding/json"
"flag"
"github.com/rs/zerolog/log"
"net/http"
"os"
"sort"
"strconv"
"strings"
"text/template"
"time"
)
type Event struct {
Name string
EventTimestamp time.Time
}
var eventlog string
//go:embed webpage.tmpl
var templateContent []byte
//Current State
var lastEvent Event
var currentState map[string]map[string]int64 //Per Name per Day in Secounds
func main() {
eventlogPath := flag.String("eventlog", "/var/ett/eventlog", "Where to store the eventlog file")
flag.Parse()
eventlog = *eventlogPath
log.Info().Msg("Start Time Tracking Service")
currentState = make(map[string]map[string]int64)
defer func() {
e := Event{
Name: "Nothing",
EventTimestamp: time.Now(),
}
addEvent(e)
}()
readStateFromFile()
runHttpServer()
}
func runHttpServer() {
http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
var taglist []string
for s, _ := range currentState {
taglist = append(taglist, s)
}
sort.Strings(taglist)
var daylist []string
for i := 0; i < 7; i++ {
g := -1 * 24 * i
t := time.Now().Add(time.Duration(g) * time.Hour)
daylist = append(daylist, t.Format("02.01.06"))
}
fm := template.FuncMap{"tominute": func(a int64) int64 {
return a / 60
}}
week := map[string]interface{}{}
for tag, _ := range currentState {
var gi int64
for i := 0; i < 7; i++ {
g := -1 * 24 * i
t := time.Now().Add(time.Duration(g) * time.Hour)
secounds := currentState[tag][t.Format("02.01.06")]
gi = gi + secounds
}
gi = gi / 60
s := strconv.FormatInt(gi, 10)
week[tag] = s
}
templ := template.Must(template.New("page").Funcs(fm).Parse(string(templateContent)))
templ.Execute(w, map[string]interface{}{
"lastEvent": lastEvent,
"taglist": taglist,
"daylist": daylist,
"currentState": currentState,
"weeklist": week,
})
/*w.Write([]byte("<h1>Time Tracking</h1>"))
w.Write([]byte("Doing "+lastEvent.Name+" since "+lastEvent.EventTimestamp.Format(time.RFC3339)+"<br>"))
w.Write([]byte("<h3>Today</h3>"))
for tag, _ := range currentState {
i := currentState[tag][time.Now().Format("02.01.06")]
i = i / 60
s := strconv.FormatInt(i, 10)
line := tag+": "+s+"<br>"
w.Write([]byte(line))
}
w.Write([]byte("<h3>Last 7 Days</h3>"))
for tag, _ := range currentState {
var gi int64
for i := 0; i <= 7; i++ {
g := -1 * 24 * i
t := time.Now().Add(time.Duration(g) * time.Hour)
secounds := currentState[tag][t.Format("02.01.06")]
gi = gi + secounds
}
gi = gi / 60
s := strconv.FormatInt(gi, 10)
line := tag+": "+s+"<br>"
w.Write([]byte(line))
}
*/
})
http.HandleFunc("/current", func(w http.ResponseWriter, r *http.Request) {
res, err := json.Marshal(lastEvent)
if err != nil {
log.Fatal().Err(err).Msg("Cant Marshal Current State")
}
w.Write(res)
})
http.HandleFunc("/tags", func(w http.ResponseWriter, r *http.Request) {
var tags []string
for s, _ := range currentState {
tags = append(tags, s)
}
res, err := json.Marshal(tags)
if err != nil {
log.Fatal().Err(err).Msg("Cant Marshal Tag list")
}
w.Write(res)
})
http.HandleFunc("/state", func(w http.ResponseWriter, r *http.Request) {
res, err := json.Marshal(currentState)
if err != nil {
log.Fatal().Err(err).Msg("Cant Marshal Tag list")
}
w.Write(res)
})
http.HandleFunc("/event", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
log.Warn().Err(err).Msg("Cant pars form Data from Request to add event")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Cant parse form Data"))
return
}
d := r.FormValue("time")
eventTimestamp := time.Now()
if d != "" {
eventTimestamp, err = time.Parse(time.RFC3339 , d)
if err != nil {
log.Warn().Err(err).Str("TimeString", d).Msg("Cant parse time from http get parameter")
}
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Cant parse date from get paremter, please use RFC3339 Data"))
return
}
name := r.FormValue("name")
e := Event{
Name: name,
EventTimestamp: eventTimestamp,
}
addEvent(e)
})
http.ListenAndServe(":8080", nil)
}
func readStateFromFile() {
if _, err := os.Stat(eventlog); err == nil {
// path/to/whatever exists
file, err := os.Open(eventlog)
if err != nil {
log.Fatal().Err(err).Msg("Cant open file")
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
details := strings.Split(scanner.Text(), ";")
t, err := time.Parse(time.RFC3339 , details[0])
if err != nil {
log.Fatal().Err(err).Str("TimeString", details[0]).Msg("Cant parse time from eventlog")
}
e := Event{
Name: details[1],
EventTimestamp: t,
}
handleEvent(e)
}
}
}
func saveEvent(e Event) {
var line string
line = e.EventTimestamp.Format(time.RFC3339)+";"+e.Name +"\r\n"
f, err := os.OpenFile(eventlog, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
panic(err)
}
defer f.Close()
if _, err = f.WriteString(line); err != nil {
panic(err)
}
}
func addEvent(e Event) {
//Write Event to Log
saveEvent(e)
handleEvent(e)
}
func handleEvent(e Event) {
log.Debug().Msg("Handle New Event")
if lastEvent.Name != "" {
duration := e.EventTimestamp.Sub(lastEvent.EventTimestamp)
log.Debug().Int64("Duration", int64(duration.Seconds())).Str("LastEventName", lastEvent.Name).Msg("Calculatet Duration for last event")
//Add to state
currentDuration := currentState[lastEvent.Name][lastEvent.EventTimestamp.Format("02.01.06")]
if len(currentState[lastEvent.Name]) == 0 {
currentState[lastEvent.Name] = make(map[string]int64)
}
currentDuration += int64(duration.Seconds())
log.Debug().Int64("Day Duration", currentDuration).Str("Day", lastEvent.EventTimestamp.Format("02.01.06")).Str("typ", lastEvent.Name).Msg("Add Duration to Day")
currentState[lastEvent.Name][lastEvent.EventTimestamp.Format("02.01.06")] = currentDuration
}
lastEvent = e
}