Skip to content

Commit 6e47358

Browse files
committed
Fixes #18, Fixes #11, Fixes #10
- Background fetch - parallell feed update - search functionality - ask to quit
1 parent 00bfc5c commit 6e47358

File tree

8 files changed

+183
-43
lines changed

8 files changed

+183
-43
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ run: build
1111
release:
1212
@mkdir release
1313
@mkdir dist
14-
@CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ GOARCH=amd64 GOOS=linux CGO_ENABLED=1 go build -ldflags "-linkmode external -extldflags -static -s -w -X $(shell go list)/internal.Version=${VERSION}" -o ./release/gorss_linux ./cmd/gorss/...
15-
@go build -ldflags "-s -w -X $(shell go list)/internal.Version=${VERSION}" -o ./release/gorss_osx ./cmd/gorss/...
14+
@GOARCH=amd64 GOOS=linux go build -ldflags "-s -w -X $(shell go list)/internal.Version=${VERSION}" -o ./release/gorss_linux ./cmd/gorss/...
15+
@GOARCH=amd64 GOOS=darwin go build -ldflags "-s -w -X $(shell go list)/internal.Version=${VERSION}" -o ./release/gorss_osx ./cmd/gorss/...
1616
@cp gorss.conf dist/
1717
@cp themes/default.theme dist/
1818
@cp -r themes dist/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ to use with the argument `-db` to the binary.
5151
- Mark articles as read
5252
- Mark all as read/unread
5353
- Undo last read (mark it as unread)
54+
- Search titles
5455

