From 6440b7f28190eff567fa411ead2adbd80d2d870e Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 10 Oct 2021 11:44:47 -0700 Subject: scrobbler: add functions to create and run it Add a new function to create a scrobbler. The function takes care of creating the mpd client. Add a function to run the scrobbler, which takes care of creating a new record when needed. This will simplify the interface for the caller, as all they really care about is: create the scrobbler, close it when we're done, and collect songs information while we listen to our music. --- internal/scrobbler/scrobbler.go | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 internal/scrobbler/scrobbler.go (limited to 'internal/scrobbler/scrobbler.go') diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go new file mode 100644 index 0000000..061b909 --- /dev/null +++ b/internal/scrobbler/scrobbler.go @@ -0,0 +1,66 @@ +package scrobbler + +import ( + "log" + + "golang.fcuny.net/mpd-stats/internal/mpd" +) + +type Scrobbler struct { + player *mpd.Player +} + +func NewScrobbler(net string, addr string) (*Scrobbler, error) { + var s Scrobbler + + p, err := mpd.NewPlayer(net, addr) + if err != nil { + return nil, err + } + + s.player = p + return &s, nil +} + +func (s *Scrobbler) Close() error { + return s.player.Close() +} + +func (s *Scrobbler) Run() error { + var ( + currentRecord *Record + previousRecord *Record + ) + + for { + e := <-s.player.Watcher.Event + if e != "" { + attrs, err := s.player.Client.CurrentSong() + if err != nil { + log.Fatalf("could not get current song: %v", err) + } + + if currentRecord == nil { + currentRecord, err = NewRecord(attrs) + if err != nil { + log.Fatalf("could not create a log: %v", err) + } + log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) + previousRecord = currentRecord + continue + } + + if currentRecord.Title != attrs["Title"] || currentRecord.Artist != attrs["Artist"] || currentRecord.Album != attrs["Album"] { + currentRecord, err = NewRecord(attrs) + if err != nil { + log.Fatalf("could not create a log: %v", err) + } + } + + if currentRecord.Id != previousRecord.Id { + log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) + previousRecord = currentRecord + } + } + } +} -- cgit v1.2.3 From c95f72a953e6a9068c1e7b89f530fa05f10c4bde Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 10 Oct 2021 13:01:21 -0700 Subject: mpd-stats: pass database path to the scrobbler When creating a scrobbler, we provide the path to the database. The scrobbler then get a handler to the database. When a new record is created, we persist it to the database using the `save` function. --- internal/scrobbler/scrobbler.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) (limited to 'internal/scrobbler/scrobbler.go') diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go index 061b909..a31569e 100644 --- a/internal/scrobbler/scrobbler.go +++ b/internal/scrobbler/scrobbler.go @@ -1,6 +1,7 @@ package scrobbler import ( + "database/sql" "log" "golang.fcuny.net/mpd-stats/internal/mpd" @@ -8,17 +9,25 @@ import ( type Scrobbler struct { player *mpd.Player + db *sql.DB } -func NewScrobbler(net string, addr string) (*Scrobbler, error) { - var s Scrobbler - +func NewScrobbler(net string, addr string, dbpath string) (*Scrobbler, error) { p, err := mpd.NewPlayer(net, addr) if err != nil { return nil, err } - s.player = p + db, err := opendatabase(dbpath) + if err != nil { + return nil, err + } + + s := Scrobbler{ + player: p, + db: db, + } + return &s, nil } @@ -47,6 +56,7 @@ func (s *Scrobbler) Run() error { } log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) previousRecord = currentRecord + s.save(currentRecord) continue } @@ -60,7 +70,20 @@ func (s *Scrobbler) Run() error { if currentRecord.Id != previousRecord.Id { log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) previousRecord = currentRecord + s.save(currentRecord) } } } } + +func (s *Scrobbler) save(record *Record) error { + _, err := s.db.Exec("insert into records(id, title, artist, album, duration, time) values(?, ?, ?, ?, ?, ?)", + record.Id, + record.Title, + record.Artist, + record.Album, + int(record.Duration.Seconds()), + record.Timestamp, + ) + return err +} -- cgit v1.2.3 From e8059bd9a8ea7c1bba29ca947c7c7fa7eedee8cb Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 10 Oct 2021 13:24:29 -0700 Subject: scrobbler: use helper function EqualAttrs To compare the current attributes with the current record, we can use the helper `EqualAttrs` which will tell us if we need to create a new record or not. --- internal/scrobbler/scrobbler.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'internal/scrobbler/scrobbler.go') diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go index a31569e..e16458c 100644 --- a/internal/scrobbler/scrobbler.go +++ b/internal/scrobbler/scrobbler.go @@ -54,13 +54,12 @@ func (s *Scrobbler) Run() error { if err != nil { log.Fatalf("could not create a log: %v", err) } - log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) previousRecord = currentRecord s.save(currentRecord) continue } - if currentRecord.Title != attrs["Title"] || currentRecord.Artist != attrs["Artist"] || currentRecord.Album != attrs["Album"] { + if !currentRecord.EqualAttrs(attrs) { currentRecord, err = NewRecord(attrs) if err != nil { log.Fatalf("could not create a log: %v", err) @@ -68,7 +67,6 @@ func (s *Scrobbler) Run() error { } if currentRecord.Id != previousRecord.Id { - log.Printf("we're playing %s/%s/%s [%s]\n", currentRecord.Artist, currentRecord.Album, currentRecord.Title, currentRecord.Duration) previousRecord = currentRecord s.save(currentRecord) } -- cgit v1.2.3 From a4fe1e3ef4e06e19f146a0a6498cdb550ef8b4f3 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Sun, 10 Oct 2021 17:52:19 -0700 Subject: scrobbler: record how long a song was played Add a column `playtime` to the records table to keep track of how long a song was played. With this information, in the future, we will be able to sum up how long we listen to music, but also which songs were skipped. --- internal/scrobbler/scrobbler.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'internal/scrobbler/scrobbler.go') diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go index e16458c..df8e46a 100644 --- a/internal/scrobbler/scrobbler.go +++ b/internal/scrobbler/scrobbler.go @@ -3,6 +3,7 @@ package scrobbler import ( "database/sql" "log" + "time" "golang.fcuny.net/mpd-stats/internal/mpd" ) @@ -67,6 +68,9 @@ func (s *Scrobbler) Run() error { } if currentRecord.Id != previousRecord.Id { + if err := s.update(previousRecord); err != nil { + log.Printf("failed to update record %s: %s", previousRecord.Id, err) + } previousRecord = currentRecord s.save(currentRecord) } @@ -75,7 +79,7 @@ func (s *Scrobbler) Run() error { } func (s *Scrobbler) save(record *Record) error { - _, err := s.db.Exec("insert into records(id, title, artist, album, duration, time) values(?, ?, ?, ?, ?, ?)", + _, err := s.db.Exec("insert into records(id, title, artist, album, duration, playtime, time) values(?, ?, ?, ?, ?, 0, ?)", record.Id, record.Title, record.Artist, @@ -85,3 +89,13 @@ func (s *Scrobbler) save(record *Record) error { ) return err } + +func (s *Scrobbler) update(record *Record) error { + tnow := time.Now() + playtime := tnow.Sub(record.Timestamp).Seconds() + _, err := s.db.Exec("update records set playtime = ? where id = ?", + int(playtime), + record.Id, + ) + return err +} -- cgit v1.2.3 From ed67fc99451452b7df32f6609f8e9798089e243f Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Mon, 11 Oct 2021 19:34:59 -0700 Subject: scrobbler: read mpd status before processing song If the status of the player is "stop", we don't have a new song to handle. In this case, if there's a current song, let's update the status and clear our state. Closes #1. --- internal/scrobbler/scrobbler.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'internal/scrobbler/scrobbler.go') diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go index df8e46a..f0f9d0e 100644 --- a/internal/scrobbler/scrobbler.go +++ b/internal/scrobbler/scrobbler.go @@ -45,25 +45,42 @@ func (s *Scrobbler) Run() error { for { e := <-s.player.Watcher.Event if e != "" { + status, err := s.player.Client.Status() + if err != nil { + log.Printf("could not read the status: %v", err) + } + + if status["state"] == "stop" { + if currentRecord != nil { + if err := s.update(currentRecord); err != nil { + log.Printf("failed to update record %s: %s", currentRecord.Id, err) + } + currentRecord = nil + } + continue + } + attrs, err := s.player.Client.CurrentSong() if err != nil { - log.Fatalf("could not get current song: %v", err) + log.Printf("could not get current song: %v", err) } if currentRecord == nil { currentRecord, err = NewRecord(attrs) if err != nil { - log.Fatalf("could not create a log: %v", err) + log.Printf("could not create a log: %v", err) } previousRecord = currentRecord - s.save(currentRecord) + if err := s.save(currentRecord); err != nil { + log.Printf("failed to insert record %s: %s", currentRecord.Id, err) + } continue } if !currentRecord.EqualAttrs(attrs) { currentRecord, err = NewRecord(attrs) if err != nil { - log.Fatalf("could not create a log: %v", err) + log.Printf("could not create a log: %v", err) } } -- cgit v1.2.3