diff options
author | Simon Garrelou <simon.garrelou@gmail.com> | 2022-12-05 21:40:13 +0100 |
---|---|---|
committer | Simon Garrelou <simon.garrelou@gmail.com> | 2022-12-05 21:40:13 +0100 |
commit | badc7b254edb837e4338152b4acd34ab5b9b5ddd (patch) | |
tree | 0b51d0f9dcaecb417749819946b4a36ca45bdddb | |
parent | 5d67e5c43c9123b2508c0b4840def4738744a4d6 (diff) | |
download | termsonic-badc7b254edb837e4338152b4acd34ab5b9b5ddd.tar.gz termsonic-badc7b254edb837e4338152b4acd34ab5b9b5ddd.zip |
Load artists & albums from the server
-rw-r--r-- | src/app.go | 24 | ||||
-rw-r--r-- | src/page_artists.go | 97 | ||||
-rw-r--r-- | src/page_config.go | 57 |
3 files changed, 153 insertions, 25 deletions
@@ -9,12 +9,18 @@ import ( | |||
9 | ) | 9 | ) |
10 | 10 | ||
11 | type app struct { | 11 | type app struct { |
12 | // General GUI | ||
12 | tv *tview.Application | 13 | tv *tview.Application |
13 | pages *tview.Pages | 14 | pages *tview.Pages |
14 | header *tview.TextView | 15 | header *tview.TextView |
15 | footer *tview.TextView | 16 | footer *tview.TextView |
16 | cfg *Config | 17 | cfg *Config |
17 | 18 | ||
19 | // Artists panel | ||
20 | artistsTree *tview.TreeView | ||
21 | songsList *tview.List | ||
22 | |||
23 | // Subsonic variables | ||
18 | sub *subsonic.Client | 24 | sub *subsonic.Client |
19 | } | 25 | } |
20 | 26 | ||
@@ -42,16 +48,28 @@ func Run(cfg *Config) { | |||
42 | }) | 48 | }) |
43 | fmt.Fprintf(a.header, `["artists"]F1: Artists[""] | ["playlists"]F2: Playlists[""] | ["config"]F3: Configuration[""]`) | 49 | fmt.Fprintf(a.header, `["artists"]F1: Artists[""] | ["playlists"]F2: Playlists[""] | ["config"]F3: Configuration[""]`) |
44 | 50 | ||
51 | a.pages.SetBorder(true) | ||
45 | a.pages.AddPage("config", configPage(a), true, false) | 52 | a.pages.AddPage("config", configPage(a), true, false) |
46 | a.pages.AddPage("artists", artistsPage(a), true, false) | 53 | a.pages.AddPage("artists", artistsPage(a), true, false) |
47 | 54 | ||
48 | mainLayout := tview.NewFlex(). | 55 | mainLayout := tview.NewFlex(). |
49 | SetDirection(tview.FlexRow). | 56 | SetDirection(tview.FlexRow). |
50 | AddItem(a.header, 0, 1, false). | 57 | AddItem(a.header, 1, 1, false). |
51 | AddItem(a.pages, 0, 3, true). | 58 | AddItem(a.pages, 0, 3, true). |
52 | AddItem(a.footer, 0, 1, false) | 59 | AddItem(a.footer, 1, 1, false) |
60 | |||
61 | if testConfig(a.cfg) != nil { | ||
62 | switchToPage(a, "config") | ||
63 | } else { | ||
64 | a.sub, _ = buildSubsonicClient(a.cfg) | ||
65 | err := refreshArtists(a) | ||
66 | if err != nil { | ||
67 | alert(a, "Could not refresh artists: %v", err) | ||
68 | } else { | ||
69 | switchToPage(a, "artists") | ||
70 | } | ||
71 | } | ||
53 | 72 | ||
54 | switchToPage(a, "config") | ||
55 | if err := a.tv.SetRoot(mainLayout, true).EnableMouse(true).SetFocus(mainLayout).Run(); err != nil { | 73 | if err := a.tv.SetRoot(mainLayout, true).EnableMouse(true).SetFocus(mainLayout).Run(); err != nil { |
56 | fmt.Printf("Error running termsonic: %v", err) | 74 | fmt.Printf("Error running termsonic: %v", err) |
57 | os.Exit(1) | 75 | 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 @@ | |||
1 | package src | 1 | package src |
2 | 2 | ||
3 | import "github.com/rivo/tview" | 3 | import ( |
4 | "fmt" | ||
5 | "time" | ||
6 | |||
7 | "github.com/gdamore/tcell/v2" | ||
8 | "github.com/rivo/tview" | ||
9 | ) | ||
10 | |||
11 | type selection struct { | ||
12 | entryType string | ||
13 | id string | ||
14 | } | ||
4 | 15 | ||
5 | func artistsPage(a *app) tview.Primitive { | 16 | func artistsPage(a *app) tview.Primitive { |
6 | grid := tview.NewGrid(). | 17 | grid := tview.NewGrid(). |
7 | SetRows(1). | 18 | SetColumns(40, 0). |
8 | SetColumns(30, 0). | ||
9 | SetBorders(true) | 19 | SetBorders(true) |
10 | 20 | ||
11 | grid.AddItem(tview.NewTextView().SetText("Artist & Album list"), 0, 0, 1, 1, 0, 0, true) | 21 | root := tview.NewTreeNode("Subsonic server").SetColor(tcell.ColorYellow) |
12 | grid.AddItem(tview.NewTextView().SetText("Song list!"), 0, 1, 1, 2, 0, 0, false) | 22 | a.artistsTree = tview.NewTreeView(). |
23 | SetRoot(root). | ||
24 | SetCurrentNode(root). | ||
25 | SetPrefixes([]string{"", " ", " "}). | ||
26 | SetSelectedFunc(func(node *tview.TreeNode) { | ||
27 | if node.GetReference() == nil { | ||
28 | return | ||
29 | } | ||
30 | |||
31 | sel := node.GetReference().(selection) | ||
32 | if sel.entryType == "artist" { | ||
33 | node.SetExpanded(!node.IsExpanded()) | ||
34 | return | ||
35 | } | ||
36 | |||
37 | loadAlbumInPanel(a, sel.id) | ||
38 | a.tv.SetFocus(a.songsList) | ||
39 | }) | ||
40 | |||
41 | a.songsList = tview.NewList() | ||
42 | a.songsList.ShowSecondaryText(false) | ||
43 | |||
44 | grid.AddItem(a.artistsTree, 0, 0, 1, 1, 0, 0, true) | ||
45 | grid.AddItem(a.songsList, 0, 1, 1, 2, 0, 0, false) | ||
13 | 46 | ||
14 | return grid | 47 | return grid |
15 | } | 48 | } |
49 | |||
50 | func refreshArtists(a *app) error { | ||
51 | artistsID3, err := a.sub.GetArtists(nil) | ||
52 | if err != nil { | ||
53 | return err | ||
54 | } | ||
55 | |||
56 | a.artistsTree.GetRoot().ClearChildren() | ||
57 | for _, index := range artistsID3.Index { | ||
58 | for _, artist := range index.Artist { | ||
59 | node := tview.NewTreeNode(artist.Name) | ||
60 | node.SetReference(selection{"artist", artist.ID}) | ||
61 | node.SetColor(tcell.ColorRed) | ||
62 | node.SetSelectable(true) | ||
63 | node.SetExpanded(false) | ||
64 | |||
65 | albums, err := a.sub.GetMusicDirectory(artist.ID) | ||
66 | if err != nil { | ||
67 | return err | ||
68 | } | ||
69 | |||
70 | for _, album := range albums.Child { | ||
71 | subnode := tview.NewTreeNode(album.Title) | ||
72 | subnode.SetReference(selection{"album", album.ID}) | ||
73 | subnode.SetColor(tcell.ColorBlue) | ||
74 | subnode.SetSelectable(true) | ||
75 | |||
76 | node.AddChild(subnode) | ||
77 | } | ||
78 | |||
79 | a.artistsTree.GetRoot().AddChild(node) | ||
80 | } | ||
81 | } | ||
82 | |||
83 | a.artistsTree.GetRoot().SetExpanded(true) | ||
84 | |||
85 | return nil | ||
86 | } | ||
87 | |||
88 | func loadAlbumInPanel(a *app, id string) error { | ||
89 | album, err := a.sub.GetMusicDirectory(id) | ||
90 | if err != nil { | ||
91 | return err | ||
92 | } | ||
93 | |||
94 | a.songsList.SetTitle(album.Name) | ||
95 | a.songsList.Clear() | ||
96 | for _, song := range album.Child { | ||
97 | dur := time.Duration(song.Duration) * time.Second | ||
98 | a.songsList.AddItem(fmt.Sprintf("%-10s %d - %s", fmt.Sprintf("[%s]", dur.String()), song.Track, song.Title), "", 0, nil) | ||
99 | } | ||
100 | |||
101 | return nil | ||
102 | } | ||
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 @@ | |||
1 | package src | 1 | package src |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | ||
4 | "net/http" | 5 | "net/http" |
5 | 6 | ||
6 | "github.com/delucks/go-subsonic" | 7 | "github.com/delucks/go-subsonic" |
@@ -8,20 +9,14 @@ import ( | |||
8 | ) | 9 | ) |
9 | 10 | ||
10 | func configPage(a *app) *tview.Form { | 11 | func configPage(a *app) *tview.Form { |
12 | var err error | ||
13 | |||
11 | form := tview.NewForm(). | 14 | form := tview.NewForm(). |
12 | AddInputField("Server URL", a.cfg.BaseURL, 40, nil, func(txt string) { a.cfg.BaseURL = txt }). | 15 | AddInputField("Server URL", a.cfg.BaseURL, 40, nil, func(txt string) { a.cfg.BaseURL = txt }). |
13 | AddInputField("Username", a.cfg.Username, 20, nil, func(txt string) { a.cfg.Username = txt }). | 16 | AddInputField("Username", a.cfg.Username, 20, nil, func(txt string) { a.cfg.Username = txt }). |
14 | AddPasswordField("Password", a.cfg.Password, 20, '*', func(txt string) { a.cfg.Password = txt }). | 17 | AddPasswordField("Password", a.cfg.Password, 20, '*', func(txt string) { a.cfg.Password = txt }). |
15 | AddButton("Test", func() { | 18 | AddButton("Test", func() { |
16 | tmpSub := &subsonic.Client{ | 19 | if err = testConfig(a.cfg); err != nil { |
17 | Client: http.DefaultClient, | ||
18 | BaseUrl: a.cfg.BaseURL, | ||
19 | User: a.cfg.Username, | ||
20 | ClientName: "termsonic", | ||
21 | PasswordAuth: true, | ||
22 | } | ||
23 | |||
24 | if err := tmpSub.Authenticate(a.cfg.Password); err != nil { | ||
25 | alert(a, "Could not auth: %v", err) | 20 | alert(a, "Could not auth: %v", err) |
26 | } else { | 21 | } else { |
27 | alert(a, "Success.") | 22 | alert(a, "Success.") |
@@ -34,14 +29,8 @@ func configPage(a *app) *tview.Form { | |||
34 | return | 29 | return |
35 | } | 30 | } |
36 | 31 | ||
37 | a.sub = &subsonic.Client{ | 32 | a.sub, err = buildSubsonicClient(a.cfg) |
38 | Client: http.DefaultClient, | 33 | if err != nil { |
39 | BaseUrl: a.cfg.BaseURL, | ||
40 | User: a.cfg.Username, | ||
41 | ClientName: "termsonic", | ||
42 | PasswordAuth: true, | ||
43 | } | ||
44 | if err := a.sub.Authenticate(a.cfg.Password); err != nil { | ||
45 | alert(a, "Could not auth: %v", err) | 34 | alert(a, "Could not auth: %v", err) |
46 | } else { | 35 | } else { |
47 | alert(a, "All good!") | 36 | alert(a, "All good!") |
@@ -49,3 +38,37 @@ func configPage(a *app) *tview.Form { | |||
49 | }) | 38 | }) |
50 | return form | 39 | return form |
51 | } | 40 | } |
41 | |||
42 | func testConfig(cfg *Config) error { | ||
43 | if cfg.BaseURL == "" { | ||
44 | return fmt.Errorf("empty base URL") | ||
45 | } | ||
46 | |||
47 | if cfg.Username == "" { | ||
48 | return fmt.Errorf("empty username") | ||
49 | } | ||
50 | |||
51 | if cfg.Password == "" { | ||
52 | return fmt.Errorf("empty password") | ||
53 | } | ||
54 | |||
55 | _, err := buildSubsonicClient(cfg) | ||
56 | return err | ||
57 | } | ||
58 | |||
59 | func buildSubsonicClient(cfg *Config) (*subsonic.Client, error) { | ||
60 | tmpSub := &subsonic.Client{ | ||
61 | Client: http.DefaultClient, | ||
62 | BaseUrl: cfg.BaseURL, | ||
63 | User: cfg.Username, | ||
64 | ClientName: "termsonic", | ||
65 | PasswordAuth: true, | ||
66 | } | ||
67 | |||
68 | err := tmpSub.Authenticate(cfg.Password) | ||
69 | if err != nil { | ||
70 | return nil, err | ||
71 | } | ||
72 | |||
73 | return tmpSub, nil | ||
74 | } | ||