mirror of
https://github.com/nkanaev/yarr.git
synced 2026-06-09 18:03:19 +00:00
refactor feedstate + swap implementation
This commit is contained in:
@@ -56,17 +56,14 @@ type FeverFavicon struct {
|
|||||||
func writeFeverJSON(c *router.Context, data map[string]any, lastRefreshed int64) {
|
func writeFeverJSON(c *router.Context, data map[string]any, lastRefreshed int64) {
|
||||||
data["api_version"] = 3
|
data["api_version"] = 3
|
||||||
data["auth"] = 1
|
data["auth"] = 1
|
||||||
|
// TODO: remove duplicates
|
||||||
data["last_refreshed_on_time"] = lastRefreshed
|
data["last_refreshed_on_time"] = lastRefreshed
|
||||||
c.JSON(http.StatusOK, data)
|
c.JSON(http.StatusOK, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastRefreshedOnTime(httpStates map[int64]storage.HTTPState) int64 {
|
func getLastRefreshedOnTime(feedStates []storage.FeedState) int64 {
|
||||||
if len(httpStates) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastRefreshed int64
|
var lastRefreshed int64
|
||||||
for _, state := range httpStates {
|
for _, state := range feedStates {
|
||||||
if state.LastRefreshed.Unix() > lastRefreshed {
|
if state.LastRefreshed.Unix() > lastRefreshed {
|
||||||
lastRefreshed = state.LastRefreshed.Unix()
|
lastRefreshed = state.LastRefreshed.Unix()
|
||||||
}
|
}
|
||||||
@@ -123,10 +120,11 @@ func (s *Server) handleFever(c *router.Context) {
|
|||||||
case formHasValue(c.Req.Form, "mark"):
|
case formHasValue(c.Req.Form, "mark"):
|
||||||
s.feverMarkHandler(c)
|
s.feverMarkHandler(c)
|
||||||
default:
|
default:
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
c.JSON(http.StatusOK, map[string]any{
|
c.JSON(http.StatusOK, map[string]any{
|
||||||
"api_version": 3,
|
"api_version": 3,
|
||||||
"auth": 1,
|
"auth": 1,
|
||||||
"last_refreshed_on_time": getLastRefreshedOnTime(s.db.ListHTTPStates()),
|
"last_refreshed_on_time": getLastRefreshedOnTime(states),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,20 +166,25 @@ func (s *Server) feverGroupsHandler(c *router.Context) {
|
|||||||
for i, folder := range folders {
|
for i, folder := range folders {
|
||||||
groups[i] = &FeverGroup{ID: folder.Id, Title: folder.Title}
|
groups[i] = &FeverGroup{ID: folder.Id, Title: folder.Title}
|
||||||
}
|
}
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"groups": groups,
|
"groups": groups,
|
||||||
"feeds_groups": feedGroups(s.db),
|
"feeds_groups": feedGroups(s.db),
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverFeedsHandler(c *router.Context) {
|
func (s *Server) feverFeedsHandler(c *router.Context) {
|
||||||
feeds := s.db.ListFeeds()
|
feeds := s.db.ListFeeds()
|
||||||
httpStates := s.db.ListHTTPStates()
|
states, _ := s.db.ListFeedStates()
|
||||||
|
statesMap := make(map[int64]storage.FeedState)
|
||||||
|
for _, state := range states {
|
||||||
|
statesMap[state.FeedID] = state
|
||||||
|
}
|
||||||
|
|
||||||
feverFeeds := make([]*FeverFeed, len(feeds))
|
feverFeeds := make([]*FeverFeed, len(feeds))
|
||||||
for i, feed := range feeds {
|
for i, feed := range feeds {
|
||||||
var lastUpdated int64
|
var lastUpdated int64
|
||||||
if state, ok := httpStates[feed.Id]; ok {
|
if state, ok := statesMap[feed.Id]; ok {
|
||||||
lastUpdated = state.LastRefreshed.Unix()
|
lastUpdated = state.LastRefreshed.Unix()
|
||||||
}
|
}
|
||||||
feverFeeds[i] = &FeverFeed{
|
feverFeeds[i] = &FeverFeed{
|
||||||
@@ -197,7 +200,7 @@ func (s *Server) feverFeedsHandler(c *router.Context) {
|
|||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"feeds": feverFeeds,
|
"feeds": feverFeeds,
|
||||||
"feeds_groups": feedGroups(s.db),
|
"feeds_groups": feedGroups(s.db),
|
||||||
}, getLastRefreshedOnTime(httpStates))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverFaviconsHandler(c *router.Context) {
|
func (s *Server) feverFaviconsHandler(c *router.Context) {
|
||||||
@@ -216,9 +219,10 @@ func (s *Server) feverFaviconsHandler(c *router.Context) {
|
|||||||
favicons[i] = &FeverFavicon{ID: feed.Id, Data: data}
|
favicons[i] = &FeverFavicon{ID: feed.Id, Data: data}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"favicons": favicons,
|
"favicons": favicons,
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
// for memory pressure reasons, we only return a limited number of items
|
// for memory pressure reasons, we only return a limited number of items
|
||||||
@@ -280,16 +284,18 @@ func (s *Server) feverItemsHandler(c *router.Context) {
|
|||||||
|
|
||||||
totalItems := s.db.CountItems()
|
totalItems := s.db.CountItems()
|
||||||
|
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"items": feverItems,
|
"items": feverItems,
|
||||||
"total_items": totalItems,
|
"total_items": totalItems,
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverLinksHandler(c *router.Context) {
|
func (s *Server) feverLinksHandler(c *router.Context) {
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"links": make([]any, 0),
|
"links": make([]any, 0),
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverUnreadItemIDsHandler(c *router.Context) {
|
func (s *Server) feverUnreadItemIDsHandler(c *router.Context) {
|
||||||
@@ -309,9 +315,10 @@ func (s *Server) feverUnreadItemIDsHandler(c *router.Context) {
|
|||||||
}
|
}
|
||||||
itemFilter.After = &items[len(items)-1].Id
|
itemFilter.After = &items[len(items)-1].Id
|
||||||
}
|
}
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"unread_item_ids": joinInts(itemIds),
|
"unread_item_ids": joinInts(itemIds),
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverSavedItemIDsHandler(c *router.Context) {
|
func (s *Server) feverSavedItemIDsHandler(c *router.Context) {
|
||||||
@@ -331,9 +338,10 @@ func (s *Server) feverSavedItemIDsHandler(c *router.Context) {
|
|||||||
}
|
}
|
||||||
itemFilter.After = &items[len(items)-1].Id
|
itemFilter.After = &items[len(items)-1].Id
|
||||||
}
|
}
|
||||||
|
states, _ := s.db.ListFeedStates()
|
||||||
writeFeverJSON(c, map[string]any{
|
writeFeverJSON(c, map[string]any{
|
||||||
"saved_item_ids": joinInts(itemIds),
|
"saved_item_ids": joinInts(itemIds),
|
||||||
}, getLastRefreshedOnTime(s.db.ListHTTPStates()))
|
}, getLastRefreshedOnTime(states))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) feverMarkHandler(c *router.Context) {
|
func (s *Server) feverMarkHandler(c *router.Context) {
|
||||||
|
|||||||
@@ -162,7 +162,15 @@ func (s *Server) handleFeedRefresh(c *router.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleFeedErrors(c *router.Context) {
|
func (s *Server) handleFeedErrors(c *router.Context) {
|
||||||
errors := s.db.GetFeedErrors()
|
errors := make(map[int64]string)
|
||||||
|
states, err := s.db.ListFeedStates()
|
||||||
|
if err == nil {
|
||||||
|
for _, state := range states {
|
||||||
|
if state.LastError != "" {
|
||||||
|
errors[state.FeedID] = state.LastError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
c.JSON(http.StatusOK, errors)
|
c.JSON(http.StatusOK, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,42 +185,3 @@ func (s *Storage) GetFeed(id int64) *Feed {
|
|||||||
}
|
}
|
||||||
return &f
|
return &f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) ResetFeedErrors() {
|
|
||||||
if _, err := s.db.Exec(`delete from feed_errors`); err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) SetFeedError(feedID int64, lastError error) {
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
insert into feed_errors (feed_id, error)
|
|
||||||
values (:feed_id, :error)
|
|
||||||
on conflict (feed_id) do update set error = excluded.error`,
|
|
||||||
sql.Named("feed_id", feedID),
|
|
||||||
sql.Named("error", lastError.Error()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) GetFeedErrors() map[int64]string {
|
|
||||||
errors := make(map[int64]string)
|
|
||||||
|
|
||||||
rows, err := s.db.Query(`select feed_id, error from feed_errors`)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var id int64
|
|
||||||
var error string
|
|
||||||
if err = rows.Scan(&id, &error); err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
errors[id] = error
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,14 +8,19 @@ import (
|
|||||||
type FeedState struct {
|
type FeedState struct {
|
||||||
FeedID int64
|
FeedID int64
|
||||||
LastRefreshed time.Time
|
LastRefreshed time.Time
|
||||||
LastError *string
|
LastError string
|
||||||
HTTPLastModified string
|
HTTPLastModified string
|
||||||
HTTPEtag string
|
HTTPEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Storage) ListFeedStates() ([]FeedState, error) {
|
func (s *Storage) ListFeedStates() ([]FeedState, error) {
|
||||||
rows, err := s.db.Query(`
|
rows, err := s.db.Query(`
|
||||||
select feed_id, last_refreshed, last_modified, etag, last_error
|
select
|
||||||
|
feed_id
|
||||||
|
, last_refreshed
|
||||||
|
, last_error
|
||||||
|
, http_lmod
|
||||||
|
, http_etag
|
||||||
from feed_states
|
from feed_states
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -29,9 +34,9 @@ func (s *Storage) ListFeedStates() ([]FeedState, error) {
|
|||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&state.FeedID,
|
&state.FeedID,
|
||||||
&state.LastRefreshed,
|
&state.LastRefreshed,
|
||||||
|
&state.LastError,
|
||||||
&state.HTTPLastModified,
|
&state.HTTPLastModified,
|
||||||
&state.HTTPEtag,
|
&state.HTTPEtag,
|
||||||
&state.LastError,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -44,14 +49,19 @@ func (s *Storage) ListFeedStates() ([]FeedState, error) {
|
|||||||
func (s *Storage) GetFeedState(feedID int64) (*FeedState, error) {
|
func (s *Storage) GetFeedState(feedID int64) (*FeedState, error) {
|
||||||
var state FeedState
|
var state FeedState
|
||||||
err := s.db.QueryRow(`
|
err := s.db.QueryRow(`
|
||||||
select feed_id, last_refreshed, last_modified, etag, last_error
|
select
|
||||||
|
feed_id
|
||||||
|
, last_refreshed
|
||||||
|
, last_error
|
||||||
|
, http_lmod
|
||||||
|
, http_etag
|
||||||
from feed_states where feed_id = :id
|
from feed_states where feed_id = :id
|
||||||
`, sql.Named("id", feedID)).Scan(
|
`, sql.Named("id", feedID)).Scan(
|
||||||
&state.FeedID,
|
&state.FeedID,
|
||||||
&state.LastRefreshed,
|
&state.LastRefreshed,
|
||||||
|
&state.LastError,
|
||||||
&state.HTTPLastModified,
|
&state.HTTPLastModified,
|
||||||
&state.HTTPEtag,
|
&state.HTTPEtag,
|
||||||
&state.LastError,
|
|
||||||
)
|
)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -79,28 +89,28 @@ func (s *Storage) UpdateFeedState(feedID int64, params UpdateFeedStateParams) (b
|
|||||||
insert into feed_states (
|
insert into feed_states (
|
||||||
feed_id
|
feed_id
|
||||||
, last_refreshed
|
, last_refreshed
|
||||||
, last_modified
|
|
||||||
, etag
|
|
||||||
, last_error
|
, last_error
|
||||||
|
, http_lmod
|
||||||
|
, http_etag
|
||||||
)
|
)
|
||||||
values (
|
values (
|
||||||
:id
|
:id
|
||||||
, coalesce(:refreshed, 0)
|
, coalesce(:last_refreshed, 0)
|
||||||
, coalesce(:last_modified, '')
|
|
||||||
, coalesce(:etag, '')
|
|
||||||
, coalesce(:last_error, '')
|
, coalesce(:last_error, '')
|
||||||
|
, coalesce(:http_lmod, '')
|
||||||
|
, coalesce(:http_etag, '')
|
||||||
)
|
)
|
||||||
on conflict (feed_id) do update set
|
on conflict (feed_id) do update set
|
||||||
last_refreshed = coalesce(:refreshed, last_refreshed),
|
last_refreshed = coalesce(:last_refreshed, last_refreshed),
|
||||||
last_modified = coalesce(:last_modified, last_modified),
|
last_error = coalesce(:last_error, last_modified),
|
||||||
etag = coalesce(:etag, etag),
|
http_lmod = coalesce(:http_lmod, http_lmod),
|
||||||
last_error = coalesce(:last_error, last_error)
|
http_etag = coalesce(:http_etag, http_etag)
|
||||||
`,
|
`,
|
||||||
sql.Named("id", feedID),
|
sql.Named("id", feedID),
|
||||||
sql.Named("refreshed", params.LastRefreshed),
|
sql.Named("last_refreshed", params.LastRefreshed),
|
||||||
sql.Named("last_modified", params.HTTPLastModified),
|
|
||||||
sql.Named("etag", params.HTTPEtag),
|
|
||||||
sql.Named("last_error", params.LastError),
|
sql.Named("last_error", params.LastError),
|
||||||
|
sql.Named("http_lmod", params.HTTPLastModified),
|
||||||
|
sql.Named("http_etag", params.HTTPEtag),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func TestUpdateFeedState_Full(t *testing.T) {
|
|||||||
if !state.LastRefreshed.Equal(now) {
|
if !state.LastRefreshed.Equal(now) {
|
||||||
t.Errorf("expected %v, got %v", now, state.LastRefreshed)
|
t.Errorf("expected %v, got %v", now, state.LastRefreshed)
|
||||||
}
|
}
|
||||||
if state.LastError == nil || *state.LastError != errMsg {
|
if state.LastError != errMsg {
|
||||||
t.Errorf("expected %s, got %v", errMsg, state.LastError)
|
t.Errorf("expected %s, got %v", errMsg, state.LastError)
|
||||||
}
|
}
|
||||||
if state.HTTPLastModified != lmod {
|
if state.HTTPLastModified != lmod {
|
||||||
@@ -70,7 +70,7 @@ func TestUpdateFeedState_Partial(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if state.LastError == nil || *state.LastError != newErr {
|
if state.LastError != newErr {
|
||||||
t.Errorf("expected %s, got %v", newErr, state.LastError)
|
t.Errorf("expected %s, got %v", newErr, state.LastError)
|
||||||
}
|
}
|
||||||
if state.HTTPEtag != etag {
|
if state.HTTPEtag != etag {
|
||||||
@@ -98,8 +98,8 @@ func TestUpdateFeedState_ClearError(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if state.LastError == nil || *state.LastError != "" {
|
if state.LastError != "" {
|
||||||
t.Errorf("expected empty string error, got %v", state.LastError)
|
t.Errorf("expected empty error string, got %v", state.LastError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +111,8 @@ func TestListFeedStates(t *testing.T) {
|
|||||||
f2 := s.CreateFeed(CreateFeedParams{Title: "F2", FeedLink: "L2"})
|
f2 := s.CreateFeed(CreateFeedParams{Title: "F2", FeedLink: "L2"})
|
||||||
|
|
||||||
errMsg := "fail"
|
errMsg := "fail"
|
||||||
etag := "e"
|
|
||||||
s.UpdateFeedState(f1.Id, UpdateFeedStateParams{LastError: &errMsg})
|
s.UpdateFeedState(f1.Id, UpdateFeedStateParams{LastError: &errMsg})
|
||||||
s.UpdateFeedState(f2.Id, UpdateFeedStateParams{HTTPEtag: &etag})
|
s.UpdateFeedState(f2.Id, UpdateFeedStateParams{HTTPEtag: ptr("e")})
|
||||||
|
|
||||||
states, err := s.ListFeedStates()
|
states, err := s.ListFeedStates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,3 +123,7 @@ func TestListFeedStates(t *testing.T) {
|
|||||||
t.Errorf("expected 2 states, got %d", len(states))
|
t.Errorf("expected 2 states, got %d", len(states))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPState struct {
|
|
||||||
FeedID int64
|
|
||||||
LastRefreshed time.Time
|
|
||||||
|
|
||||||
LastModified string
|
|
||||||
Etag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) ListHTTPStates() map[int64]HTTPState {
|
|
||||||
result := make(map[int64]HTTPState)
|
|
||||||
rows, err := s.db.Query(`select feed_id, last_refreshed, last_modified, etag from http_states`)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
for rows.Next() {
|
|
||||||
var state HTTPState
|
|
||||||
err = rows.Scan(
|
|
||||||
&state.FeedID,
|
|
||||||
&state.LastRefreshed,
|
|
||||||
&state.LastModified,
|
|
||||||
&state.Etag,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result[state.FeedID] = state
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) GetHTTPState(feedID int64) *HTTPState {
|
|
||||||
row := s.db.QueryRow(`
|
|
||||||
select feed_id, last_refreshed, last_modified, etag
|
|
||||||
from http_states where feed_id = :feed_id
|
|
||||||
`, sql.Named("feed_id", feedID))
|
|
||||||
|
|
||||||
if row == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var state HTTPState
|
|
||||||
row.Scan(
|
|
||||||
&state.FeedID,
|
|
||||||
&state.LastRefreshed,
|
|
||||||
&state.LastModified,
|
|
||||||
&state.Etag,
|
|
||||||
)
|
|
||||||
return &state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storage) SetHTTPState(feedID int64, lastModified, etag string) {
|
|
||||||
_, err := s.db.Exec(`
|
|
||||||
insert into http_states (feed_id, last_modified, etag, last_refreshed)
|
|
||||||
values (:feed_id, :last_modified, :etag, datetime())
|
|
||||||
on conflict (feed_id) do update set last_modified = :last_modified, etag = :etag, last_refreshed = datetime()`,
|
|
||||||
sql.Named("feed_id", feedID),
|
|
||||||
sql.Named("last_modified", lastModified),
|
|
||||||
sql.Named("etag", etag),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -350,20 +350,27 @@ func m12_remove_feed_sizes(tx *sql.Tx) error {
|
|||||||
func m13_consolidate_feed_states(tx *sql.Tx) error {
|
func m13_consolidate_feed_states(tx *sql.Tx) error {
|
||||||
sql := `
|
sql := `
|
||||||
create table feed_states (
|
create table feed_states (
|
||||||
feed_id references feeds(id) on delete cascade unique,
|
feed_id references feeds(id) on delete cascade unique
|
||||||
last_refreshed datetime not null default 0,
|
, last_refreshed datetime not null default 0
|
||||||
last_modified string not null default '',
|
, last_error string not null default ''
|
||||||
etag string not null default '',
|
|
||||||
last_error string not null default ''
|
, http_lmod string not null default ''
|
||||||
|
, http_etag string not null default ''
|
||||||
);
|
);
|
||||||
|
|
||||||
insert into feed_states (feed_id, last_refreshed, last_modified, etag, last_error)
|
insert into feed_states (
|
||||||
|
feed_id
|
||||||
|
, last_refreshed
|
||||||
|
, last_error
|
||||||
|
, http_lmod
|
||||||
|
, http_etag
|
||||||
|
)
|
||||||
select
|
select
|
||||||
f.id,
|
f.id,
|
||||||
coalesce(h.last_refreshed, 0),
|
coalesce(h.last_refreshed, 0),
|
||||||
|
coalesce(e.error, '')
|
||||||
coalesce(h.last_modified, ''),
|
coalesce(h.last_modified, ''),
|
||||||
coalesce(h.etag, ''),
|
coalesce(h.etag, ''),
|
||||||
coalesce(e.error, '')
|
|
||||||
from feeds f
|
from feeds f
|
||||||
left join http_states h on f.id = h.feed_id
|
left join http_states h on f.id = h.feed_id
|
||||||
left join feed_errors e on f.id = e.feed_id
|
left join feed_errors e on f.id = e.feed_id
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/nkanaev/yarr/src/content/scraper"
|
"github.com/nkanaev/yarr/src/content/scraper"
|
||||||
"github.com/nkanaev/yarr/src/parser"
|
"github.com/nkanaev/yarr/src/parser"
|
||||||
@@ -162,9 +163,9 @@ func ConvertItems(items []parser.Item, feed storage.Feed) []storage.Item {
|
|||||||
func listItems(f storage.Feed, db *storage.Storage) ([]storage.Item, error) {
|
func listItems(f storage.Feed, db *storage.Storage) ([]storage.Item, error) {
|
||||||
lmod := ""
|
lmod := ""
|
||||||
etag := ""
|
etag := ""
|
||||||
if state := db.GetHTTPState(f.Id); state != nil {
|
if state, _ := db.GetFeedState(f.Id); state != nil {
|
||||||
lmod = state.LastModified
|
lmod = state.HTTPLastModified
|
||||||
etag = state.Etag
|
etag = state.HTTPEtag
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := client.getConditional(f.FeedLink, lmod, etag)
|
res, err := client.getConditional(f.FeedLink, lmod, etag)
|
||||||
@@ -190,8 +191,13 @@ func listItems(f storage.Feed, db *storage.Storage) ([]storage.Item, error) {
|
|||||||
|
|
||||||
lmod = res.Header.Get("Last-Modified")
|
lmod = res.Header.Get("Last-Modified")
|
||||||
etag = res.Header.Get("Etag")
|
etag = res.Header.Get("Etag")
|
||||||
|
now := time.Now().UTC()
|
||||||
if lmod != "" || etag != "" {
|
if lmod != "" || etag != "" {
|
||||||
db.SetHTTPState(f.Id, lmod, etag)
|
db.UpdateFeedState(f.Id, storage.UpdateFeedStateParams{
|
||||||
|
HTTPLastModified: &lmod,
|
||||||
|
HTTPEtag: &etag,
|
||||||
|
LastRefreshed: &now,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return ConvertItems(feed.Items, f), nil
|
return ConvertItems(feed.Items, f), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func (w *Worker) RefreshFeeds() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) refresher(feeds []storage.Feed) {
|
func (w *Worker) refresher(feeds []storage.Feed) {
|
||||||
w.db.ResetFeedErrors()
|
// w.db.ResetFeedErrors()
|
||||||
|
|
||||||
srcqueue := make(chan storage.Feed, len(feeds))
|
srcqueue := make(chan storage.Feed, len(feeds))
|
||||||
dstqueue := make(chan []storage.Item)
|
dstqueue := make(chan []storage.Item)
|
||||||
@@ -136,9 +136,13 @@ func (w *Worker) refresher(feeds []storage.Feed) {
|
|||||||
|
|
||||||
func (w *Worker) worker(srcqueue <-chan storage.Feed, dstqueue chan<- []storage.Item) {
|
func (w *Worker) worker(srcqueue <-chan storage.Feed, dstqueue chan<- []storage.Item) {
|
||||||
for feed := range srcqueue {
|
for feed := range srcqueue {
|
||||||
|
empty := ""
|
||||||
|
w.db.UpdateFeedState(feed.Id, storage.UpdateFeedStateParams{LastError: &empty})
|
||||||
|
|
||||||
items, err := listItems(feed, w.db)
|
items, err := listItems(feed, w.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.db.SetFeedError(feed.Id, err)
|
errMsg := err.Error()
|
||||||
|
w.db.UpdateFeedState(feed.Id, storage.UpdateFeedStateParams{LastError: &errMsg})
|
||||||
}
|
}
|
||||||
dstqueue <- items
|
dstqueue <- items
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user