コード例 #1
0
ファイル: init.go プロジェクト: rosetree/glass
func (c *Init) Run(ctx *cli.Context) error {
	c.Println("Writing version control hooks...")
	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)
	}

	err = vc.Hook()
	if err != nil {
		return errwrap.Wrapf("Failed to write hooks: {{err}}", err)
	}

	c.Println("Hooks written!")
	err = NewStart().Run(ctx)
	if err != nil {
		return err
	}

	err = NewPull().Run(ctx)
	if err != nil {
		if errwrap.Contains(err, vcs.ErrNoRemote.Error()) {
			c.Println("No remote found, skipping pull")
		} else {
			return err
		}
	}

	return nil
}
コード例 #2
0
ファイル: keeper.go プロジェクト: rosetree/glass
func (k *Keeper) Load() error {
	f, err := os.Open(k.ledgerPath)
	if err != nil {
		if !os.IsNotExist(err) {
			return errwrap.Wrapf(fmt.Sprintf("Failed to open '%s': {{err}}", k.ledgerPath), err)
		}
	} else {
		defer f.Close()
		dec := json.NewDecoder(f)
		err := dec.Decode(k)
		if err != nil {
			return errwrap.Wrapf(fmt.Sprintf("Failed to decode JSON in '%s': {{err}}", k.ledgerPath), err)
		}

		//immediately restart and link save channel if not paused
		for _, t := range k.keeperData.Timers {
			t.SetSave(k.save)
			if !t.IsPaused() {
				t.Start()
			}
		}

	}

	return nil
}
コード例 #3
0
ファイル: pull.go プロジェクト: rosetree/glass
func (c *Pull) 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)
	}

	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.Pull(remote)
	if err != nil {
		if err == vcs.ErrNoRemoteTimeData {
			c.Printf("Remote '%s' has no time data (yet), nothing to pull\n", remote)
			return nil
		}

		return errwrap.Wrapf("Failed to pull time data: {{err}}", err)
	}

	c.Println("Time data was pulled successfully")
	return nil
}
コード例 #4
0
ファイル: server.go プロジェクト: rosetree/glass
func (s *Server) timersCreate(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		s.Respond(w, err)
		return
	}

	if dirs, ok := r.Form["dir"]; !ok {
		s.Respond(w, fmt.Errorf("dir parameter is mandatory"))
		return
	} else {
		for _, dir := range dirs {
			t, err := NewTimer(dir)
			if err != nil {
				s.Respond(w, errwrap.Wrapf("Failed to create new timer: {{err}}", err))
				return
			}

			err = s.keeper.Add(t)
			if err != nil {
				s.Respond(w, errwrap.Wrapf("Failed to add new timer to keeper: {{err}}", err))
				return
			}
		}
	}

	w.WriteHeader(http.StatusCreated)
}
コード例 #5
0
ファイル: install.go プロジェクト: rosetree/glass
func (c *Install) Run(ctx *cli.Context) error {
	c.Println("Installing the Timeglass background service...")

	//attempt to install
	cmd := exec.Command("glass-daemon", "install")
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout

	err := cmd.Run()
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Failed to install Daemon: {{err}}"), err)
	}

	c.Println("Starting the Timeglass background service...")

	//attempt to start
	cmd = exec.Command("glass-daemon", "start")
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	err = cmd.Run()
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Failed to start Daemon: {{err}}"), err)
	}

	c.Println("Done!")
	return nil
}
コード例 #6
0
ファイル: sum.go プロジェクト: rosetree/glass
func (c *Sum) Run(ctx *cli.Context) error {
	dir, err := os.Getwd()
	if err != nil {
		return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
	}

	//write the vcs
	vc, err := vcs.GetVCS(dir)
	if err != nil {
		return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
	}

	//retrieve commits through piped stdin or arguments
	commits := []string{}
	if !isatty.IsTerminal(os.Stdin.Fd()) {
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			commits = append(commits, scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			c.Println(err)
		}
	} else {
		if ctx.Args().First() != "" {
			commits = append(commits, ctx.Args().First())
		}
		commits = append(commits, ctx.Args().Tail()...)
	}

	if len(commits) == 0 {
		return fmt.Errorf("Please provide at least one commit through STDIN or as an argument.")
	}

	//map time data from the vcs
	list := []vcs.TimeData{}
	for _, c := range commits {
		data, err := vc.Show(c)
		if err != nil {
			if err == vcs.ErrNoCommitTimeData {
				//ignore if a commit has no time attached
				continue
			}

			return errwrap.Wrapf(fmt.Sprintf("Failed to show time notes for '%s': {{err}}", c), err)
		}

		list = append(list, data)
	}

	//reduce to output
	var total time.Duration
	for _, data := range list {
		total += data.Total()
	}

	fmt.Fprintln(os.Stdout, total)
	return nil
}
コード例 #7
0
ファイル: server.go プロジェクト: rosetree/glass
func (s *Server) timersInfo(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		s.Respond(w, err)
		return
	}

	timers := []*Timer{}
	if dirs, ok := r.Form["dir"]; !ok {
		s.Respond(w, fmt.Errorf("dir parameter is mandatory"))
		return
	} else {
		for _, dir := range dirs {
			t, err := s.keeper.Get(dir)
			if err != nil {
				s.Respond(w, errwrap.Wrapf("Failed to get timer: {{err}}", err))
				return
			}

			timers = append(timers, t)
		}
	}

	s.Respond(w, timers)
}
コード例 #8
0
ファイル: client.go プロジェクト: rosetree/glass
func (c *Client) Call(method string, params url.Values) ([]byte, error) {
	loc := fmt.Sprintf("%s/api/%s?%s", c.endpoint, method, params.Encode())
	resp, err := c.Get(loc)
	if err != nil {
		return nil, ErrRequestFailed
	}

	body := bytes.NewBuffer(nil)
	defer resp.Body.Close()
	_, err = io.Copy(body, resp.Body)
	if err != nil {
		return body.Bytes(), errwrap.Wrapf(fmt.Sprintf("Failed to buffer response body: {{err}}"), err)
	}

	if resp.StatusCode > 299 {
		errresp := &struct {
			Error string
		}{}

		err := json.Unmarshal(body.Bytes(), &errresp)
		if err != nil || errresp.Error == "" {
			return body.Bytes(), fmt.Errorf("Unexpected StatusCode returned from Deamon: '%d', body: '%s'", resp.StatusCode, body.String())
		} else if strings.Contains(errresp.Error, "No known timer") {
			return body.Bytes(), ErrTimerNotFound
		}

		return body.Bytes(), fmt.Errorf(errresp.Error)
	}

	return body.Bytes(), nil
}
コード例 #9
0
ファイル: timer.go プロジェクト: rosetree/glass
func (t *Timer) Stop() {
	if !t.running {
		return
	}

	if t.monitor != nil {

		//@todo Remove this at some point, it normalizes
		//time after rapid stop start usage on OSX
		//for unkown reasons, long term solution should probably
		//involve some mechanism that prevents the darwin monitor
		//form stopping to quickly after being started
		<-time.After(time.Millisecond)

		err := t.monitor.Stop()
		if err != nil {
			log.Print(errwrap.Wrapf("Failed to stop monitor: {{err}}", err))
		}

		t.monitor = nil
	}

	t.stopto <- struct{}{}
	t.stoptick <- struct{}{}

	t.timerData.Paused = true
	t.running = false
}
コード例 #10
0
ファイル: punch.go プロジェクト: rosetree/glass
func (c *Punch) Run(ctx *cli.Context) error {
	dir, err := os.Getwd()
	if err != nil {
		return errwrap.Wrapf("Failed to fetch current working dir: {{err}}", err)
	}

	var input string
	if isatty.IsTerminal(os.Stdin.Fd()) {
		c.Println("Reading input from argument...")
		input = ctx.Args().First()
	} else {
		c.Println("Reading input from Stdin...")
		bytes, err := ioutil.ReadAll(os.Stdin)
		if err != nil {
			return errwrap.Wrapf("Failed to read time from Stdin: {{err}}", err)
		}
		input = string(bytes)
	}

	if input == "" {
		return fmt.Errorf("Please provide the time you spent as the first argument")
	}

	t, err := time.ParseDuration(input)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Failed to parse provided argument '%s' as a valid duration (e.g 1h2m10s): {{err}}", input), err)
	}

	//write the vcs
	vc, err := vcs.GetVCS(dir)
	if err != nil {
		return errwrap.Wrapf("Failed to setup VCS: {{err}}", err)
	}

	c.Printf("Persisting %s to version control...", t)
	err = vc.Persist(t)
	if err != nil {
		return errwrap.Wrapf("Failed to log time into VCS: {{err}}", err)
	}

	c.Println("Done!")
	return nil
}
コード例 #11
0
ファイル: start.go プロジェクト: rosetree/glass
func (c *Start) 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)
	}

	c.Println("Starting timer...")

	client := NewClient()
	err = client.CreateTimer(vc.Root())
	if err != nil {
		return errwrap.Wrapf("Failed to create timer: {{err}}", err)
	}

	c.Println("Timer started!")
	return nil
}
コード例 #12
0
ファイル: pause.go プロジェクト: rosetree/glass
func (c *Pause) 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)
	}

	c.Printf("Pausing timer...")

	client := NewClient()
	err = client.PauseTimer(vc.Root())
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Failed to pause timer: {{err}}"), err)
	}

	c.Printf("Done!")
	return nil
}
コード例 #13
0
ファイル: paths.go プロジェクト: rosetree/glass
func SystemTimeglassPathCreateIfNotExist() (string, error) {
	path, err := SystemTimeglassPath()
	if err != nil {
		return "", err
	}

	err = os.MkdirAll(path, 0755)
	if err != nil {
		return "", errwrap.Wrapf(fmt.Sprintf("Failed to create Timeglass system dir '%s': {{err}}", path), err)
	}

	return path, nil
}
コード例 #14
0
ファイル: client.go プロジェクト: rosetree/glass
func (c *Client) Info() (map[string]interface{}, error) {
	data, err := c.Call("", url.Values{})
	if err != nil {
		return nil, err
	}

	v := map[string]interface{}{}
	err = json.Unmarshal(data, &v)
	if err != nil {
		return nil, errwrap.Wrapf(fmt.Sprintf("Failed to deserialize '%s' into map: {{err}}", data), err)
	}

	return v, nil
}
コード例 #15
0
ファイル: config.go プロジェクト: rosetree/glass
func (t *MBU) UnmarshalJSON(data []byte) error {
	raw, err := strconv.Unquote(string(data))
	if err != nil {
		return err
	}

	parsed, err := time.ParseDuration(raw)
	if err != nil {
		return errwrap.Wrapf("Failed to parse duration: {{err}}", err)
	}

	*t = MBU(parsed)
	return nil
}
コード例 #16
0
ファイル: main.go プロジェクト: rosetree/glass
func (p *daemon) Start(s service.Service) error {
	var err error

	path, err := SystemTimeglassPathCreateIfNotExist()
	if err != nil {
		return errwrap.Wrapf("Failed to find Timeglass system path: {{err}}", err)
	}

	p.keeper, err = NewKeeper(path)
	if err != nil {
		return errwrap.Wrapf("Failed to create time keeper: {{err}}", err)
	}

	bind := "127.0.0.1:3838"
	p.server, err = NewServer(bind, p.keeper)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Failed to create server on '%s': {{err}}, is the service already running?", bind), err)
	}

	go p.server.checkVersion()
	go p.keeper.Start()
	go p.run()
	return nil
}
コード例 #17
0
ファイル: keeper.go プロジェクト: rosetree/glass
func (k *Keeper) Save() error {
	f, err := os.OpenFile(k.ledgerPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		return err
	}
	defer f.Close()

	enc := json.NewEncoder(f)
	err = enc.Encode(k)
	if err != nil {
		return errwrap.Wrapf(fmt.Sprintf("Error saving ledger to '%s': {{err}}", k.ledgerPath), err)
	}

	return nil
}
コード例 #18
0
ファイル: logger.go プロジェクト: rosetree/glass
func NewLogger(w io.Writer) (*Logger, error) {
	l := &Logger{}
	path, err := SystemTimeglassPathCreateIfNotExist()
	if err != nil {
		return nil, errwrap.Wrapf("Failed to find Timeglass system path: {{err}}", err)
	}

	l.path = filepath.Join(path, "daemon.log")
	l.file, err = os.OpenFile(l.path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, err
	}

	l.Writer = io.MultiWriter(l.file, w)
	return l, nil
}
コード例 #19
0
ファイル: client.go プロジェクト: rosetree/glass
func (c *Client) ReadTimer(dir string) (*daemon.Timer, error) {
	timers := []*daemon.Timer{}
	params := url.Values{}
	params.Set("dir", dir)

	data, err := c.Call("timers.info", params)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(data, &timers)
	if err != nil {
		return nil, errwrap.Wrapf(fmt.Sprintf("Failed to deserialize '%s' into a list of timers: {{err}}", data), err)
	}

	if len(timers) < 1 {
		return nil, fmt.Errorf("Expected at least one timer from the daemon")
	}

	return timers[0], nil
}
コード例 #20
0
ファイル: server.go プロジェクト: rosetree/glass
func (s *Server) timersDelete(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		s.Respond(w, err)
		return
	}

	if dirs, ok := r.Form["dir"]; !ok {
		s.Respond(w, fmt.Errorf("dir parameter is mandatory"))
		return
	} else {
		for _, dir := range dirs {
			err := s.keeper.Remove(dir)
			if err != nil {
				s.Respond(w, errwrap.Wrapf("Failed to remove timer: {{err}}", err))
				return
			}
		}
	}

	w.WriteHeader(http.StatusNoContent)
}
コード例 #21
0
ファイル: server.go プロジェクト: rosetree/glass
func NewServer(httpb string, keeper *Keeper) (*Server, error) {
	l, err := net.Listen("tcp", httpb)
	if err != nil {
		return nil, errwrap.Wrapf(fmt.Sprintf("Failed to create listener on '%s': {{err}}", httpb), err)
	}

	mux := http.NewServeMux()
	s := &Server{
		keeper:   keeper,
		httpb:    httpb,
		listener: l,

		Server: &http.Server{Handler: mux},
	}

	mux.HandleFunc("/api/", s.api)
	mux.HandleFunc("/api/timers.create", s.timersCreate)
	mux.HandleFunc("/api/timers.pause", s.timersPause)
	mux.HandleFunc("/api/timers.delete", s.timersDelete)
	mux.HandleFunc("/api/timers.reset", s.timersReset)
	mux.HandleFunc("/api/timers.info", s.timersInfo)
	return s, nil
}
コード例 #22
0
ファイル: config.go プロジェクト: rosetree/glass
func ReadConfig(dir, sysdir string) (*Config, error) {

	//get system wide config
	sysconf := &Config{}
	sysconfp := filepath.Join(sysdir, confFilename)
	sysconfdata, err := ioutil.ReadFile(sysconfp)
	if err != nil && !os.IsNotExist(err) {
		return nil, errwrap.Wrapf(fmt.Sprintf("Failed to read system configuration file '%s' even though it exist: {{err}}", sysconfp), err)
	} else if os.IsNotExist(err) {
		sysconf = DefaultConfig
	} else {
		err := json.Unmarshal(sysconfdata, &sysconf)
		if err != nil {
			return nil, errwrap.Wrapf(fmt.Sprintf("Failed to parse system configuration '%s': {{err}}", sysconfp), err)
		}

		err = mergo.Merge(sysconf, DefaultConfig)
		if err != nil {
			return nil, errwrap.Wrapf("Failed to merge system config with default config: {{err}} ", err)
		}
	}

	//get project wide config
	conf := &Config{}
	confp := filepath.Join(dir, confFilename)
	confdata, err := ioutil.ReadFile(confp)
	if err != nil && !os.IsNotExist(err) {
		return nil, errwrap.Wrapf(fmt.Sprintf("Failed to read project configuration  file '%s' even though it exist: {{err}}", confp), err)
	} else if os.IsNotExist(err) {
		conf = sysconf
	} else {
		err := json.Unmarshal(confdata, &conf)
		if err != nil {
			return nil, errwrap.Wrapf(fmt.Sprintf("Failed to parse project configuration '%s': {{err}}", confp), err)
		}

		err = mergo.Merge(conf, sysconf)
		if err != nil {
			return nil, errwrap.Wrapf("Failed to merge project config with system config: {{err}} ", err)
		}
	}

	return conf, nil
}
コード例 #23
0
ファイル: push.go プロジェクト: rosetree/glass
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
}
コード例 #24
0
ファイル: status.go プロジェクト: rosetree/glass
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
}
コード例 #25
0
ファイル: timer.go プロジェクト: rosetree/glass
// 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):
			}
		}
	}()
}