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("

Time Tracking

")) w.Write([]byte("Doing "+lastEvent.Name+" since "+lastEvent.EventTimestamp.Format(time.RFC3339)+"
")) w.Write([]byte("

Today

")) for tag, _ := range currentState { i := currentState[tag][time.Now().Format("02.01.06")] i = i / 60 s := strconv.FormatInt(i, 10) line := tag+": "+s+"
" w.Write([]byte(line)) } w.Write([]byte("

Last 7 Days

")) 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+"
" 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 }