// Start get called in a multitude of different situations: // - when the service starts after a reboot and loads timer state from the ledger // - when a new timer is added (for a new project) func (t *Timer) Start() { var err error //already running and not failed? no-op if t.running && t.HasFailed() == "" { return } t.timerData.Failed = "" //load project/ system specific configuration sysdir, err := SystemTimeglassPathCreateIfNotExist() if err != nil { err = errwrap.Wrapf(fmt.Sprintf("Failed to read system config: {{err}}"), err) t.timerData.Failed = err.Error() } conf, err := config.ReadConfig(t.Dir(), sysdir) if err != nil { err = errwrap.Wrapf(fmt.Sprintf("Failed to read configuration for '%s': {{err}}, using default", t.Dir()), err) t.timerData.Failed = err.Error() conf = config.DefaultConfig } t.timerData.MBU = time.Duration(conf.MBU) t.timerData.Timeout = 4 * t.timerData.MBU //lazily initiate control members t.stopto = make(chan struct{}) t.stoptick = make(chan struct{}) t.reset = make(chan struct{}) //setup monitor, if not done yet wakeup := make(chan monitor.DirEvent) merrs := make(chan error) if t.monitor == nil { t.monitor, err = monitor.New(t.Dir(), monitor.Recursive, t.timerData.Latency) if err != nil { err = errwrap.Wrapf(fmt.Sprintf("Failed to create monitor for directory '%s': {{err}}", t.Dir()), err) t.timerData.Failed = err.Error() log.Print(err) } else { wakeup, err = t.monitor.Start() if err != nil { err = errwrap.Wrapf("Failed to start monitor: {{err}}", err) t.timerData.Failed = err.Error() log.Print(err) } merrs = t.monitor.Errors() } } else { wakeup = t.monitor.Events() merrs = t.monitor.Errors() } //handle stops, pauses, timeouts and wakeups log.Printf("Timer for project '%s' was started (and unpaused) explicitely", t.Dir()) t.timerData.Paused = false t.running = true go func() { for { t.EmitSave() select { case <-t.stopto: log.Printf("Timer for project '%s' was stopped (and paused) explicitely", t.Dir()) return case merr := <-merrs: log.Printf("Monitor Error: %s", merr) t.timerData.Failed = merr.Error() case <-time.After(t.timerData.Timeout): if !t.IsPaused() { log.Printf("Timer for project '%s' timed out after %s", t.Dir(), t.timerData.Timeout) } t.Pause() case ev := <-wakeup: if t.IsPaused() { log.Printf("Timer for project '%s' woke up after some activity in '%s'", t.Dir(), ev.Dir()) t.Unpause() } else { log.Printf("Timer saw activity for project '%s' in '%s' but is already unpaused", t.Dir(), ev.Dir()) } } } }() //handle time modifications here go func() { for { if !t.timerData.Paused { t.timerData.Time += t.timerData.MBU } t.EmitSave() select { case <-t.stoptick: return case <-t.reset: t.timerData.Time = 0 log.Printf("Timer for project '%s' was reset", t.Dir()) case <-time.After(t.timerData.MBU): } } }() }
func (c *Status) Run(ctx *cli.Context) error { dir, err := os.Getwd() if err != nil { return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err) } vc, err := vcs.GetVCS(dir) if err != nil { return errwrap.Wrapf("Failed to setup VCS: {{err}}", err) } sysdir, err := daemon.SystemTimeglassPath() if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to get system config path: {{err}}"), err) } conf, err := config.ReadConfig(vc.Root(), sysdir) if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to read configuration: {{err}}"), err) } client := NewClient() //fetch information on overall daemon c.Printf("Fetching daemon info...") dinfo, err := client.Info() if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to fetch daemon info: {{err}}"), err) } curr, _ := strconv.Atoi(strings.Replace(dinfo["version"].(string), ".", "", 2)) recent, _ := strconv.Atoi(strings.Replace(dinfo["newest_version"].(string), ".", "", 2)) if curr != 0 && recent > curr { c.Println("A new version is available, please upgrade: https://github.com/timeglass/glass/releases") } //fetch information on the timer specific to this directory c.Printf("Fetching timer info...") timer, err := client.ReadTimer(vc.Root()) if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to fetch timer: {{err}}"), err) } if reason := timer.HasFailed(); reason != "" { c.Printf("Timer has failed: %s", reason) } else { if timer.IsPaused() { c.Printf("Timer is currently: PAUSED") } else { c.Printf("Timer is currently: RUNNING") } } tmpls := ctx.String("template") if ctx.Bool("commit-template") { tmpls = conf.CommitMessage } //we got some template specified if tmpls != "" { //parse temlate and only report error if we're talking to a human tmpl, err := template.New("commit-msg").Parse(tmpls) if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to parse commit_message: '%s' in configuration as a text/template: {{err}}", conf.CommitMessage), err) } //execute template and write to stdout err = tmpl.Execute(os.Stdout, timer.Time()) if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to execute commit_message: template for time '%s': {{err}}", timer.Time()), err) } } else { //just print c.Printf("Timer reads: %s", timer.Time()) } return nil }
func (c *Push) Run(ctx *cli.Context) error { dir, err := os.Getwd() if err != nil { return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err) } sysdir, err := daemon.SystemTimeglassPath() if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to get system config path: {{err}}"), err) } conf, err := config.ReadConfig(dir, sysdir) if err != nil { return errwrap.Wrapf(fmt.Sprintf("Failed to read configuration: {{err}}"), err) } //hooks require us require us to check the refs that are pushed over stdin //to prevent inifinte push loop refs := "" if !isatty.IsTerminal(os.Stdin.Fd()) { bytes, err := ioutil.ReadAll(os.Stdin) if err != nil { return errwrap.Wrapf("Failed to read from stdin: {{err}}", err) } refs = string(bytes) //when `glass push` triggers the pre-push hook it will not //provide any refs on stdin //this probalby means means there is nothing left to push and //we return here to prevent recursive push if refs == "" { return nil } //configuration can explicitly request not to push time data automatically //on hook usage if !conf.AutoPush { return nil } } vc, err := vcs.GetVCS(dir) if err != nil { return errwrap.Wrapf("Failed to setup VCS: {{err}}", err) } remote := ctx.Args().First() if remote == "" { remote, err = vc.DefaultRemote() if err != nil { return errwrap.Wrapf("Failed to determine default remote: {{err}}", err) } } err = vc.Push(remote, refs) if err != nil { if err == vcs.ErrNoLocalTimeData { c.Printf("Local clone has no time data (yet), nothing to push to '%s'. Start a timer and commit changes to record local time data.\n", remote) return nil } return errwrap.Wrapf("Failed to push time data: {{err}}", err) } return nil }