mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-29 21:27:05 +02:00
parent
0f4b04c641
commit
ed191d2f15
5 changed files with 68 additions and 22 deletions
|
@ -61,6 +61,13 @@ include-skipped = true
|
|||
# If true (default), new listens will be appended to the existing file. Set to
|
||||
# false to overwrite the file and create a new scrobbler log on every run.
|
||||
append = true
|
||||
# Specify the time zone of the listens in the scrobbler log. While the log files
|
||||
# are supposed to contain Unix timestamps, which are always in UTC, the player
|
||||
# writing the log might not be time zone aware. This can cause the timestamps
|
||||
# to be in a different time zone. Use the time-zone setting to specify a
|
||||
# different time zone, e.g. "Europe/Berlin" or "America/New_York".
|
||||
# The default is UTC.
|
||||
time-zone = "UTC"
|
||||
|
||||
[service.jspf]
|
||||
# Write listens and loves to JSPF playlist files (https://xspf.org/jspf)
|
||||
|
|
|
@ -18,6 +18,7 @@ package scrobblerlog
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -34,6 +35,7 @@ type ScrobblerLogBackend struct {
|
|||
includeSkipped bool
|
||||
append bool
|
||||
file *os.File
|
||||
timezone *time.Location
|
||||
log scrobblerlog.ScrobblerLog
|
||||
}
|
||||
|
||||
|
@ -53,6 +55,10 @@ func (b *ScrobblerLogBackend) Options() []models.BackendOption {
|
|||
Label: i18n.Tr("Append to file"),
|
||||
Type: models.Bool,
|
||||
Default: "true",
|
||||
}, {
|
||||
Name: "time-zone",
|
||||
Label: i18n.Tr("Specify a time zone for the listen timestamps"),
|
||||
Type: models.String,
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -60,6 +66,14 @@ func (b *ScrobblerLogBackend) InitConfig(config *config.ServiceConfig) error {
|
|||
b.filePath = config.GetString("file-path")
|
||||
b.includeSkipped = config.GetBool("include-skipped", false)
|
||||
b.append = config.GetBool("append", true)
|
||||
timezone := config.GetString("time-zone")
|
||||
if timezone != "" {
|
||||
location, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid time-zone %q: %w", timezone, err)
|
||||
}
|
||||
b.log.FallbackTimezone = location
|
||||
}
|
||||
b.log = scrobblerlog.ScrobblerLog{
|
||||
TZ: scrobblerlog.TZ_UTC,
|
||||
Client: "Rockbox unknown $Revision$",
|
||||
|
|
|
@ -33,3 +33,13 @@ func TestInitConfig(t *testing.T) {
|
|||
err := backend.InitConfig(&service)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInitConfigInvalidTimezone(t *testing.T) {
|
||||
c := viper.New()
|
||||
configuredTimezone := "Invalid/Timezone"
|
||||
c.Set("time-zone", configuredTimezone)
|
||||
service := config.NewServiceConfig("test", c)
|
||||
backend := scrobblerlog.ScrobblerLogBackend{}
|
||||
err := backend.InitConfig(&service)
|
||||
assert.ErrorContains(t, err, `Invalid time-zone "Invalid/Timezone"`)
|
||||
}
|
||||
|
|
|
@ -71,10 +71,12 @@ type Record struct {
|
|||
|
||||
// Represents a scrobbler log file.
|
||||
type ScrobblerLog struct {
|
||||
TZ TZInfo
|
||||
Client string
|
||||
Records []Record
|
||||
location *time.Location
|
||||
TZ TZInfo
|
||||
Client string
|
||||
Records []Record
|
||||
// Timezone to be used for timestamps in the log file,
|
||||
// if TZ is set to [TZ_UNKNOWN].
|
||||
FallbackTimezone *time.Location
|
||||
}
|
||||
|
||||
func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error {
|
||||
|
@ -173,7 +175,6 @@ func (l *ScrobblerLog) ReadHeader(reader *bufio.Reader) error {
|
|||
timezone, found := strings.CutPrefix(text, "#TZ/")
|
||||
if found {
|
||||
l.TZ = TZInfo(timezone)
|
||||
l.location = locationFromTimezone(l.TZ)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -223,6 +224,11 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
|
|||
return record, err
|
||||
}
|
||||
|
||||
var timezone *time.Location = nil
|
||||
if l.TZ == TZ_UNKNOWN {
|
||||
timezone = l.FallbackTimezone
|
||||
}
|
||||
|
||||
record = Record{
|
||||
ArtistName: row[0],
|
||||
AlbumName: row[1],
|
||||
|
@ -230,7 +236,7 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
|
|||
TrackNumber: trackNumber,
|
||||
Duration: time.Duration(duration) * time.Second,
|
||||
Rating: Rating(row[5]),
|
||||
Timestamp: timeFromLocalTimestamp(timestamp, l.location),
|
||||
Timestamp: timeFromLocalTimestamp(timestamp, timezone),
|
||||
}
|
||||
|
||||
if len(row) > 7 {
|
||||
|
@ -240,26 +246,20 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
|
|||
return record, nil
|
||||
}
|
||||
|
||||
// Convert the timezone string from the header to a time.Location.
|
||||
// Often this is set to "UNKNOWN" in the log file, in which case it defaults
|
||||
// to UTC.
|
||||
func locationFromTimezone(timezone TZInfo) *time.Location {
|
||||
location, err := time.LoadLocation(string(timezone))
|
||||
if err != nil {
|
||||
return time.UTC
|
||||
}
|
||||
return location
|
||||
}
|
||||
|
||||
// Convert a Unix timestamp to a time.Time object, but treat the timestamp
|
||||
// Convert a Unix timestamp to a [time.Time] object, but treat the timestamp
|
||||
// as being in the given location's timezone instead of UTC.
|
||||
// If location is nil, the timestamp is returned as UTC.
|
||||
func timeFromLocalTimestamp(timestamp int64, location *time.Location) time.Time {
|
||||
t := time.Unix(timestamp, 0)
|
||||
|
||||
// The time is now in UTC. Get the offset to the requested timezone.
|
||||
_, offset := t.In(location).Zone()
|
||||
if offset != 0 {
|
||||
t = t.Add(time.Duration(offset) * time.Second)
|
||||
// The time is now in UTC. Get the offset to the requested timezone
|
||||
// and shift the time accordingly.
|
||||
if location != nil {
|
||||
_, offset := t.In(location).Zone()
|
||||
if offset != 0 {
|
||||
t = t.Add(time.Duration(offset) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
|
|
@ -81,6 +81,21 @@ func TestParserExcludeSkipped(t *testing.T) {
|
|||
record4.MusicBrainzRecordingID)
|
||||
}
|
||||
|
||||
func TestParserFallbackTimezone(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
data := bytes.NewBufferString(testScrobblerLog)
|
||||
result := scrobblerlog.ScrobblerLog{
|
||||
FallbackTimezone: time.FixedZone("UTC+2", 7200),
|
||||
}
|
||||
err := result.Parse(data, false)
|
||||
require.NoError(t, err)
|
||||
record1 := result.Records[0]
|
||||
assert.Equal(
|
||||
time.Unix(1260342084, 0).Add(2*time.Hour),
|
||||
record1.Timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
data := make([]byte, 0, 10)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue