~chrisppy/beagles

44ee55a920401496e132ba3f6de5d0b34a4a96fc — Chris Palmer 1 year, 4 months ago 4d2b356
Update error text, and panic for now, from db
7 files changed, 262 insertions(+), 288 deletions(-)

M README.md
M db.go
M main.go
M model.go
M rss.go
M statusline.go
M ui.go
M README.md => README.md +1 -22
@@ 4,28 4,7 @@

[![builds.sr.ht status](https://builds.sr.ht/~chrisppy/beagles/.build.yml.svg)](https://builds.sr.ht/~chrisppy/beagles/.build.yml?)

This is still a WIP with the following left to do for a first release

- [x] Add podcast
- [x] Remove podcast
- [ ] Update podcasts
- [ ] Add UI element to show errors
- [ ] Add Player functionality
  - [ ] Speed Control
    - [ ] Add to Default Config
    - [ ] Add Hotkey
  - [ ] Play/Pause
    - [ ] Add Widget
    - [ ] Add Hotkeys
    - [ ] Save position on pause
    - [ ] Mark read when finished in the database
    - [ ] Add audio playback - beep
  - [ ] Skip
    - [ ] Add Hotkeys
    - [ ] Add Audio capability
- [ ] Add man page for configurability, with note in help
- [ ] Add subscription page
- [ ] Add rss page for feeds without podcasts
This is still a WIP

## Use


M db.go => db.go +90 -92
@@ 27,17 27,17 @@ import (
)

const (
	queueTbl   = "queue"
	episodeTbl = "episode"
	podcastTbl = "podcast"
	queueTbl = "queue"
	itemTbl  = "item"
	feedTbl  = "feed"
)

// Storage contains the main data needed to run the app.
type Storage struct {
	Podcasts Podcasts
	Episodes Episodes
	Queue    Queue
	Path     string
	Feeds Feeds
	Items Items
	Queue Queue
	Path  string
}

func openDB(path string) (*bolt.DB, error) {


@@ 57,21 57,21 @@ func ReadDB(path string) (*Storage, error) {
	defer db.Close()

	s := &Storage{
		Podcasts: make(Podcasts),
		Episodes: make(Episodes),
		Queue:    make(Queue, 0),
		Path:     path,
		Feeds: make(Feeds),
		Items: make(Items),
		Queue: make(Queue, 0),
		Path:  path,
	}

	if err := db.View(func(tx *bolt.Tx) error {
		pb := tx.Bucket([]byte(podcastTbl))
		pb := tx.Bucket([]byte(feedTbl))
		if pb != nil {
			if err := pb.ForEach(func(k, v []byte) error {
				podcast := &Podcast{}
				if err := json.Unmarshal(v, podcast); err != nil {
				feed := &Feed{}
				if err := json.Unmarshal(v, feed); err != nil {
					return err
				}
				s.Podcasts[string(k)] = podcast
				s.Feeds[string(k)] = feed

				return nil
			}); err != nil {


@@ 79,14 79,14 @@ func ReadDB(path string) (*Storage, error) {
			}
		}

		eb := tx.Bucket([]byte(episodeTbl))
		eb := tx.Bucket([]byte(itemTbl))
		if eb != nil {
			if err := eb.ForEach(func(k, v []byte) error {
				episode := &Episode{}
				if err := json.Unmarshal(v, episode); err != nil {
				item := &Item{}
				if err := json.Unmarshal(v, item); err != nil {
					return err
				}
				s.Episodes[string(k)] = episode
				s.Items[string(k)] = item

				return nil
			}); err != nil {


@@ 117,64 117,64 @@ func ReadDB(path string) (*Storage, error) {

		return nil
	}); err != nil {
		return nil, err
		return nil, fmt.Errorf("unable to read db: %s", err.Error())
	}

	return s, nil
}

// CreatePodcast will collect the rss feed and process through the elements
// CreateFeed will collect the rss feed and process through the elements
// and add the relevant data elements to the database
func (s *Storage) CreatePodcast(url string) error {
	feed, err := rss.Fetch(url)
func (s *Storage) CreateFeed(url string) error {
	f, err := rss.Fetch(url)
	if err != nil {
		return err
		return fmt.Errorf("unable to fetch url: %s", err.Error())
	}

	podcast := convertFromFeed(feed)
	if podcast == nil {
	feed := convertFromFeed(f)
	if feed == nil {
		return fmt.Errorf("failed getting rss feed for url: %s", url)
	}
	key := podcast.UpdateURL
	key := feed.UpdateURL

	db, err := openDB(s.Path)
	if err != nil {
		return err
		return fmt.Errorf("unable to open db: %s", err.Error())
	}
	defer db.Close()

	for i, item := range feed.Items {
		episode := convertFromItem(item)
		if episode == nil {
	for i, it := range f.Items {
		item := convertFromItem(it)
		if item == nil {
			return fmt.Errorf("failure while processing title: %s", item.Title)
		}
		episode.PodcastURL = key
		episodeJSON, err := json.Marshal(episode)
		item.FeedURL = key
		itemJSON, err := json.Marshal(item)
		if err != nil {
			return err
			return fmt.Errorf("unable to marshal item: %s", err.Error())
		}

		queue := &QueueItem{
			QueuePOS:    len(s.Queue),
			Episode:     episode,
			Item:        item,
			PlaybackPOS: 0,
		}
		queueJSON, err := json.Marshal(queue)
		if err != nil {
			return err
			return fmt.Errorf("unable to marshal queue: %s", err.Error())
		}
		s.Queue = append(s.Queue, queue)

		ekey := episode.Location
		s.Episodes[ekey] = episode
		podcast.Episodes[i] = ekey
		ekey := item.Location
		s.Items[ekey] = item
		feed.Items[i] = ekey
		if err := db.Update(func(tx *bolt.Tx) error {
			eb, err := tx.CreateBucketIfNotExists([]byte(episodeTbl))
			eb, err := tx.CreateBucketIfNotExists([]byte(itemTbl))
			if err != nil {
				return err
			}

			if err := eb.Put([]byte(ekey), episodeJSON); err != nil {
			if err := eb.Put([]byte(ekey), itemJSON); err != nil {
				return err
			}



@@ 189,36 189,35 @@ func (s *Storage) CreatePodcast(url string) error {

			return nil
		}); err != nil {
			return err
			return fmt.Errorf("unable to add item and queue: %s", err.Error())
		}

	}

	podcastJSON, err := json.Marshal(podcast)
	feedJSON, err := json.Marshal(feed)
	if err != nil {
		return err
		return fmt.Errorf("unable to marshal feed: %s", err.Error())
	}
	if err := db.Update(func(tx *bolt.Tx) error {
		eb, err := tx.CreateBucketIfNotExists([]byte(podcastTbl))
		eb, err := tx.CreateBucketIfNotExists([]byte(feedTbl))
		if err != nil {
			return err
		}

		if err := eb.Put([]byte(key), podcastJSON); err != nil {
		if err := eb.Put([]byte(key), feedJSON); err != nil {
			return err
		}

		return nil
	}); err != nil {
		return err
		return fmt.Errorf("unable to add feed: %s", err.Error())
	}
	s.Podcasts[key] = podcast
	s.Feeds[key] = feed

	return nil
}

// DeletePodcast will delete the Podcast, Episodes, and relevant items it the queue.
func (s *Storage) DeletePodcast(url string) error {
// DeleteFeed will delete the Feed, Items, and relevant items it the queue.
func (s *Storage) DeleteFeed(url string) error {
	if url == "" {
		return fmt.Errorf("url was empty, unable to delete")
	}


@@ 229,23 228,23 @@ func (s *Storage) DeletePodcast(url string) error {
	}
	defer db.Close()

	episodes := s.Podcasts[url].Episodes
	for _, key := range episodes {
		// Delete from episode database
	items := s.Feeds[url].Items
	for _, key := range items {
		// Delete from item database
		if err := db.Update(func(tx *bolt.Tx) error {
			if err := tx.Bucket([]byte(queueTbl)).Delete([]byte(key)); err != nil {
				return err
			}
			return tx.Bucket([]byte(episodeTbl)).Delete([]byte(key))
			return tx.Bucket([]byte(itemTbl)).Delete([]byte(key))

		}); err != nil {
			return err
			return fmt.Errorf("unable to delete queue and item: %s", err.Error())
		}

		// check if queue is affected by the delete
		index := -1
		for i, e := range s.Queue {
			if e.Episode.Location == key {
			if e.Item.Location == key {
				index = i
				break
			}


@@ 255,16 254,15 @@ func (s *Storage) DeletePodcast(url string) error {
			s.Queue = append(s.Queue[:index], s.Queue[index+1:]...)
		}

		delete(s.Episodes, key)
		delete(s.Items, key)
	}

	if err := db.Update(func(tx *bolt.Tx) error {
		return tx.Bucket([]byte(podcastTbl)).Delete([]byte(url))

		return tx.Bucket([]byte(feedTbl)).Delete([]byte(url))
	}); err != nil {
		return err
		return fmt.Errorf("unable to delete feed: %s", err.Error())
	}
	delete(s.Podcasts, url)
	delete(s.Feeds, url)

	return nil
}


@@ 277,22 275,22 @@ func (s *Storage) Update() error {
	}
	defer db.Close()

	for _, podcast := range s.Podcasts {
		updateURL := podcast.UpdateURL
	for _, f := range s.Feeds {
		updateURL := f.UpdateURL

		episodes, err := findNewEpisodes(podcast)
		items, err := findNewItems(f)
		if err != nil {
			return err
		}

		for _, episode := range episodes {
			location := episode.Location
		for _, item := range items {
			location := item.Location

			q := &QueueItem{
				Episode:  episode,
				QueuePOS: len(s.Episodes),
				Item:     item,
				QueuePOS: len(s.Items),
			}
			episodeJSON, err := json.Marshal(episode)
			itemJSON, err := json.Marshal(item)
			if err != nil {
				return err
			}


@@ 307,8 305,8 @@ func (s *Storage) Update() error {
					return err
				}

				// Add to Episode Database
				if err := tx.Bucket([]byte(episodeTbl)).Put([]byte(location), episodeJSON); err != nil {
				// Add to Item Database
				if err := tx.Bucket([]byte(itemTbl)).Put([]byte(location), itemJSON); err != nil {
					return err
				}



@@ 318,22 316,22 @@ func (s *Storage) Update() error {
				return err
			}

			s.Episodes[location] = episode
			s.Items[location] = item
			s.Queue = append(s.Queue, q)

			s.Podcasts[updateURL].Episodes = append(s.Podcasts[updateURL].Episodes, location)
			s.Podcasts[updateURL].Unread++
			s.Feeds[updateURL].Items = append(s.Feeds[updateURL].Items, location)
			s.Feeds[updateURL].Unread++
		}

		if len(episodes) != 0 {
			podcastJSON, err := json.Marshal(s.Podcasts[updateURL])
		if len(items) != 0 {
			feedJSON, err := json.Marshal(s.Feeds[updateURL])
			if err != nil {
				return err
			}

			if err := db.Update(func(tx *bolt.Tx) error {
				// Update Podcast Database
				if err := tx.Bucket([]byte(episodeTbl)).Put([]byte(updateURL), podcastJSON); err != nil {
				// Update Feed Database
				if err := tx.Bucket([]byte(itemTbl)).Put([]byte(updateURL), feedJSON); err != nil {
					return err
				}



@@ 348,8 346,8 @@ func (s *Storage) Update() error {
	return nil
}

// MarkPlayed with update the Storage to note the an episode has been read,
// will update the Podcast unread count and remove the episode from the queue.
// MarkPlayed with update the Storage to note the an item has been read,
// will update the Feed unread count and remove the item from the queue.
func (s *Storage) MarkPlayed(location string) error {
	if location == "" {
		return fmt.Errorf("location was empty, unable to mark played")


@@ 361,15 359,15 @@ func (s *Storage) MarkPlayed(location string) error {
	}
	defer db.Close()

	episode := s.Episodes[location]
	episode.Read = true
	item := s.Items[location]
	item.Read = true

	podcast := s.Podcasts[episode.PodcastURL]
	s.Podcasts[episode.PodcastURL].Unread--
	feed := s.Feeds[item.FeedURL]
	s.Feeds[item.FeedURL].Unread--

	index := -1
	for i, e := range s.Queue {
		if e.Episode.Location == location {
		if e.Item.Location == location {
			index = i
			break
		}


@@ 379,12 377,12 @@ func (s *Storage) MarkPlayed(location string) error {
		s.Queue = append(s.Queue[:index], s.Queue[index+1:]...)
	}

	episodeJSON, err := json.Marshal(episode)
	itemJSON, err := json.Marshal(item)
	if err != nil {
		return err
	}

	podcastJSON, err := json.Marshal(podcast)
	feedJSON, err := json.Marshal(feed)
	if err != nil {
		return err
	}


@@ 395,13 393,13 @@ func (s *Storage) MarkPlayed(location string) error {
			return err
		}

		// Mark read in Episode Database
		if err := tx.Bucket([]byte(episodeTbl)).Put([]byte(location), episodeJSON); err != nil {
		// Mark read in Item Database
		if err := tx.Bucket([]byte(itemTbl)).Put([]byte(location), itemJSON); err != nil {
			return err
		}

		// Update Unread Count is Podcast Database
		if err := tx.Bucket([]byte(podcastTbl)).Put([]byte(podcast.UpdateURL), podcastJSON); err != nil {
		// Update Unread Count is Feed Database
		if err := tx.Bucket([]byte(feedTbl)).Put([]byte(feed.UpdateURL), feedJSON); err != nil {
			return err
		}


M main.go => main.go +2 -2
@@ 52,9 52,9 @@ func main() {

	for x, item := range db.Queue {
		if x == 0 {
			i.ShowNotes.setText(item.Episode.Content)
			i.ShowNotes.setText(item.Item.Content)
		}
		i.Queue.add(item.Episode.Title)
		i.Queue.add(item.Item.Title)
	}

	if err := i.App.run(i.Pages, true); err != nil {

M model.go => model.go +26 -26
@@ 21,43 21,43 @@ import (
	"time"
)

// Queue of the episodes to be played
// Queue of the items to be played
type Queue []*QueueItem

// QueueItem contains relevant data for the queue
type QueueItem struct {
	QueuePOS    int      `json:"queueposition"`
	Episode     *Episode `json:"episode"`
	PlaybackPOS int      `json:"playbackposition"`
	QueuePOS    int   `json:"queueposition"`
	Item        *Item `json:"item"`
	PlaybackPOS int   `json:"playbackposition"`
}

// Podcasts is a map of the UpdateURL to the Podcast.
type Podcasts map[string]*Podcast
// Feeds is a map of the UpdateURL to the Feed.
type Feeds map[string]*Feed

// Podcast is the structure of the Feed from RSS that we care about.  The
// Episodes contains the unique Location.
type Podcast struct {
// Feed is the structure of the Feed from RSS that we care about.  The
// Items contains the unique Location.
type Feed struct {
	Title       string   `json:"title"`
	Description string   `json:"description"`
	UpdateURL   string   `json:"updateurl"`
	Unread      uint32   `json:"unread"`
	Episodes    []string `json:"episodes"`
	Items       []string `json:"items"`
}

// Episodes is a map of the Location to the Episode
type Episodes map[string]*Episode

// Episode is the structure of the Item from RSS that we care about.
type Episode struct {
	PodcastURL string    `json:"podcasturl"`
	Title      string    `json:"title"`
	Summary    string    `json:"summary"`
	Content    string    `json:"content"`
	Link       string    `json:"link"`
	Date       time.Time `json:"date"`
	ID         string    `json:"id"`
	Location   string    `json:"location"`
	Type       string    `json:"type"`
	Length     uint      `json:"length"`
	Read       bool      `json:"read"`
// Items is a map of the Location to the Item
type Items map[string]*Item

// Item is the structure of the Item from RSS that we care about.
type Item struct {
	FeedURL  string    `json:"feedurl"`
	Title    string    `json:"title"`
	Summary  string    `json:"summary"`
	Content  string    `json:"content"`
	Link     string    `json:"link"`
	Date     time.Time `json:"date"`
	ID       string    `json:"id"`
	Location string    `json:"location"`
	Type     string    `json:"type"`
	Length   uint      `json:"length"`
	Read     bool      `json:"read"`
}

M rss.go => rss.go +30 -24
@@ 18,60 18,66 @@
package main

import (
	"fmt"

	"github.com/SlyMarbo/rss"
)

func findNewEpisodes(podcast *Podcast) ([]*Episode, error) {
	feed, err := rss.Fetch(podcast.UpdateURL)
func findNewItems(f *Feed) ([]*Item, error) {
	feed, err := rss.Fetch(f.UpdateURL)
	if err != nil {
		return nil, err
		return nil, fmt.Errorf("unable to fetch url: %s", err.Error())
	}

	episodes := make([]*Episode, 0)
	items := make([]*Item, 0)
it:
	for _, item := range feed.Items {
		for _, location := range podcast.Episodes {
		for _, location := range f.Items {
			if item.Enclosures[0].URL == location {
				continue it
			}
		}
		episodes = append(episodes, convertFromItem(item))
		items = append(items, convertFromItem(item))
	}

	return episodes, nil
	return items, nil
}

func convertFromItem(item *rss.Item) *Episode {
func convertFromItem(item *rss.Item) *Item {
	if item == nil {
		return nil
	}

	episode := &Episode{
		Title:    item.Title,
		Summary:  item.Summary,
		Content:  item.Content,
		Link:     item.Link,
		Date:     item.Date,
		ID:       item.ID,
		Location: item.Enclosures[0].URL,
		Type:     item.Enclosures[0].Type,
		Length:   item.Enclosures[0].Length,
		Read:     false,
	i := &Item{
		Title:   item.Title,
		Summary: item.Summary,
		Content: item.Content,
		Link:    item.Link,
		Date:    item.Date,
		ID:      item.ID,
		Read:    false,
	}

	if len(item.Enclosures) > 0 {
		e := item.Enclosures[0]
		i.Location = e.URL
		i.Type = e.Type
		i.Length = e.Length
	}
	return episode

	return i
}

func convertFromFeed(feed *rss.Feed) *Podcast {
func convertFromFeed(feed *rss.Feed) *Feed {
	if feed == nil {
		return nil
	}

	podcast := &Podcast{
	return &Feed{
		Title:       feed.Title,
		Description: feed.Description,
		UpdateURL:   feed.UpdateURL,
		Unread:      feed.Unread,
		Episodes:    make([]string, len(feed.Items)),
		Items:       make([]string, len(feed.Items)),
	}
	return podcast
}

M statusline.go => statusline.go +1 -1
@@ 29,7 29,7 @@ type statusLine struct {
func newStatusLine(theme *themeType) *statusLine {
	line := tui.NewTextView()
	line.SetTextAlign(tui.AlignLeft)
	line.SetText("-- QUEUE --")
	line.SetText("-- LIST --")

	bgColor := theme.BackgroundColor
	fgColor := theme.ForegroundColor

M ui.go => ui.go +112 -121
@@ 99,54 99,13 @@ func initUI(db *Storage, theme *themeType) *ui {

			return event
		case tcell.KeyLeft:
			switch i.Status {
			case showNotesStatus:
				if i.InCmdMode {
					return event
				}

				i.setStatus(queueStatus)

				return nil
			}
			return i.moveLeft(event)
		case tcell.KeyDown:
			switch i.Status {
			case queueStatus:
				if i.InCmdMode {
					return event
				}

				index := i.Queue.increment()
				if index != -1 {
					i.ShowNotes.setText(i.DB.Queue[index].Episode.Content)
				}

				return nil
			}
			return i.moveDown(event)
		case tcell.KeyUp:
			switch i.Status {
			case queueStatus:
				if i.InCmdMode {
					return event
				}

				index := i.Queue.decrement()
				if index != -1 {
					i.ShowNotes.setText(i.DB.Queue[index].Episode.Content)
				}

				return nil
			}
			return i.moveUp(event)
		case tcell.KeyRight:
			switch i.Status {
			case queueStatus:
				if i.InCmdMode {
					return event
				}

				i.setStatus(showNotesStatus)
				return nil
			}
			return i.moveRight(event)
		case tcell.KeyRune:
			switch event.Rune() {
			case ':':


@@ 157,54 116,13 @@ func initUI(db *Storage, theme *themeType) *ui {
				i.setStatus(commandStatus)
				return nil
			case 'h':
				switch i.Status {
				case showNotesStatus:
					if i.InCmdMode {
						return event
					}
					i.setStatus(queueStatus)

					return nil
				}
				return i.moveLeft(event)
			case 'j':
				switch i.Status {
				case queueStatus:
					if i.InCmdMode {
						return event
					}
					index := i.Queue.increment()
					if index != -1 {
						i.ShowNotes.setText(i.DB.Queue[index].Episode.Content)
					}

					return nil
				}
				return i.moveDown(event)
			case 'k':
				switch i.Status {
				case queueStatus:
					if i.InCmdMode {
						return event
					}

					index := i.Queue.decrement()
					if index != -1 {
						i.ShowNotes.setText(i.DB.Queue[index].Episode.Content)
					}

					return nil
				}
				return i.moveUp(event)
			case 'l':
				switch i.Status {
				case queueStatus:
					if i.InCmdMode {
						return event
					}

					i.setStatus(showNotesStatus)

					return nil
				}

				return i.moveRight(event)
			}
		}



@@ 225,36 143,16 @@ func initUI(db *Storage, theme *themeType) *ui {
		switch args[0] {
		case ":quit", ":q":
			i.App.exit()
		case ":help", ":h":
		case ":help", ":h", ":?":
			i.setStatus(helpStatus)
		case ":playlist":
		case ":list":
			i.setStatus(queueStatus)
		case ":add":
			i.DB.CreatePodcast(args[1])
			i.App.draw(func() {
				i.ShowNotes.clear()
				i.Queue.clear()
				for x, item := range db.Queue {
					if x == 0 {
						i.ShowNotes.setText(item.Episode.Content)
					}
					i.Queue.add(item.Episode.Title)
				}
			})
			i.addFeed(args)
		case ":remove", ":rm":
			i.DB.DeletePodcast(args[1])
			i.App.draw(func() {
				i.ShowNotes.clear()
				i.Queue.clear()
				for x, item := range db.Queue {
					if x == 0 {
						i.ShowNotes.setText(item.Episode.Content)
					}
					i.Queue.add(item.Episode.Title)
				}
			})
			i.deleteFeed(args)
		case ":update", ":up":
			// TODO: Update feeds
			i.updateFeed(args)
		}
		i.CommandLine.clear()
		i.InCmdMode = false


@@ 265,6 163,99 @@ func initUI(db *Storage, theme *themeType) *ui {
	return i
}

func (i *ui) updateFeed(args []string) {
	// TODO: Update feeds
}

func (i *ui) addFeed(args []string) {
	if err := i.DB.CreateFeed(args[1]); err != nil {
		panic(err.Error())
	}
	i.App.draw(func() {
		i.ShowNotes.clear()
		i.Queue.clear()
		for x, item := range i.DB.Queue {
			if x == 0 {
				i.ShowNotes.setText(item.Item.Content)
			}
			i.Queue.add(item.Item.Title)
		}
	})

}
func (i *ui) deleteFeed(args []string) {
	if err := i.DB.DeleteFeed(args[1]); err != nil {
		panic(err.Error())
	}
	i.App.draw(func() {
		i.ShowNotes.clear()
		i.Queue.clear()
		for x, item := range i.DB.Queue {
			if x == 0 {
				i.ShowNotes.setText(item.Item.Content)
			}
			i.Queue.add(item.Item.Title)
		}
	})
}

func (i *ui) moveLeft(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case showNotesStatus:
		if i.InCmdMode {
			return event
		}
		i.setStatus(queueStatus)

		return nil
	}
	return event
}
func (i *ui) moveRight(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case queueStatus:
		if i.InCmdMode {
			return event
		}

		i.setStatus(showNotesStatus)

		return nil
	}
	return event
}
func (i *ui) moveUp(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case queueStatus:
		if i.InCmdMode {
			return event
		}

		index := i.Queue.decrement()
		if index != -1 {
			i.ShowNotes.setText(i.DB.Queue[index].Item.Content)
		}

		return nil
	}
	return event
}
func (i *ui) moveDown(event *tcell.EventKey) *tcell.EventKey {
	switch i.Status {
	case queueStatus:
		if i.InCmdMode {
			return event
		}
		index := i.Queue.increment()
		if index != -1 {
			i.ShowNotes.setText(i.DB.Queue[index].Item.Content)
		}

		return nil
	}
	return event
}

func (i *ui) createQueuePage(theme *themeType) {
	qc := tui.NewFlex()
	qc.SetDirection(tui.FlexColumn)


@@ 318,13 309,13 @@ COMMANDS:
	update,up
		update all feeds

	help,h
	help,h,?
		display help page

	playlist
		display playlist page
	list
		display list page
	
	quit, q
	quit,q
		exit the application
`



@@ 371,13 362,13 @@ func (i *ui) setStatus(s statusType) {
		i.App.switchTo(i.CommandLine.Widget)
		return
	case showNotesStatus:
		i.StatusLine.setText("-- SHOWNOTES --")
		i.StatusLine.setText("-- CONTENT --")
		i.App.switchTo(i.ShowNotes.Widget)
	case helpStatus:
		i.StatusLine.setText("-- HELP --")
		i.Pages.SwitchToPage("help")
	default:
		i.StatusLine.setText("-- PLAYLIST --")
		i.StatusLine.setText("-- LIST --")
		i.Pages.SwitchToPage("queue")
	}
	i.Status = s