Moved specifc backends into separate packages

This commit is contained in:
Philipp Wolfer 2023-11-12 01:14:53 +01:00
parent dfaf21b234
commit 48c8843f91
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
17 changed files with 127 additions and 98 deletions

View file

@ -26,37 +26,16 @@ import (
"fmt"
"reflect"
"strings"
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/dump"
"go.uploadedlobster.com/scotty/backends/funkwhale"
"go.uploadedlobster.com/scotty/backends/listenbrainz"
"go.uploadedlobster.com/scotty/backends/maloja"
"go.uploadedlobster.com/scotty/backends/scrobblerlog"
"go.uploadedlobster.com/scotty/models"
)
type Backend interface {
FromConfig(config *viper.Viper) Backend
}
type ListenExport interface {
ExportListens(oldestTimestamp time.Time) ([]models.Listen, error)
}
type ListenImport interface {
ImportListens(listens []models.Listen, oldestTimestamp time.Time) (ImportResult, error)
}
type LovesExport interface {
ExportLoves(oldestTimestamp time.Time) ([]models.Love, error)
}
type LovesImport interface {
ImportLoves(loves []models.Love, oldestTimestamp time.Time) (ImportResult, error)
}
type ImportResult struct {
Count int
LastTimestamp time.Time
}
type BackendInfo struct {
Name string
ExportCapabilities []Capability
@ -96,15 +75,15 @@ func GetBackends() []BackendInfo {
return backends
}
var knownBackends = map[string]func() Backend{
"dump": func() Backend { return &DumpBackend{} },
"funkwhale-api": func() Backend { return &FunkwhaleApiBackend{} },
"listenbrainz-api": func() Backend { return &ListenBrainzApiBackend{} },
"maloja-api": func() Backend { return &MalojaApiBackend{} },
"scrobbler-log": func() Backend { return &ScrobblerLogBackend{} },
var knownBackends = map[string]func() models.Backend{
"dump": func() models.Backend { return &dump.DumpBackend{} },
"funkwhale-api": func() models.Backend { return &funkwhale.FunkwhaleApiBackend{} },
"listenbrainz-api": func() models.Backend { return &listenbrainz.ListenBrainzApiBackend{} },
"maloja-api": func() models.Backend { return &maloja.MalojaApiBackend{} },
"scrobbler-log": func() models.Backend { return &scrobblerlog.ScrobblerLogBackend{} },
}
func resolveBackend(config *viper.Viper) (string, Backend, error) {
func resolveBackend(config *viper.Viper) (string, models.Backend, error) {
backendName := config.GetString("backend")
backendType := knownBackends[backendName]
if backendType == nil {
@ -113,43 +92,43 @@ func resolveBackend(config *viper.Viper) (string, Backend, error) {
return backendName, backendType().FromConfig(config), nil
}
func implementsInterface[T interface{}](backend Backend) (bool, string) {
func implementsInterface[T interface{}](backend models.Backend) (bool, string) {
expectedInterface := reflect.TypeOf((*T)(nil)).Elem()
implements := backend != nil && reflect.TypeOf(backend).Implements(expectedInterface)
return implements, expectedInterface.Name()
}
func getExportCapabilities(backend Backend) []string {
func getExportCapabilities(backend models.Backend) []string {
caps := make([]Capability, 0)
var name string
var found bool
name, found = checkCapability[ListenExport](backend, "export")
name, found = checkCapability[models.ListenExport](backend, "export")
if found {
caps = append(caps, name)
}
name, found = checkCapability[LovesExport](backend, "export")
name, found = checkCapability[models.LovesExport](backend, "export")
if found {
caps = append(caps, name)
}
return caps
}
func getImportCapabilities(backend Backend) []Capability {
func getImportCapabilities(backend models.Backend) []Capability {
caps := make([]Capability, 0)
var name string
var found bool
name, found = checkCapability[ListenImport](backend, "import")
name, found = checkCapability[models.ListenImport](backend, "import")
if found {
caps = append(caps, name)
}
name, found = checkCapability[LovesImport](backend, "import")
name, found = checkCapability[models.LovesImport](backend, "import")
if found {
caps = append(caps, name)
}
return caps
}
func checkCapability[T interface{}](backend Backend, suffix string) (string, bool) {
func checkCapability[T interface{}](backend models.Backend, suffix string) (string, bool) {
implements, name := implementsInterface[T](backend)
if implements {
cap, found := strings.CutSuffix(strings.ToLower(name), suffix)

View file

@ -28,27 +28,29 @@ import (
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"go.uploadedlobster.com/scotty/backends"
"go.uploadedlobster.com/scotty/backends/dump"
"go.uploadedlobster.com/scotty/models"
)
func TestResolveBackend(t *testing.T) {
config := viper.New()
config.Set("backend", "dump")
backend, err := backends.ResolveBackend[backends.ListenImport](config)
backend, err := backends.ResolveBackend[models.ListenImport](config)
assert.NoError(t, err)
assert.IsType(t, backends.DumpBackend{}, backend)
assert.IsType(t, dump.DumpBackend{}, backend)
}
func TestResolveBackendUnknown(t *testing.T) {
config := viper.New()
config.Set("backend", "foo")
_, err := backends.ResolveBackend[backends.ListenImport](config)
_, err := backends.ResolveBackend[models.ListenImport](config)
assert.EqualError(t, err, "Unknown backend foo")
}
func TestResolveBackendInvalidInterface(t *testing.T) {
config := viper.New()
config.Set("backend", "dump")
_, err := backends.ResolveBackend[backends.ListenExport](config)
_, err := backends.ResolveBackend[models.ListenExport](config)
assert.EqualError(t, err, "Backend dump does not implement ListenExport")
}

View file

@ -19,7 +19,7 @@ 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.
*/
package backends
package dump
import (
"fmt"
@ -31,12 +31,12 @@ import (
type DumpBackend struct{}
func (b DumpBackend) FromConfig(config *viper.Viper) Backend {
func (b DumpBackend) FromConfig(config *viper.Viper) models.Backend {
return b
}
func (b DumpBackend) ImportListens(listens []models.Listen, oldestTimestamp time.Time) (ImportResult, error) {
result := ImportResult{
func (b DumpBackend) ImportListens(listens []models.Listen, oldestTimestamp time.Time) (models.ImportResult, error) {
result := models.ImportResult{
Count: len(listens),
LastTimestamp: oldestTimestamp,
}
@ -49,8 +49,8 @@ func (b DumpBackend) ImportListens(listens []models.Listen, oldestTimestamp time
return result, nil
}
func (b DumpBackend) ImportLoves(loves []models.Love, oldestTimestamp time.Time) (ImportResult, error) {
result := ImportResult{
func (b DumpBackend) ImportLoves(loves []models.Love, oldestTimestamp time.Time) (models.ImportResult, error) {
result := models.ImportResult{
Count: len(loves),
LastTimestamp: oldestTimestamp,
}

View file

@ -35,7 +35,7 @@ type Client struct {
token string
}
func New(serverUrl string, token string) Client {
func NewClient(serverUrl string, token string) Client {
resty := resty.New()
resty.SetBaseURL(serverUrl)
resty.SetAuthScheme("Bearer")

View file

@ -34,7 +34,7 @@ import (
func TestNewClient(t *testing.T) {
serverUrl := "https://funkwhale.example.com"
token := "foobar123"
client := funkwhale.New(serverUrl, token)
client := funkwhale.NewClient(serverUrl, token)
assert.Equal(t, serverUrl, client.HttpClient.BaseURL)
assert.Equal(t, token, client.HttpClient.Token)
}
@ -44,7 +44,7 @@ func TestGetHistoryListenings(t *testing.T) {
token := "thetoken"
serverUrl := "https://funkwhale.example.com"
client := funkwhale.New(serverUrl, token)
client := funkwhale.NewClient(serverUrl, token)
setupHttpMock(t, client.HttpClient.GetClient(),
"https://funkwhale.example.com/api/v1/history/listenings",
"testdata/listenings.json")

View file

@ -19,26 +19,25 @@ 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.
*/
package backends
package funkwhale
import (
"slices"
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/funkwhale"
"go.uploadedlobster.com/scotty/models"
)
const FunkwhaleClientName = "Funkwhale"
type FunkwhaleApiBackend struct {
client funkwhale.Client
client Client
username string
}
func (b FunkwhaleApiBackend) FromConfig(config *viper.Viper) Backend {
b.client = funkwhale.New(
func (b FunkwhaleApiBackend) FromConfig(config *viper.Viper) models.Backend {
b.client = NewClient(
config.GetString("server-url"),
config.GetString("token"),
)
@ -48,7 +47,7 @@ func (b FunkwhaleApiBackend) FromConfig(config *viper.Viper) Backend {
func (b FunkwhaleApiBackend) ExportListens(oldestTimestamp time.Time) ([]models.Listen, error) {
page := 1
perPage := funkwhale.MaxItemsPerGet
perPage := MaxItemsPerGet
listens := make([]models.Listen, 0)
@ -85,7 +84,7 @@ out:
return listens, nil
}
func ListenFromFunkwhale(fwListen funkwhale.Listening) models.Listen {
func ListenFromFunkwhale(fwListen Listening) models.Listen {
track := fwListen.Track
listen := models.Listen{
UserName: fwListen.User.UserName,

View file

@ -19,14 +19,13 @@ 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.
*/
package backends_test
package funkwhale_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uploadedlobster.com/scotty/backends"
"go.uploadedlobster.com/scotty/backends/funkwhale"
"go.uploadedlobster.com/scotty/models"
)
@ -58,7 +57,7 @@ func TestListenFromFunkwhale(t *testing.T) {
},
},
}
listen := backends.ListenFromFunkwhale(fwListen)
listen := funkwhale.ListenFromFunkwhale(fwListen)
assert.Equal(t, time.Unix(1699574369, 0).Unix(), listen.ListenedAt.Unix())
assert.Equal(t, fwListen.User.UserName, listen.UserName)
assert.Equal(t, time.Duration(414*time.Second), listen.Duration)
@ -71,5 +70,5 @@ func TestListenFromFunkwhale(t *testing.T) {
assert.Equal(t, models.MBID(fwListen.Track.RecordingMbid), listen.RecordingMbid)
assert.Equal(t, models.MBID(fwListen.Track.Album.ReleaseMbid), listen.ReleaseMbid)
assert.Equal(t, models.MBID(fwListen.Track.Artist.ArtistMbid), listen.ArtistMbids[0])
assert.Equal(t, backends.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
assert.Equal(t, funkwhale.FunkwhaleClientName, listen.AdditionalInfo["media_player"])
}

View file

@ -38,7 +38,7 @@ type Client struct {
MaxResults int
}
func New(token string) Client {
func NewClient(token string) Client {
resty := resty.New()
resty.SetBaseURL(listenBrainzBaseURL)
resty.SetAuthScheme("Token")

View file

@ -34,7 +34,7 @@ import (
func TestNewClient(t *testing.T) {
token := "foobar123"
client := listenbrainz.New(token)
client := listenbrainz.NewClient(token)
assert.Equal(t, token, client.HttpClient.Token)
assert.Equal(t, listenbrainz.DefaultItemsPerGet, client.MaxResults)
}
@ -43,7 +43,7 @@ func TestGetListens(t *testing.T) {
defer httpmock.DeactivateAndReset()
token := "thetoken"
client := listenbrainz.New(token)
client := listenbrainz.NewClient(token)
setupHttpMock(t, client.HttpClient.GetClient(),
"https://api.listenbrainz.org/1/user/outsidecontext/listens",
"testdata/listens.json")

View file

@ -19,25 +19,24 @@ 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.
*/
package backends
package listenbrainz
import (
"slices"
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/listenbrainz"
"go.uploadedlobster.com/scotty/models"
)
type ListenBrainzApiBackend struct {
client listenbrainz.Client
client Client
username string
}
func (b ListenBrainzApiBackend) FromConfig(config *viper.Viper) Backend {
b.client = listenbrainz.New(config.GetString("token"))
b.client.MaxResults = listenbrainz.MaxItemsPerGet
func (b ListenBrainzApiBackend) FromConfig(config *viper.Viper) models.Backend {
b.client = NewClient(config.GetString("token"))
b.client.MaxResults = MaxItemsPerGet
b.username = config.GetString("username")
return b
}
@ -77,7 +76,7 @@ out:
return listens, nil
}
func ListenFromListenBrainz(lbListen listenbrainz.Listen) models.Listen {
func ListenFromListenBrainz(lbListen Listen) models.Listen {
track := lbListen.TrackMetadata
listen := models.Listen{
ListenedAt: time.Unix(lbListen.ListenedAt, 0),

View file

@ -19,14 +19,13 @@ 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.
*/
package backends_test
package listenbrainz_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uploadedlobster.com/scotty/backends"
"go.uploadedlobster.com/scotty/backends/listenbrainz"
"go.uploadedlobster.com/scotty/models"
)
@ -50,7 +49,7 @@ func TestListenFromListenBrainz(t *testing.T) {
},
},
}
listen := backends.ListenFromListenBrainz(lbListen)
listen := listenbrainz.ListenFromListenBrainz(lbListen)
assert.Equal(t, time.Unix(1699289873, 0), listen.ListenedAt)
assert.Equal(t, lbListen.UserName, listen.UserName)
assert.Equal(t, time.Duration(528235*time.Millisecond), listen.Duration)
@ -58,7 +57,6 @@ func TestListenFromListenBrainz(t *testing.T) {
assert.Equal(t, lbListen.TrackMetadata.ReleaseName, listen.ReleaseName)
assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames)
assert.Equal(t, 8, listen.TrackNumber)
assert.Equal(t, 8, listen.TrackNumber)
assert.Equal(t, models.MBID("e225fb84-dc9a-419e-adcd-9890f59ec432"), listen.RecordingMbid)
assert.Equal(t, models.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid)
assert.Equal(t, models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid)

View file

@ -32,7 +32,7 @@ type Client struct {
token string
}
func New(serverUrl string, token string) Client {
func NewClient(serverUrl string, token string) Client {
resty := resty.New()
resty.SetBaseURL(serverUrl)
resty.SetHeader("Accept", "application/json")

View file

@ -19,7 +19,7 @@ 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.
*/
package backends
package maloja
import (
"slices"
@ -27,16 +27,15 @@ import (
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/maloja"
"go.uploadedlobster.com/scotty/models"
)
type MalojaApiBackend struct {
client maloja.Client
client Client
}
func (b MalojaApiBackend) FromConfig(config *viper.Viper) Backend {
b.client = maloja.New(
func (b MalojaApiBackend) FromConfig(config *viper.Viper) models.Backend {
b.client = NewClient(
config.GetString("server-url"),
config.GetString("token"),
)
@ -76,7 +75,7 @@ out:
return listens, nil
}
func ListenFromMaloja(mlListen maloja.Listen) models.Listen {
func ListenFromMaloja(mlListen Listen) models.Listen {
track := mlListen.Track
listen := models.Listen{
ListenedAt: time.Unix(mlListen.ListenedAt, 0),

View file

@ -19,14 +19,13 @@ 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.
*/
package backends
package scrobblerlog
import (
"os"
"time"
"github.com/spf13/viper"
"go.uploadedlobster.com/scotty/backends/scrobblerlog"
"go.uploadedlobster.com/scotty/models"
)
@ -35,7 +34,7 @@ type ScrobblerLogBackend struct {
includeSkipped bool
}
func (b ScrobblerLogBackend) FromConfig(config *viper.Viper) Backend {
func (b ScrobblerLogBackend) FromConfig(config *viper.Viper) models.Backend {
b.filePath = config.GetString("file-path")
b.includeSkipped = config.GetBool("include-skipped")
return b
@ -49,7 +48,7 @@ func (b ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time) ([]models.
defer file.Close()
result, err := scrobblerlog.Parse(file, b.includeSkipped)
result, err := Parse(file, b.includeSkipped)
if err != nil {
return nil, err
}
@ -57,8 +56,8 @@ func (b ScrobblerLogBackend) ExportListens(oldestTimestamp time.Time) ([]models.
return result.Listens, nil
}
func (b ScrobblerLogBackend) ImportListens(listens []models.Listen, oldestTimestamp time.Time) (ImportResult, error) {
result := ImportResult{
func (b ScrobblerLogBackend) ImportListens(listens []models.Listen, oldestTimestamp time.Time) (models.ImportResult, error) {
result := models.ImportResult{
Count: 0,
LastTimestamp: oldestTimestamp,
}
@ -70,13 +69,13 @@ func (b ScrobblerLogBackend) ImportListens(listens []models.Listen, oldestTimest
defer file.Close()
log := scrobblerlog.ScrobblerLog{
log := ScrobblerLog{
Timezone: "UNKNOWN",
Client: "Rockbox unknown $Revision$",
Listens: listens,
}
lastTimestamp, err := scrobblerlog.Write(file, &log)
lastTimestamp, err := Write(file, &log)
if err != nil {
return result, err