1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-06-27 16:36:00 +00:00

Support localized feed errors generated by background workers

This commit is contained in:
Frédéric Guillot 2018-02-27 21:08:32 -08:00
parent 9694861cb6
commit 953d0a2dc0
18 changed files with 59 additions and 65 deletions

View file

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/miniflux/miniflux/config" "github.com/miniflux/miniflux/config"
"github.com/miniflux/miniflux/locale"
"github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/reader/feed" "github.com/miniflux/miniflux/reader/feed"
"github.com/miniflux/miniflux/scheduler" "github.com/miniflux/miniflux/scheduler"
@ -26,9 +27,10 @@ func Run(cfg *config.Config, store *storage.Storage) {
signal.Notify(stop, os.Interrupt) signal.Notify(stop, os.Interrupt)
signal.Notify(stop, syscall.SIGTERM) signal.Notify(stop, syscall.SIGTERM)
feedHandler := feed.NewFeedHandler(store) translator := locale.Load()
feedHandler := feed.NewFeedHandler(store, translator)
pool := scheduler.NewWorkerPool(feedHandler, cfg.WorkerPoolSize()) pool := scheduler.NewWorkerPool(feedHandler, cfg.WorkerPoolSize())
server := newServer(cfg, store, pool, feedHandler) server := newServer(cfg, store, pool, feedHandler, translator)
scheduler.NewFeedScheduler( scheduler.NewFeedScheduler(
store, store,

View file

@ -23,9 +23,8 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router { func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool, translator *locale.Translator) *mux.Router {
router := mux.NewRouter() router := mux.NewRouter()
translator := locale.Load()
templateEngine := template.NewEngine(cfg, router, translator) templateEngine := template.NewEngine(cfg, router, translator)
apiController := api.NewController(store, feedHandler) apiController := api.NewController(store, feedHandler)

View file

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/miniflux/miniflux/config" "github.com/miniflux/miniflux/config"
"github.com/miniflux/miniflux/locale"
"github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/reader/feed" "github.com/miniflux/miniflux/reader/feed"
"github.com/miniflux/miniflux/scheduler" "github.com/miniflux/miniflux/scheduler"
@ -18,7 +19,7 @@ import (
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
) )
func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) *http.Server { func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, translator *locale.Translator) *http.Server {
certFile := cfg.CertFile() certFile := cfg.CertFile()
keyFile := cfg.KeyFile() keyFile := cfg.KeyFile()
certDomain := cfg.CertDomain() certDomain := cfg.CertDomain()
@ -28,7 +29,7 @@ func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.Worke
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second, IdleTimeout: 60 * time.Second,
Addr: cfg.ListenAddr(), Addr: cfg.ListenAddr(),
Handler: routes(cfg, store, feedHandler, pool), Handler: routes(cfg, store, feedHandler, pool, translator),
} }
if certDomain != "" && certCache != "" { if certDomain != "" && certCache != "" {

View file

@ -27,6 +27,6 @@ func (l LocalizedError) Localize(translation *locale.Language) string {
} }
// NewLocalizedError returns a new LocalizedError. // NewLocalizedError returns a new LocalizedError.
func NewLocalizedError(message string, args ...interface{}) LocalizedError { func NewLocalizedError(message string, args ...interface{}) *LocalizedError {
return LocalizedError{message: message, args: args} return &LocalizedError{message: message, args: args}
} }

View file

@ -14,7 +14,7 @@ import (
) )
// Parse returns a normalized feed struct from a Atom feed. // Parse returns a normalized feed struct from a Atom feed.
func Parse(data io.Reader) (*model.Feed, error) { func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
atomFeed := new(atomFeed) atomFeed := new(atomFeed)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
decoder.CharsetReader = encoding.CharsetReader decoder.CharsetReader = encoding.CharsetReader

View file

@ -8,8 +8,6 @@ import (
"bytes" "bytes"
"testing" "testing"
"time" "time"
"github.com/miniflux/miniflux/errors"
) )
func TestParseAtomSample(t *testing.T) { func TestParseAtomSample(t *testing.T) {
@ -430,8 +428,4 @@ func TestParseInvalidXml(t *testing.T) {
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
if _, ok := err.(errors.LocalizedError); !ok {
t.Error("The error returned must be a LocalizedError")
}
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/miniflux/miniflux/errors" "github.com/miniflux/miniflux/errors"
"github.com/miniflux/miniflux/http" "github.com/miniflux/miniflux/http"
"github.com/miniflux/miniflux/locale"
"github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model" "github.com/miniflux/miniflux/model"
"github.com/miniflux/miniflux/reader/icon" "github.com/miniflux/miniflux/reader/icon"
@ -30,7 +31,8 @@ var (
// Handler contains all the logic to create and refresh feeds. // Handler contains all the logic to create and refresh feeds.
type Handler struct { type Handler struct {
store *storage.Storage store *storage.Storage
translator *locale.Translator
} }
// CreateFeed fetch, parse and store a new feed. // CreateFeed fetch, parse and store a new feed.
@ -44,7 +46,7 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool)
client := http.NewClient(url) client := http.NewClient(url)
response, err := client.Get() response, err := client.Get()
if err != nil { if err != nil {
if _, ok := err.(errors.LocalizedError); ok { if _, ok := err.(*errors.LocalizedError); ok {
return nil, err return nil, err
} }
return nil, errors.NewLocalizedError(errRequestFailed, err) return nil, errors.NewLocalizedError(errRequestFailed, err)
@ -68,9 +70,9 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool)
return nil, errors.NewLocalizedError(errEncoding, err) return nil, errors.NewLocalizedError(errEncoding, err)
} }
subscription, err := parseFeed(body) subscription, feedErr := parseFeed(body)
if err != nil { if feedErr != nil {
return nil, err return nil, feedErr
} }
feedProcessor := processor.NewFeedProcessor(userID, h.store, subscription) feedProcessor := processor.NewFeedProcessor(userID, h.store, subscription)
@ -110,6 +112,13 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool)
// RefreshFeed fetch and update a feed if necessary. // RefreshFeed fetch and update a feed if necessary.
func (h *Handler) RefreshFeed(userID, feedID int64) error { func (h *Handler) RefreshFeed(userID, feedID int64) error {
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Handler:RefreshFeed] feedID=%d", feedID)) defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Handler:RefreshFeed] feedID=%d", feedID))
userLanguage, err := h.store.UserLanguage(userID)
if err != nil {
logger.Error("[Handler:RefreshFeed] %v", err)
userLanguage = "en_US"
}
currentLanguage := h.translator.GetLanguage(userLanguage)
originalFeed, err := h.store.FeedByID(userID, feedID) originalFeed, err := h.store.FeedByID(userID, feedID)
if err != nil { if err != nil {
@ -124,14 +133,14 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
response, err := client.Get() response, err := client.Get()
if err != nil { if err != nil {
var customErr errors.LocalizedError var customErr errors.LocalizedError
if lerr, ok := err.(errors.LocalizedError); ok { if lerr, ok := err.(*errors.LocalizedError); ok {
customErr = lerr customErr = *lerr
} else { } else {
customErr = errors.NewLocalizedError(errRequestFailed, err) customErr = *errors.NewLocalizedError(errRequestFailed, err)
} }
originalFeed.ParsingErrorCount++ originalFeed.ParsingErrorCount++
originalFeed.ParsingErrorMsg = customErr.Error() originalFeed.ParsingErrorMsg = customErr.Localize(currentLanguage)
h.store.UpdateFeed(originalFeed) h.store.UpdateFeed(originalFeed)
return customErr return customErr
} }
@ -141,7 +150,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
if response.HasServerFailure() { if response.HasServerFailure() {
err := errors.NewLocalizedError(errServerFailure, response.StatusCode) err := errors.NewLocalizedError(errServerFailure, response.StatusCode)
originalFeed.ParsingErrorCount++ originalFeed.ParsingErrorCount++
originalFeed.ParsingErrorMsg = err.Error() originalFeed.ParsingErrorMsg = err.Localize(currentLanguage)
h.store.UpdateFeed(originalFeed) h.store.UpdateFeed(originalFeed)
return err return err
} }
@ -153,7 +162,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
if response.ContentLength == 0 { if response.ContentLength == 0 {
err := errors.NewLocalizedError(errEmptyFeed) err := errors.NewLocalizedError(errEmptyFeed)
originalFeed.ParsingErrorCount++ originalFeed.ParsingErrorCount++
originalFeed.ParsingErrorMsg = err.Error() originalFeed.ParsingErrorMsg = err.Localize(currentLanguage)
h.store.UpdateFeed(originalFeed) h.store.UpdateFeed(originalFeed)
return err return err
} }
@ -163,10 +172,10 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
return errors.NewLocalizedError(errEncoding, err) return errors.NewLocalizedError(errEncoding, err)
} }
subscription, err := parseFeed(body) subscription, parseErr := parseFeed(body)
if err != nil { if parseErr != nil {
originalFeed.ParsingErrorCount++ originalFeed.ParsingErrorCount++
originalFeed.ParsingErrorMsg = err.Error() originalFeed.ParsingErrorMsg = parseErr.Localize(currentLanguage)
h.store.UpdateFeed(originalFeed) h.store.UpdateFeed(originalFeed)
return err return err
} }
@ -209,6 +218,6 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
} }
// NewFeedHandler returns a feed handler. // NewFeedHandler returns a feed handler.
func NewFeedHandler(store *storage.Storage) *Handler { func NewFeedHandler(store *storage.Storage, translator *locale.Translator) *Handler {
return &Handler{store: store} return &Handler{store, translator}
} }

