diff --git a/assets/prepare/assets.go b/assets/prepare/assets.go index 8467c69..b8ca225 100644 --- a/assets/prepare/assets.go +++ b/assets/prepare/assets.go @@ -1,10 +1,12 @@ -//nolint:gochecknoglobals,golint,stylecheck +//nolint:gochecknoglobals package prepare import "embed" +// Files are files to be copied to the system. //go:embed files var Files embed.FS +// Templates are the used templates for creating file on the system. //go:embed templates var Templates embed.FS diff --git a/internal/config/config.go b/internal/config/config.go index dead9c2..ccef878 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,7 @@ import ( api "go.xsfx.dev/schnutibox/pkg/api/v1" ) +// Cfg stores a global config object. var Cfg Config type Config struct { diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index a3c6eb8..fdcf604 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -4,18 +4,64 @@ package metrics import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + api "go.xsfx.dev/schnutibox/pkg/api/v1" ) -var ( - TracksPlayed = promauto.NewCounterVec( - prometheus.CounterOpts{ - Name: "schnutibox_played_tracks_total", - }, - []string{"rfid", "name"}) +// Plays is a map of tracked plays. +// Its a map, so its easier to check if the metric is already initialized +// and usable. The Key string is the RFID identification. +var Plays = make(map[string]*api.IdentifyResponse) - BoxErrors = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "schnutbox_errors_total", - }, - ) +// NewPlay initialize a new play metric. +func NewPlay(rfid, name string, uris []string) { + if _, ok := Plays[rfid]; !ok { + Plays[rfid] = &api.IdentifyResponse{ + Name: name, + Uris: uris, + } + } +} + +// Play is the play metric. +var Play = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "schnutibox_plays", + Help: "play metrics", + }, + []string{"rfid", "name"}, ) + +// BoxErrors counts schnutibox errors. +var BoxErrors = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "schnutibox_errors_total", + Help: "counter of errors", + }, +) + +// tracksEqual checks if uris slices are equal. +// This is needed to search for the right play item. +func tracksEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +} + +// Set sets `1` on play gauge if item is playing, a `0` on every other play. +func Set(uris []string, state string) { + for r, p := range Plays { + if tracksEqual(uris, p.Uris) && state == "play" { + Play.WithLabelValues(r, p.Name).Set(1) + } else { + Play.WithLabelValues(r, p.Name).Set(0) + } + } +} diff --git a/pkg/prepare/prepare.go b/pkg/prepare/prepare.go index e19cde7..22db625 100644 --- a/pkg/prepare/prepare.go +++ b/pkg/prepare/prepare.go @@ -33,6 +33,7 @@ const ( snapclientGroup = "snapclient" ) +// Cfg represents the structured data for the schnutibox config file. var Cfg = struct { RFIDReader string ReadOnly bool diff --git a/pkg/run/run.go b/pkg/run/run.go index 7ac4ada..c54f5f7 100644 --- a/pkg/run/run.go +++ b/pkg/run/run.go @@ -3,6 +3,7 @@ package run import ( "fmt" + "time" "github.com/fhs/gompd/v2/mpd" "github.com/rs/zerolog" @@ -14,6 +15,8 @@ import ( "go.xsfx.dev/schnutibox/pkg/web" ) +const TickerTime = time.Second + type mpc struct { conn *mpd.Client } @@ -37,9 +40,6 @@ func (m *mpc) clear(logger zerolog.Logger) error { func (m *mpc) play(logger zerolog.Logger, rfid string, name string, uris []string) error { logger.Info().Msg("trying to add tracks") - // Metric labels. - mLabels := []string{rfid, name} - // Stop playing track. if err := m.stop(logger); err != nil { metrics.BoxErrors.Inc() @@ -65,11 +65,60 @@ func (m *mpc) play(logger zerolog.Logger, rfid string, name string, uris []strin } } - metrics.TracksPlayed.WithLabelValues(mLabels...).Inc() + metrics.NewPlay(rfid, name, uris) return m.conn.Play(-1) } +func (m *mpc) watch() { + log.Debug().Msg("starting watch") + + ticker := time.NewTicker(TickerTime) + + go func() { + for { + <-ticker.C + + // Check if we can connect to MPD server. + if err := m.conn.Ping(); err != nil { + log.Error().Err(err).Msg("could not ping MPD server") + metrics.BoxErrors.Inc() + + continue + } + + // Getting playlist info. + attrs, err := m.conn.PlaylistInfo(-1, -1) + if err != nil { + log.Error().Err(err).Msg("could not get playlist info") + metrics.BoxErrors.Inc() + + continue + } + + // Stores the tracklist it got from the MPD server. + uris := []string{} + + // Builds uri list. + for _, a := range attrs { + uris = append(uris, a["file"]) + } + + // Gettings MPD state. + s, err := m.conn.Status() + if err != nil { + log.Error().Err(err).Msg("could not get status") + metrics.BoxErrors.Inc() + + continue + } + + // Sets the metrics. + metrics.Set(uris, s["state"]) + } + }() +} + func Run(cmd *cobra.Command, args []string) { log.Info().Msg("starting the RFID reader") @@ -80,6 +129,16 @@ func Run(cmd *cobra.Command, args []string) { log.Fatal().Err(err).Msg("could not start RFID reader") } + // Create MPD connection on every received event. + c, err := mpd.Dial("tcp", fmt.Sprintf("%s:%d", config.Cfg.MPD.Hostname, config.Cfg.MPD.Port)) + if err != nil { + log.Fatal().Err(err).Msg("could not connect to MPD server") + } + + m := newMpc(c) + + m.watch() + go func() { var id string @@ -89,16 +148,6 @@ func Run(cmd *cobra.Command, args []string) { logger := log.With().Str("id", id).Logger() logger.Info().Msg("received id") - // Create MPD connection on every received event. - c, err := mpd.Dial("tcp", fmt.Sprintf("%s:%d", config.Cfg.MPD.Hostname, config.Cfg.MPD.Port)) - if err != nil { - logger.Error().Err(err).Msg("could not connect to MPD server") - - continue - } - - m := newMpc(c) - // Check of stop tag was detected. if id == config.Cfg.Meta.Stop { logger.Info().Msg("stopping")