From badc7b254edb837e4338152b4acd34ab5b9b5ddd Mon Sep 17 00:00:00 2001 From: Simon Garrelou Date: Mon, 5 Dec 2022 21:40:13 +0100 Subject: Load artists & albums from the server --- src/app.go | 24 +++++++++++-- src/page_artists.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/page_config.go | 57 +++++++++++++++++++++---------- 3 files changed, 153 insertions(+), 25 deletions(-) diff --git a/src/app.go b/src/app.go index c18c1f5..58fc0db 100644 --- a/src/app.go +++ b/src/app.go @@ -9,12 +9,18 @@ import ( ) type app struct { + // General GUI tv *tview.Application pages *tview.Pages header *tview.TextView footer *tview.TextView cfg *Config + // Artists panel + artistsTree *tview.TreeView + songsList *tview.List + + // Subsonic variables sub *subsonic.Client } @@ -42,16 +48,28 @@ func Run(cfg *Config) { }) fmt.Fprintf(a.header, `["artists"]F1: Artists[""] | ["playlists"]F2: Playlists[""] | ["config"]F3: Configuration[""]`) + a.pages.SetBorder(true) a.pages.AddPage("config", configPage(a), true, false) a.pages.AddPage("artists", artistsPage(a), true, false) mainLayout := tview.NewFlex(). SetDirection(tview.FlexRow). - AddItem(a.header, 0, 1, false). + AddItem(a.header, 1, 1, false). AddItem(a.pages, 0, 3, true). - AddItem(a.footer, 0, 1, false) + AddItem(a.footer, 1, 1, false) + + if testConfig(a.cfg) != nil { + switchToPage(a, "config") + } else { + a.sub, _ = buildSubsonicClient(a.cfg) + err := refreshArtists(a) + if err != nil { + alert(a, "Could not refresh artists: %v", err) + } else { + switchToPage(a, "artists") + } + } - switchToPage(a, "config") if err := a.tv.SetRoot(mainLayout, true).EnableMouse(true).SetFocus(mainLayout).Run(); err != nil { fmt.Printf("Error running termsonic: %v", err) os.Exit(1) diff --git a/src/page_artists.go b/src/page_artists.go index 6b6def6..c064785 100644 --- a/src/page_artists.go +++ b/src/page_artists.go @@ -1,15 +1,102 @@ package src -import "github.com/rivo/tview" +import ( + "fmt" + "time" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +type selection struct { + entryType string + id string +} func artistsPage(a *app) tview.Primitive { grid := tview.NewGrid(). - SetRows(1). - SetColumns(30, 0). + SetColumns(40, 0). SetBorders(true) - grid.AddItem(tview.NewTextView().SetText("Artist & Album list"), 0, 0, 1, 1, 0, 0, true) - grid.AddItem(tview.NewTextView().SetText("Song list!"), 0, 1, 1, 2, 0, 0, false) + root := tview.NewTreeNode("Subsonic server").SetColor(tcell.ColorYellow) + a.artistsTree = tview.NewTreeView(). + SetRoot(root). + SetCurrentNode(root). + SetPrefixes([]string{"", " ", " "}). + SetSelectedFunc(func(node *tview.TreeNode) { + if node.GetReference() == nil { + return + } + + sel := node.GetReference().(selection) + if sel.entryType == "artist" { + node.SetExpanded(!node.IsExpanded()) + return + } + + loadAlbumInPanel(a, sel.id) + a.tv.SetFocus(a.songsList) + }) + + a.songsList = tview.NewList() + a.songsList.ShowSecondaryText(false) + + grid.AddItem(a.artistsTree, 0, 0, 1, 1, 0, 0, true) + grid.AddItem(a.songsList, 0, 1, 1, 2, 0, 0, false) return grid } + +func refreshArtists(a *app) error { + artistsID3, err := a.sub.GetArtists(nil) + if err != nil { + return err + } + + a.artistsTree.GetRoot().ClearChildren() + for _, index := range artistsID3.Index { + for _, artist := range index.Artist { + node := tview.NewTreeNode(artist.Name) + node.SetReference(selection{"artist", artist.ID}) + node.SetColor(tcell.ColorRed) + node.SetSelectable(true) + node.SetExpanded(false) + + albums, err := a.sub.GetMusicDirectory(artist.ID) + if err != nil { + return err + } + + for _, album := range albums.Child { + subnode := tview.NewTreeNode(album.Title) + subnode.SetReference(selection{"album", album.ID}) + subnode.SetColor(tcell.ColorBlue) + subnode.SetSelectable(true) + + node.AddChild(subnode) + } + + a.artistsTree.GetRoot().AddChild(node) + } + } + + a.artistsTree.GetRoot().SetExpanded(true) + + return nil +} + +func loadAlbumInPanel(a *app, id string) error { + album, err := a.sub.GetMusicDirectory(id) + if err != nil { + return err + } + + a.songsList.SetTitle(album.Name) + a.songsList.Clear() + for _, song := range album.Child { + dur := time.Duration(song.Duration) * time.Second + a.songsList.AddItem(fmt.Sprintf("%-10s %d - %s", fmt.Sprintf("[%s]", dur.String()), song.Track, song.Title), "", 0, nil) + } + + return nil +} diff --git a/src/page_config.go b/src/page_config.go index bb8afca..090a70b 100644 --- a/src/page_config.go +++ b/src/page_config.go @@ -1,6 +1,7 @@ package src import ( + "fmt" "net/http" "github.com/delucks/go-subsonic" @@ -8,20 +9,14 @@ import ( ) func configPage(a *app) *tview.Form { + var err error + form := tview.NewForm(). AddInputField("Server URL", a.cfg.BaseURL, 40, nil, func(txt string) { a.cfg.BaseURL = txt }). AddInputField("Username", a.cfg.Username, 20, nil, func(txt string) { a.cfg.Username = txt }). AddPasswordField("Password", a.cfg.Password, 20, '*', func(txt string) { a.cfg.Password = txt }). AddButton("Test", func() { - tmpSub := &subsonic.Client{ - Client: http.DefaultClient, - BaseUrl: a.cfg.BaseURL, - User: a.cfg.Username, - ClientName: "termsonic", - PasswordAuth: true, - } - - if err := tmpSub.Authenticate(a.cfg.Password); err != nil { + if err = testConfig(a.cfg); err != nil { alert(a, "Could not auth: %v", err) } else { alert(a, "Success.") @@ -34,14 +29,8 @@ func configPage(a *app) *tview.Form { return } - a.sub = &subsonic.Client{ - Client: http.DefaultClient, - BaseUrl: a.cfg.BaseURL, - User: a.cfg.Username, - ClientName: "termsonic", - PasswordAuth: true, - } - if err := a.sub.Authenticate(a.cfg.Password); err != nil { + a.sub, err = buildSubsonicClient(a.cfg) + if err != nil { alert(a, "Could not auth: %v", err) } else { alert(a, "All good!") @@ -49,3 +38,37 @@ func configPage(a *app) *tview.Form { }) return form } + +func testConfig(cfg *Config) error { + if cfg.BaseURL == "" { + return fmt.Errorf("empty base URL") + } + + if cfg.Username == "" { + return fmt.Errorf("empty username") + } + + if cfg.Password == "" { + return fmt.Errorf("empty password") + } + + _, err := buildSubsonicClient(cfg) + return err +} + +func buildSubsonicClient(cfg *Config) (*subsonic.Client, error) { + tmpSub := &subsonic.Client{ + Client: http.DefaultClient, + BaseUrl: cfg.BaseURL, + User: cfg.Username, + ClientName: "termsonic", + PasswordAuth: true, + } + + err := tmpSub.Authenticate(cfg.Password) + if err != nil { + return nil, err + } + + return tmpSub, nil +} -- cgit v1.2.3