View file

@ -7,11 +7,11 @@ package feed
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"errors"
"io" "io"
"strings" "strings"
"time" "time"
"github.com/miniflux/miniflux/errors"
"github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model" "github.com/miniflux/miniflux/model"
"github.com/miniflux/miniflux/reader/atom" "github.com/miniflux/miniflux/reader/atom"
@ -66,13 +66,13 @@ func DetectFeedFormat(r io.Reader) string {
return FormatUnknown return FormatUnknown
} }
func parseFeed(r io.Reader) (*model.Feed, error) { func parseFeed(r io.Reader) (*model.Feed, *errors.LocalizedError) {
defer timer.ExecutionTime(time.Now(), "[Feed:ParseFeed]") defer timer.ExecutionTime(time.Now(), "[Feed:ParseFeed]")
var buffer bytes.Buffer var buffer bytes.Buffer
size, _ := io.Copy(&buffer, r) size, _ := io.Copy(&buffer, r)
if size == 0 { if size == 0 {
return nil, errors.New("This feed is empty") return nil, errors.NewLocalizedError("This feed is empty")
} }
str := stripInvalidXMLCharacters(buffer.String()) str := stripInvalidXMLCharacters(buffer.String())
@ -90,7 +90,7 @@ func parseFeed(r io.Reader) (*model.Feed, error) {
case FormatRDF: case FormatRDF:
return rdf.Parse(reader) return rdf.Parse(reader)
default: default:
return nil, errors.New("Unsupported feed format") return nil, errors.NewLocalizedError("Unsupported feed format")
} }
} }

