Compare commits

...

No commits in common. "30a50b775fe10624f41fb1462236b0332ab32108" and "acb051d7a36ca0e27bb85d9c791dfd84f173b787" have entirely different histories.

15 changed files with 206 additions and 138 deletions

View File

@ -47,6 +47,17 @@ steps:
event: event:
- tag - tag
- name: gitea release
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_api_key
base_url: https://git.nemunai.re/
files: deploy/reveil-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
when:
event:
- tag
- name: docker - name: docker
image: plugins/docker image: plugins/docker
settings: settings:

View File

@ -33,6 +33,17 @@ func declareAlarmRoutes(cfg *config.Config, router *gin.RouterGroup) {
c.JSON(http.StatusOK, true) c.JSON(http.StatusOK, true)
}) })
router.POST("/alarm/next", func(c *gin.Context) {
if player.CommonPlayer == nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "No player currently playing"})
return
} else {
player.CommonPlayer.NextTrack()
}
c.JSON(http.StatusOK, true)
})
router.DELETE("/alarm", func(c *gin.Context) { router.DELETE("/alarm", func(c *gin.Context) {
if player.CommonPlayer != nil { if player.CommonPlayer != nil {
err := player.CommonPlayer.Stop() err := player.CommonPlayer.Stop()

View File

@ -13,7 +13,7 @@ import (
func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), router *gin.RouterGroup) { func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTimer func(), router *gin.RouterGroup) {
router.GET("/alarms/next", func(c *gin.Context) { router.GET("/alarms/next", func(c *gin.Context) {
alarm, err := reveil.GetNextAlarm(db) alarm, err := reveil.GetNextAlarm(cfg, db)
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return return
@ -200,8 +200,8 @@ func declareAlarmsRoutes(cfg *config.Config, db *reveil.LevelDBStorage, resetTim
repeatedAlarmsRoutes.GET("", func(c *gin.Context) { repeatedAlarmsRoutes.GET("", func(c *gin.Context) {
alarm := c.MustGet("alarm").(*reveil.AlarmRepeated) alarm := c.MustGet("alarm").(*reveil.AlarmRepeated)
alarm.FillExcepts(db) alarm.FillExcepts(cfg, db)
alarm.NextTime = alarm.GetNextOccurence(db) alarm.NextTime = alarm.GetNextOccurence(cfg, db)
c.JSON(http.StatusOK, alarm) c.JSON(http.StatusOK, alarm)
}) })

3
app.go
View File

@ -64,6 +64,7 @@ func (app *App) Start() {
Handler: app.router, Handler: app.router,
} }
log.Println("Current timezone:", app.cfg.Timezone.String())
app.ResetTimer() app.ResetTimer()
log.Printf("Ready, listening on %s\n", app.cfg.Bind) log.Printf("Ready, listening on %s\n", app.cfg.Bind)
@ -78,7 +79,7 @@ func (app *App) ResetTimer() {
app.nextAlarm = nil app.nextAlarm = nil
} }
if na, err := reveil.GetNextAlarm(app.db); err == nil && na != nil { if na, err := reveil.GetNextAlarm(app.cfg, app.db); err == nil && na != nil {
app.nextAlarm = time.AfterFunc(time.Until(*na), func() { app.nextAlarm = time.AfterFunc(time.Until(*na), func() {
app.nextAlarm = nil app.nextAlarm = nil
reveil.RemoveOldAlarmsSingle(app.db) reveil.RemoveOldAlarmsSingle(app.db)

View File

@ -17,6 +17,7 @@ func (c *Config) declareFlags() {
flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions") flag.StringVar(&c.ActionsDir, "actions-dir", c.ActionsDir, "Path to the directory containing the actions")
flag.StringVar(&c.RoutinesDir, "routines-dir", c.RoutinesDir, "Path to the directory containing the routines") flag.StringVar(&c.RoutinesDir, "routines-dir", c.RoutinesDir, "Path to the directory containing the routines")
flag.IntVar(&c.SampleRate, "samplerate", c.SampleRate, "Samplerate for unifying output stream") flag.IntVar(&c.SampleRate, "samplerate", c.SampleRate, "Samplerate for unifying output stream")
flag.Var(&c.Timezone, "timezone", "Timezone to use when dealing with times")
// Others flags are declared in some other files when they need specials configurations // Others flags are declared in some other files when they need specials configurations
} }

View File

@ -18,6 +18,7 @@ type Config struct {
ActionsDir string ActionsDir string
RoutinesDir string RoutinesDir string
Timezone Timezone
SampleRate int SampleRate int
} }

View File

@ -3,6 +3,7 @@ package config
import ( import (
"encoding/base64" "encoding/base64"
"net/url" "net/url"
"time"
) )
type JWTSecretKey []byte type JWTSecretKey []byte
@ -42,3 +43,33 @@ func (i *URL) Set(value string) error {
i.URL = u i.URL = u
return nil return nil
} }
type Timezone struct {
tz *time.Location
}
func (tz *Timezone) GetLocation() *time.Location {
if tz.tz != nil {
return tz.tz
} else {
return time.Local
}
}
func (tz *Timezone) String() string {
if tz.tz != nil {
return tz.tz.String()
} else {
return time.Local.String()
}
}
func (tz *Timezone) Set(value string) error {
newtz, err := time.LoadLocation(value)
if err != nil {
return err
}
tz.tz = newtz
return nil
}

View File

@ -7,10 +7,10 @@ import (
) )
// FromEnv analyzes all the environment variables to find each one // FromEnv analyzes all the environment variables to find each one
// starting by GUSTUS_ // starting by REVEIL_
func (c *Config) FromEnv() error { func (c *Config) FromEnv() error {
for _, line := range os.Environ() { for _, line := range os.Environ() {
if strings.HasPrefix(line, "GUSTUS_") { if strings.HasPrefix(line, "REVEIL_") {
err := c.parseLine(line) err := c.parseLine(line)
if err != nil { if err != nil {
return fmt.Errorf("error in environment (%q): %w", line, err) return fmt.Errorf("error in environment (%q): %w", line, err)

10
go.mod
View File

@ -3,13 +3,12 @@ module git.nemunai.re/nemunaire/reveil
go 1.18 go 1.18
require ( require (
github.com/faiface/beep v0.0.0-00010101000000-000000000000 github.com/faiface/beep v1.1.0
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
) )
require ( require (
github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
@ -17,7 +16,7 @@ require (
github.com/goccy/go-json v0.9.7 // indirect github.com/goccy/go-json v0.9.7 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/hajimehoshi/go-mp3 v0.3.0 // indirect github.com/hajimehoshi/go-mp3 v0.3.0 // indirect
github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 // indirect github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/icza/bitio v1.0.0 // indirect github.com/icza/bitio v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
@ -30,11 +29,12 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
) )
replace github.com/faiface/beep => github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755

13
go.sum
View File

@ -1,13 +1,11 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755 h1:rkuKNEd+Izze/hA44R1kzPs9BXa544xXeufys8x0fkc=
github.com/MarkKremer/beep v1.0.3-0.20221013180303-756ceb286755/go.mod h1:PWWzyIlbyHQjQ/gJzGiMyDAvjo/t5L8TC8qkyX8UfWs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7 h1:tmSauY5l3s/Cp5n+cEiG1epUR2AejmdHeMJMycMFxb0= github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
github.com/ebitengine/purego v0.0.0-20220907032450-cf3e27c364c7/go.mod h1:Eh8I3yvknDYZeCuXH9kRNaPuHEwvXDCk378o9xszmHg= github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
@ -39,8 +37,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g= github.com/hajimehoshi/go-mp3 v0.3.0 h1:fTM5DXjp/DL2G74HHAs/aBGiS9Tg7wnp+jkU38bHy4g=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4 h1:m29xzbn3Pv5MgvgjMPs7m28uhUgVt3B3AIGjQLgkqUI= github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
github.com/hajimehoshi/oto/v2 v2.4.0-alpha.4/go.mod h1:OdGUICBjy7upAjvqqacbB63XIuYR3fqXZ7kYtlVYJgQ= github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8= github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8=
@ -103,9 +101,12 @@ github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"sort" "sort"
"time" "time"
"git.nemunai.re/nemunaire/reveil/config"
) )
type Date time.Time type Date time.Time
@ -38,7 +40,7 @@ func (h *Hour) UnmarshalJSON(src []byte) error {
return nil return nil
} }
func GetNextAlarm(db *LevelDBStorage) (*time.Time, error) { func GetNextAlarm(cfg *config.Config, db *LevelDBStorage) (*time.Time, error) {
alarmsRepeated, err := GetAlarmsRepeated(db) alarmsRepeated, err := GetAlarmsRepeated(db)
if err != nil { if err != nil {
return nil, err return nil, err
@ -46,7 +48,7 @@ func GetNextAlarm(db *LevelDBStorage) (*time.Time, error) {
var closestAlarm *time.Time var closestAlarm *time.Time
for _, alarm := range alarmsRepeated { for _, alarm := range alarmsRepeated {
next := alarm.GetNextOccurence(db) next := alarm.GetNextOccurence(cfg, db)
if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) { if next != nil && (closestAlarm == nil || closestAlarm.After(*next)) {
closestAlarm = next closestAlarm = next
} }
@ -92,7 +94,7 @@ type AlarmRepeated struct {
NextTime *time.Time `json:"next_time,omitempty"` NextTime *time.Time `json:"next_time,omitempty"`
} }
func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error { func (a *AlarmRepeated) FillExcepts(cfg *config.Config, db *LevelDBStorage) error {
if a.IgnoreExceptions { if a.IgnoreExceptions {
return nil return nil
} }
@ -112,7 +114,7 @@ func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error {
end := time.Time(*exception.End).AddDate(0, 0, 1) end := time.Time(*exception.End).AddDate(0, 0, 1)
for t := time.Time(*exception.Start); end.After(t); t = t.AddDate(0, 0, 1) { for t := time.Time(*exception.Start); end.After(t); t = t.AddDate(0, 0, 1) {
if t.Weekday() == a.Weekday { if t.Weekday() == a.Weekday {
a.Excepts = append(a.Excepts, time.Date(t.Year(), t.Month(), t.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location())) a.Excepts = append(a.Excepts, time.Date(t.Year(), t.Month(), t.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, cfg.Timezone.GetLocation()))
t.AddDate(0, 0, 6) t.AddDate(0, 0, 6)
} }
} }
@ -123,14 +125,14 @@ func (a *AlarmRepeated) FillExcepts(db *LevelDBStorage) error {
return nil return nil
} }
func (a *AlarmRepeated) GetNextOccurence(db *LevelDBStorage) *time.Time { func (a *AlarmRepeated) GetNextOccurence(cfg *config.Config, db *LevelDBStorage) *time.Time {
if len(a.Excepts) == 0 { if len(a.Excepts) == 0 {
a.FillExcepts(db) a.FillExcepts(cfg, db)
} }
now := time.Now() now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, now.Location()) today := time.Date(now.Year(), now.Month(), now.Day(), time.Time(*a.StartTime).Hour(), time.Time(*a.StartTime).Minute(), time.Time(*a.StartTime).Second(), 0, cfg.Timezone.GetLocation())
if now.After(today) { if now.After(today) {
today = today.AddDate(0, 0, 1) today = today.AddDate(0, 0, 1)
} }

View File

@ -6,19 +6,12 @@ import (
"math" "math"
"math/rand" "math/rand"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/flac"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/faiface/beep/wav"
"git.nemunai.re/nemunaire/reveil/config" "git.nemunai.re/nemunaire/reveil/config"
"git.nemunai.re/nemunaire/reveil/model" "git.nemunai.re/nemunaire/reveil/model"
) )
@ -29,8 +22,8 @@ type Player struct {
Playlist []string Playlist []string
MaxRunTime time.Duration MaxRunTime time.Duration
Stopper chan bool Stopper chan bool
currentCmd *exec.Cmd
sampleRate beep.SampleRate currentCmdCh chan bool
claironTime time.Duration claironTime time.Duration
claironFile string claironFile string
@ -38,7 +31,7 @@ type Player struct {
ntick int64 ntick int64
hasClaironed bool hasClaironed bool
launched time.Time launched time.Time
volume *effects.Volume volume uint16
dontUpdateVolume bool dontUpdateVolume bool
reverseOrder bool reverseOrder bool
playedItem int playedItem int
@ -49,6 +42,10 @@ func WakeUp(cfg *config.Config) (err error) {
return fmt.Errorf("Unable to start the player: a player is already running") return fmt.Errorf("Unable to start the player: a player is already running")
} }
seed := time.Now().Unix()
seed -= seed % 172800
rand.Seed(seed)
CommonPlayer, err = NewPlayer(cfg) CommonPlayer, err = NewPlayer(cfg)
if err != nil { if err != nil {
return err return err
@ -67,10 +64,11 @@ func NewPlayer(cfg *config.Config) (*Player, error) {
p := Player{ p := Player{
Stopper: make(chan bool, 1), Stopper: make(chan bool, 1),
currentCmdCh: make(chan bool, 1),
MaxRunTime: settings.MaxRunTime * time.Minute, MaxRunTime: settings.MaxRunTime * time.Minute,
sampleRate: beep.SampleRate(cfg.SampleRate),
claironTime: settings.GongInterval * time.Minute, claironTime: settings.GongInterval * time.Minute,
claironFile: reveil.CurrentGongPath(cfg), claironFile: reveil.CurrentGongPath(cfg),
reverseOrder: int(time.Now().Unix()/86400)%2 == 0,
} }
// Load our track list // Load our track list
@ -100,59 +98,53 @@ func NewPlayer(cfg *config.Config) (*Player, error) {
return &p, nil return &p, nil
} }
func loadFile(filepath string) (name string, s beep.StreamSeekCloser, format beep.Format, err error) { func (p *Player) playFile(filepath string) (err error) {
var fd *os.File p.currentCmd = exec.Command("paplay", filepath)
if err = p.currentCmd.Start(); err != nil {
name = path.Base(filepath) log.Println("Running paplay err: ", err.Error())
p.currentCmdCh <- true
fd, err = os.Open(filepath)
if err != nil {
return return
} }
switch strings.ToLower(path.Ext(filepath)) { log.Println("Running paplay ", filepath)
case ".flac":
s, format, err = flac.Decode(fd)
case ".mp3":
s, format, err = mp3.Decode(fd)
default:
s, format, err = wav.Decode(fd)
}
if err != nil { err = p.currentCmd.Wait()
fd.Close() p.currentCmdCh <- true
return
}
return return
} }
func (p *Player) WakeUp() { func (p *Player) WakeUp() {
log.Println("RUN WAKEUP FUNC")
log.Println("Playlist in use:", strings.Join(p.Playlist, " ; ")) log.Println("Playlist in use:", strings.Join(p.Playlist, " ; "))
// Create infinite stream // Prepare sound player
stream := beep.Iterate(func() beep.Streamer { p.volume = 3500
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
p.launched = time.Now()
// Prepare graceful shutdown
maxRun := time.After(p.MaxRunTime)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP)
p.currentCmdCh <- true
loop:
for {
select {
case <-p.currentCmdCh:
if !p.hasClaironed && time.Since(p.launched) >= p.claironTime { if !p.hasClaironed && time.Since(p.launched) >= p.claironTime {
log.Println("clairon time!") log.Println("clairon time!")
p.claironTime += p.claironTime / 2 p.claironTime += p.claironTime / 2
_, sample, format, err := loadFile(p.claironFile) p.SetVolume(65535)
if err == nil {
p.volume.Volume = 0.1
p.dontUpdateVolume = true p.dontUpdateVolume = true
if format.SampleRate != p.sampleRate { go p.playFile(p.claironFile)
return beep.Resample(3, format.SampleRate, p.sampleRate, sample)
} else { } else {
return sample
}
} else {
log.Println("Error loading clairon:", err)
}
}
p.dontUpdateVolume = false p.dontUpdateVolume = false
p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500)
p.SetVolume(p.volume)
if p.reverseOrder { if p.reverseOrder {
p.playedItem -= 1 p.playedItem -= 1
@ -166,54 +158,25 @@ func (p *Player) WakeUp() {
p.playedItem = len(p.Playlist) - 1 p.playedItem = len(p.Playlist) - 1
} }
// Load our current item log.Println("Next track: ", p.Playlist[p.playedItem])
_, sample, format, err := loadFile(p.Playlist[p.playedItem]) go p.playFile(p.Playlist[p.playedItem])
if err != nil {
log.Println("Error loading audio file %s: %s", p.Playlist[p.playedItem], err.Error())
return nil
} }
// Resample if needed
log.Println("playing list item:", p.playedItem, "/", len(p.Playlist), ":", p.Playlist[p.playedItem])
if format.SampleRate != p.sampleRate {
return beep.Resample(3, format.SampleRate, p.sampleRate, sample)
} else {
return sample
}
})
// Prepare sound player
log.Println("Initializing sound player...")
speaker.Init(p.sampleRate, p.sampleRate.N(time.Second/10))
defer speaker.Close()
p.volume = &effects.Volume{stream, 10, -2, false}
speaker.Play(p.volume)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
p.launched = time.Now()
// Prepare graceful shutdown
maxRun := time.After(p.MaxRunTime)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, syscall.SIGHUP)
loop:
for {
select {
case <-p.Stopper:
log.Println("Stopper activated")
break loop
case <-maxRun:
log.Println("Max run time exhausted")
break loop
case <-ticker.C: case <-ticker.C:
p.ntick += 1 p.ntick += 1
if !p.dontUpdateVolume { if !p.dontUpdateVolume {
p.volume.Volume = -2 - math.Log(5/float64(p.ntick))/3 p.volume = 3500 + uint16(math.Log(1+float64(p.ntick)/8)*9500)
p.SetVolume(p.volume)
} }
case <-p.Stopper:
log.Println("Stopper activated")
break loop
case <-maxRun:
log.Println("Max run time exhausted")
break loop
case <-interrupt: case <-interrupt:
break loop break loop
} }
@ -223,31 +186,47 @@ loop:
// Calm down music // Calm down music
loopcalm: loopcalm:
for i := 0; i < 2000; i += 1 { for i := 0; i < 128 && p.volume >= 15000; i += 1 {
p.volume.Volume -= 0.001 timer := time.NewTimer(40 * time.Millisecond)
p.volume -= 256
p.SetVolume(p.volume)
timer := time.NewTimer(4 * time.Millisecond)
select { select {
case <-p.Stopper: case <-p.Stopper:
log.Println("Hard stop received...") log.Println("Hard stop received...")
timer.Stop() timer.Stop()
p.volume.Volume = 0 p.volume = 0
break loopcalm break loopcalm
case <-timer.C: case <-timer.C:
break break
} }
} }
if p.currentCmd != nil && p.currentCmd.Process != nil {
p.currentCmd.Process.Kill()
}
p.SetVolume(65535)
if p == CommonPlayer { if p == CommonPlayer {
log.Println("Destoying common player") log.Println("Destoying common player")
CommonPlayer = nil CommonPlayer = nil
// TODO: find a better way to deallocate the card
os.Exit(42)
} }
} }
func (p *Player) NextTrack() {
if p.currentCmd != nil && p.currentCmd.Process != nil {
p.currentCmd.Process.Kill()
}
}
func (p *Player) SetVolume(volume uint16) error {
cmd := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d", volume))
return cmd.Run()
}
func (p *Player) Stop() error { func (p *Player) Stop() error {
log.Println("Trying to stop the player") log.Println("Trying to stop the player")
p.Stopper <- true p.Stopper <- true

22
ui/src/lib/Toaster.svelte Normal file
View File

@ -0,0 +1,22 @@
<script>
import {
Toast,
ToastBody,
ToastHeader,
} from 'sveltestrap';
import { ToastsStore } from '$lib/stores/toasts';
</script>
<div class="toast-container position-absolute top-0 end-0 p-3">
{#each $ToastsStore.toasts as toast}
<Toast>
<ToastHeader toggle={toast.close} icon={toast.color}>
{#if toast.title}{toast.title}{:else}Réveil{/if}
</ToastHeader>
<ToastBody>
{toast.msg}
</ToastBody>
</Toast>
{/each}
</div>

View File

@ -8,6 +8,14 @@
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import Toaster from '$lib/components/Toaster.svelte'; import Toaster from '$lib/components/Toaster.svelte';
import { ToastsStore } from '$lib/stores/toasts';
window.onunhandledrejection = (e) => {
ToastsStore.addErrorToast({
message: e.reason,
timeout: 7500,
})
}
</script> </script>
<svelte:head> <svelte:head>

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"short_name": "Gustus", "short_name": "Réveil",
"name": "Gustus", "name": "Réveil",
"version": "0.1", "version": "0.1",
"author": "nemucorp", "author": "nemucorp",
"start_url": "/", "start_url": "/",
@ -12,9 +12,9 @@
"sizes": "512x512" "sizes": "512x512"
} }
], ],
"background_color": "#d62a49", "background_color": "#e83e8c",
"display": "standalone", "display": "standalone",
"scope": "/", "scope": "/",
"theme_color": "#ffffff", "theme_color": "#ffffff",
"description": "Retrouvez facilement toutes vos recettes préférées" "description": "Quand est-ce qu'on se lève ?"
} }