Skip to content

Commit

Permalink
feat: simplify adding articles as read (don't download everything)
Browse files Browse the repository at this point in the history
  • Loading branch information
TypicalAM committed Aug 20, 2023
1 parent 22787ea commit fd6a9b0
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 77 deletions.
41 changes: 8 additions & 33 deletions internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,34 +128,6 @@ func (b Backend) DownloadItem(feedName string, index int) tea.Cmd {
}
}

// MarkAsRead marks an article as read.
func (b Backend) MarkAsRead(feedName string, index int) tea.Cmd {
return func() tea.Msg {
item, err := b.indexToItem(feedName, index)
if err != nil {
return FetchErrorMsg{err, "Error while getting the article"}
}

log.Println("Marking as read:", item.Title)
b.ReadStatus.MarkAsRead(*item)
return nil
}
}

// MarkAsUnread marks an article as unread.
func (b Backend) MarkAsUnread(feedName string, index int) tea.Cmd {
return func() tea.Msg {
item, err := b.indexToItem(feedName, index)
if err != nil {
return FetchErrorMsg{err, "Error while getting the article"}
}

log.Println("Marking as unread:", item.Title)
b.ReadStatus.MarkAsUnread(*item)
return nil
}
}

// Close closes the backend and saves its components.
func (b Backend) Close() error {
if err := b.Rss.Save(); err != nil {
Expand All @@ -173,18 +145,21 @@ func (b Backend) Close() error {
func (b Backend) articlesToSuccessMsg(items cache.SortableArticles) FetchArticleSuccessMsg {
sort.Sort(items)
result := make([]list.Item, len(items))
contents := make([]string, len(items))

for i, item := range items {
if b.ReadStatus.IsRead(item) {
if b.ReadStatus.IsRead(item.Link) {
item.Title = "✓ " + item.Title
}

result[i] = simplelist.NewItem(item.Title, betterDesc(item.Description))
contents[i] = rss.YassifyItem(&items[i])
result[i] = ArticleItem{
ArtTitle: item.Title,
Desc: betterDesc(item.Description),
MarkdownContent: rss.YassifyItem(&items[i]),
FeedURL: item.Link,
}
}

return FetchArticleSuccessMsg{result, contents}
return FetchArticleSuccessMsg{result}
}

// indexToItem resolves an index to an item.
Expand Down
19 changes: 8 additions & 11 deletions internal/backend/cache/read_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"path/filepath"

"github.com/mmcdole/gofeed"
"github.com/spaolacci/murmur3"
)

Expand Down Expand Up @@ -78,19 +77,19 @@ func (rs ReadStatus) Save() error {
}

// MarkAsRead adds an article to the set.
func (rs *ReadStatus) MarkAsRead(item gofeed.Item) {
rs.set[hashArticle(item)] = struct{}{}
func (rs *ReadStatus) MarkAsRead(url string) {
rs.set[hashArticle(url)] = struct{}{}
}

// IsRead checks if an article is already in the set.
func (rs ReadStatus) IsRead(item gofeed.Item) bool {
_, ok := rs.set[hashArticle(item)]
func (rs ReadStatus) IsRead(url string) bool {
_, ok := rs.set[hashArticle(url)]
return ok
}

// MarkAsUnread removes an article from the set.
func (rs *ReadStatus) MarkAsUnread(item gofeed.Item) {
delete(rs.set, hashArticle(item))
func (rs *ReadStatus) MarkAsUnread(url string) {
delete(rs.set, hashArticle(url))
}

// marshal converts the set to bytes.
Expand All @@ -117,10 +116,8 @@ func unmarshal(data []byte) (map[uint32]struct{}, error) {
}

// hashArticle hashes the gofeed.Item to a uint32.
func hashArticle(item gofeed.Item) uint32 {
func hashArticle(url string) uint32 {
h := murmur3.New32()
h.Write([]byte(item.Title))
h.Write([]byte(item.Link))
h.Write([]byte(item.GUID))
h.Write([]byte(url))
return h.Sum32()
}
45 changes: 31 additions & 14 deletions internal/backend/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,30 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

// ArticleItem is an item that contains article data.
type ArticleItem struct {
list.Item
ArtTitle string
Desc string
MarkdownContent string
FeedURL string
}

// FilterValue fulfills the list.Item interface
func (a ArticleItem) FilterValue() string {
return a.ArtTitle
}

// Title fulfills the list.DefaultItem interface
func (a ArticleItem) Title() string {
return a.ArtTitle
}

// Description fulfills the list.DefaultItem interface
func (a ArticleItem) Description() string {
return a.Desc
}

// Fetcher fetches the data, it is used by tabs to query data.
type Fetcher func(feedname string) tea.Cmd

Expand All @@ -17,8 +41,7 @@ type FetchSuccessMsg struct{ Items []list.Item }

// FetchArticleSuccessMsg is sent on article fetch success.
type FetchArticleSuccessMsg struct {
Items []list.Item
ArticleContents []string
Items []list.Item
}

// FetchErrorMsg is sent on fetch error.
Expand Down Expand Up @@ -81,25 +104,19 @@ func MakeChoice(question string, defaultChoice bool) tea.Cmd {
}

// MarkAsReadMsg contains info needed to mark an item as read.
type MarkAsReadMsg struct {
FeedName string
Index int
}
type MarkAsReadMsg string

// MarkAsRead is called from a tab to tell the browser that an item needs to be marked as read.
func MarkAsRead(feedName string, index int) tea.Cmd {
return func() tea.Msg { return MarkAsReadMsg{feedName, index} }
func MarkAsRead(url string) tea.Cmd {
return func() tea.Msg { return MarkAsReadMsg(url) }
}

// MarkAsUnreadMsg contains info needed to mark an item as unread.
type MarkAsUnreadMsg struct {
FeedName string
Index int
}
type MarkAsUnreadMsg string

// MarkAsUnread is called from a tab to tell the browser that an item needs to be marked as unread.
func MarkAsUnread(feedName string, index int) tea.Cmd {
return func() tea.Msg { return MarkAsUnreadMsg{feedName, index} }
func MarkAsUnread(url string) tea.Cmd {
return func() tea.Msg { return MarkAsUnreadMsg(url) }
}

// SetEnableKeybindMsg contains the desired state of the keybinds.
Expand Down
6 changes: 4 additions & 2 deletions internal/ui/browser/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m.downloadItem(msg)

case backend.MarkAsReadMsg:
return m, m.backend.MarkAsRead(msg.FeedName, msg.Index)
m.backend.ReadStatus.MarkAsRead(string(msg))
return m, nil

case backend.MarkAsUnreadMsg:
return m, m.backend.MarkAsUnread(msg.FeedName, msg.Index)
m.backend.ReadStatus.MarkAsRead(string(msg))
return m, nil

case backend.MakeChoiceMsg:
bg := m.View()
Expand Down
33 changes: 16 additions & 17 deletions internal/ui/tab/feed/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/TypicalAM/goread/internal/backend"
"github.com/TypicalAM/goread/internal/theme"
"github.com/TypicalAM/goread/internal/ui/popup"
"github.com/TypicalAM/goread/internal/ui/simplelist"
"github.com/TypicalAM/goread/internal/ui/tab"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
Expand Down Expand Up @@ -107,7 +106,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil

case backend.FetchArticleSuccessMsg:
return m.loadTab(msg.Items, msg.ArticleContents), nil
return m.loadTab(msg.Items), nil

case backend.SetEnableKeybindMsg:
m.keymap.SetEnabled(bool(msg))
Expand Down Expand Up @@ -174,13 +173,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, backend.DeleteItem(m, fmt.Sprintf("%d", m.list.Index()))

case key.Matches(msg, m.keymap.MarkAsUnread):
item := m.list.SelectedItem().(list.DefaultItem)
if strings.HasPrefix(item.Title(), "✓ ") {
title := strings.Join(strings.Split(item.Title(), " ")[1:], " ")
m.list.SetItem(m.list.Index(), simplelist.NewItem(title, item.Description()))
item := m.list.SelectedItem().(backend.ArticleItem)
if strings.HasPrefix(item.ArtTitle, "✓ ") {
item.ArtTitle = strings.Join(strings.Split(item.ArtTitle, " ")[1:], " ")
m.list.SetItem(m.list.Index(), item)
}

return m, backend.MarkAsUnread(m.title, m.list.Index())
return m, backend.MarkAsUnread(item.FeedURL)

case key.Matches(msg, m.keymap.CycleSelection):
if !m.viewportFocused {
Expand Down Expand Up @@ -220,16 +219,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

// loadTab is fired when the items are retrieved from the backend
func (m Model) loadTab(items []list.Item, articleContents []string) tab.Tab {
func (m Model) loadTab(items []list.Item) tab.Tab {
itemDelegate := list.NewDefaultDelegate()
itemDelegate.ShowDescription = true
itemDelegate.Styles = m.style.listItems
itemDelegate.SetHeight(3)

// Wrap the descs, it's better to do it upfront then to rely on the list pagination
for i := range items {
item := items[i].(list.DefaultItem)
items[i] = simplelist.NewItem(item.Title(), wrap.String(item.Description(), m.style.listWidth-4))
item := items[i].(backend.ArticleItem)
item.Desc = wrap.String(item.Desc, m.style.listWidth-4)
items[i] = item
}

m.list = list.New(items, itemDelegate, m.style.listWidth, m.height)
Expand All @@ -243,7 +243,6 @@ func (m Model) loadTab(items []list.Item, articleContents []string) tab.Tab {
m.list.KeyMap.CloseFullHelp.SetEnabled(false)

m.viewport = viewport.New(m.style.viewportWidth, m.height)
m.articleContent = articleContents

colorTr, err := glamour.NewTermRenderer(
glamour.WithStyles(m.colors.MarkdownStyle),
Expand Down Expand Up @@ -285,7 +284,7 @@ func (m Model) updateViewport() (tab.Tab, tea.Cmd) {
return m, nil
}

rawText := m.articleContent[m.list.Index()]
rawText := m.list.SelectedItem().(backend.ArticleItem).MarkdownContent
styledText, err := m.colorTr.Render(rawText)
if err != nil {
m.viewport.SetContent(fmt.Sprintf("We have encountered an error styling the content: %s", err))
Expand All @@ -303,12 +302,12 @@ func (m Model) updateViewport() (tab.Tab, tea.Cmd) {
m.viewport.SetYOffset(0)

// Mark this item as read and prepend a ✓
item := m.list.SelectedItem().(list.DefaultItem)
if !strings.HasPrefix(item.Title(), "✓ ") {
m.list.SetItem(m.list.Index(), simplelist.NewItem("✓ "+item.Title(), item.Description()))
item := m.list.SelectedItem().(backend.ArticleItem)
if !strings.HasPrefix(item.ArtTitle, "✓ ") {
item.ArtTitle = "✓ " + item.ArtTitle
m.list.SetItem(m.list.Index(), item)
}

return m, backend.MarkAsRead(m.title, m.list.Index())
return m, backend.MarkAsRead(item.FeedURL)
}

// View the tab
Expand Down

0 comments on commit fd6a9b0

Please sign in to comment.