View file

@ -13,7 +13,7 @@ import (
) )
// Parse returns a normalized feed struct from a JON feed. // Parse returns a normalized feed struct from a JON feed.
func Parse(data io.Reader) (*model.Feed, error) { func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(jsonFeed) feed := new(jsonFeed)
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
if err := decoder.Decode(&feed); err != nil { if err := decoder.Decode(&feed); err != nil {

View file

@ -9,8 +9,6 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/miniflux/miniflux/errors"
) )
func TestParseJsonFeed(t *testing.T) { func TestParseJsonFeed(t *testing.T) {
@ -377,8 +375,4 @@ func TestParseInvalidJSON(t *testing.T) {
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
if _, ok := err.(errors.LocalizedError); !ok {
t.Error("The error returned must be a LocalizedError")
}
} }

View file

@ -13,7 +13,7 @@ import (
) )
// Parse reads an OPML file and returns a SubcriptionList. // Parse reads an OPML file and returns a SubcriptionList.
func Parse(data io.Reader) (SubcriptionList, error) { func Parse(data io.Reader) (SubcriptionList, *errors.LocalizedError) {
feeds := new(opml) feeds := new(opml)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
decoder.CharsetReader = encoding.CharsetReader decoder.CharsetReader = encoding.CharsetReader

View file

@ -7,8 +7,6 @@ package opml
import ( import (
"bytes" "bytes"
"testing" "testing"
"github.com/miniflux/miniflux/errors"
) )
func TestParseOpmlWithoutCategories(t *testing.T) { func TestParseOpmlWithoutCategories(t *testing.T) {
@ -130,8 +128,4 @@ func TestParseInvalidXML(t *testing.T) {
if err == nil { if err == nil {
t.Error("Parse should generate an error") t.Error("Parse should generate an error")
} }
if _, ok := err.(errors.LocalizedError); !ok {
t.Error("The error returned must be a LocalizedError")
}
} }

View file

@ -14,7 +14,7 @@ import (
) )
// Parse returns a normalized feed struct from a RDF feed. // Parse returns a normalized feed struct from a RDF feed.
func Parse(data io.Reader) (*model.Feed, error) { func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(rdfFeed) feed := new(rdfFeed)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
decoder.CharsetReader = encoding.CharsetReader decoder.CharsetReader = encoding.CharsetReader

View file

@ -9,8 +9,6 @@ import (
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/miniflux/miniflux/errors"
) )
func TestParseRDFSample(t *testing.T) { func TestParseRDFSample(t *testing.T) {
@ -330,8 +328,4 @@ func TestParseInvalidXml(t *testing.T) {
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
if _, ok := err.(errors.LocalizedError); !ok {
t.Error("The error returned must be a LocalizedError")
}
} }

View file

@ -14,7 +14,7 @@ import (
) )
// Parse returns a normalized feed struct from a RSS feed. // Parse returns a normalized feed struct from a RSS feed.
func Parse(data io.Reader) (*model.Feed, error) { func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(rssFeed) feed := new(rssFeed)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
decoder.CharsetReader = encoding.CharsetReader decoder.CharsetReader = encoding.CharsetReader

View file

@ -8,8 +8,6 @@ import (
"bytes" "bytes"
"testing" "testing"
"time" "time"
"github.com/miniflux/miniflux/errors"
) )
func TestParseRss2Sample(t *testing.T) { func TestParseRss2Sample(t *testing.T) {
@ -564,8 +562,4 @@ func TestParseInvalidXml(t *testing.T) {
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
if _, ok := err.(errors.LocalizedError); !ok {
t.Error("The error returned must be a LocalizedError")
}
} }

View file

@ -175,6 +175,18 @@ func (s *Storage) UpdateUser(user *model.User) error {
return nil return nil
} }
// UserLanguage returns the language of the given user.
func (s *Storage) UserLanguage(userID int64) (language string, err error) {
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserLanguage] userID=%d", userID))
err = s.db.QueryRow(`SELECT language FROM users WHERE id = $1`, userID).Scan(&language)
if err == sql.ErrNoRows {
return "en_US", nil
} else if err != nil {
return "", fmt.Errorf("unable to fetch user language: %v", err)
}
return language, nil
}
// UserByID finds a user by the ID. // UserByID finds a user by the ID.
func (s *Storage) UserByID(userID int64) (*model.User, error) { func (s *Storage) UserByID(userID int64) (*model.User, error) {
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID)) defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID))

View file

@ -85,8 +85,9 @@ func (f *funcMap) Map() template.FuncMap {
case string: case string:
return f.Language.Get(key.(string), args...) return f.Language.Get(key.(string), args...)
case errors.LocalizedError: case errors.LocalizedError:
err := key.(errors.LocalizedError) return key.(errors.LocalizedError).Localize(f.Language)
return err.Localize(f.Language) case *errors.LocalizedError:
return key.(*errors.LocalizedError).Localize(f.Language)
case error: case error:
return key.(error).Error() return key.(error).Error()
default: default: