1
0
mirror of https://github.com/makew0rld/amfora.git synced 2024-06-19 19:25:24 +00:00

Switch subscription-based naming

This commit is contained in:
makeworld 2020-11-27 17:01:29 -05:00
parent ba28b2b5f9
commit f6e89fdaa1
15 changed files with 155 additions and 153 deletions

View File

@ -1,6 +1,6 @@
# Notes # Notes
## Feeds (temp) ## Subscriptions (temp)
- TODO: remove all logger lines - TODO: remove all logger lines
## Issues ## Issues

View File

@ -128,9 +128,9 @@ Features in *italics* are in the master branch, but not in the latest release.
- Manage and browse them - Manage and browse them
- Similar to [Kristall](https://github.com/MasterQ32/kristall) - Similar to [Kristall](https://github.com/MasterQ32/kristall)
- https://lists.orbitalfox.eu/archives/gemini/2020/001400.html - https://lists.orbitalfox.eu/archives/gemini/2020/001400.html
- [x] Subscribe to feeds and display them - [x] Subscriptions
- Tracking page changes is also supported
- RSS, Atom, and [JSON Feeds](https://jsonfeed.org/) are all supported - RSS, Atom, and [JSON Feeds](https://jsonfeed.org/) are all supported
- So is tracking any page to be notified when it changes
- [ ] Stream support - [ ] Stream support
- [ ] Table of contents for pages - [ ] Table of contents for pages
- [ ] History browser - [ ] History browser

View File

@ -7,8 +7,8 @@ import (
"github.com/makeworld-the-better-one/amfora/client" "github.com/makeworld-the-better-one/amfora/client"
"github.com/makeworld-the-better-one/amfora/config" "github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/display" "github.com/makeworld-the-better-one/amfora/display"
"github.com/makeworld-the-better-one/amfora/feeds"
"github.com/makeworld-the-better-one/amfora/logger" "github.com/makeworld-the-better-one/amfora/logger"
"github.com/makeworld-the-better-one/amfora/subscriptions"
) )
var ( var (
@ -45,9 +45,9 @@ func main() {
fmt.Fprintf(os.Stderr, "Config error: %v\n", err) fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
os.Exit(1) os.Exit(1)
} }
err = feeds.Init() err = subscriptions.Init()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "feeds.json error: %v\n", err) fmt.Fprintf(os.Stderr, "subscriptions.json error: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View File

@ -39,9 +39,9 @@ var bkmkPath string
var DownloadsDir string var DownloadsDir string
// Feeds // Subscriptions
var feedDir string var subscriptionDir string
var FeedPath string var SubscriptionPath string
// Command for opening HTTP(S) URLs in the browser, from "a-general.http" in config. // Command for opening HTTP(S) URLs in the browser, from "a-general.http" in config.
var HTTPCommand []string var HTTPCommand []string
@ -103,18 +103,18 @@ func Init() error {
// Feeds dir and path // Feeds dir and path
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// In APPDATA beside other Amfora files // In APPDATA beside other Amfora files
feedDir = amforaAppData subscriptionDir = amforaAppData
} else { } else {
// XDG data dir on POSIX systems // XDG data dir on POSIX systems
xdg_data, ok := os.LookupEnv("XDG_DATA_HOME") xdg_data, ok := os.LookupEnv("XDG_DATA_HOME")
if ok && strings.TrimSpace(xdg_data) != "" { if ok && strings.TrimSpace(xdg_data) != "" {
feedDir = filepath.Join(xdg_data, "amfora") subscriptionDir = filepath.Join(xdg_data, "amfora")
} else { } else {
// Default to ~/.local/share/amfora // Default to ~/.local/share/amfora
feedDir = filepath.Join(home, ".local", "share", "amfora") subscriptionDir = filepath.Join(home, ".local", "share", "amfora")
} }
} }
FeedPath = filepath.Join(feedDir, "feeds.json") SubscriptionPath = filepath.Join(subscriptionDir, "subscriptions.json")
// *** Create necessary files and folders *** // *** Create necessary files and folders ***
@ -152,7 +152,7 @@ func Init() error {
f.Close() f.Close()
} }
// Feeds // Feeds
err = os.MkdirAll(feedDir, 0755) err = os.MkdirAll(subscriptionDir, 0755)
if err != nil { if err != nil {
return err return err
} }
@ -234,9 +234,9 @@ func Init() error {
viper.SetDefault("url-handlers.other", "off") viper.SetDefault("url-handlers.other", "off")
viper.SetDefault("cache.max_size", 0) viper.SetDefault("cache.max_size", 0)
viper.SetDefault("cache.max_pages", 20) viper.SetDefault("cache.max_pages", 20)
viper.SetDefault("feeds.popup", true) viper.SetDefault("subscriptions.popup", true)
viper.SetDefault("feeds.update_interval", 1800) viper.SetDefault("subscriptions.update_interval", 1800)
viper.SetDefault("feeds.workers", 3) viper.SetDefault("subscriptions.workers", 3)
viper.SetConfigFile(configPath) viper.SetConfigFile(configPath)
viper.SetConfigType("toml") viper.SetConfigType("toml")

View File

@ -135,22 +135,22 @@ max_pages = 30 # The maximum number of pages the cache will store
# Note that HTTP and HTTPS are treated as separate protocols here. # Note that HTTP and HTTPS are treated as separate protocols here.
[feeds] [subscriptions]
# For tracking feeds and pages # For tracking feeds and pages
# Whether a pop-up appears when viewing a potential feed # Whether a pop-up appears when viewing a potential feed
popup = true popup = true
# How often to check for new feeds and pages in the background, in seconds. # How often to check for updates to subscriptions in the background, in seconds.
# Note Amfora will check for updates on browser start anyway. # Set it to 0 to disable this feature. You can still update individual feeds
# Set it to 0 or below to disable this feature. You can still update individual # manually, or restart the browser.
# feeds manually, or just restart the browser to update all of them. #
# Note Amfora will check for updates on browser start no matter what this setting is.
update_interval = 1800 # 30 mins update_interval = 1800 # 30 mins
# How many pages/feeds can be checked at the same time when updating. # How many subscriptions can be checked at the same time when updating.
# If you are tracking many feeds and pages you may want to increase this for # If you have many subscriptions you may want to increase this for faster
# faster update times. # update times. Any value below 1 will be corrected to 1.
# Any value below 1 will be corrected to 1.
workers = 3 workers = 3
@ -212,8 +212,8 @@ workers = 3
# yesno_modal_text # yesno_modal_text
# tofu_modal_bg # tofu_modal_bg
# tofu_modal_text # tofu_modal_text
# feed_modal_bg # subscription_modal_bg
# feed_modal_text # subscription_modal_text
# input_modal_bg # input_modal_bg
# input_modal_text # input_modal_text

View File

@ -26,20 +26,20 @@ var theme = map[string]tcell.Color{
"btn_bg": tcell.ColorNavy, // All modal buttons "btn_bg": tcell.ColorNavy, // All modal buttons
"btn_text": tcell.ColorWhite, "btn_text": tcell.ColorWhite,
"dl_choice_modal_bg": tcell.ColorPurple, "dl_choice_modal_bg": tcell.ColorPurple,
"dl_choice_modal_text": tcell.ColorWhite, "dl_choice_modal_text": tcell.ColorWhite,
"dl_modal_bg": tcell.Color130, // xterm:DarkOrange3, #af5f00 "dl_modal_bg": tcell.Color130, // xterm:DarkOrange3, #af5f00
"dl_modal_text": tcell.ColorWhite, "dl_modal_text": tcell.ColorWhite,
"info_modal_bg": tcell.ColorGray, "info_modal_bg": tcell.ColorGray,
"info_modal_text": tcell.ColorWhite, "info_modal_text": tcell.ColorWhite,
"error_modal_bg": tcell.ColorMaroon, "error_modal_bg": tcell.ColorMaroon,
"error_modal_text": tcell.ColorWhite, "error_modal_text": tcell.ColorWhite,
"yesno_modal_bg": tcell.ColorPurple, "yesno_modal_bg": tcell.ColorPurple,
"yesno_modal_text": tcell.ColorWhite, "yesno_modal_text": tcell.ColorWhite,
"tofu_modal_bg": tcell.ColorMaroon, "tofu_modal_bg": tcell.ColorMaroon,
"tofu_modal_text": tcell.ColorWhite, "tofu_modal_text": tcell.ColorWhite,
"feed_modal_bg": tcell.Color61, // xterm:SlateBlue3, #5f5faf "subscription_modal_bg": tcell.Color61, // xterm:SlateBlue3, #5f5faf
"feed_modal_text": tcell.ColorWhite, "subscription_modal_text": tcell.ColorWhite,
"input_modal_bg": tcell.ColorGreen, "input_modal_bg": tcell.ColorGreen,
"input_modal_text": tcell.ColorWhite, "input_modal_text": tcell.ColorWhite,

View File

@ -132,22 +132,22 @@ max_pages = 30 # The maximum number of pages the cache will store
# Note that HTTP and HTTPS are treated as separate protocols here. # Note that HTTP and HTTPS are treated as separate protocols here.
[feeds] [subscriptions]
# For tracking feeds and pages # For tracking feeds and pages
# Whether a pop-up appears when viewing a potential feed # Whether a pop-up appears when viewing a potential feed
popup = true popup = true
# How often to check for new feeds and pages in the background, in seconds. # How often to check for updates to subscriptions in the background, in seconds.
# Note Amfora will check for updates on browser start anyway. # Set it to 0 to disable this feature. You can still update individual feeds
# Set it to 0 or below to disable this feature. You can still update individual # manually, or restart the browser.
# feeds manually, or just restart the browser to update all of them. #
# Note Amfora will check for updates on browser start no matter what this setting is.
update_interval = 1800 # 30 mins update_interval = 1800 # 30 mins
# How many pages/feeds can be checked at the same time when updating. # How many subscriptions can be checked at the same time when updating.
# If you are tracking many feeds and pages you may want to increase this for # If you have many subscriptions you may want to increase this for faster
# faster update times. # update times. Any value below 1 will be corrected to 1.
# Any value below 1 will be corrected to 1.
workers = 3 workers = 3
@ -209,8 +209,8 @@ workers = 3
# yesno_modal_text # yesno_modal_text
# tofu_modal_bg # tofu_modal_bg
# tofu_modal_text # tofu_modal_text
# feed_modal_bg # subscription_modal_bg
# feed_modal_text # subscription_modal_text
# input_modal_bg # input_modal_bg
# input_modal_text # input_modal_text

View File

@ -294,11 +294,11 @@ func Init() {
} }
return nil return nil
case tcell.KeyCtrlA: case tcell.KeyCtrlA:
Feeds(tabs[curTab]) Subscriptions(tabs[curTab])
tabs[curTab].addToHistory("about:feeds") tabs[curTab].addToHistory("about:subscriptions")
return nil return nil
case tcell.KeyCtrlX: case tcell.KeyCtrlX:
go addFeed() go addSubscription()
return nil return nil
case tcell.KeyRune: case tcell.KeyRune:
// Regular key was sent // Regular key was sent
@ -583,9 +583,9 @@ func URL(u string) {
tabs[curTab].addToHistory("about:bookmarks") tabs[curTab].addToHistory("about:bookmarks")
return return
} }
if u == "about:feeds" { //nolint:goconst if u == "about:subscriptions" { //nolint:goconst
Feeds(tabs[curTab]) Subscriptions(tabs[curTab])
tabs[curTab].addToHistory("about:feeds") tabs[curTab].addToHistory("about:subscriptions")
return return
} }
if u == "about:newtab" { if u == "about:newtab" {

View File

@ -42,8 +42,8 @@ Ctrl-R, R|Reload a page, discarding the cached version.
Ctrl-B|View bookmarks Ctrl-B|View bookmarks
Ctrl-D|Add, change, or remove a bookmark for the current page. Ctrl-D|Add, change, or remove a bookmark for the current page.
Ctrl-S|Save the current page to your downloads. Ctrl-S|Save the current page to your downloads.
Ctrl-A|View tracked feeds and pages. Ctrl-A|View subscriptions
Ctrl-X|Track or update the current feed/page. Ctrl-X|Add or update a subscription
q, Ctrl-Q|Quit q, Ctrl-Q|Quit
Ctrl-C|Hard quit. This can be used when in the middle of downloading, Ctrl-C|Hard quit. This can be used when in the middle of downloading,
|for example. |for example.

View File

@ -18,7 +18,7 @@ You can customize this page by creating a gemtext file called newtab.gmi, in Amf
Happy browsing! Happy browsing!
=> about:bookmarks Bookmarks => about:bookmarks Bookmarks
=> about:feeds Feed and Page Tracking => about:subscriptions Feed and Page Tracking
=> //gemini.circumlunar.space Project Gemini => //gemini.circumlunar.space Project Gemini
=> https://github.com/makeworld-the-better-one/amfora Amfora homepage [HTTPS] => https://github.com/makeworld-the-better-one/amfora Amfora homepage [HTTPS]

View File

@ -16,9 +16,9 @@ import (
"github.com/makeworld-the-better-one/amfora/cache" "github.com/makeworld-the-better-one/amfora/cache"
"github.com/makeworld-the-better-one/amfora/client" "github.com/makeworld-the-better-one/amfora/client"
"github.com/makeworld-the-better-one/amfora/config" "github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/feeds"
"github.com/makeworld-the-better-one/amfora/renderer" "github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs" "github.com/makeworld-the-better-one/amfora/structs"
"github.com/makeworld-the-better-one/amfora/subscriptions"
"github.com/makeworld-the-better-one/amfora/webbrowser" "github.com/makeworld-the-better-one/amfora/webbrowser"
"github.com/makeworld-the-better-one/go-gemini" "github.com/makeworld-the-better-one/go-gemini"
"github.com/makeworld-the-better-one/go-isemoji" "github.com/makeworld-the-better-one/go-isemoji"
@ -39,9 +39,9 @@ func followLink(t *tab, prev, next string) {
t.addToHistory("about:bookmarks") t.addToHistory("about:bookmarks")
return return
} }
if next == "about:feeds" { if next == "about:subscriptions" {
Feeds(t) Subscriptions(t)
t.addToHistory("about:feeds") t.addToHistory("about:subscriptions")
return return
} }
if strings.HasPrefix(next, "about:") { if strings.HasPrefix(next, "about:") {
@ -340,7 +340,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
t.mode = tabModeDone t.mode = tabModeDone
go func(p *structs.Page) { go func(p *structs.Page) {
if b && t.hasContent() && !feeds.IsTracked(s) && viper.GetBool("feeds.popup") { if b && t.hasContent() && !subscriptions.IsSubscribed(s) && viper.GetBool("subscriptions.popup") {
// The current page might be an untracked feed, and the user wants // The current page might be an untracked feed, and the user wants
// to be notified in such cases. // to be notified in such cases.
@ -365,9 +365,9 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
Bookmarks(t) Bookmarks(t)
return ret("about:bookmarks", true) return ret("about:bookmarks", true)
} }
if u == "about:feeds" { if u == "about:subscriptions" {
Feeds(t) Subscriptions(t)
return ret("about:feeds", true) return ret("about:subscriptions", true)
} }
u = normalizeURL(u) u = normalizeURL(u)
@ -613,10 +613,10 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
// First see if it's a feed, and ask the user about adding it if it is // First see if it's a feed, and ask the user about adding it if it is
filename := path.Base(parsed.Path) filename := path.Base(parsed.Path)
mediatype, _, _ := mime.ParseMediaType(res.Meta) mediatype, _, _ := mime.ParseMediaType(res.Meta)
feed, ok := feeds.GetFeed(mediatype, filename, res.Body) feed, ok := subscriptions.GetFeed(mediatype, filename, res.Body)
if ok { if ok {
go func() { go func() {
added := addFeedDirect(u, feed, feeds.IsTracked(u)) added := addFeedDirect(u, feed, subscriptions.IsSubscribed(u))
if !added { if !added {
// Otherwise offer download choices // Otherwise offer download choices
go dlChoice("That file could not be displayed. What would you like to do?", u, res) go dlChoice("That file could not be displayed. What would you like to do?", u, res)

View File

@ -11,15 +11,15 @@ import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/cache" "github.com/makeworld-the-better-one/amfora/cache"
"github.com/makeworld-the-better-one/amfora/config" "github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/feeds"
"github.com/makeworld-the-better-one/amfora/logger" "github.com/makeworld-the-better-one/amfora/logger"
"github.com/makeworld-the-better-one/amfora/renderer" "github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs" "github.com/makeworld-the-better-one/amfora/structs"
"github.com/makeworld-the-better-one/amfora/subscriptions"
"github.com/mmcdole/gofeed" "github.com/mmcdole/gofeed"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var feedPageUpdated time.Time var subscriptionPageUpdated time.Time
// toLocalDay truncates the provided time to a date only, // toLocalDay truncates the provided time to a date only,
// but converts to the local time first. // but converts to the local time first.
@ -29,21 +29,21 @@ func toLocalDay(t time.Time) time.Time {
} }
// Feeds displays the feeds page on the current tab. // Feeds displays the feeds page on the current tab.
func Feeds(t *tab) { func Subscriptions(t *tab) {
logger.Log.Println("display.Feeds called") logger.Log.Println("display.Subscriptions called")
// Retrieve cached version if there hasn't been any updates // Retrieve cached version if there hasn't been any updates
p, ok := cache.GetPage("about:feeds") p, ok := cache.GetPage("about:subscriptions")
if feedPageUpdated.After(feeds.LastUpdated) && ok { if subscriptionPageUpdated.After(subscriptions.LastUpdated) && ok {
logger.Log.Println("using cached feeds page") logger.Log.Println("using cached subscriptions page")
setPage(t, p) setPage(t, p)
t.applyBottomBar() t.applyBottomBar()
return return
} }
logger.Log.Println("started rendering feeds page") logger.Log.Println("started rendering subscriptions page")
feedPageRaw := "# Feeds & Pages\n\n" + subscriptionPageRaw := "# Subscriptions\n\n" +
"See the help (by pressing ?) for details on how to use this page.\n\n" + "See the help (by pressing ?) for details on how to use this page.\n\n" +
"If you just opened Amfora then updates will appear incrementally. Reload the page to see them.\n" "If you just opened Amfora then updates will appear incrementally. Reload the page to see them.\n"
@ -58,7 +58,7 @@ func Feeds(t *tab) {
// the UTC timezone is specified. I believe gemfeed does this. // the UTC timezone is specified. I believe gemfeed does this.
curDay := toLocalDay(time.Now()).Add(26 * time.Hour) curDay := toLocalDay(time.Now()).Add(26 * time.Hour)
pe := feeds.GetPageEntries() pe := subscriptions.GetPageEntries()
for _, entry := range pe.Entries { // From new to old for _, entry := range pe.Entries { // From new to old
// Convert to local time, remove sub-day info // Convert to local time, remove sub-day info
@ -67,24 +67,24 @@ func Feeds(t *tab) {
if pub.Before(curDay) { if pub.Before(curDay) {
// This post is on a new day, add a day header // This post is on a new day, add a day header
curDay = pub curDay = pub
feedPageRaw += fmt.Sprintf("\n## %s\n\n", curDay.Format("Jan 02, 2006")) subscriptionPageRaw += fmt.Sprintf("\n## %s\n\n", curDay.Format("Jan 02, 2006"))
} }
if entry.Title == "" || entry.Title == "/" { if entry.Title == "" || entry.Title == "/" {
// Just put author/title // Just put author/title
// Mainly used for when you're tracking the root domain of a site // Mainly used for when you're tracking the root domain of a site
feedPageRaw += fmt.Sprintf("=>%s %s\n", entry.URL, entry.Prefix) subscriptionPageRaw += fmt.Sprintf("=>%s %s\n", entry.URL, entry.Prefix)
} else { } else {
// Include title and dash // Include title and dash
feedPageRaw += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Prefix, entry.Title) subscriptionPageRaw += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Prefix, entry.Title)
} }
} }
content, links := renderer.RenderGemini(feedPageRaw, textWidth(), leftMargin(), false) content, links := renderer.RenderGemini(subscriptionPageRaw, textWidth(), leftMargin(), false)
page := structs.Page{ page := structs.Page{
Raw: feedPageRaw, Raw: subscriptionPageRaw,
Content: content, Content: content,
Links: links, Links: links,
URL: "about:feeds", URL: "about:subscriptions",
Width: termW, Width: termW,
Mediatype: structs.TextGemini, Mediatype: structs.TextGemini,
} }
@ -92,26 +92,26 @@ func Feeds(t *tab) {
setPage(t, &page) setPage(t, &page)
t.applyBottomBar() t.applyBottomBar()
feedPageUpdated = time.Now() subscriptionPageUpdated = time.Now()
logger.Log.Println("done rendering feeds page") logger.Log.Println("done rendering subscriptions page")
} }
// openFeedModal displays the "Add feed/page" modal // openSubscriptionModal displays the "Add subscription" modal
// It returns whether the user wanted to add the feed/page. // It returns whether the user wanted to subscribe to feed/page.
// The tracked arg specifies whether this feed/page is already // The subscribed arg specifies whether this feed/page is already
// being tracked. // subscribed to.
func openFeedModal(validFeed, tracked bool) bool { func openSubscriptionModal(validFeed, subscribed bool) bool {
logger.Log.Println("display.openFeedModal called") logger.Log.Println("display.openFeedModal called")
// Reuses yesNoModal // Reuses yesNoModal
if viper.GetBool("a-general.color") { if viper.GetBool("a-general.color") {
yesNoModal. yesNoModal.
SetBackgroundColor(config.GetColor("feed_modal_bg")). SetBackgroundColor(config.GetColor("subscription_modal_bg")).
SetTextColor(config.GetColor("feed_modal_text")) SetTextColor(config.GetColor("subscription_modal_text"))
yesNoModal.GetFrame(). yesNoModal.GetFrame().
SetBorderColor(config.GetColor("feed_modal_text")). SetBorderColor(config.GetColor("subscription_modal_text")).
SetTitleColor(config.GetColor("feed_modal_text")) SetTitleColor(config.GetColor("subscription_modal_text"))
} else { } else {
yesNoModal. yesNoModal.
SetBackgroundColor(tcell.ColorBlack). SetBackgroundColor(tcell.ColorBlack).
@ -121,18 +121,18 @@ func openFeedModal(validFeed, tracked bool) bool {
SetTitleColor(tcell.ColorWhite) SetTitleColor(tcell.ColorWhite)
} }
if validFeed { if validFeed {
yesNoModal.GetFrame().SetTitle("Feed Tracking") yesNoModal.GetFrame().SetTitle("Feed Subscription")
if tracked { if subscribed {
yesNoModal.SetText("This is already being tracked. Would you like to manually update it?") yesNoModal.SetText("You are already subscribed to this feed. Would you like to manually update it?")
} else { } else {
yesNoModal.SetText("Would you like to start tracking this feed?") yesNoModal.SetText("Would you like to subscribe to this feed?")
} }
} else { } else {
yesNoModal.GetFrame().SetTitle("Page Tracking") yesNoModal.GetFrame().SetTitle("Page Subscription")
if tracked { if subscribed {
yesNoModal.SetText("This is already being tracked. Would you like to manually update it?") yesNoModal.SetText("You are already subscribed to this page. Would you like to manually update it?")
} else { } else {
yesNoModal.SetText("Would you like to start tracking this page?") yesNoModal.SetText("Would you like to subscribe to this page?")
} }
} }
@ -148,12 +148,12 @@ func openFeedModal(validFeed, tracked bool) bool {
return resp return resp
} }
// getFeedFromPage is like feeds.GetFeed but takes a structs.Page as input. // getFeedFromPage is like subscriptions.GetFeed but takes a structs.Page as input.
func getFeedFromPage(p *structs.Page) (*gofeed.Feed, bool) { func getFeedFromPage(p *structs.Page) (*gofeed.Feed, bool) {
parsed, _ := url.Parse(p.URL) parsed, _ := url.Parse(p.URL)
filename := path.Base(parsed.Path) filename := path.Base(parsed.Path)
r := strings.NewReader(p.Raw) r := strings.NewReader(p.Raw)
return feeds.GetFeed(p.RawMediatype, filename, r) return subscriptions.GetFeed(p.RawMediatype, filename, r)
} }
// addFeedDirect is only for adding feeds, not pages. // addFeedDirect is only for adding feeds, not pages.
@ -166,8 +166,8 @@ func getFeedFromPage(p *structs.Page) (*gofeed.Feed, bool) {
func addFeedDirect(u string, feed *gofeed.Feed, tracked bool) bool { func addFeedDirect(u string, feed *gofeed.Feed, tracked bool) bool {
logger.Log.Println("display.addFeedDirect called") logger.Log.Println("display.addFeedDirect called")
if openFeedModal(true, tracked) { if openSubscriptionModal(true, tracked) {
err := feeds.AddFeed(u, feed) err := subscriptions.AddFeed(u, feed)
if err != nil { if err != nil {
Error("Feed Error", err.Error()) Error("Feed Error", err.Error())
} }
@ -176,10 +176,10 @@ func addFeedDirect(u string, feed *gofeed.Feed, tracked bool) bool {
return false return false
} }
// addFeed goes through the process of tracking the current page/feed. // addFeed goes through the process of subscribing to the current page/feed.
// It is the high-level way of doing it. It should be called in a goroutine. // It is the high-level way of doing it. It should be called in a goroutine.
func addFeed() { func addSubscription() {
logger.Log.Println("display.addFeed called") logger.Log.Println("display.addSubscription called")
t := tabs[curTab] t := tabs[curTab]
p := t.page p := t.page
@ -190,15 +190,15 @@ func addFeed() {
} }
feed, isFeed := getFeedFromPage(p) feed, isFeed := getFeedFromPage(p)
tracked := feeds.IsTracked(p.URL) tracked := subscriptions.IsSubscribed(p.URL)
if openFeedModal(isFeed, tracked) { if openSubscriptionModal(isFeed, tracked) {
var err error var err error
if isFeed { if isFeed {
err = feeds.AddFeed(p.URL, feed) err = subscriptions.AddFeed(p.URL, feed)
} else { } else {
err = feeds.AddPage(p.URL, strings.NewReader(p.Raw)) err = subscriptions.AddPage(p.URL, strings.NewReader(p.Raw))
} }
if err != nil { if err != nil {

View File

@ -1,4 +1,4 @@
package feeds package subscriptions
import ( import (
"net/url" "net/url"
@ -10,7 +10,7 @@ import (
) )
// This file contains funcs for creating PageEntries, which // This file contains funcs for creating PageEntries, which
// are consumed by display/feeds.go // are consumed by display/subscriptions.go
// getURL returns a URL to be used in a PageEntry, from a // getURL returns a URL to be used in a PageEntry, from a
// list of URLs for that item. It prefers gemini URLs, then // list of URLs for that item. It prefers gemini URLs, then
@ -42,7 +42,7 @@ func getURL(urls []string) string {
// so this function needs to be called again to get updates. // so this function needs to be called again to get updates.
// It always returns sorted entries - by post time, from newest to oldest. // It always returns sorted entries - by post time, from newest to oldest.
func GetPageEntries() *PageEntries { func GetPageEntries() *PageEntries {
logger.Log.Println("feeds.GetPageEntries called") logger.Log.Println("subscriptions.GetPageEntries called")
var pe PageEntries var pe PageEntries

View File

@ -1,4 +1,4 @@
package feeds package subscriptions
import ( import (
"sync" "sync"
@ -77,8 +77,8 @@ var data = jsonData{
Pages: make(map[string]*pageJSON), Pages: make(map[string]*pageJSON),
} }
// PageEntry is a single item on a feed page. // PageEntry is a single item on a subscriptions page.
// It is used both for tracked feeds and pages. // It is used for both feeds and pages.
type PageEntry struct { type PageEntry struct {
Prefix string // Feed/log title, author, etc - something before the post title Prefix string // Feed/log title, author, etc - something before the post title
Title string Title string
@ -86,9 +86,10 @@ type PageEntry struct {
Published time.Time Published time.Time
} }
// PageEntries is new-to-old list of Entry structs, used to create a feed page. // PageEntries is new-to-old list of Entry structs, used to create a
// It should always be assumed to be sorted when used in other packages. // subscriptions page.
// Sorted by post time, from newest to oldest. // It should always be assumed to be sorted when used in other packages,
// by post time, from newest to oldest.
type PageEntries struct { type PageEntries struct {
Entries []*PageEntry Entries []*PageEntry
} }

View File

@ -1,4 +1,4 @@
package feeds package subscriptions
import ( import (
"crypto/sha256" "crypto/sha256"
@ -31,15 +31,15 @@ var (
ErrNotFeed = errors.New("not a valid feed") ErrNotFeed = errors.New("not a valid feed")
) )
var writeMu = sync.Mutex{} // Prevent concurrent writes to feeds.json file var writeMu = sync.Mutex{} // Prevent concurrent writes to subscriptions.json file
// LastUpdated is the time when the in-memory data was last updated. // LastUpdated is the time when the in-memory data was last updated.
// It can be used to know if the feed page should be regenerated. // It can be used to know if the subscriptions page should be regenerated.
var LastUpdated time.Time var LastUpdated time.Time
// Init should be called after config.Init. // Init should be called after config.Init.
func Init() error { func Init() error {
f, err := os.Open(config.FeedPath) f, err := os.Open(config.SubscriptionPath)
if err == nil { if err == nil {
// File exists and could be opened // File exists and could be opened
defer f.Close() defer f.Close()
@ -50,26 +50,26 @@ func Init() error {
dec := json.NewDecoder(f) dec := json.NewDecoder(f)
err = dec.Decode(&data) err = dec.Decode(&data)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return fmt.Errorf("feeds.json is corrupted: %w", err) //nolint:goerr113 return fmt.Errorf("subscriptions.json is corrupted: %w", err) //nolint:goerr113
} }
} }
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
// There's an error opening the file, but it's not bc is doesn't exist // There's an error opening the file, but it's not bc is doesn't exist
return fmt.Errorf("open feeds.json error: %w", err) //nolint:goerr113 return fmt.Errorf("open subscriptions.json error: %w", err) //nolint:goerr113
} }
LastUpdated = time.Now() LastUpdated = time.Now()
if viper.GetInt("feeds.update_interval") > 0 { if viper.GetInt("subscriptions.update_interval") > 0 {
// Update feeds and pages every so often // Update subscriptions every so often
go func() { go func() {
for { for {
updateAll() updateAll()
time.Sleep(time.Duration(viper.GetInt("feeds.update_interval")) * time.Second) time.Sleep(time.Duration(viper.GetInt("subscriptions.update_interval")) * time.Second)
} }
}() }()
} else { } else {
// User disabled automatic feed/page updates // User disabled automatic updates
// So just update once at the beginning // So just update once at the beginning
go updateAll() go updateAll()
} }
@ -77,9 +77,10 @@ func Init() error {
return nil return nil
} }
// IsTracked returns true if the feed/page URL is already being tracked. // IsSubscribed returns true if the URL is already subscribed to,
func IsTracked(url string) bool { // whether a feed or page.
logger.Log.Println("feeds.IsTracked called") func IsSubscribed(url string) bool {
logger.Log.Println("subscriptions.IsSubscribed called")
data.feedMu.RLock() data.feedMu.RLock()
for u := range data.Feeds { for u := range data.Feeds {
@ -103,7 +104,7 @@ func IsTracked(url string) bool {
// GetFeed returns a Feed object and a bool indicating whether the passed // GetFeed returns a Feed object and a bool indicating whether the passed
// content was actually recognized as a feed. // content was actually recognized as a feed.
func GetFeed(mediatype, filename string, r io.Reader) (*gofeed.Feed, bool) { func GetFeed(mediatype, filename string, r io.Reader) (*gofeed.Feed, bool) {
logger.Log.Println("feeds.GetFeed called") logger.Log.Println("subscriptions.GetFeed called")
if r == nil { if r == nil {
return nil, false return nil, false
@ -124,14 +125,14 @@ func GetFeed(mediatype, filename string, r io.Reader) (*gofeed.Feed, bool) {
} }
func writeJSON() error { func writeJSON() error {
logger.Log.Println("feeds.writeJSON called") logger.Log.Println("subscriptions.writeJSON called")
writeMu.Lock() writeMu.Lock()
defer writeMu.Unlock() defer writeMu.Unlock()
f, err := os.OpenFile(config.FeedPath, os.O_WRONLY|os.O_CREATE, 0666) f, err := os.OpenFile(config.SubscriptionPath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil { if err != nil {
logger.Log.Println("feeds.writeJSON error", err) logger.Log.Println("subscriptions.writeJSON error", err)
return err return err
} }
defer f.Close() defer f.Close()
@ -140,12 +141,12 @@ func writeJSON() error {
enc.SetIndent("", " ") enc.SetIndent("", " ")
data.Lock() data.Lock()
logger.Log.Println("feeds.writeJSON acquired data lock") logger.Log.Println("subscriptions.writeJSON acquired data lock")
err = enc.Encode(&data) err = enc.Encode(&data)
data.Unlock() data.Unlock()
if err != nil { if err != nil {
logger.Log.Println("feeds.writeJSON error", err) logger.Log.Println("subscriptions.writeJSON error", err)
} }
return err return err
@ -155,7 +156,7 @@ func writeJSON() error {
// It can be used to update a feed for a URL, although the package // It can be used to update a feed for a URL, although the package
// will handle that on its own. // will handle that on its own.
func AddFeed(url string, feed *gofeed.Feed) error { func AddFeed(url string, feed *gofeed.Feed) error {
logger.Log.Println("feeds.AddFeed called") logger.Log.Println("subscriptions.AddFeed called")
if feed == nil { if feed == nil {
panic("feed is nil") panic("feed is nil")
@ -205,7 +206,7 @@ func AddFeed(url string, feed *gofeed.Feed) error {
// It can be used to update the page as well, although the package // It can be used to update the page as well, although the package
// will handle that on its own. // will handle that on its own.
func AddPage(url string, r io.Reader) error { func AddPage(url string, r io.Reader) error {
logger.Log.Println("feeds.AddPage called") logger.Log.Println("subscriptions.AddPage called")
if r == nil { if r == nil {
return nil return nil
@ -241,7 +242,7 @@ func AddPage(url string, r io.Reader) error {
} }
func updateFeed(url string) error { func updateFeed(url string) error {
logger.Log.Println("feeds.updateFeed called") logger.Log.Println("subscriptions.updateFeed called")
res, err := client.Fetch(url) res, err := client.Fetch(url)
if err != nil { if err != nil {
@ -268,7 +269,7 @@ func updateFeed(url string) error {
} }
func updatePage(url string) error { func updatePage(url string) error {
logger.Log.Println("feeds.updatePage called") logger.Log.Println("subscriptions.updatePage called")
res, err := client.Fetch(url) res, err := client.Fetch(url)
if err != nil { if err != nil {
@ -286,10 +287,10 @@ func updatePage(url string) error {
return AddPage(url, res.Body) return AddPage(url, res.Body)
} }
// updateAll updates all feeds and pages using workers. // updateAll updates all subscriptions using workers.
// It only returns once all the workers are done. // It only returns once all the workers are done.
func updateAll() { func updateAll() {
logger.Log.Println("feeds.updateAll called") logger.Log.Println("subscriptions.updateAll called")
// TODO: Is two goroutines the right amount? // TODO: Is two goroutines the right amount?
@ -318,7 +319,7 @@ func updateAll() {
return return
} }
numWorkers := viper.GetInt("feeds.workers") numWorkers := viper.GetInt("subscriptions.workers")
if numWorkers < 1 { if numWorkers < 1 {
numWorkers = 1 numWorkers = 1
} }