diff options
Diffstat (limited to 'tools/mpd-stats')
-rw-r--r-- | tools/mpd-stats/LICENSE.txt | 20 | ||||
-rw-r--r-- | tools/mpd-stats/Makefile | 12 | ||||
-rw-r--r-- | tools/mpd-stats/README.org | 22 | ||||
-rw-r--r-- | tools/mpd-stats/cmd/mpd-scrobbler/main.go | 57 | ||||
-rw-r--r-- | tools/mpd-stats/go.mod | 9 | ||||
-rw-r--r-- | tools/mpd-stats/go.sum | 6 | ||||
-rw-r--r-- | tools/mpd-stats/internal/mpd/mpd.go | 54 | ||||
-rw-r--r-- | tools/mpd-stats/internal/scrobbler/db.go | 55 | ||||
-rw-r--r-- | tools/mpd-stats/internal/scrobbler/record.go | 42 | ||||
-rw-r--r-- | tools/mpd-stats/internal/scrobbler/record_test.go | 53 | ||||
-rw-r--r-- | tools/mpd-stats/internal/scrobbler/scrobbler.go | 118 | ||||
-rw-r--r-- | tools/mpd-stats/systemd/mpd-scrobbler.service | 42 |
12 files changed, 0 insertions, 490 deletions
diff --git a/tools/mpd-stats/LICENSE.txt b/tools/mpd-stats/LICENSE.txt deleted file mode 100644 index e614d87..0000000 --- a/tools/mpd-stats/LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2021 Franck Cuny - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tools/mpd-stats/Makefile b/tools/mpd-stats/Makefile deleted file mode 100644 index 0edb9dd..0000000 --- a/tools/mpd-stats/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -GO_INSTALL_ARGS := -trimpath - -.PHONY: install - -install: - @go install $(GO_INSTALL_ARGS) ./cmd/mpd-scrobbler - @install -m 0644 systemd/mpd-scrobbler.service $(HOME)/.config/systemd/user/ - @echo "reloading systemd" - @systemctl --user daemon-reload - @echo "starting the unit" - @systemctl --user restart mpd-scrobbler - @systemctl --user status mpd-scrobbler diff --git a/tools/mpd-stats/README.org b/tools/mpd-stats/README.org deleted file mode 100644 index 8c0a7d9..0000000 --- a/tools/mpd-stats/README.org +++ /dev/null @@ -1,22 +0,0 @@ -#+TITLE: mpd-stats - -Log played songs to extract statistics. This is similar to what libre.fm used to do, but locally. - -* Logging -Collect logs from mpd. A log record is composed of the following fields: -- id: UUID -- song's name: the name of the song -- song's album: the name of the album -- song's artist: the name of the artist -- song's duration: the duration of the song -- date: date the song was played - -The logs are recorded in a database (sqlite3 to start). -* Install -The Makefile assumes the system is running Linux and systemd. - -Run =make install=. This will: -- install the binary in your =GOPATH= (using =go install=) -- install a systemd unit file under =$HOME/.config/systemd/user= -- reload systemd unit files -- start the service diff --git a/tools/mpd-stats/cmd/mpd-scrobbler/main.go b/tools/mpd-stats/cmd/mpd-scrobbler/main.go deleted file mode 100644 index c2693a4..0000000 --- a/tools/mpd-stats/cmd/mpd-scrobbler/main.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - "path/filepath" - - "golang.fcuny.net/mpd-stats/internal/scrobbler" -) - -func main() { - var ( - mpdHost = flag.String("host", "localhost", "The MPD server to connect to (default: localhost)") - mpdPort = flag.Int("port", 6600, "The TCP port of the MPD server to connect to (default: 6600)") - ) - flag.Parse() - - net := "tcp" - addr := fmt.Sprintf("%s:%d", *mpdHost, *mpdPort) - - dbpath, err := getDbPath() - if err != nil { - log.Fatalf("failed to get the path to the database: %v", err) - } - - s, err := scrobbler.NewScrobbler(net, addr, dbpath) - if err != nil { - log.Fatalf("failed to create a client: %v", err) - } - - defer func() { - if err := s.Close(); err != nil { - log.Fatalf("failed to close the scrobbler: %v", err) - } - }() - - s.Run() -} - -func getDbPath() (string, error) { - xch := os.Getenv("XDG_CONFIG_HOME") - if xch == "" { - home := os.Getenv("HOME") - xch = filepath.Join(home, ".config") - } - - scrobblerHome := filepath.Join(xch, "mpd-scrobbler") - if _, err := os.Stat(scrobblerHome); os.IsNotExist(err) { - if err := os.Mkdir(scrobblerHome, 0755); err != nil { - return "", err - } - } - - return filepath.Join(scrobblerHome, "scrobbler.sql"), nil -} diff --git a/tools/mpd-stats/go.mod b/tools/mpd-stats/go.mod deleted file mode 100644 index cc9971c..0000000 --- a/tools/mpd-stats/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module golang.fcuny.net/mpd-stats - -go 1.17 - -require ( - github.com/fhs/gompd/v2 v2.2.0 - github.com/google/uuid v1.3.0 - github.com/mattn/go-sqlite3 v1.14.8 -) diff --git a/tools/mpd-stats/go.sum b/tools/mpd-stats/go.sum deleted file mode 100644 index fab0f00..0000000 --- a/tools/mpd-stats/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/fhs/gompd/v2 v2.2.0 h1:zdSYAAOzQ5cCCgYa5CoXkL0Vr0Cqb/b5JmTobirLc90= -github.com/fhs/gompd/v2 v2.2.0/go.mod h1:nNdZtcpD5VpmzZbRl5rV6RhxeMmAWTxEsSIMBkmMIy4= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= -github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= diff --git a/tools/mpd-stats/internal/mpd/mpd.go b/tools/mpd-stats/internal/mpd/mpd.go deleted file mode 100644 index 859348e..0000000 --- a/tools/mpd-stats/internal/mpd/mpd.go +++ /dev/null @@ -1,54 +0,0 @@ -package mpd - -import ( - "log" - "time" - - "github.com/fhs/gompd/v2/mpd" -) - -const ( - // List of subsystems: https://mpd.readthedocs.io/en/latest/protocol.html#querying-mpd-s-status - SubSystemPlayer = "player" -) - -type Player struct { - Watcher *mpd.Watcher - Client *mpd.Client -} - -func NewPlayer(net string, addr string) (*Player, error) { - var ( - p Player - err error - ) - - // We are only subscribing to the player subsystem - p.Watcher, err = mpd.NewWatcher(net, addr, "", SubSystemPlayer) - if err != nil { - log.Fatalf("failed to create a watcher: %v", err) - } - - p.Client, err = mpd.Dial(net, addr) - if err != nil { - log.Fatalf("failed to start mpd client: %v", err) - } - - go func() { - for range time.Tick(30 * time.Second) { - p.Client.Ping() - } - }() - - return &p, nil -} - -func (p *Player) Close() error { - if err := p.Watcher.Close(); err != nil { - return err - } - if err := p.Client.Close(); err != nil { - return err - } - return nil -} diff --git a/tools/mpd-stats/internal/scrobbler/db.go b/tools/mpd-stats/internal/scrobbler/db.go deleted file mode 100644 index 5f80aa4..0000000 --- a/tools/mpd-stats/internal/scrobbler/db.go +++ /dev/null @@ -1,55 +0,0 @@ -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/tools/mpd-stats/internal/scrobbler/record.go b/tools/mpd-stats/internal/scrobbler/record.go deleted file mode 100644 index e252fd3..0000000 --- a/tools/mpd-stats/internal/scrobbler/record.go +++ /dev/null @@ -1,42 +0,0 @@ -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/tools/mpd-stats/internal/scrobbler/record_test.go b/tools/mpd-stats/internal/scrobbler/record_test.go deleted file mode 100644 index 3bf8554..0000000 --- a/tools/mpd-stats/internal/scrobbler/record_test.go +++ /dev/null @@ -1,53 +0,0 @@ -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/tools/mpd-stats/internal/scrobbler/scrobbler.go b/tools/mpd-stats/internal/scrobbler/scrobbler.go deleted file mode 100644 index eb4eb9c..0000000 --- a/tools/mpd-stats/internal/scrobbler/scrobbler.go +++ /dev/null @@ -1,118 +0,0 @@ -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 == mpd.SubSystemPlayer { - 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 -} diff --git a/tools/mpd-stats/systemd/mpd-scrobbler.service b/tools/mpd-stats/systemd/mpd-scrobbler.service deleted file mode 100644 index e033a99..0000000 --- a/tools/mpd-stats/systemd/mpd-scrobbler.service +++ /dev/null @@ -1,42 +0,0 @@ -[Unit] -Description=mpd scrobbler -Documentation=https://git.fcuny.net/fcuny/mpd-stats -ConditionFileIsExecutable=%h/workspace/go/bin/mpd-scrobbler - -[Service] -ExecStart=%h/workspace/go/bin/mpd-scrobbler -Restart=on-failure - -PrivateTmp=yes -ProtectSystem=strict -NoNewPrivileges=yes -ProtectHome=yes - -# Prohibit access to any kind of namespacing: -RestrictNamespaces=yes - -# Make cgroup file system hierarchy inaccessible: -ProtectControlGroups=yes - -# Deny access to other user’s information in /proc: -ProtectProc=invisible - -# Only allow access to /proc pid files, no other files: -ProcSubset=pid - -# This daemon must not create any new files, but set the umask to 077 just in case. -UMask=077 - -# Filter dangerous system calls. The following is listed as safe basic choice -# in systemd.exec(5): -SystemCallArchitectures=native -SystemCallFilter=@system-service -SystemCallFilter=~@privileged -SystemCallFilter=~@resources -SystemCallErrorNumber=EPERM - -# Deny kernel execution domain changing: -LockPersonality=yes - -# Deny memory mappings that are writable and executable: -MemoryDenyWriteExecute=yes |