Example #1
0
// 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):
			}
		}
	}()
}
Example #2
0
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
}
Example #3
0
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
}