-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The time is persisted when the program is stopped and resumed, now. Additionally, the elapsed time is saved every 30 seconds.
- Loading branch information
1 parent
ed48c82
commit 9f89362
Showing
8 changed files
with
266 additions
and
51 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package stopwatch | ||
|
||
import ( | ||
"sync" | ||
"time" | ||
|
||
tea "github.com/charmbracelet/bubbletea" | ||
) | ||
|
||
var ( | ||
lastID int | ||
idMtx sync.Mutex | ||
) | ||
|
||
func nextID() int { | ||
idMtx.Lock() | ||
defer idMtx.Unlock() | ||
lastID++ | ||
return lastID | ||
} | ||
|
||
// TickMsg is a message that is sent on every timer tick. | ||
type TickMsg struct { | ||
// ID is the identifier of the stopwatch that sends the message. This makes | ||
// it possible to determine which stopwatch a tick belongs to when there | ||
// are multiple stopwatches running. | ||
// | ||
// Note, however, that a stopwatch will reject ticks from other | ||
// stopwatches, so it's safe to flow all TickMsgs through all stopwatches | ||
// and have them still behave appropriately. | ||
ID int | ||
} | ||
|
||
// StartStopMsg is sent when the stopwatch should start or stop. | ||
type StartStopMsg struct { | ||
ID int | ||
running bool | ||
} | ||
|
||
// ResetMsg is sent when the stopwatch should reset. | ||
type ResetMsg struct { | ||
ID int | ||
} | ||
|
||
// Model for the stopwatch component. | ||
type Model struct { | ||
d time.Duration | ||
id int | ||
running bool | ||
|
||
// How long to wait before every tick. Defaults to 1 second. | ||
Interval time.Duration | ||
} | ||
|
||
// NewWithInterval creates a new stopwatch with the given timeout and tick | ||
// interval. | ||
func NewWithInterval(interval time.Duration, elapsed int) Model { | ||
t := time.Duration(elapsed) * interval | ||
return Model{ | ||
d: t, | ||
Interval: interval, | ||
id: nextID(), | ||
} | ||
} | ||
|
||
// New creates a new stopwatch with 1s interval. | ||
func New(elapsed int) Model { | ||
return NewWithInterval(time.Second, elapsed) | ||
} | ||
|
||
// ID returns the unique ID of the model. | ||
func (m Model) ID() int { | ||
return m.id | ||
} | ||
|
||
// Start starts the stopwatch. | ||
func (m Model) Start() tea.Cmd { | ||
return tea.Batch(func() tea.Msg { | ||
return StartStopMsg{ID: m.id, running: true} | ||
}, tick(m.id, m.Interval)) | ||
} | ||
|
||
// Stop stops the stopwatch. | ||
func (m Model) Stop() tea.Cmd { | ||
return func() tea.Msg { | ||
return StartStopMsg{ID: m.id, running: false} | ||
} | ||
} | ||
|
||
// Toggle stops the stopwatch if it is running and starts it if it is stopped. | ||
func (m Model) Toggle() tea.Cmd { | ||
if m.Running() { | ||
return m.Stop() | ||
} | ||
return m.Start() | ||
} | ||
|
||
// Reset resets the stopwatch to 0. | ||
func (m Model) Reset() tea.Cmd { | ||
return func() tea.Msg { | ||
return ResetMsg{ID: m.id} | ||
} | ||
} | ||
|
||
// Running returns true if the stopwatch is running or false if it is stopped. | ||
func (m Model) Running() bool { | ||
return m.running | ||
} | ||
|
||
// Update handles the timer tick. | ||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||
switch msg := msg.(type) { | ||
case StartStopMsg: | ||
if msg.ID != m.id { | ||
return m, nil | ||
} | ||
m.running = msg.running | ||
case ResetMsg: | ||
if msg.ID != m.id { | ||
return m, nil | ||
} | ||
m.d = 0 | ||
case TickMsg: | ||
if !m.running || msg.ID != m.id { | ||
break | ||
} | ||
m.d += m.Interval | ||
return m, tick(m.id, m.Interval) | ||
} | ||
|
||
return m, nil | ||
} | ||
|
||
// Elapsed returns the time elapsed. | ||
func (m Model) Elapsed() time.Duration { | ||
return m.d | ||
} | ||
|
||
// View of the timer component. | ||
func (m Model) View() string { | ||
return m.d.String() | ||
} | ||
|
||
func tick(id int, d time.Duration) tea.Cmd { | ||
return tea.Tick(d, func(_ time.Time) tea.Msg { | ||
return TickMsg{ID: id} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
CREATE TABLE IF NOT EXISTS counters ( | ||
name TEXT NOT NULL UNIQUE, | ||
elapsed INTEGER NOT NULL DEFAULT 0 | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package utils | ||
|
||
import ( | ||
"database/sql" | ||
"embed" | ||
"log" | ||
"os" | ||
|
||
_ "github.com/mattn/go-sqlite3" | ||
) | ||
|
||
const DBFile string = "countup.sh.db" | ||
|
||
var DBInstance *sql.DB | ||
|
||
var ps = string(os.PathSeparator) | ||
|
||
func GetDataDirectory() string { | ||
dir, err := os.UserConfigDir() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
fullpath := dir + ps + "countup.sh" + ps | ||
mkdirerr := os.MkdirAll(fullpath, os.ModePerm) | ||
if mkdirerr != nil { | ||
log.Fatal(mkdirerr) | ||
} | ||
return fullpath | ||
} | ||
|
||
func DBNew() { | ||
userConfigDir := GetDataDirectory() | ||
fullPath := userConfigDir + DBFile | ||
_, staterr := os.Stat(fullPath) | ||
|
||
instance, err := sql.Open("sqlite3", fullPath) | ||
if err != nil { | ||
log.Fatal("Error opening database: ", err) | ||
} | ||
if DBInstance == nil { | ||
DBInstance = instance | ||
} | ||
if os.IsNotExist(staterr) { | ||
createAndPrefillDatabase() | ||
} | ||
} | ||
|
||
func DBClose() { | ||
DBInstance.Close() | ||
} | ||
|
||
//go:embed db.sql | ||
var sqlfile embed.FS | ||
|
||
func createAndPrefillDatabase() { | ||
readfile, err := sqlfile.ReadFile("db.sql") | ||
if err != nil { | ||
log.Fatal("Error reading db.sql: ", err) | ||
} | ||
str := string(readfile) | ||
_, err = DBInstance.Exec(str) | ||
if err != nil { | ||
log.Fatal("Error creating database: ", err, DBInstance.Stats()) | ||
} | ||
} | ||
|
||
type DBCounter struct { | ||
Name string | ||
Elapsed int | ||
} | ||
|
||
func DBGetCounter(name string) DBCounter { | ||
var counter DBCounter | ||
err := DBInstance.QueryRow("SELECT name, elapsed FROM counters WHERE name = ?", name).Scan(&counter.Name, &counter.Elapsed) | ||
if err != nil { | ||
counter = DBCounter{ | ||
Name: name, | ||
Elapsed: 0, | ||
} | ||
} | ||
return counter | ||
} | ||
|
||
func DBSetCounter(name string, elapsed int) { | ||
_, err := DBInstance.Exec("INSERT OR REPLACE INTO counters (name, elapsed) VALUES (?, ?)", name, elapsed) | ||
if err != nil { | ||
log.Fatal("Error setting counter: ", err) | ||
} | ||
} |