diff options
Diffstat (limited to '')
| -rw-r--r-- | internal/scrobbler/db.go | 55 | ||||
| -rw-r--r-- | internal/scrobbler/record.go | 42 | ||||
| -rw-r--r-- | internal/scrobbler/record_test.go | 53 | ||||
| -rw-r--r-- | internal/scrobbler/scrobbler.go | 118 |
4 files changed, 268 insertions, 0 deletions
diff --git a/internal/scrobbler/db.go b/internal/scrobbler/db.go new file mode 100644 index 0000000..5f80aa4 --- /dev/null +++ b/internal/scrobbler/db.go @@ -0,0 +1,55 @@ +package scrobbler + +import ( + "database/sql" + "fmt" + "os" + + _ "github.com/mattn/go-sqlite3" +) + +func initdb(dbpath string) error { + if _, err := os.Stat(dbpath); err == nil { + return fmt.Errorf("%s already exists", dbpath) + } + + db, err := sql.Open("sqlite3", dbpath) + if err != nil { + return err + } + defer db.Close() + + sqlStmt := `create table records (id text primary key, + title text, + artist text, + album text, + duration int, + playtime int, + time timestamp + );` + + _, err = db.Exec(sqlStmt) + if err != nil { + return err + } + + return nil +} + +func opendatabase(dbpath string) (*sql.DB, error) { + var err error + _, err = os.Stat(dbpath) + + if err != nil { + if err := initdb(dbpath); err != nil { + return nil, err + } + } + + db, err := sql.Open("sqlite3", dbpath) + if err != nil { + return nil, fmt.Errorf("unable to open database: %s", err) + } + + return db, nil +} diff --git a/internal/scrobbler/record.go b/internal/scrobbler/record.go new file mode 100644 index 0000000..e252fd3 --- /dev/null +++ b/internal/scrobbler/record.go @@ -0,0 +1,42 @@ +package scrobbler + +import ( + "strconv" + "time" + + "github.com/fhs/gompd/v2/mpd" + "github.com/google/uuid" +) + +type Record struct { + Id uuid.UUID + Title string + Album string + Artist string + Duration time.Duration + Timestamp time.Time +} + +func NewRecord(attrs mpd.Attrs) (*Record, error) { + record := Record{ + Id: uuid.New(), + Title: attrs["Title"], + Album: attrs["Album"], + Artist: attrs["Artist"], + Timestamp: time.Now(), + } + + dur, err := strconv.ParseFloat(attrs["duration"], 32) + if err != nil { + return nil, err + } + + record.Duration = time.Second * time.Duration(dur) + return &record, nil +} + +func (r *Record) EqualAttrs(attrs mpd.Attrs) bool { + return r.Title == attrs["Title"] && + r.Album == attrs["Album"] && + r.Artist == attrs["Artist"] +} diff --git a/internal/scrobbler/record_test.go b/internal/scrobbler/record_test.go new file mode 100644 index 0000000..3bf8554 --- /dev/null +++ b/internal/scrobbler/record_test.go @@ -0,0 +1,53 @@ +package scrobbler + +import ( + "testing" + + "github.com/fhs/gompd/v2/mpd" +) + +func TestNewRecord(t *testing.T) { + song := mpd.Attrs{ + "Artist": "Nine Inch Nails", + "Album": "The Downward Spiral", + "Title": "Reptile", + "duration": "411.00", + } + + record, err := NewRecord(song) + if err != nil { + t.Errorf("NewRecord returned an error: %s", err) + } + if record == nil { + t.Errorf("NewRecord returned nil record") + } +} + +func TestRecordEqualAttrs(t *testing.T) { + s1 := mpd.Attrs{ + "Artist": "Nine Inch Nails", + "Album": "The Downward Spiral", + "Title": "Reptile", + "duration": "411.00", + } + + s2 := mpd.Attrs{ + "Artist": "Nine Inch Nails", + "Album": "The Downward Spiral", + "Title": "Closer", + "duration": "373.00", + } + + r, err := NewRecord(s1) + if err != nil { + t.Errorf("NewRecord returned an error: %s", err) + } + + if !r.EqualAttrs(s1) { + t.Errorf("EqualAttrs expected true got false") + } + + if r.EqualAttrs(s2) { + t.Errorf("EqualAttrs expected false got true") + } +} diff --git a/internal/scrobbler/scrobbler.go b/internal/scrobbler/scrobbler.go new file mode 100644 index 0000000..f0f9d0e --- /dev/null +++ b/internal/scrobbler/scrobbler.go @@ -0,0 +1,118 @@ +package scrobbler + +import ( + "database/sql" + "log" + "time" + + "golang.fcuny.net/mpd-stats/internal/mpd" +) + +type Scrobbler struct { + player *mpd.Player + db *sql.DB +} + +func NewScrobbler(net string, addr string, dbpath string) (*Scrobbler, error) { + p, err := mpd.NewPlayer(net, addr) + if err != nil { + return nil, err + } + + db, err := opendatabase(dbpath) + if err != nil { + return nil, err + } + + s := Scrobbler{ + player: p, + db: db, + } + + 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 != "" { + 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.Printf("could not get current song: %v", err) + } + + if currentRecord == nil { + currentRecord, err = NewRecord(attrs) + if err != nil { + log.Printf("could not create a log: %v", err) + } + previousRecord = 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.Printf("could not create a log: %v", err) + } + } + + 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) + } + } + } +} + +func (s *Scrobbler) save(record *Record) error { + _, err := s.db.Exec("insert into records(id, title, artist, album, duration, playtime, time) values(?, ?, ?, ?, ?, 0, ?)", + record.Id, + record.Title, + record.Artist, + record.Album, + int(record.Duration.Seconds()), + record.Timestamp, + ) + 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 +} |
