~chrisppy/beagles

65570b11bda4763f4cf4655bfbcb53be96051940 — Chris Palmer 25 days ago ca3ac2c
Updates to add feed updated & fix feed markunread
9 files changed, 110 insertions(+), 70 deletions(-)

M db/db.go
M db/feed.go
M go.mod
M go.sum
M ui/actions.go
M ui/content.go
M ui/helpContent.go
M ui/status.go
M ui/ui.go
M db/db.go => db/db.go +10 -3
@@ 187,6 187,11 @@ func (s *Storage) Backup(path string) error {
			Items:       make([]barefeed.Item, len(v.Items)),
		}

		if v.Updated != nil {
			up := barefeed.ToTimestamp(*v.Updated)
			f.Updated = &up
		}

		j := 0
		for k1 := range v.Items {
			val, ok := s.Items[k1]


@@ 343,7 348,7 @@ func (s *Storage) Update() (Items, error) {
	for _, f := range s.Feeds {
		updateURL := f.UpdateURL

		items, err := f.FindNewItems(s.Items, s.GeminiPath)
		items, updated, err := f.FindNewItems(s.Items, s.GeminiPath)
		if err != nil {
			if err := db.Close(); err != nil {
				return nil, err


@@ 351,7 356,7 @@ func (s *Storage) Update() (Items, error) {
			return nil, fmt.Errorf("error finding new items for `%s`: %s", updateURL, err.Error())
		}

		if err := s.Feeds.AddItems(db, updateURL, items); err != nil {
		if err := s.Feeds.AddItems(db, updateURL, items, updated); err != nil {
			if err := db.Close(); err != nil {
				return nil, err
			}


@@ 471,7 476,9 @@ func (s *Storage) MarkUnread(key string) (Items, error) {
	}

	for k := range itemMap {
		if !s.Items[k].Read {
		if _, ok := s.Items[k]; !ok {
			continue
		} else if !s.Items[k].Read {
			continue
		}


M db/feed.go => db/feed.go +26 -9
@@ 21,6 21,7 @@ import (
	"encoding/json"
	"fmt"
	"sort"
	"time"

	"git.sr.ht/~chrisppy/beagles/util"
	"github.com/mmcdole/gofeed"


@@ 41,9 42,17 @@ type Feed struct {
	Description string          `json:"description"`
	UpdateURL   string          `json:"updateurl"`
	Link        string          `json:"link"`
	Updated     *time.Time      `json:"updated"`
	Items       map[string]bool `json:"items"`
}

func getFeedUpdated(feed *gofeed.Feed) *time.Time {
	if feed.UpdatedParsed != nil {
		return feed.UpdatedParsed
	}
	return feed.PublishedParsed
}

func processFeed(feed *gofeed.Feed, url string) (*Feed, error) {
	if feed == nil {
		return nil, nil


@@ 59,6 68,7 @@ func processFeed(feed *gofeed.Feed, url string) (*Feed, error) {
		Description: d,
		UpdateURL:   url,
		Link:        feed.FeedLink,
		Updated:     getFeedUpdated(feed),
		Items:       make(map[string]bool),
	}



@@ 75,18 85,23 @@ func processFeed(feed *gofeed.Feed, url string) (*Feed, error) {

// FindNewItems will process the items under the feed and add return any new
// ones since last update
func (f *Feed) FindNewItems(citems map[string]*Item, gmniPath string) (map[string]*Item, error) {
func (f *Feed) FindNewItems(citems map[string]*Item, gmniPath string) (Items, *time.Time, error) {
	feed, err := util.ParseFeed(f.UpdateURL, gmniPath)
	if err != nil {
		return nil, fmt.Errorf("unable to fetch url: %s", err.Error())
		return nil, nil, fmt.Errorf("unable to fetch url: %s", err.Error())
	}

	updated := getFeedUpdated(feed)

	if feed == nil {
		return nil, nil
		return nil, nil, nil
	} else if f.Updated != nil && updated != nil {
		if f.Updated == updated {
			return nil, nil, nil
		}
	}

	items := make(map[string]*Item)

	items := make(Items)
	for _, item := range feed.Items {
		link := getLink(item)



@@ 96,13 111,13 @@ func (f *Feed) FindNewItems(citems map[string]*Item, gmniPath string) (map[strin

		i, err := processItem(item)
		if err != nil {
			return nil, err
			return nil, nil, err
		}
		i.FeedURL = f.UpdateURL
		items[i.Link] = i
	}

	return items, nil
	return items, updated, nil
}

// Compare will determine equality of strings


@@ 200,12 215,12 @@ func (f Feeds) Delete(db *bolt.DB, url string) (*Feed, error) {
}

// AddItems will update the feeds map of items and unread count
func (f Feeds) AddItems(db *bolt.DB, url string, items Items) error {
func (f Feeds) AddItems(db *bolt.DB, url string, items Items, updated *time.Time) error {
	if url == "" {
		return fmt.Errorf("url was empty, unable to delete")
	}

	if len(items) == 0 {
	if len(items) == 0 && updated == nil {
		return nil
	}



@@ 214,6 229,8 @@ func (f Feeds) AddItems(db *bolt.DB, url string, items Items) error {
		return fmt.Errorf("feed does not exist")
	}

	feed.Updated = updated

	for _, item := range items {
		feed.Items[item.Link] = false
	}

M go.mod => go.mod +1 -1
@@ 4,7 4,7 @@ go 1.15

require (
	git.sr.ht/~adnano/go-gemini v0.1.10
	git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201229170024-75dc5f7d571e
	git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60
	git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e
	git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc
	git.sr.ht/~sircmpwn/getopt v0.0.0-20201218204720-9961a9c6298f

M go.sum => go.sum +2 -2
@@ 1,7 1,7 @@
git.sr.ht/~adnano/go-gemini v0.1.10 h1:enuYuY2pC+1BsP1GE73wLIyhXc4r+Ryx6TUubynmSgo=
git.sr.ht/~adnano/go-gemini v0.1.10/go.mod h1:If1VxEWcZDrRt5FeAFnGTcM2Ud1E3BXs3VJ5rnZWKq0=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201229170024-75dc5f7d571e h1:gWpCPRsUQ3cfRtgVQk29W/TrljnjzseSUFh52V0aY4M=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201229170024-75dc5f7d571e/go.mod h1:V/4QgRdPISXxrbyn/0YNdrh1qR2PHk8hdcIJsdmJFlQ=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60 h1:B9GcMx6b4PirZp+kywPHnKwggOGKopWgwd+J4FvxXQg=
git.sr.ht/~chrisppy/go-barefeed v0.0.0-20201231052557-c9dc6d4deb60/go.mod h1:V/4QgRdPISXxrbyn/0YNdrh1qR2PHk8hdcIJsdmJFlQ=
git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e h1:XOqA6g2gAr/ma6/W8xlhyt7CUIhkSsi+vGBMpXGiaxk=
git.sr.ht/~chrisppy/go-opml v0.0.0-20201229021831-d223d86f160e/go.mod h1:dhjKH7fks5UN50Cnma/JCtxx8IRw8xAicwKV9gAtsHk=
git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc h1:51BD67xFX+bozd3ZRuOUfalrhx4/nQSh6A9lI08rYOk=

M ui/actions.go => ui/actions.go +28 -32
@@ 52,6 52,8 @@ func (i *UI) onCommandEnter(key tcell.Key) {
		case ":unhide":
			i.unhide()
		case ":update", ":up":
			i.commandLine.setText("updating feeds...")
			i.app.draw()
			go i.updateFeed(args)
		default:
			i.commandLine.setError("unrecognized command: " + input)


@@ 345,7 347,6 @@ func (i *UI) markUnread(event *tcell.EventKey) *tcell.EventKey {
		}

		nitems, err := i.DB.MarkUnread(key)

		if err != nil {
			i.commandLine.setError(err.Error())
			return


@@ 459,10 460,9 @@ func (i *UI) unmarkFavorite(event *tcell.EventKey) *tcell.EventKey {
			i.commandLine.setError(err.Error())
			return
		}
		i.setStatus(i.status)
	})

	i.app.draw()

	return nil
}



@@ 496,10 496,9 @@ func (i *UI) openLink(event *tcell.EventKey) *tcell.EventKey {
				i.app.draw()
			}
		}()
		i.setStatus(i.status)
	})

	i.app.draw()

	return nil
}



@@ 525,12 524,12 @@ func (i *UI) play(event *tcell.EventKey) *tcell.EventKey {
		return event
	}

	i.app.update(func() {
		item, ok := i.DB.Items[key]
		if !ok {
			return
		}
	item, ok := i.DB.Items[key]
	if !ok {
		return nil
	}

	i.app.update(func() {
		if err := item.ExtPlay(ep.Bin, ep.Args, i.DownloadDataHome, i.DB.Feeds); err != nil {
			i.commandLine.setError(err.Error())
			i.app.draw()


@@ 539,6 538,7 @@ func (i *UI) play(event *tcell.EventKey) *tcell.EventKey {
		i.setStatus(i.status)
	})
	i.app.draw()

	return nil
}



@@ 562,12 562,12 @@ func (i *UI) download(event *tcell.EventKey) *tcell.EventKey {
	i.commandLine.setText("downloading...")
	i.app.draw()

	i.app.update(func() {
		item, ok := i.DB.Items[key]
		if !ok {
			return
		}
	item, ok := i.DB.Items[key]
	if !ok {
		return nil
	}

	i.app.update(func() {
		if err := item.Download(i.DownloadDataHome, i.DB.Feeds); err != nil {
			i.commandLine.setError(err.Error())
			return


@@ 581,29 581,25 @@ func (i *UI) download(event *tcell.EventKey) *tcell.EventKey {
}

func (i *UI) updateFeed(args []string) {
	i.commandLine.setText("updating feeds...")
	i.app.draw()

	i.app.update(func() {
		nitems, err := i.DB.Update()
		if err != nil {
			i.Logger.Errorln(err.Error())
		}
	nitems, err := i.DB.Update()
	if err != nil {
		i.Logger.Errorln(err.Error())
		return
	}

		for _, item := range nitems {
	for _, item := range nitems.Sort() {
		i.app.update(func() {
			i.list.insert(item)
			i.sub.insert(item, i.hideRead)
			if i.Config.Podcast.AutoDownload {
				if err := item.Download(i.DownloadDataHome, i.DB.Feeds); err != nil {
					i.Logger.Errorln(err.Error())
				}
		})
		if i.Config.Podcast.AutoDownload {
			if err := item.Download(i.DownloadDataHome, i.DB.Feeds); err != nil {
				i.Logger.Errorln(err.Error())
			}
		}
	}

		i.commandLine.setText("")
		i.setStatus(i.status)
	})
	i.app.draw()
	i.setStatus(i.status)
}

func (i *UI) addFeed(args []string) {

M ui/content.go => ui/content.go +32 -14
@@ 18,8 18,8 @@
package ui

import (
	"fmt"
	"strings"
	"time"

	"git.sr.ht/~chrisppy/beagles/db"
	"git.sr.ht/~chrisppy/beagles/util"


@@ 50,20 50,31 @@ func (i *UI) newContent() *content {
	}
}

func (w *content) setDescription(text string, isHTML bool) {
func (w *content) setDescription(text string, updated *time.Time, isHTML bool) {
	// TODO: Handle gemini content to text
	w.Widget.SetTextColor(w.TxtColor)

	var sb strings.Builder

	if updated != nil {
		sb.WriteString("Feed Updated: ")
		sb.WriteString(updated.Format("2006-01-02"))
		sb.WriteByte('\n')
		sb.WriteByte('\n')
	}

	if isHTML {
		t, err := util.StripHTML(text)
		if err != nil {
			w.Widget.SetTextColor(w.ErrColor)
			t = err.Error()
		}
		w.Widget.SetText(t)
		sb.WriteString(t)
	} else {
		w.Widget.SetText(text)
		sb.WriteString(text)
	}

	w.Widget.SetText(sb.String())
	w.Widget.ScrollToBeginning()
}



@@ 76,34 87,41 @@ func (w *content) setText(item *db.Item) {
		return
	}

	content := ""
	var sb strings.Builder
	if item.Title != "" {
		content = fmt.Sprintf("%s\n\n", item.Title)
		sb.WriteString(item.Title)
		sb.WriteByte('\n')
		sb.WriteByte('\n')
	}

	content = fmt.Sprintf("%s%s\n\n", content, item.Date.Format("2006-01-02"))
	sb.WriteString(item.Date.Format("2006-01-02"))
	sb.WriteByte('\n')
	sb.WriteByte('\n')

	if item.Link != "" {
		content = fmt.Sprintf("%s%s\n\n", content, item.Link)
		sb.WriteString(item.Link)
		sb.WriteByte('\n')
		sb.WriteByte('\n')
	}

	c := item.Content
	var text string
	if c == "" {
		text = "No Content"
		sb.WriteString("No Content")
		sb.WriteByte('\n')
	} else if strings.HasPrefix(item.Link, "http") {
		t, err := util.StripHTML(c)
		if err != nil {
			w.Widget.SetTextColor(w.ErrColor)
			t = err.Error()
		}
		text = t
		sb.WriteString(t)
		sb.WriteByte('\n')
	} else if strings.HasPrefix(item.Link, "gemini") {
		text = c
		sb.WriteString(c)
		sb.WriteByte('\n')
	}
	content = fmt.Sprintf("%s%s\n", content, text)

	w.Widget.SetText(content)
	w.Widget.SetText(sb.String())
	w.Widget.ScrollToBeginning()
}


M ui/helpContent.go => ui/helpContent.go +1 -1
@@ 138,7 138,7 @@ SEE ALSO:
		i.Config.Play.Text)

	c := i.newContent()
	c.setDescription(content, false)
	c.setDescription(content, nil, false)

	return c
}

M ui/status.go => ui/status.go +9 -7
@@ 86,14 86,16 @@ func (i *UI) setStatus(s statusType) {
		cltext = "/"
	}

	if i.Mode == normal {
		i.panels.SetCurrentPanel(panel)
		i.status = s
	}
	i.app.update(func() {
		if i.Mode == normal {
			i.panels.SetCurrentPanel(panel)
			i.status = s
		}

	i.commandLine.setText(cltext)
	i.statusLine.setText(sltext)
	i.app.switchTo(widget)
		i.commandLine.setText(cltext)
		i.statusLine.setText(sltext)
		i.app.switchTo(widget)
	})

	i.app.draw()
}

M ui/ui.go => ui/ui.go +1 -1
@@ 104,7 104,7 @@ func (i *UI) Init() {
		if it, ok := i.DB.Items[key]; ok {
			i.subContent.setText(it)
		} else if f, ok := i.DB.Feeds[key]; ok {
			i.subContent.setDescription(f.Description, true)
			i.subContent.setDescription(f.Description, f.Updated, true)
		} else {
			i.subContent.clear()
		}