Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for refreshing feed icons #2123

Closed
wants to merge 9 commits into from
Closed
7 changes: 7 additions & 0 deletions internal/database/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,4 +834,11 @@ var migrations = []func(tx *sql.Tx) error{
_, err = tx.Exec(sql)
return
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE feeds ADD COLUMN icon_url text default '';
`
_, err = tx.Exec(sql)
return err
},
}
10 changes: 5 additions & 5 deletions internal/model/feed.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ type Feed struct {
FetchViaProxy bool `json:"fetch_via_proxy"`
HideGlobally bool `json:"hide_globally"`
AppriseServiceURLs string `json:"apprise_service_urls"`
IconURL string `json:"icon_url"`

// Non persisted attributes
Category *Category `json:"category,omitempty"`
Icon *FeedIcon `json:"icon"`
Entries Entries `json:"entries,omitempty"`

TTL int `json:"-"`
IconURL string `json:"-"`
UnreadCount int `json:"-"`
ReadCount int `json:"-"`
NumberOfVisibleEntries int `json:"-"`
TTL int `json:"-"`
UnreadCount int `json:"-"`
ReadCount int `json:"-"`
NumberOfVisibleEntries int `json:"-"`
}

type FeedCounters struct {
Expand Down
1 change: 1 addition & 0 deletions internal/model/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Icon struct {
Hash string `json:"hash"`
MimeType string `json:"mime_type"`
Content []byte `json:"-"`
URL string `json:"-"`
}

// DataURL returns the data URL of the icon.
Expand Down
49 changes: 40 additions & 9 deletions internal/reader/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
subscription.ID,
subscription.SiteURL,
subscription.IconURL,
false,
)

return subscription, nil
Expand Down Expand Up @@ -189,6 +190,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
subscription.ID,
subscription.SiteURL,
subscription.IconURL,
false,
)
return subscription, nil
}
Expand Down Expand Up @@ -321,20 +323,42 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
originalFeed.EtagHeader = responseHandler.ETag()
originalFeed.LastModifiedHeader = responseHandler.LastModified()

checkFeedIcon(
store,
requestBuilder,
originalFeed.ID,
originalFeed.SiteURL,
updatedFeed.IconURL,
)
if icon.URLHasChanged(originalFeed.IconURL, updatedFeed.IconURL, updatedFeed.SiteURL) {
originalFeed.IconURL = updatedFeed.IconURL
forceRefresh = true
} else {
slog.Debug("Feed icon not modified",
slog.Int64("user_id", userID),
slog.Int64("feed_id", feedID),
slog.String("feed_icon_url", originalFeed.IconURL),
)
}
} else {
slog.Debug("Feed not modified",
slog.Int64("user_id", userID),
slog.Int64("feed_id", feedID),
)
}

// new request without LastTimeout or ETag to allow for icons that are older than the feed
requestBuilder = fetcher.NewRequestBuilder()
rystaf marked this conversation as resolved.
Show resolved Hide resolved
requestBuilder.WithUsernameAndPassword(originalFeed.Username, originalFeed.Password)
requestBuilder.WithUserAgent(originalFeed.UserAgent, config.Opts.HTTPClientUserAgent())
requestBuilder.WithCookie(originalFeed.Cookie)
requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
requestBuilder.UseProxy(originalFeed.FetchViaProxy)
requestBuilder.IgnoreTLSErrors(originalFeed.AllowSelfSignedCertificates)

checkFeedIcon(
store,
requestBuilder,
originalFeed.ID,
originalFeed.SiteURL,
originalFeed.IconURL,
forceRefresh,
)

originalFeed.ResetErrorCounter()

if storeErr := store.UpdateFeed(originalFeed); storeErr != nil {
Expand All @@ -347,8 +371,8 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
return nil
}

func checkFeedIcon(store *storage.Storage, requestBuilder *fetcher.RequestBuilder, feedID int64, websiteURL, feedIconURL string) {
if !store.HasIcon(feedID) {
func checkFeedIcon(store *storage.Storage, requestBuilder *fetcher.RequestBuilder, feedID int64, websiteURL, feedIconURL string, forceRefresh bool) {
if !store.HasIcon(feedID) || forceRefresh {
iconFinder := icon.NewIconFinder(requestBuilder, websiteURL, feedIconURL)
if icon, err := iconFinder.FindIcon(); err != nil {
slog.Debug("Unable to find feed icon",
Expand All @@ -364,6 +388,13 @@ func checkFeedIcon(store *storage.Storage, requestBuilder *fetcher.RequestBuilde
slog.String("feed_icon_url", feedIconURL),
)
} else {
if forceRefresh {
if err := store.RemoveFeedIcon(feedID); err != nil {
slog.Debug("Unable to remove Feed Icon",
rystaf marked this conversation as resolved.
Show resolved Hide resolved
slog.Int64("feed_id", feedID),
)
}
}
if err := store.CreateFeedIcon(feedID, icon); err != nil {
slog.Error("Unable to store feed icon",
slog.Int64("feed_id", feedID),
Expand Down
9 changes: 9 additions & 0 deletions internal/reader/icon/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (f *IconFinder) DownloadIcon(iconURL string) (*model.Icon, error) {
Hash: crypto.HashFromBytes(responseBody),
MimeType: responseHandler.ContentType(),
Content: responseBody,
URL: iconURL,
}

return icon, nil
Expand Down Expand Up @@ -287,3 +288,11 @@ func parseImageDataURL(value string) (*model.Icon, error) {

return icon, nil
}

func URLHasChanged(original, updated, siteURL string) bool {
if updated == "" {
return false
}
updated, _ = urllib.AbsoluteURL(siteURL, updated)
return original != updated
}
4 changes: 3 additions & 1 deletion internal/storage/feed_query_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
c.hide_globally as category_hidden,
fi.icon_id,
u.timezone,
f.apprise_service_urls
f.apprise_service_urls,
f.icon_url
FROM
feeds f
LEFT JOIN
Expand Down Expand Up @@ -230,6 +231,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
&iconID,
&tz,
&feed.AppriseServiceURLs,
&feed.IconURL,
)

if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions internal/storage/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,19 @@ func (s *Storage) CreateFeedIcon(feedID int64, icon *model.Icon) error {
if err != nil {
return fmt.Errorf(`store: unable to create feed icon: %v`, err)
}
_, err = s.db.Exec(`UPDATE feeds SET icon_url=$1 WHERE ID=$2`, icon.URL, feedID)
if err != nil {
return fmt.Errorf(`store: unable to save icon_url: %v`, err)
}

return nil
}
func (s *Storage) RemoveFeedIcon(feedID int64) error {
if _, err := s.db.Exec(`DELETE FROM feed_icons WHERE feed_id=$1`, feedID); err != nil {
rystaf marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf(`store: unable to remove feed icon: %v`, err)
}
return nil
}

// Icons returns all icons that belongs to a user.
func (s *Storage) Icons(userID int64) (model.Icons, error) {
Expand Down
Loading