5556
## Configuration Example (Default config)
5657
It's possible to specify configuration file as a flag, default is `gorss.conf`.
@@ -110,6 +111,7 @@ and name fields. (See the example below for supported options).
110111
"keySwitchWindows": "Tab",
111112
"keyQuit": "Esc",
112113
"keyUndoLastRead": "u",
114+
"keySearchPromt": "/",
113115
"customCommands": [
114116
{
115117
"key": "j",

gorss.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"voxel",
44
"gorss",
55
"doit",
6+
"devel",
67
"lallassu"
78
],
89
"OPMLFile": "../example_ompl.xml",
@@ -43,6 +44,7 @@
4344
"keySwitchWindows": "Tab",
4445
"keyQuit": "Esc",
4546
"keyUndoLastRead": "u",
47+
"keySearchPromt": "/",
4648
"customCommands": [
4749
{
4850
"key": "j",

internal/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Config struct {
4444
KeySwitchWindows string `json:"keySwitchWindows"`
4545
KeyQuit string `json:"keyQuit"`
4646
KeyUndoLastRead string `json:"keyUndoLastRead"`
47+
KeySearchPromt string `json:"keySearchPromt"`
4748
CustomCommands []Command `json:"customCommands"`
4849
}
4950

internal/controller.go

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,21 @@ import (
1818

1919
// Controller handles the logic and keep everything together
2020
type Controller struct {
21-
rss *RSS
22-
db *DB
23-
win *Window
24-
activeFeed string
25-
linksToOpen []string
26-
quit chan int
27-
articles []Article
28-
aLock sync.Mutex
29-
conf Config
30-
theme Theme
31-
isUpdated bool
32-
prevArticle *Article
33-
undoArticle *Article
34-
lastUpdate time.Time
21+
rss *RSS
22+
db *DB
23+
win *Window
24+
activeFeed string
25+
linksToOpen []string
26+
quit chan int
27+
articles []Article
28+
aLock sync.Mutex
29+
conf Config
30+
theme Theme
31+
isUpdated bool
32+
prevArticle *Article
33+
undoArticle *Article
34+
lastUpdate time.Time
35+
searchResults int
3536
}
3637

3738
// Init initiates the controller with database handles etc.
@@ -102,7 +103,7 @@ func (c *Controller) GetConfigKeys() map[string]string {
102103
// UpdateLoop updates the feeds and windows
103104
func (c *Controller) UpdateLoop() {
104105
c.GetArticlesFromDB()
105-
c.UpdateFeeds() // Start by updating feeds.
106+
go c.UpdateFeeds() // Start by updating feeds.
106107
c.ShowFeeds()
107108
go func() {
108109
updateWin := time.NewTicker(time.Duration(30) * time.Second)
@@ -116,9 +117,11 @@ func (c *Controller) UpdateLoop() {
116117
}
117118
c.ShowFeeds()
118119
case <-updateFeeds.C:
119-
c.UpdateFeeds()
120-
c.db.CleanupDB()
121-
c.win.StatusUpdate()
120+
go func() {
121+
c.UpdateFeeds()
122+
c.db.CleanupDB()
123+
c.win.StatusUpdate()
124+
}()
122125
case <-c.quit:
123126
c.Quit()
124127
return
@@ -242,6 +245,7 @@ func (c *Controller) ShowFeeds() {
242245
}
243246
}
244247
c.win.AddToFeeds(fmt.Sprintf("[%s]Highlight", c.theme.Highlights), "", hc, total, &Article{feed: "highlight"})
248+
c.win.AddToFeeds(fmt.Sprintf("[%s]Search Results", c.theme.Highlights), "", c.searchResults, c.searchResults, &Article{feed: "result"})
245249

246250
type feed struct {
247251
count int
@@ -295,6 +299,8 @@ func (c *Controller) ShowArticles(feed string) {
295299

296300
if feed == "" {
297301
feed = "highlight"
302+
} else if feed == "result" {
303+
c.searchResults = 0
298304
}
299305

300306
c.activeFeed = feed
@@ -304,6 +310,19 @@ func (c *Controller) ShowArticles(feed string) {
304310
if !a.highlight {
305311
continue
306312
}
313+
} else if feed == "result" {
314+
match := false
315+
for _, f := range strings.Fields(c.win.currSearch) {
316+
// Insensitive search
317+
if strings.Contains(strings.ToLower(a.title), strings.ToLower(f)) {
318+
match = true
319+
c.searchResults++
320+
break
321+
}
322+
}
323+
if !match {
324+
continue
325+
}
307326
} else if feed == "allarticles" {
308327
// pass - take all articles
309328
} else if feed == "unread" {
@@ -334,6 +353,7 @@ func (c *Controller) ShowArticles(feed string) {
334353
c.isUpdated = false
335354

336355
c.win.articles.ScrollToBeginning()
356+
c.ShowFeeds()
337357
}
338358

339359
// GetArticleForSelection returns the article instance for the selected article
@@ -409,7 +429,6 @@ func (c *Controller) SelectArticle(row, col int) {
409429
c.db.MarkRead(c.prevArticle)
410430
c.prevArticle.read = true
411431
}
412-
413432
}
414433

415434
// Input handles keystrokes
@@ -420,8 +439,11 @@ func (c *Controller) Input(e *tcell.EventKey) *tcell.EventKey {
420439
}
421440

422441
switch keyName {
442+
case c.conf.KeySearchPromt:
443+
c.win.Search()
444+
423445
case c.conf.KeyQuit:
424-
c.quit <- 1
446+
c.win.AskQuit()
425447

426448
case c.conf.KeySwitchWindows:
427449
c.win.SwitchFocus()
@@ -452,7 +474,7 @@ func (c *Controller) Input(e *tcell.EventKey) *tcell.EventKey {
452474
// Remove the linkmarker icon
453475
r, _ := c.win.articles.GetSelection()
454476
cell := c.win.articles.GetCell(r, 1)
455-
cell.SetText(fmt.Sprintf("%s", c.theme.UnreadMarker))
477+
cell.SetText(c.theme.UnreadMarker)
456478
}
457479
if c.activeFeed != "unread" {
458480
c.ShowArticles(c.activeFeed)

internal/rss.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log"
66
"net/http"
7+
"sync"
78

89
"github.com/gilliek/go-opml/opml"
910
"github.com/mmcdole/gofeed"
@@ -69,20 +70,32 @@ func (r *RSS) Update() {
6970
displayName string
7071
feed *gofeed.Feed
7172
}{}
73+
74+
var mu sync.Mutex
75+
76+
var wg sync.WaitGroup
77+
7278
for _, f := range r.c.conf.Feeds {
73-
feed, err := r.FetchURL(fp, f.URL)
74-
if err != nil {
75-
log.Printf("error fetching url: %s, err: %v", f.URL, err)
76-
continue
77-
}
78-
r.feeds = append(r.feeds, struct {
79-
displayName string
80-
feed *gofeed.Feed
81-
}{
82-
f.Name,
83-
feed,
84-
})
79+
wg.Add(1)
80+
go func(f Feed) {
81+
feed, err := r.FetchURL(fp, f.URL)
82+
if err != nil {
83+
log.Printf("error fetching url: %s, err: %v", f.URL, err)
84+
} else {
85+
mu.Lock()
86+
r.feeds = append(r.feeds, struct {
87+
displayName string
88+
feed *gofeed.Feed
89+
}{
90+
f.Name,
91+
feed,
92+
})
93+
mu.Unlock()
94+
}
95+
wg.Done()
96+
}(f)
8597
}
98+
wg.Wait()
8699
}
87100

88101
// FetchURL fetches the feed URL and also fakes the user-agent to be able

internal/version.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
package internal
22

3+
// Version is version of the application
34
var Version string

internal/window.go

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ type Window struct {
3333
showHelp bool
3434
nArticles int
3535
nFeeds int
36+
askQuit bool
37+
currSearch string
3638
}
3739

3840
const (
@@ -223,8 +225,87 @@ func (w *Window) UpdateStatusTicker() {
223225
}()
224226
}
225227

228+
// Search asks the user to input search query
229+
func (w *Window) Search() {
230+
w.askQuit = true
231+
w.flexStatus.RemoveItem(w.status)
232+
w.currSearch = ""
233+
234+
inputField := tview.NewInputField().
235+
SetLabel("find: ").
236+
SetFieldWidth(20).
237+
SetFieldBackgroundColor(tcell.ColorBlack)
238+
239+
capt := func(e *tcell.EventKey) *tcell.EventKey {
240+
keyName := string(e.Name())
241+
if strings.Contains(keyName, "Rune") {
242+
keyName = string(e.Rune())
243+
}
244+
245+
if strings.EqualFold(keyName, "esc") {
246+
w.flexStatus.RemoveItem(inputField)
247+
w.flexStatus.AddItem(w.status, 1, 1, false)
248+
w.app.SetInputCapture(w.c.Input)
249+
w.app.SetFocus(w.articles)
250+
}
251+
252+
if strings.EqualFold(keyName, "enter") {
253+
w.flexStatus.RemoveItem(inputField)
254+
w.flexStatus.AddItem(w.status, 1, 1, false)
255+
w.app.SetInputCapture(w.c.Input)
256+
w.app.SetFocus(w.articles)
257+
258+
w.feeds.Select(2, 0)
259+
w.articles.Select(0, 3)
260+
261+
} else {
262+
w.currSearch += keyName
263+
}
264+
265+
return e
266+
}
267+
w.flexStatus.AddItem(inputField, 1, 0, false)
268+
w.app.SetFocus(inputField)
269+
w.app.SetInputCapture(capt)
270+
}
271+
272+
// AskQuit asks the user to quit or not.
273+
func (w *Window) AskQuit() {
274+
w.askQuit = true
275+
w.flexStatus.RemoveItem(w.status)
276+
277+
inputField := tview.NewInputField().
278+
SetLabel("Quit [Y/n]? ").
279+
SetFieldWidth(5).
280+
SetFieldBackgroundColor(tcell.ColorBlack)
281+
282+
x := func(e *tcell.EventKey) *tcell.EventKey {
283+
keyName := string(e.Name())
284+
if strings.Contains(keyName, "Rune") {
285+
keyName = string(e.Rune())
286+
}
287+
288+
if strings.EqualFold(keyName, "y") || strings.EqualFold(keyName, "enter") {
289+
w.c.quit <- 1
290+
}
291+
292+
w.flexStatus.RemoveItem(inputField)
293+
w.flexStatus.AddItem(w.status, 1, 1, false)
294+
w.app.SetInputCapture(w.c.Input)
295+
w.app.SetFocus(w.articles)
296+
297+
return e
298+
}
299+
w.flexStatus.AddItem(inputField, 1, 0, false)
300+
w.app.SetFocus(inputField)
301+
w.app.SetInputCapture(x)
302+
}
303+
226304
// StatusUpdate updates the status window with updated information
227305
func (w *Window) StatusUpdate() {
306+
if w.askQuit {
307+
return
308+
}
228309
// Update time
229310
c := w.status.GetCell(0, 0)
230311
c.SetText(
@@ -557,12 +638,12 @@ func (w *Window) AddToArticles(a *Article, markedWeb bool) {
557638
tc.SetReference(a)
558639
w.articles.SetCell(w.nArticles, 3, tc)
559640

560-
if a.highlight {
641+
if w.c.activeFeed == "result" {
561642
hTitle := ""
562643
fields := strings.Fields(a.title)
563644
for _, f := range fields {
564645
found := false
565-
for _, h := range w.c.conf.Highlights {
646+
for _, h := range strings.Fields(w.currSearch) {
566647
if strings.Contains(strings.ToLower(f), strings.ToLower(h)) {
567648
found = true
568649
}
@@ -575,8 +656,26 @@ func (w *Window) AddToArticles(a *Article, markedWeb bool) {
575656
}
576657
tc.SetText(hTitle)
577658
} else {
578-
tc.SetText(a.title)
579-
659+
if a.highlight {
660+
hTitle := ""
661+
fields := strings.Fields(a.title)
662+
for _, f := range fields {
663+
found := false
664+
for _, h := range w.c.conf.Highlights {
665+
if strings.Contains(strings.ToLower(f), strings.ToLower(h)) {
666+
found = true
667+
}
668+
}
669+
if found {
670+
hTitle += fmt.Sprintf("[%s]"+f+" [%s]", w.c.theme.Highlights, w.c.theme.Title)
671+
} else {
672+
hTitle += f + " "
673+
}
674+
}
675+
tc.SetText(hTitle)
676+
} else {
677+
tc.SetText(a.title)
678+
}
580679
}
581680

582681
str := time.Since(a.published).Round(time.Minute).String()
@@ -637,10 +736,10 @@ func (w *Window) AddPreview(a *Article) {
637736

638737
// GetTime returns the timestring formatted as (%h%m < 24 hours < %d)
639738
func GetTime(ts string) string {
640-
d_rex := regexp.MustCompile(`(\d+)h`)
641-
d_res := d_rex.FindStringSubmatch(ts)
642-
if len(d_res) > 0 {
643-
if i, err := strconv.Atoi(d_res[1]); err == nil {
739+
dDrex := regexp.MustCompile(`(\d+)h`)
740+
dRes := dDrex.FindStringSubmatch(ts)
741+
if len(dRes) > 0 {
742+
if i, err := strconv.Atoi(dRes[1]); err == nil {
644743
if i > 23 {
645744
days := i / 24
646745
return strconv.Itoa(days) + "d"

0 commit comments

Comments
 (0)