From d948d2d725741961828fee5892419056d898cd94 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 11 Nov 2023 18:11:00 -0800 Subject: [PATCH] beginning of adding Jellyfin support --- backend/config.go | 8 ++++ .../jellyfin/jellyfinmediaprovider.go | 24 ++++++++++++ backend/mediaprovider/server.go | 7 ++++ .../mediaprovider/subsonic/subsonicserver.go | 19 ++++++++++ backend/servermanager.go | 38 +++++++++++-------- go.mod | 5 ++- go.sum | 6 +++ ui/dialogs/addeditserverdialog.go | 21 +++++++++- 8 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 backend/mediaprovider/jellyfin/jellyfinmediaprovider.go create mode 100644 backend/mediaprovider/server.go create mode 100644 backend/mediaprovider/subsonic/subsonicserver.go diff --git a/backend/config.go b/backend/config.go index 3ffc073..685e75a 100644 --- a/backend/config.go +++ b/backend/config.go @@ -8,7 +8,15 @@ import ( "github.com/pelletier/go-toml/v2" ) +type ServerType string + +const ( + ServerTypeSubsonic ServerType = "Subsonic" + ServerTypeJellyfin ServerType = "Jellyfin" +) + type ServerConnection struct { + ServerType ServerType Hostname string AltHostname string Username string diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go new file mode 100644 index 0000000..16a6b65 --- /dev/null +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -0,0 +1,24 @@ +package jellyfin + +import ( + "errors" + + "github.com/dweymouth/go-jellyfin" +) + +type jellyfinMediaProvider struct { + client *jellyfin.Client + prefetchCoverCB func(coverArtID string) +} + +func (j *jellyfinMediaProvider) SetPrefetchCoverCallback(cb func(coverArtID string)) { + j.prefetchCoverCB = cb +} + +func (jellyfinMediaProvider) CreatePlaylist(name string, trackIDs []string) error { + return errors.New("unimplemented") +} + +func (j *jellyfinMediaProvider) DeletePlaylist(id string) error { + return j.client.DeletePlaylist(id) +} diff --git a/backend/mediaprovider/server.go b/backend/mediaprovider/server.go new file mode 100644 index 0000000..94e7218 --- /dev/null +++ b/backend/mediaprovider/server.go @@ -0,0 +1,7 @@ +package mediaprovider + +type Server interface { + Ping() bool + Login(username, password string) error + MediaProvider() MediaProvider +} diff --git a/backend/mediaprovider/subsonic/subsonicserver.go b/backend/mediaprovider/subsonic/subsonicserver.go new file mode 100644 index 0000000..5c1149f --- /dev/null +++ b/backend/mediaprovider/subsonic/subsonicserver.go @@ -0,0 +1,19 @@ +package subsonic + +import ( + subsonicCli "github.com/dweymouth/go-subsonic/subsonic" + "github.com/dweymouth/supersonic/backend/mediaprovider" +) + +type SubsonicServer struct { + subsonicCli.Client +} + +func (s *SubsonicServer) Login(username, password string) error { + s.User = username + return s.Client.Authenticate(password) +} + +func (s *SubsonicServer) MediaProvider() mediaprovider.MediaProvider { + return SubsonicMediaProvider(&s.Client) +} diff --git a/backend/servermanager.go b/backend/servermanager.go index 1efd86f..a23a458 100644 --- a/backend/servermanager.go +++ b/backend/servermanager.go @@ -43,7 +43,7 @@ func (s *ServerManager) ConnectToServer(conf *ServerConfig, password string) err if err != nil { return err } - s.Server = subsonicMP.SubsonicMediaProvider(cli) + s.Server = cli.MediaProvider() s.Server.SetPrefetchCoverCallback(s.prefetchCoverCB) s.LoggedInUser = conf.Username s.ServerID = conf.ID @@ -156,25 +156,31 @@ func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) return keyring.Set(s.appName, server.ID.String(), password) } -func (s *ServerManager) connect(connection ServerConnection, password string) (*subsonic.Client, error) { - cli := &subsonic.Client{ - Client: &http.Client{Timeout: 10 * time.Second}, - BaseUrl: connection.Hostname, - User: connection.Username, - PasswordAuth: connection.LegacyAuth, - ClientName: "supersonic", +func (s *ServerManager) connect(connection ServerConnection, password string) (mediaprovider.Server, error) { + var cli, altCli mediaprovider.Server + + cli = &subsonicMP.SubsonicServer{ + Client: subsonic.Client{ + Client: &http.Client{Timeout: 10 * time.Second}, + BaseUrl: connection.Hostname, + User: connection.Username, + PasswordAuth: connection.LegacyAuth, + ClientName: "supersonic", + }, } - altCli := &subsonic.Client{ - Client: &http.Client{Timeout: 10 * time.Second}, - BaseUrl: connection.AltHostname, - User: connection.Username, - PasswordAuth: connection.LegacyAuth, - ClientName: "supersonic", + altCli = &subsonicMP.SubsonicServer{ + Client: subsonic.Client{ + Client: &http.Client{Timeout: 10 * time.Second}, + BaseUrl: connection.AltHostname, + User: connection.Username, + PasswordAuth: connection.LegacyAuth, + ClientName: "supersonic", + }, } pingChan := make(chan bool, 2) // false for primary hostname, true for alternate - pingFunc := func(delay time.Duration, cli *subsonic.Client, val bool) { + pingFunc := func(delay time.Duration, cli mediaprovider.Server, val bool) { <-time.After(delay) - if err := cli.Authenticate(password); err == nil { + if err := cli.Login(connection.Username, password); err == nil { pingChan <- val } } diff --git a/go.mod b/go.mod index 9072fce..b452a61 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091 // indirect github.com/fredbi/uri v1.0.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect @@ -38,10 +39,10 @@ require ( github.com/stretchr/testify v1.8.4 // indirect github.com/tevino/abool v1.2.0 // indirect github.com/yuin/goldmark v1.5.5 // indirect - golang.org/x/image v0.13.0 // indirect + golang.org/x/image v0.14.0 // indirect golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect ) diff --git a/go.sum b/go.sum index 92fe015..6121a62 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20231110162149-a0e470497555 h1:8S+d0LuwdTUEipEzFeXp8rNwTQD47dBdDOg9+FI1+Vw= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20231110162149-a0e470497555/go.mod h1:AWM1iPM2YfliduZ4u/kQzP9E6ARIWm0gg+57GpYzWro= +github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091 h1:UiyWNmeig46pkNCj7Pqc0Ta4JdxYKeXnBwuX+NOFCyM= +github.com/dweymouth/go-jellyfin v0.0.0-20231112010253-d93b76137091/go.mod h1:BMwS4vdjEYf1gmjPGSKCzWP/I6YlI6fkefJ9nsjBjaU= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20231105161622-54b5aec28363 h1:MIH7MAWWPPVRKEKxz+RJubn+ycyQPimHn1Zvoxs1KRI= @@ -337,6 +339,8 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -497,6 +501,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/ui/dialogs/addeditserverdialog.go b/ui/dialogs/addeditserverdialog.go index e6b8c9f..1806283 100644 --- a/ui/dialogs/addeditserverdialog.go +++ b/ui/dialogs/addeditserverdialog.go @@ -14,6 +14,7 @@ import ( type AddEditServerDialog struct { widget.BaseWidget + ServerType backend.ServerType Nickname string Host string AltHost string @@ -35,6 +36,7 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen a := &AddEditServerDialog{} a.ExtendBaseWidget(a) if prefillServer != nil { + a.ServerType = prefillServer.ServerType a.Nickname = prefillServer.Nickname a.Host = prefillServer.Hostname a.AltHost = prefillServer.AltHostname @@ -44,6 +46,21 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen titleLabel := widget.NewLabel(title) titleLabel.TextStyle.Bold = true + legacyAuthCheck := widget.NewCheckWithData("Use legacy authentication", binding.BindBool(&a.LegacyAuth)) + serverTypeChoice := widget.NewRadioGroup([]string{"Subsonic", "Jellyfin"}, func(s string) { + a.ServerType = backend.ServerType(s) + if s == string(backend.ServerTypeSubsonic) { + legacyAuthCheck.Show() + } else { + legacyAuthCheck.Hide() + } + }) + serverTypeChoice.Horizontal = true + selected := backend.ServerTypeSubsonic + if a.ServerType == backend.ServerTypeJellyfin { + selected = backend.ServerTypeJellyfin + } + serverTypeChoice.Selected = string(selected) a.passField = widget.NewPasswordEntry() a.passField.OnSubmitted = func(_ string) { a.doSubmit() } userField := widget.NewEntryWithData(binding.BindString(&a.Username)) @@ -62,8 +79,6 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen a.promptText = widget.NewRichTextWithText("") a.promptText.Hidden = true - legacyAuthCheck := widget.NewCheckWithData("Use legacy authentication", binding.BindBool(&a.LegacyAuth)) - var bottomRow *fyne.Container if cancelable { bottomRow = container.NewHBox( @@ -81,6 +96,8 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen a.container = container.NewVBox( container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()), container.New(layout.NewFormLayout(), + widget.NewLabel("Type"), + serverTypeChoice, widget.NewLabel("Nickname"), nickField, widget.NewLabel("Hostname"),