menu/tracks_menu.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package menu

import (
    "os"
    "os/exec"
    "runtime"
    "strconv"
    "strings"

    "github.com/bogem/nehm/api"
    "github.com/bogem/nehm/color"
    "github.com/bogem/nehm/logs"
    "github.com/bogem/nehm/track"
)

func NewTracksMenu(firstPageURL string) *TracksMenu {
    return &TracksMenu{paginator: api.NewPaginator(firstPageURL)}
}

// TracksMenu gets tracks from paginator, shows them in menu
// and returns selected tracks.
//
// TracksMenu finishes when user pushes 'd' button.
type TracksMenu struct {
    paginator *api.Paginator
    tracks    []track.Track
    err       error

    // isSelected holds the ids of selected tracks. With map we can
    // detect really fast if track is selected.
    isSelected map[int]bool
    // selectedTracks holds selected tracks in initial sequence.
    selectedTracks []track.Track

    selectionFinished bool
}

// Show gets tracks from tm.paginator, show these tracks,
// adds id of selected track to tm.isSelected to detect, what track is selected,
// adds selected to tm.selectedTracks and returns them.
func (tm *TracksMenu) Show() []track.Track {
    tm.getNextPage()
    if tm.err != nil {
        logs.FATAL.Fatalln(tm.err)
    }
    if len(tm.tracks) == 0 {
        logs.FEEDBACK.Println("There are no tracks to show")
        os.Exit(0)
    }

    tm.isSelected = make(map[int]bool)
    for !tm.selectionFinished {
        if tm.err != nil {
            if tm.err == api.ErrLastPage {
                tm.getPrevPage()
                continue
            }
            if tm.err == api.ErrFirstPage {
                tm.getNextPage()
                continue
            }

            logs.ERROR.Println(tm.err)
            logs.FEEDBACK.Print("There was an error. Do you want to download selected tracks before exit? (Y/n): ")
            answer := readInput()
            if strings.EqualFold(answer, "n") {
                os.Exit(1)
            } else {
                break
            }
        }

        trackItems := tm.formTrackItems(tm.tracks)
        clearScreen()
        tm.showMenu(trackItems)
    }

    return tm.selectedTracks
}

var trackItems []MenuItem

func (tm *TracksMenu) formTrackItems(tracks []track.Track) []MenuItem {
    trackItems = trackItems[:0]

    for i, t := range tracks {
        desc := t.Fullname() + " (" + t.Duration() + ")"

        var trackItem MenuItem
        if _, contains := tm.isSelected[t.ID()]; contains {
            trackItem = MenuItem{
                Index: color.GreenString("A"),
                Desc:  desc,
            }
        } else {
            t := t // https://golang.org/doc/faq#closures_and_goroutines
            trackItem = MenuItem{
                Index: strconv.Itoa(i + 1),
                Desc:  desc,
                Run: func() {
                    tm.isSelected[t.ID()] = true
                    tm.selectedTracks = append(tm.selectedTracks, t)
                },
            }
        }
        trackItems = append(trackItems, trackItem)
    }
    return trackItems
}

// clearPath is used for holding the path to 'clear' or 'cls' binary,
// so exec.Command() don't have to always look the path to command.
var clearPath string

func clearScreen() {
    if clearPath == "error" {
        return
    }

    if clearPath == "" {
        var err error
        if runtime.GOOS == "windows" {
            clearPath, err = exec.LookPath("cls")
        } else {
            clearPath, err = exec.LookPath("clear")
        }
        if err != nil { // if there is no clear command, just do not clear the screen ...
            clearPath = "error" // ... and don't check it later
            return
        }
    }

    cmd := exec.Command(clearPath)
    cmd.Stdout = os.Stdout
    cmd.Run()
}

var menu Menu

func (tm *TracksMenu) showMenu(trackItems []MenuItem) {
    menu.Reset()
    menu.AddItems(trackItems...)
    menu.AddNewline()
    menu.AddItems(tm.controlItems()...)
    menu.Show()
}

func (tm *TracksMenu) controlItems() []MenuItem {
    items := make([]MenuItem, 0, 3)
    items = append(items, MenuItem{
        Index: "d",
        Desc:  "Download tracks",
        Run:   func() { tm.selectionFinished = true },
    })
    if !tm.paginator.OnLastPage() {
        items = append(items, MenuItem{
            Index: "n",
            Desc:  "Next page",
            Run:   func() { tm.getNextPage() },
        })
    }
    if !tm.paginator.OnFirstPage() {
        items = append(items, MenuItem{
            Index: "p",
            Desc:  "Prev page",
            Run:   func() { tm.getPrevPage() },
        })
    }
    return items
}

func (tm *TracksMenu) getNextPage() {
    tm.tracks, tm.err = tm.paginator.NextPage()
}

func (tm *TracksMenu) getPrevPage() {
    tm.tracks, tm.err = tm.paginator.PrevPage()
}