Initial working commit
This commit is contained in:
parent
af8cb2af79
commit
ee545423c8
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,3 +24,4 @@ _testmain.go
|
|||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
10
README.md
10
README.md
@ -1,3 +1,11 @@
|
|||||||
# gomodoro
|
# gomodoro
|
||||||
|
|
||||||
A simple command line pomodoro timer
|
A simple command line pomodoro timer
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run `gomodoro` to start the default timer. It will print the current status on the command line.
|
||||||
|
|
||||||
|
You can customize the duration and the logging interval by using the `-duration` or `-interval` flags. Example: A 30 min timer logging every 1 min would be `gomodoro -duration 30m -interval 1m` (Not working right now)
|
||||||
|
|
||||||
|
Only one gomodoro timer can be running at a time. If you are running one in the background on another thread, you can display the status by using `gomodoro -status`
|
||||||
|
139
main.go
Normal file
139
main.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigDir = ".config/gomodoro"
|
||||||
|
LogFileName = "gomodoro.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gomodoro holds the config for the current timer
|
||||||
|
type TimerConfig struct {
|
||||||
|
StartTime time.Time
|
||||||
|
Duration time.Duration
|
||||||
|
Interval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// PomoStatus Represents the status of the Gomodor timer at a given moment
|
||||||
|
type PomoStatus struct {
|
||||||
|
Status string
|
||||||
|
StartTime int64
|
||||||
|
DurationSeconds float64
|
||||||
|
RemainingSeconds float64
|
||||||
|
Done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeRemaining returns the remaining duration rounded to the nearest interval
|
||||||
|
func timeRemaining(start time.Time, duration time.Duration, interval time.Duration) time.Duration {
|
||||||
|
return (duration - time.Since(start)).Round(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmtJSON returns a JSON string for a given interface and safely returns an err
|
||||||
|
func fmtJSON(obj interface{}) (string, error) {
|
||||||
|
bstring, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bstring[:]), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// logRemaining prints a JSON formatted string logging the time remaining in the timer
|
||||||
|
func logRemaining(logFile *os.File, timerConfig TimerConfig) error {
|
||||||
|
timeRemaining := timeRemaining(timerConfig.StartTime, timerConfig.Duration, timerConfig.Interval)
|
||||||
|
err := logTextStatus(logFile, timerConfig, timeRemaining.String(), false)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// logTextStatus logs the status of a timer with a given string
|
||||||
|
func logTextStatus(logFile *os.File, timerConfig TimerConfig, status string, done bool) error {
|
||||||
|
// TODO: Find a better way than calculating this twice
|
||||||
|
timeRemaining := timeRemaining(timerConfig.StartTime, timerConfig.Duration, timerConfig.Interval)
|
||||||
|
jstring, err := fmtJSON(&PomoStatus{
|
||||||
|
Status: status,
|
||||||
|
StartTime: timerConfig.StartTime.Unix(),
|
||||||
|
DurationSeconds: timerConfig.Duration.Seconds(),
|
||||||
|
RemainingSeconds: timeRemaining.Seconds(),
|
||||||
|
Done: done,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
logFile.WriteString(jstring + "\n")
|
||||||
|
fmt.Printf("%s / %s\n", status, timerConfig.Duration.String())
|
||||||
|
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryLock() (net.Listener, error) {
|
||||||
|
return net.Listen("unix", "/tmp/gomodoro")
|
||||||
|
}
|
||||||
|
|
||||||
|
func openLogFile() (*os.File, error) {
|
||||||
|
dir := path.Join(os.Getenv("HOME"), ConfigDir)
|
||||||
|
os.MkdirAll(dir, os.ModePerm)
|
||||||
|
logFile, err := os.OpenFile(path.Join(dir, LogFileName), os.O_APPEND|os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||||
|
return logFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var duration time.Duration
|
||||||
|
flag.DurationVar(&duration, "duration", 25*time.Minute, "Gomodoro duration")
|
||||||
|
var interval time.Duration
|
||||||
|
flag.DurationVar(&interval, "interval", 1*time.Second, "Gomodoro tick interval")
|
||||||
|
var status bool
|
||||||
|
flag.BoolVar(&status, "status", false, "Only display current Gomodoro status")
|
||||||
|
|
||||||
|
// Ensure obtain lock
|
||||||
|
lock, err := tryLock()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: More helpful message
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer lock.Close()
|
||||||
|
|
||||||
|
// Open file for logging
|
||||||
|
logFile, err := openLogFile()
|
||||||
|
if err != nil {
|
||||||
|
lock.Close()
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer logFile.Close()
|
||||||
|
|
||||||
|
// Start timer
|
||||||
|
start := time.Now()
|
||||||
|
timerConfig := TimerConfig{
|
||||||
|
StartTime: start,
|
||||||
|
Duration: duration,
|
||||||
|
Interval: interval,
|
||||||
|
}
|
||||||
|
tick := time.Tick(interval)
|
||||||
|
end := time.After(duration)
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt, os.Kill, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-tick:
|
||||||
|
logRemaining(logFile, timerConfig)
|
||||||
|
case <-end:
|
||||||
|
logTextStatus(logFile, timerConfig, "Complete", true)
|
||||||
|
return
|
||||||
|
case <-stop:
|
||||||
|
logTextStatus(logFile, timerConfig, "Interrupted", true)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user