Tvheadend 4.3 sends "leaf": 0 for a network HDHomeRun, which stopped the tuner setup before it found the tuners.
- commit
- 1b49589c385a1f85374e287f46985396a4fe37aa
- parent
- 1d4e2e34c6b6f80b84b52a7abaa9f79a9d35cf6c
- author
- FelineStateMachine <226808055+FelineStateMachine@users.noreply.github.com>
| M | internal/ | 24 | +++++++++++++++--- |
| A | internal/ | 40 | ++++++++++++++++++++++++++++++ |
@@ -255,9 +255,9 @@ func (c *Client) Frontends(ctx context.Context) ([]Frontend, error) {
255255 var walk func(uuid string, depth int) error256256 walk = func(uuid string, depth int) error {257257 var nodes []struct {258- UUID string `json:"uuid"`259- Text string `json:"text"`260- Leaf bool `json:"leaf"`258+ UUID string `json:"uuid"`259+ Text string `json:"text"`260+ Leaf flexBool `json:"leaf"`261261 }262262 if err := c.get(ctx, "/api/hardware/tree", url.Values{"uuid": {uuid}}, &nodes); err != nil {263263 return err@@ -270,7 +270,7 @@ func (c *Client) Frontends(ctx context.Context) ([]Frontend, error) {
270270 if strings.Contains(class, "_frontend_atsc") {271271 out = append(out, Frontend{UUID: n.UUID, Class: class, Name: n.Text})272272 }273- if !n.Leaf && depth < 3 {273+ if !bool(n.Leaf) && depth < 3 {274274 if err := walk(n.UUID, depth+1); err != nil {275275 return err276276 }@@ -281,6 +281,22 @@ func (c *Client) Frontends(ctx context.Context) ([]Frontend, error) {
281281 return out, walk("root", 0)282282 }283283 284+// flexBool is a JSON boolean that Tvheadend sometimes writes as 0 or 1285+// (the hardware tree's "leaf").286+type flexBool bool287+288+func (b *flexBool) UnmarshalJSON(raw []byte) error {289+ switch string(raw) {290+ case "true", "1":291+ *b = true292+ case "false", "0", "null":293+ *b = false294+ default:295+ return fmt.Errorf("%s is not a boolean", raw)296+ }297+ return nil298+}299+284300 func (c *Client) class(ctx context.Context, uuid string) (string, error) {285301 var body struct {286302 Entries []struct {@@ -0,0 +1,40 @@
1+package tvh2+3+import (4+ "context"5+ "encoding/json"6+ "net/http"7+ "net/http/httptest"8+ "testing"9+)10+11+// TestFrontendsHDHomeRun walks a hardware tree as Tvheadend 4.3 serves it12+// for a network HDHomeRun, "leaf" a number, down to its ATSC tuners.13+func TestFrontendsHDHomeRun(t *testing.T) {14+ tree := map[string]string{15+ "root": `[{"uuid":"dev","text":"HDHomeRun(10A0B0C0) - 169.254.1.2","leaf":0}]`,16+ "dev": `[{"uuid":"tuner0","text":"HDHomeRun ATSC-T Tuner #0 (169.254.1.2)","leaf":1},{"uuid":"tuner1","text":"HDHomeRun ATSC-T Tuner #1 (169.254.1.2)","leaf":true}]`,17+ "tuner0": `[]`, "tuner1": `[]`,18+ }19+ class := map[string]string{"dev": "tvhdhomerun_client", "tuner0": "tvhdhomerun_frontend_atsc_t", "tuner1": "tvhdhomerun_frontend_atsc_t"}20+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {21+ uuid := r.URL.Query().Get("uuid")22+ switch r.URL.Path {23+ case "/api/hardware/tree":24+ w.Write([]byte(tree[uuid]))25+ case "/api/idnode/load":26+ json.NewEncoder(w).Encode(map[string]any{"entries": []map[string]string{{"class": class[uuid]}}})27+ default:28+ http.NotFound(w, r)29+ }30+ }))31+ defer srv.Close()32+33+ fes, err := New(srv.URL, srv.Client()).Frontends(context.Background())34+ if err != nil {35+ t.Fatal(err)36+ }37+ if len(fes) != 2 || fes[0].UUID != "tuner0" || fes[1].Class != "tvhdhomerun_frontend_atsc_t" {38+ t.Fatalf("frontends: %+v", fes)39+ }40+}